diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index e7ce554..8a4a78f 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -10,12 +10,11 @@ permissions: contents: read jobs: - test: + lint: runs-on: ubuntu-latest - steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: setup deno uses: denoland/setup-deno@v2 @@ -28,5 +27,17 @@ jobs: - name: lint run: deno lint + test: + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: setup deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + - name: test run: deno task test diff --git a/deno.json b/deno.json index b6b6bd8..396146d 100644 --- a/deno.json +++ b/deno.json @@ -30,7 +30,7 @@ "deno-dom/constructor-lock": "https://deno.land/x/deno_dom@v0.1.41/src/constructor-lock.ts", "esbuild": "npm:esbuild@0.24.2", "esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@0.11.1", - "effection": "jsr:@effection/effection@3.1.0", + "effection": "jsr:@effection/effection@3.6.1", "@std/assert": "jsr:@std/assert@1.0.10", "@std/path": "jsr:@std/path@1.0.8", "@std/http": "jsr:@std/http@1.0.12", diff --git a/deno.lock b/deno.lock index 85b0859..c316cca 100644 --- a/deno.lock +++ b/deno.lock @@ -5,7 +5,7 @@ "jsr:@david/code-block-writer@^13.0.2": "13.0.3", "jsr:@deno/cache-dir@~0.10.3": "0.10.3", "jsr:@deno/dnt@0.41.3": "0.41.3", - "jsr:@effection/effection@3.1.0": "3.1.0", + "jsr:@effection/effection@3.6.1": "3.6.1", "jsr:@frontside/continuation@0.1.6": "0.1.6", "jsr:@luca/esbuild-deno-loader@0.11.1": "0.11.1", "jsr:@std/assert@0.223": "0.223.0", @@ -30,7 +30,7 @@ "jsr:@std/html@^1.0.3": "1.0.3", "jsr:@std/http@*": "1.0.12", "jsr:@std/http@1.0.12": "1.0.12", - "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/internal@^1.0.5": "1.0.12", "jsr:@std/io@0.223": "0.223.0", "jsr:@std/media-types@^1.1.0": "1.1.0", "jsr:@std/media-types@~0.219.1": "0.219.1", @@ -87,11 +87,10 @@ "jsr:@ts-morph/bootstrap" ] }, - "@effection/effection@3.1.0": { - "integrity": "3891a1ad37df3b85f03bce582c61f47904576a001b31163af29ba79c3b677f5f", + "@effection/effection@3.6.1": { + "integrity": "bb42143de920c70be9b67ecbcecfa08c5b1f77c977b3604121b17aef91645664", "dependencies": [ - "jsr:@frontside/continuation", - "jsr:@std/assert@1.0.10" + "jsr:@frontside/continuation" ] }, "@frontside/continuation@0.1.6": { @@ -197,8 +196,8 @@ "jsr:@std/streams@^1.0.8" ] }, - "@std/internal@1.0.5": { - "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" }, "@std/io@0.223.0": { "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", @@ -1263,7 +1262,7 @@ "dependencies": [ "jsr:@b-fuze/deno-dom@0.1.48", "jsr:@deno/dnt@0.41.3", - "jsr:@effection/effection@3.1.0", + "jsr:@effection/effection@3.6.1", "jsr:@luca/esbuild-deno-loader@0.11.1", "jsr:@std/assert@1.0.10", "jsr:@std/http@1.0.12", diff --git a/lib/render.ts b/lib/render.ts index 4b6f0bd..59d0b10 100644 --- a/lib/render.ts +++ b/lib/render.ts @@ -5,7 +5,7 @@ export const CurrentSlot = createContext("slot"); export const CurrentDocument = createContext("document"); export function* useDocument() { - return yield* CurrentDocument; + return yield* CurrentDocument.expect(); } type ASTNode = JSXElement; @@ -13,9 +13,9 @@ type ASTNode = JSXElement; export function render(jsx: JSXElement): Operation { return { *[Symbol.iterator]() { - let doc = yield* CurrentDocument; + let doc = yield* CurrentDocument.expect(); let nodes = toNodes(jsx, doc); - let slot = yield* CurrentSlot; + let slot = yield* CurrentSlot.expect(); slot.replace(...nodes); }, }; diff --git a/lib/revolution.ts b/lib/revolution.ts index 8848974..83b268c 100644 --- a/lib/revolution.ts +++ b/lib/revolution.ts @@ -44,7 +44,7 @@ const RevolutionOptions: Context = createContext< ); export function* useRevolutionOptions(): Operation { - return yield* RevolutionOptions; + return yield* RevolutionOptions.expect(); } function createApp({ diff --git a/lib/route.ts b/lib/route.ts index 24bce6a..4372192 100644 --- a/lib/route.ts +++ b/lib/route.ts @@ -4,7 +4,7 @@ import { match, type MatchResult, type ParamData } from "path-to-regexp"; import { concat } from "./middleware.ts"; import type { Middleware } from "./types.ts"; -const ParamsContext = createContext["params"]>( +export const ParamsContext = createContext["params"]>( "revolution.params", ); diff --git a/lib/server.ts b/lib/server.ts index eb5ca5c..5732d03 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -2,7 +2,7 @@ import type { Operation } from "effection"; import type { Handler } from "./types.ts"; import { action, call, once, race, resource, useScope } from "effection"; -import { getframe } from "effection"; +import { ParamsContext } from "./route.ts"; export interface ServerInfo { hostname: string; @@ -16,7 +16,7 @@ export interface ServerOptions { interface Driver { operation(): Operation; - context: Record; + params: Record | undefined; } export const _driver = Symbol.for("revolution.driver"); @@ -38,8 +38,10 @@ export function useServer(options: ServerOptions): Operation { if (driver) { scope.run(function* () { - let frame = yield* getframe(); - Object.assign(frame.context, driver.context); + // Restore the captured params context if it was set + if (driver.params) { + yield* ParamsContext.set(driver.params); + } try { yield* race([aborted(request), driver.operation()]); @@ -55,17 +57,17 @@ export function useServer(options: ServerOptions): Operation { return response; }); - let info = yield* action( - function* (resolve) { - server = Deno.serve({ - handler, - port: options.port, - onListen(info) { - resolve(info); - }, - }); - }, - ); + let info = yield* action((resolve, _reject) => { + server = Deno.serve({ + handler, + port: options.port, + onListen(info) { + resolve(info); + }, + }); + // Return cleanup function (required by both effection v3 and v4) + return () => {}; + }); try { yield* provide(info); } finally { @@ -89,11 +91,13 @@ export function* drive( response: Response, operation: () => Operation, ): Operation { - let { context } = yield* getframe(); + // Capture the current params context so it can be restored in the driver scope + let params = yield* ParamsContext.get(); + return Object.create(response, { [_driver]: { enumerable: false, - value: { context, operation }, + value: { params, operation }, }, }); } diff --git a/tasks/build-npm.ts b/tasks/build-npm.ts index 24eb624..c15b3b1 100644 --- a/tasks/build-npm.ts +++ b/tasks/build-npm.ts @@ -29,6 +29,7 @@ await build({ target: "ES2020", sourceMap: true, }, + package: { // package.json properties name: "@frontside/revolution", @@ -47,6 +48,9 @@ await build({ node: ">= 18", }, sideEffects: false, + peerDependencies: { + effection: "^3.0.0 || ^4.0.0", + }, }, }); diff --git a/test/suite.ts b/test/suite.ts index d3addd9..d4fef76 100644 --- a/test/suite.ts +++ b/test/suite.ts @@ -99,6 +99,10 @@ declare global { Object.defineProperty(Promise.prototype, Symbol.iterator, { get(this: Promise) { - return call(this)[Symbol.iterator]; + // Wrap the promise in a function for v4 compatibility + // v3 accepted call(promise) directly, v4 requires call(() => promise) + // deno-lint-ignore no-this-alias + const promise = this; + return call(() => promise)[Symbol.iterator]; }, });