diff --git a/biome.json b/biome.json index 60ca3937..e8596e13 100644 --- a/biome.json +++ b/biome.json @@ -23,6 +23,9 @@ "linter": { "rules": { "a11y": "off", + "correctness": { + "useExhaustiveDependencies": "off" + }, "suspicious": { "noArrayIndexKey": "off" } diff --git a/docs/package.json b/docs/package.json index 2db67997..186aac18 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,7 +16,7 @@ "react-dom": "^19.2.3", "typescript": "^5.9.3", "vike": "^0.4.249", - "vite": "^7.2.4" + "vite": "^7.3.1" }, "type": "module", "version": null diff --git a/example/app-cloudflare/package.json b/example/app-cloudflare/package.json index d1cddbf2..ced52287 100644 --- a/example/app-cloudflare/package.json +++ b/example/app-cloudflare/package.json @@ -10,19 +10,19 @@ "test:types": "tsc --noEmit" }, "devDependencies": { - "@photonjs/cloudflare": "^0.1.13", - "@photonjs/core": "^0.1.21", - "@photonjs/hono": "^0.1.12", + "@cloudflare/vite-plugin": "^1.24.0", "@photonjs/runtime": "^0.1.16", + "@universal-deploy/store": "^0.0.8-beta.1", "e2e": "workspace:", "playwright": "^1.57.0", "typescript": "^5.9.3", - "vite": "^7.2.4", - "wrangler": "^4.56.0" + "vite": "^7.3.1", + "wrangler": "^4.64.0" }, "dependencies": { - "@universal-middleware/core": "^0.4.14", + "@universal-middleware/core": "^0.4.16", + "@universal-middleware/hono": "^0.4.20", "awesome-framework": "0.0.0", - "hono": "^4.11.1" + "hono": "^4.11.5" } } diff --git a/example/app-cloudflare/server.ts b/example/app-cloudflare/server.ts index 223966ce..5a709dbd 100644 --- a/example/app-cloudflare/server.ts +++ b/example/app-cloudflare/server.ts @@ -1,13 +1,21 @@ -import { apply, serve } from "@photonjs/hono"; +import { env } from "cloudflare:workers"; +import photonEntry from "virtual:photon:entry"; +import type { ServerOptions } from "@photonjs/runtime"; +import { apply } from "@universal-middleware/hono"; +import awesomeMiddlewares from "awesome-framework/middlewares"; import { Hono } from "hono"; -function startApp() { +function startApp(): ServerOptions { const app = new Hono(); - // awesomeFramework will automatically be injected by apply - apply(app); + apply(app, [...awesomeMiddlewares, photonEntry.fetch]); - return serve(app); + return { + fetch(request) { + console.log("TEST_VAL", env.TEST_VAL); + return app.fetch(request); + }, + }; } export default startApp(); diff --git a/example/app-cloudflare/src/middlewares/bar.ts b/example/app-cloudflare/src/middlewares/bar.ts index 717026bc..e1efa707 100644 --- a/example/app-cloudflare/src/middlewares/bar.ts +++ b/example/app-cloudflare/src/middlewares/bar.ts @@ -1,3 +1,5 @@ -export default function bar() { - return new Response("bar"); -} +export default { + fetch() { + return new Response("bar"); + }, +}; diff --git a/example/app-cloudflare/src/middlewares/foo.ts b/example/app-cloudflare/src/middlewares/foo.ts index 292bceae..f9604664 100644 --- a/example/app-cloudflare/src/middlewares/foo.ts +++ b/example/app-cloudflare/src/middlewares/foo.ts @@ -1,11 +1,5 @@ -import { enhance } from "@universal-middleware/core"; - -function foo() { - return new Response("foo"); -} - -export default enhance(foo, { - name: "foo", - path: "/foo", - method: "GET", -}); +export default { + fetch() { + return new Response("foo"); + }, +}; diff --git a/example/app-cloudflare/virtual.d.ts b/example/app-cloudflare/virtual.d.ts new file mode 100644 index 00000000..63402f6f --- /dev/null +++ b/example/app-cloudflare/virtual.d.ts @@ -0,0 +1,7 @@ +declare module "virtual:photon:entry" { + const mod: { + fetch: (request: Request) => Response | Promise; + }; + + export default mod; +} diff --git a/example/app-cloudflare/vite.config.ts b/example/app-cloudflare/vite.config.ts index cd3cf4f9..0bdcea86 100644 --- a/example/app-cloudflare/vite.config.ts +++ b/example/app-cloudflare/vite.config.ts @@ -1,26 +1,37 @@ -/// -import { cloudflare } from "@photonjs/cloudflare/vite"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { photon } from "@photonjs/runtime/vite"; +import { addEntry } from "@universal-deploy/store"; import { awesomeFramework } from "awesome-framework/vite"; import { defineConfig } from "vite"; +addEntry({ + id: "./src/middlewares/foo.ts", + method: "GET", + route: "/foo", +}); +addEntry({ + id: "./src/middlewares/bar.ts", + method: "GET", + route: "/bar", +}); + export default defineConfig({ - photon: { - server: "server.ts", - entries: { - // foo entry declares its route with `enhance` directly inside the file - foo: "src/middlewares/foo.ts", - // bar entry route is declared here, and `enhance` is not used - bar: { - id: "src/middlewares/bar.ts", - route: "/bar", - }, - }, - }, plugins: [ - // Will be replaced with a photon.target setting + photon({ + entry: "./server.ts", + }), cloudflare({ + viteEnvironment: { + name: "ssr", + }, inspectorPort: false, - }), // not needed when using @photonjs/auto + }), awesomeFramework(), ], + + ssr: { + optimizeDeps: { + include: ["@universal-middleware/core"], + }, + }, }); diff --git a/example/app-cloudflare/worker-configuration.d.ts b/example/app-cloudflare/worker-configuration.d.ts index d44d5ea0..dea4e878 100644 --- a/example/app-cloudflare/worker-configuration.d.ts +++ b/example/app-cloudflare/worker-configuration.d.ts @@ -1,9 +1,9 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: d20b510db9d05da5d99ffc740588bd38) -// Runtime types generated with workerd@1.20251125.0 2025-04-03 +// Generated by Wrangler by running `wrangler types` (hash: c50ec0ed385478f946241785a0cd8e47) +// Runtime types generated with workerd@1.20260210.0 2025-04-03 declare namespace Cloudflare { interface Env { - COUNTER: DurableObjectNamespace /* Counter */; + TEST_VAL: "test"; } } interface Env extends Cloudflare.Env {} @@ -489,8 +489,10 @@ interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +type DurableObjectRoutingMode = "primary-only"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; + routingMode?: DurableObjectRoutingMode; } interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> { } @@ -1377,6 +1379,12 @@ declare abstract class PromiseRejectionEvent extends Event { */ declare class FormData { constructor(); + /** + * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) + */ + append(name: string, value: string | Blob): void; /** * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. * @@ -1413,6 +1421,12 @@ declare class FormData { * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) */ has(name: string): boolean; + /** + * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) + */ + set(name: string, value: string | Blob): void; /** * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. * @@ -1739,7 +1753,7 @@ interface Request> e * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) */ signal: AbortSignal; - cf: Cf | undefined; + cf?: Cf; /** * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request. * @@ -2074,6 +2088,8 @@ interface Transformer { expectedLength?: number; } interface StreamPipeOptions { + preventAbort?: boolean; + preventCancel?: boolean; /** * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. * @@ -2092,8 +2108,6 @@ interface StreamPipeOptions { * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. */ preventClose?: boolean; - preventAbort?: boolean; - preventCancel?: boolean; signal?: AbortSignal; } type ReadableStreamReadResult = { @@ -2368,13 +2382,13 @@ declare abstract class TransformStreamDefaultController { terminate(): void; } interface ReadableWritablePair { + readable: ReadableStream; /** * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. * * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. */ writable: WritableStream; - readable: ReadableStream; } /** * The **`WritableStream`** interface of the Streams API provides a standard abstraction for writing streaming data to a destination, known as a sink. @@ -3250,7 +3264,7 @@ interface WorkerStubEntrypointOptions { props?: any; } interface WorkerLoader { - get(name: string, getCode: () => WorkerLoaderWorkerCode | Promise): WorkerStub; + get(name: string | null, getCode: () => WorkerLoaderWorkerCode | Promise): WorkerStub; } interface WorkerLoaderModule { js?: string; @@ -5472,7 +5486,7 @@ interface Ai_Cf_Qwen_Qwq_32B_Messages { }; })[]; /** - * JSON schema that should be fufilled for the response. + * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** @@ -5738,7 +5752,7 @@ interface Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Messages { }; })[]; /** - * JSON schema that should be fufilled for the response. + * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** @@ -5829,7 +5843,7 @@ interface Ai_Cf_Google_Gemma_3_12B_It_Prompt { */ prompt: string; /** - * JSON schema that should be fufilled for the response. + * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** @@ -5988,7 +6002,7 @@ interface Ai_Cf_Google_Gemma_3_12B_It_Messages { }; })[]; /** - * JSON schema that should be fufilled for the response. + * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** @@ -6260,7 +6274,7 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages { })[]; response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; /** - * JSON schema that should be fufilled for the response. + * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** @@ -6490,7 +6504,7 @@ interface Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Messages_Inner { })[]; response_format?: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_JSON_Mode; /** - * JSON schema that should be fufilled for the response. + * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** @@ -7540,7 +7554,7 @@ interface Ai_Cf_Ai4Bharat_Indictrans2_En_Indic_1B_Input { */ text: string | string[]; /** - * Target langauge to translate to + * Target language to translate to */ target_language: "asm_Beng" | "awa_Deva" | "ben_Beng" | "bho_Deva" | "brx_Deva" | "doi_Deva" | "eng_Latn" | "gom_Deva" | "gon_Deva" | "guj_Gujr" | "hin_Deva" | "hne_Deva" | "kan_Knda" | "kas_Arab" | "kas_Deva" | "kha_Latn" | "lus_Latn" | "mag_Deva" | "mai_Deva" | "mal_Mlym" | "mar_Deva" | "mni_Beng" | "mni_Mtei" | "npi_Deva" | "ory_Orya" | "pan_Guru" | "san_Deva" | "sat_Olck" | "snd_Arab" | "snd_Deva" | "tam_Taml" | "tel_Telu" | "urd_Arab" | "unr_Deva"; } @@ -8432,7 +8446,7 @@ type AiOptions = { * Maximum 5 tags are allowed each request. * Duplicate tags will removed. */ - tags: string[]; + tags?: string[]; gateway?: GatewayOptions; returnRawResponse?: boolean; prefix?: string; @@ -9400,6 +9414,10 @@ interface D1Meta { * The region of the database instance that executed the query. */ served_by_region?: string; + /** + * The three letters airport code of the colo that executed the query. + */ + served_by_colo?: string; /** * True if-and-only-if the database instance that executed the query was the primary. */ @@ -9488,6 +9506,15 @@ declare abstract class D1PreparedStatement { // ignored when `Disposable` is included in the standard lib. interface Disposable { } +/** + * The returned data after sending an email + */ +interface EmailSendResult { + /** + * The Email Message ID + */ + messageId: string; +} /** * An email message that can be sent from a Worker. */ @@ -9529,19 +9556,50 @@ interface ForwardableEmailMessage extends EmailMessage { * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). * @returns A promise that resolves when the email message is forwarded. */ - forward(rcptTo: string, headers?: Headers): Promise; + forward(rcptTo: string, headers?: Headers): Promise; /** * Reply to the sender of this email message with a new EmailMessage object. * @param message The reply message. * @returns A promise that resolves when the email message is replied. */ - reply(message: EmailMessage): Promise; + reply(message: EmailMessage): Promise; +} +/** A file attachment for an email message */ +type EmailAttachment = { + disposition: 'inline'; + contentId: string; + filename: string; + type: string; + content: string | ArrayBuffer | ArrayBufferView; +} | { + disposition: 'attachment'; + contentId?: undefined; + filename: string; + type: string; + content: string | ArrayBuffer | ArrayBufferView; +}; +/** An Email Address */ +interface EmailAddress { + name: string; + email: string; } /** * A binding that allows a Worker to send email messages. */ interface SendEmail { - send(message: EmailMessage): Promise; + send(message: EmailMessage): Promise; + send(builder: { + from: string | EmailAddress; + to: string | string[]; + subject: string; + replyTo?: string | EmailAddress; + cc?: string | string[]; + bcc?: string | string[]; + headers?: Record; + text?: string; + html?: string; + attachments?: EmailAttachment[]; + }): Promise; } declare abstract class EmailEvent extends ExtendableEvent { readonly message: ForwardableEmailMessage; @@ -9574,7 +9632,7 @@ interface Hyperdrive { /** * Connect directly to Hyperdrive as if it's your database, returning a TCP socket. * - * Calling this method returns an idential socket to if you call + * Calling this method returns an identical socket to if you call * `connect("host:port")` using the `host` and `port` fields from this object. * Pick whichever approach works better with your preferred DB client library. * @@ -9907,7 +9965,7 @@ declare module "cloudflare:pipelines" { protected ctx: ExecutionContext; constructor(ctx: ExecutionContext, env: Env); /** - * run recieves an array of PipelineRecord which can be + * run receives an array of PipelineRecord which can be * transformed and returned to the pipeline * @param records Incoming records from the pipeline to be transformed * @param metadata Information about the specific pipeline calling the transformation entrypoint @@ -10188,6 +10246,7 @@ declare namespace CloudflareWorkersModule { timeout?: WorkflowTimeoutDuration | number; }): Promise>; } + export type WorkflowInstanceStatus = 'queued' | 'running' | 'paused' | 'errored' | 'terminated' | 'complete' | 'waiting' | 'waitingForPause' | 'unknown'; export abstract class WorkflowEntrypoint | unknown = unknown> implements Rpc.WorkflowEntrypointBranded { [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never; protected ctx: ExecutionContext; @@ -10221,12 +10280,14 @@ type MarkdownDocument = { blob: Blob; }; type ConversionResponse = { + id: string; name: string; mimeType: string; format: 'markdown'; tokens: number; data: string; } | { + id: string; name: string; mimeType: string; format: 'error'; @@ -10244,6 +10305,7 @@ type ConversionOptions = { images?: EmbeddedImageConversionOptions & { convertOGImage?: boolean; }; + hostname?: string; }; docx?: { images?: EmbeddedImageConversionOptions; @@ -10381,6 +10443,15 @@ declare namespace TailStream { readonly level: "debug" | "error" | "info" | "log" | "warn"; readonly message: object; } + interface DroppedEventsDiagnostic { + readonly diagnosticsType: "droppedEvents"; + readonly count: number; + } + interface StreamDiagnostic { + readonly type: 'streamDiagnostic'; + // To add new diagnostic types, define a new interface and add it to this union type. + readonly diagnostic: DroppedEventsDiagnostic; + } // This marks the worker handler return information. // This is separate from Outcome because the worker invocation can live for a long time after // returning. For example - Websockets that return an http upgrade response but then continue @@ -10397,7 +10468,7 @@ declare namespace TailStream { readonly type: "attributes"; readonly info: Attribute[]; } - type EventType = Onset | Outcome | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | Return | Attributes; + type EventType = Onset | Outcome | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | StreamDiagnostic | Return | Attributes; // Context in which this trace event lives. interface SpanContext { // Single id for the entire top-level invocation @@ -10411,7 +10482,7 @@ declare namespace TailStream { // For Hibernate and Mark this would be the span under which they were emitted. // spanId is not set ONLY if: // 1. This is an Onset event - // 2. We are not inherting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation) + // 2. We are not inheriting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation) readonly spanId?: string; } interface TailEvent { @@ -10777,8 +10848,11 @@ type InstanceStatus = { | 'complete' | 'waiting' // instance is hibernating and waiting for sleep or event to finish | 'waitingForPause' // instance is finishing the current work to pause | 'unknown'; - error?: string; - output?: object; + error?: { + name: string; + message: string; + }; + output?: unknown; }; interface WorkflowError { code?: number; diff --git a/example/app-cloudflare/wrangler.toml b/example/app-cloudflare/wrangler.toml index b41ba1d8..84bc7b2d 100644 --- a/example/app-cloudflare/wrangler.toml +++ b/example/app-cloudflare/wrangler.toml @@ -1,4 +1,7 @@ name = "app-cloudflare-photon" compatibility_date = "2025-04-03" -main = "virtual:photon:cloudflare:server-entry" +main = "virtual:ud:catch-all" preview_urls = true + +[vars] +TEST_VAL = "test" diff --git a/example/app-hono/package.json b/example/app-hono/package.json index 6754972e..bacf20f4 100644 --- a/example/app-hono/package.json +++ b/example/app-hono/package.json @@ -12,17 +12,18 @@ "test:types": "tsc --noEmit" }, "devDependencies": { - "@photonjs/core": "^0.1.21", - "@photonjs/hono": "^0.1.12", "@photonjs/runtime": "^0.1.16", + "@universal-deploy/node": "^0.0.3", + "@universal-deploy/store": "^0.0.8-beta.1", "e2e": "workspace:", "playwright": "^1.57.0", "typescript": "^5.9.3", - "vite": "^7.2.4" + "vite": "^7.3.1" }, "dependencies": { - "@universal-middleware/core": "^0.4.14", + "@universal-middleware/core": "^0.4.16", + "@universal-middleware/hono": "^0.4.20", "awesome-framework": "0.0.0", - "hono": "^4.11.1" + "hono": "^4.11.5" } } diff --git a/example/app-hono/server.ts b/example/app-hono/server.ts index 427adb15..42e66766 100644 --- a/example/app-hono/server.ts +++ b/example/app-hono/server.ts @@ -1,13 +1,17 @@ -import { apply, serve } from "@photonjs/hono"; +import photonEntry from "virtual:photon:entry"; +import type { ServerOptions } from "@photonjs/runtime"; +import { apply } from "@universal-middleware/hono"; +import awesomeMiddlewares from "awesome-framework/middlewares"; import { Hono } from "hono"; -function startServer() { +function startServer(): ServerOptions { const app = new Hono(); - // awesomeFramework will automatically be injected by apply - apply(app); + apply(app, [...awesomeMiddlewares, photonEntry.fetch]); - return serve(app); + return { + fetch: app.fetch, + }; } export default startServer(); diff --git a/example/app-hono/src/middlewares/bar.ts b/example/app-hono/src/middlewares/bar.ts index 717026bc..e1efa707 100644 --- a/example/app-hono/src/middlewares/bar.ts +++ b/example/app-hono/src/middlewares/bar.ts @@ -1,3 +1,5 @@ -export default function bar() { - return new Response("bar"); -} +export default { + fetch() { + return new Response("bar"); + }, +}; diff --git a/example/app-hono/src/middlewares/foo.ts b/example/app-hono/src/middlewares/foo.ts index 292bceae..f9604664 100644 --- a/example/app-hono/src/middlewares/foo.ts +++ b/example/app-hono/src/middlewares/foo.ts @@ -1,11 +1,5 @@ -import { enhance } from "@universal-middleware/core"; - -function foo() { - return new Response("foo"); -} - -export default enhance(foo, { - name: "foo", - path: "/foo", - method: "GET", -}); +export default { + fetch() { + return new Response("foo"); + }, +}; diff --git a/example/app-hono/testRun.ts b/example/app-hono/testRun.ts deleted file mode 100644 index 84bca180..00000000 --- a/example/app-hono/testRun.ts +++ /dev/null @@ -1,68 +0,0 @@ -export { testRun }; - -import { autoRetry, expect, fetch, fetchHtml, getServerUrl, page, run, test } from "@brillout/test-e2e"; - -function testRun(cmd: `pnpm run ${string}`) { - run(cmd, { - // Preview => builds app which takes a long time - additionalTimeout: 120 * 1000, - }); - - test("page content is rendered to HTML", async () => { - const html = await fetchHtml("/"); - expect(html).toContain("

Hello Vite!

"); - expect(html).toContain('