@effing/fn 0.33.1 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ // src/responses.ts
2
+ import { annieStream } from "@effing/annie";
3
+ function detectImageContentType(bytes) {
4
+ if (bytes[0] === 137 && bytes[1] === 80) return "image/png";
5
+ if (bytes[0] === 255 && bytes[1] === 216) return "image/jpeg";
6
+ throw new Error("Unsupported image format: expected PNG or JPEG");
7
+ }
8
+ function imageResponse(bytes, options = {}) {
9
+ const { headers: extraHeaders, cacheControl = "public, max-age=3600" } = options;
10
+ const headers = new Headers(extraHeaders);
11
+ headers.set("Content-Type", detectImageContentType(bytes));
12
+ if (cacheControl) {
13
+ headers.set("Cache-Control", cacheControl);
14
+ }
15
+ if (!(bytes.buffer instanceof ArrayBuffer)) {
16
+ throw new TypeError("SharedArrayBuffer is not supported");
17
+ }
18
+ return new Response(bytes.buffer, { status: 200, headers });
19
+ }
20
+ function annieResponse(frames, options = {}) {
21
+ const {
22
+ headers: extraHeaders,
23
+ cacheControl = "public, max-age=3600",
24
+ filename,
25
+ ...streamOptions
26
+ } = options;
27
+ const stream = annieStream(frames, streamOptions);
28
+ const headers = new Headers(extraHeaders);
29
+ headers.set("Content-Type", "application/x-tar");
30
+ if (cacheControl) {
31
+ headers.set("Cache-Control", cacheControl);
32
+ }
33
+ if (filename) {
34
+ headers.set("Content-Disposition", `inline; filename="${filename}.tar"`);
35
+ }
36
+ return new Response(stream, { status: 200, headers });
37
+ }
38
+ function effieResponse(data, options = {}) {
39
+ const { headers: extraHeaders, cacheControl = "public, max-age=3600" } = options;
40
+ const headers = new Headers(extraHeaders);
41
+ if (cacheControl) {
42
+ headers.set("Cache-Control", cacheControl);
43
+ }
44
+ return Response.json(data, { status: 200, headers });
45
+ }
46
+
47
+ export {
48
+ imageResponse,
49
+ annieResponse,
50
+ effieResponse
51
+ };
52
+ //# sourceMappingURL=chunk-UHDIQDAF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/responses.ts"],"sourcesContent":["import type { EffieData, EffieSources } from \"@effing/effie\";\nimport { annieStream } from \"@effing/annie\";\nimport type { AnnieStreamOptions } from \"@effing/annie\";\n\nexport type ImageResponseOptions = {\n headers?: HeadersInit;\n cacheControl?: string;\n};\n\nfunction detectImageContentType(bytes: Uint8Array): string {\n if (bytes[0] === 0x89 && bytes[1] === 0x50) return \"image/png\";\n if (bytes[0] === 0xff && bytes[1] === 0xd8) return \"image/jpeg\";\n throw new Error(\"Unsupported image format: expected PNG or JPEG\");\n}\n\nexport function imageResponse(\n bytes: Uint8Array,\n options: ImageResponseOptions = {},\n): Response {\n const { headers: extraHeaders, cacheControl = \"public, max-age=3600\" } =\n options;\n\n const headers = new Headers(extraHeaders);\n headers.set(\"Content-Type\", detectImageContentType(bytes));\n if (cacheControl) {\n headers.set(\"Cache-Control\", cacheControl);\n }\n\n if (!(bytes.buffer instanceof ArrayBuffer)) {\n throw new TypeError(\"SharedArrayBuffer is not supported\");\n }\n\n return new Response(bytes.buffer, { status: 200, headers });\n}\n\nexport type AnnieResponseOptions = AnnieStreamOptions & {\n headers?: HeadersInit;\n cacheControl?: string;\n filename?: string;\n};\n\nexport function annieResponse(\n frames: AsyncIterable<Buffer>,\n options: AnnieResponseOptions = {},\n): Response {\n const {\n headers: extraHeaders,\n cacheControl = \"public, max-age=3600\",\n filename,\n ...streamOptions\n } = options;\n\n const stream = annieStream(frames, streamOptions);\n\n const headers = new Headers(extraHeaders);\n headers.set(\"Content-Type\", \"application/x-tar\");\n if (cacheControl) {\n headers.set(\"Cache-Control\", cacheControl);\n }\n if (filename) {\n headers.set(\"Content-Disposition\", `inline; filename=\"${filename}.tar\"`);\n }\n\n return new Response(stream, { status: 200, headers });\n}\n\nexport type EffieResponseOptions = {\n headers?: HeadersInit;\n cacheControl?: string;\n};\n\nexport function effieResponse<S extends EffieSources>(\n data: EffieData<S>,\n options: EffieResponseOptions = {},\n): Response {\n const { headers: extraHeaders, cacheControl = \"public, max-age=3600\" } =\n options;\n\n const headers = new Headers(extraHeaders);\n if (cacheControl) {\n headers.set(\"Cache-Control\", cacheControl);\n }\n\n return Response.json(data, { status: 200, headers });\n}\n"],"mappings":";AACA,SAAS,mBAAmB;AAQ5B,SAAS,uBAAuB,OAA2B;AACzD,MAAI,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM,GAAM,QAAO;AACnD,MAAI,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM,IAAM,QAAO;AACnD,QAAM,IAAI,MAAM,gDAAgD;AAClE;AAEO,SAAS,cACd,OACA,UAAgC,CAAC,GACvB;AACV,QAAM,EAAE,SAAS,cAAc,eAAe,uBAAuB,IACnE;AAEF,QAAM,UAAU,IAAI,QAAQ,YAAY;AACxC,UAAQ,IAAI,gBAAgB,uBAAuB,KAAK,CAAC;AACzD,MAAI,cAAc;AAChB,YAAQ,IAAI,iBAAiB,YAAY;AAAA,EAC3C;AAEA,MAAI,EAAE,MAAM,kBAAkB,cAAc;AAC1C,UAAM,IAAI,UAAU,oCAAoC;AAAA,EAC1D;AAEA,SAAO,IAAI,SAAS,MAAM,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAC5D;AAQO,SAAS,cACd,QACA,UAAgC,CAAC,GACvB;AACV,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,eAAe;AAAA,IACf;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,SAAS,YAAY,QAAQ,aAAa;AAEhD,QAAM,UAAU,IAAI,QAAQ,YAAY;AACxC,UAAQ,IAAI,gBAAgB,mBAAmB;AAC/C,MAAI,cAAc;AAChB,YAAQ,IAAI,iBAAiB,YAAY;AAAA,EAC3C;AACA,MAAI,UAAU;AACZ,YAAQ,IAAI,uBAAuB,qBAAqB,QAAQ,OAAO;AAAA,EACzE;AAEA,SAAO,IAAI,SAAS,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;AACtD;AAOO,SAAS,cACd,MACA,UAAgC,CAAC,GACvB;AACV,QAAM,EAAE,SAAS,cAAc,eAAe,uBAAuB,IACnE;AAEF,QAAM,UAAU,IAAI,QAAQ,YAAY;AACxC,MAAI,cAAc;AAChB,YAAQ,IAAI,iBAAiB,YAAY;AAAA,EAC3C;AAEA,SAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AACrD;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,41 +1,8 @@
1
- import { z } from 'zod';
2
- import { EffieSources, EffieData, EffieWebUrl } from '@effing/effie';
1
+ import { F as FnModuleLoader, a as FnUrlBuilder, b as FnKind, c as FnModule, B as Bounds } from './types-8Q-DJL6t.js';
2
+ export { e as AnnieFnModule, A as AnnieRunnerReturn, f as EffieFnModule, E as EffieRunnerReturn, d as ImageFnModule, I as ImageRunnerReturn, R as RunnerArgs } from './types-8Q-DJL6t.js';
3
+ import { EffieWebUrl, EffieSources, EffieData } from '@effing/effie';
3
4
  import { AnnieStreamOptions } from '@effing/annie';
4
-
5
- type Bounds = Readonly<{
6
- width: number;
7
- height: number;
8
- }>;
9
- type RunnerArgs<P> = {
10
- props: P;
11
- bounds: Bounds;
12
- };
13
- type FnKind = "image" | "annie" | "effie";
14
- type BaseFnModule = {
15
- propsSchema: z.ZodSchema<unknown>;
16
- previewProps: object;
17
- };
18
- type ImageRunnerReturn = Promise<Buffer>;
19
- type AnnieRunnerReturn = AsyncIterable<Buffer>;
20
- type EffieRunnerReturn<S extends EffieSources = EffieSources> = Promise<EffieData<S>>;
21
- type ImageFnModule = BaseFnModule & {
22
- runner: (args: RunnerArgs<unknown>) => ImageRunnerReturn;
23
- };
24
- type AnnieFnModule = BaseFnModule & {
25
- runner: (args: RunnerArgs<unknown>) => AnnieRunnerReturn;
26
- };
27
- type EffieFnModule = BaseFnModule & {
28
- runner: (args: RunnerArgs<unknown>) => EffieRunnerReturn;
29
- };
30
- type FnModule<K extends FnKind> = K extends "image" ? ImageFnModule : K extends "annie" ? AnnieFnModule : EffieFnModule;
31
- type FnModuleLoader = {
32
- loadModule<K extends FnKind>(kind: K, id: string): Promise<FnModule<K>>;
33
- listModules(kind: FnKind): string[];
34
- hasModule(kind: FnKind, id: string): boolean;
35
- };
36
- type FnUrlBuilder = {
37
- buildUrl<P extends Record<string, unknown>>(kind: FnKind, id: string, props: P, bounds: Bounds): Promise<EffieWebUrl>;
38
- };
5
+ import 'zod';
39
6
 
40
7
  declare function initFnRuntime(config: {
41
8
  moduleLoader: FnModuleLoader;
@@ -63,4 +30,4 @@ type EffieResponseOptions = {
63
30
  };
64
31
  declare function effieResponse<S extends EffieSources>(data: EffieData<S>, options?: EffieResponseOptions): Response;
65
32
 
66
- export { type AnnieFnModule, type AnnieResponseOptions, type AnnieRunnerReturn, type Bounds, type EffieFnModule, type EffieResponseOptions, type EffieRunnerReturn, type FnKind, type FnModule, type FnModuleLoader, type FnUrlBuilder, type ImageFnModule, type ImageResponseOptions, type ImageRunnerReturn, type RunnerArgs, annieResponse, effieResponse, fnModule, fnModuleExists, fnModuleIds, fnUrl, imageResponse, initFnRuntime };
33
+ export { type AnnieResponseOptions, Bounds, type EffieResponseOptions, FnKind, FnModule, FnModuleLoader, FnUrlBuilder, type ImageResponseOptions, annieResponse, effieResponse, fnModule, fnModuleExists, fnModuleIds, fnUrl, imageResponse, initFnRuntime };
package/dist/index.js CHANGED
@@ -1,3 +1,9 @@
1
+ import {
2
+ annieResponse,
3
+ effieResponse,
4
+ imageResponse
5
+ } from "./chunk-UHDIQDAF.js";
6
+
1
7
  // src/runtime.ts
2
8
  var moduleLoader = null;
3
9
  var urlBuilder = null;
@@ -33,52 +39,6 @@ function fnModuleIds(kind) {
33
39
  function fnModuleExists(kind, id) {
34
40
  return getModuleLoader().hasModule(kind, id);
35
41
  }
36
-
37
- // src/responses.ts
38
- import { annieStream } from "@effing/annie";
39
- function detectImageContentType(bytes) {
40
- if (bytes[0] === 137 && bytes[1] === 80) return "image/png";
41
- if (bytes[0] === 255 && bytes[1] === 216) return "image/jpeg";
42
- throw new Error("Unsupported image format: expected PNG or JPEG");
43
- }
44
- function imageResponse(bytes, options = {}) {
45
- const { headers: extraHeaders, cacheControl = "public, max-age=3600" } = options;
46
- const headers = new Headers(extraHeaders);
47
- headers.set("Content-Type", detectImageContentType(bytes));
48
- if (cacheControl) {
49
- headers.set("Cache-Control", cacheControl);
50
- }
51
- if (!(bytes.buffer instanceof ArrayBuffer)) {
52
- throw new TypeError("SharedArrayBuffer is not supported");
53
- }
54
- return new Response(bytes.buffer, { status: 200, headers });
55
- }
56
- function annieResponse(frames, options = {}) {
57
- const {
58
- headers: extraHeaders,
59
- cacheControl = "public, max-age=3600",
60
- filename,
61
- ...streamOptions
62
- } = options;
63
- const stream = annieStream(frames, streamOptions);
64
- const headers = new Headers(extraHeaders);
65
- headers.set("Content-Type", "application/x-tar");
66
- if (cacheControl) {
67
- headers.set("Cache-Control", cacheControl);
68
- }
69
- if (filename) {
70
- headers.set("Content-Disposition", `inline; filename="${filename}.tar"`);
71
- }
72
- return new Response(stream, { status: 200, headers });
73
- }
74
- function effieResponse(data, options = {}) {
75
- const { headers: extraHeaders, cacheControl = "public, max-age=3600" } = options;
76
- const headers = new Headers(extraHeaders);
77
- if (cacheControl) {
78
- headers.set("Cache-Control", cacheControl);
79
- }
80
- return Response.json(data, { status: 200, headers });
81
- }
82
42
  export {
83
43
  annieResponse,
84
44
  effieResponse,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime.ts","../src/responses.ts"],"sourcesContent":["import type { EffieWebUrl } from \"@effing/effie\";\nimport type {\n Bounds,\n FnKind,\n FnModule,\n FnModuleLoader,\n FnUrlBuilder,\n} from \"./types\";\n\nlet moduleLoader: FnModuleLoader | null = null;\nlet urlBuilder: FnUrlBuilder | null = null;\n\nexport function initFnRuntime(config: {\n moduleLoader: FnModuleLoader;\n urlBuilder: FnUrlBuilder;\n}): void {\n moduleLoader = config.moduleLoader;\n urlBuilder = config.urlBuilder;\n}\n\nfunction getModuleLoader(): FnModuleLoader {\n if (!moduleLoader) {\n throw new Error(\n \"attempting to access module loader before initFnRuntime() was called\",\n );\n }\n return moduleLoader;\n}\n\nfunction getUrlBuilder(): FnUrlBuilder {\n if (!urlBuilder) {\n throw new Error(\n \"attempting to access URL builder before initFnRuntime() was called\",\n );\n }\n return urlBuilder;\n}\n\nexport function fnModule<K extends FnKind>(\n kind: K,\n id: string,\n): Promise<FnModule<K>> {\n return getModuleLoader().loadModule(kind, id);\n}\n\nexport function fnUrl<P extends Record<string, unknown>>(\n kind: FnKind,\n id: string,\n props: P,\n bounds: Bounds,\n): Promise<EffieWebUrl> {\n return getUrlBuilder().buildUrl(kind, id, props, bounds);\n}\n\nexport function fnModuleIds(kind: FnKind): string[] {\n return getModuleLoader().listModules(kind);\n}\n\nexport function fnModuleExists(kind: FnKind, id: string): boolean {\n return getModuleLoader().hasModule(kind, id);\n}\n","import type { EffieData, EffieSources } from \"@effing/effie\";\nimport { annieStream } from \"@effing/annie\";\nimport type { AnnieStreamOptions } from \"@effing/annie\";\n\nexport type ImageResponseOptions = {\n headers?: HeadersInit;\n cacheControl?: string;\n};\n\nfunction detectImageContentType(bytes: Uint8Array): string {\n if (bytes[0] === 0x89 && bytes[1] === 0x50) return \"image/png\";\n if (bytes[0] === 0xff && bytes[1] === 0xd8) return \"image/jpeg\";\n throw new Error(\"Unsupported image format: expected PNG or JPEG\");\n}\n\nexport function imageResponse(\n bytes: Uint8Array,\n options: ImageResponseOptions = {},\n): Response {\n const { headers: extraHeaders, cacheControl = \"public, max-age=3600\" } =\n options;\n\n const headers = new Headers(extraHeaders);\n headers.set(\"Content-Type\", detectImageContentType(bytes));\n if (cacheControl) {\n headers.set(\"Cache-Control\", cacheControl);\n }\n\n if (!(bytes.buffer instanceof ArrayBuffer)) {\n throw new TypeError(\"SharedArrayBuffer is not supported\");\n }\n\n return new Response(bytes.buffer, { status: 200, headers });\n}\n\nexport type AnnieResponseOptions = AnnieStreamOptions & {\n headers?: HeadersInit;\n cacheControl?: string;\n filename?: string;\n};\n\nexport function annieResponse(\n frames: AsyncIterable<Buffer>,\n options: AnnieResponseOptions = {},\n): Response {\n const {\n headers: extraHeaders,\n cacheControl = \"public, max-age=3600\",\n filename,\n ...streamOptions\n } = options;\n\n const stream = annieStream(frames, streamOptions);\n\n const headers = new Headers(extraHeaders);\n headers.set(\"Content-Type\", \"application/x-tar\");\n if (cacheControl) {\n headers.set(\"Cache-Control\", cacheControl);\n }\n if (filename) {\n headers.set(\"Content-Disposition\", `inline; filename=\"${filename}.tar\"`);\n }\n\n return new Response(stream, { status: 200, headers });\n}\n\nexport type EffieResponseOptions = {\n headers?: HeadersInit;\n cacheControl?: string;\n};\n\nexport function effieResponse<S extends EffieSources>(\n data: EffieData<S>,\n options: EffieResponseOptions = {},\n): Response {\n const { headers: extraHeaders, cacheControl = \"public, max-age=3600\" } =\n options;\n\n const headers = new Headers(extraHeaders);\n if (cacheControl) {\n headers.set(\"Cache-Control\", cacheControl);\n }\n\n return Response.json(data, { status: 200, headers });\n}\n"],"mappings":";AASA,IAAI,eAAsC;AAC1C,IAAI,aAAkC;AAE/B,SAAS,cAAc,QAGrB;AACP,iBAAe,OAAO;AACtB,eAAa,OAAO;AACtB;AAEA,SAAS,kBAAkC;AACzC,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBAA8B;AACrC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,SACd,MACA,IACsB;AACtB,SAAO,gBAAgB,EAAE,WAAW,MAAM,EAAE;AAC9C;AAEO,SAAS,MACd,MACA,IACA,OACA,QACsB;AACtB,SAAO,cAAc,EAAE,SAAS,MAAM,IAAI,OAAO,MAAM;AACzD;AAEO,SAAS,YAAY,MAAwB;AAClD,SAAO,gBAAgB,EAAE,YAAY,IAAI;AAC3C;AAEO,SAAS,eAAe,MAAc,IAAqB;AAChE,SAAO,gBAAgB,EAAE,UAAU,MAAM,EAAE;AAC7C;;;AC3DA,SAAS,mBAAmB;AAQ5B,SAAS,uBAAuB,OAA2B;AACzD,MAAI,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM,GAAM,QAAO;AACnD,MAAI,MAAM,CAAC,MAAM,OAAQ,MAAM,CAAC,MAAM,IAAM,QAAO;AACnD,QAAM,IAAI,MAAM,gDAAgD;AAClE;AAEO,SAAS,cACd,OACA,UAAgC,CAAC,GACvB;AACV,QAAM,EAAE,SAAS,cAAc,eAAe,uBAAuB,IACnE;AAEF,QAAM,UAAU,IAAI,QAAQ,YAAY;AACxC,UAAQ,IAAI,gBAAgB,uBAAuB,KAAK,CAAC;AACzD,MAAI,cAAc;AAChB,YAAQ,IAAI,iBAAiB,YAAY;AAAA,EAC3C;AAEA,MAAI,EAAE,MAAM,kBAAkB,cAAc;AAC1C,UAAM,IAAI,UAAU,oCAAoC;AAAA,EAC1D;AAEA,SAAO,IAAI,SAAS,MAAM,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAC5D;AAQO,SAAS,cACd,QACA,UAAgC,CAAC,GACvB;AACV,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,eAAe;AAAA,IACf;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,SAAS,YAAY,QAAQ,aAAa;AAEhD,QAAM,UAAU,IAAI,QAAQ,YAAY;AACxC,UAAQ,IAAI,gBAAgB,mBAAmB;AAC/C,MAAI,cAAc;AAChB,YAAQ,IAAI,iBAAiB,YAAY;AAAA,EAC3C;AACA,MAAI,UAAU;AACZ,YAAQ,IAAI,uBAAuB,qBAAqB,QAAQ,OAAO;AAAA,EACzE;AAEA,SAAO,IAAI,SAAS,QAAQ,EAAE,QAAQ,KAAK,QAAQ,CAAC;AACtD;AAOO,SAAS,cACd,MACA,UAAgC,CAAC,GACvB;AACV,QAAM,EAAE,SAAS,cAAc,eAAe,uBAAuB,IACnE;AAEF,QAAM,UAAU,IAAI,QAAQ,YAAY;AACxC,MAAI,cAAc;AAChB,YAAQ,IAAI,iBAAiB,YAAY;AAAA,EAC3C;AAEA,SAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AACrD;","names":[]}
1
+ {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["import type { EffieWebUrl } from \"@effing/effie\";\nimport type {\n Bounds,\n FnKind,\n FnModule,\n FnModuleLoader,\n FnUrlBuilder,\n} from \"./types\";\n\nlet moduleLoader: FnModuleLoader | null = null;\nlet urlBuilder: FnUrlBuilder | null = null;\n\nexport function initFnRuntime(config: {\n moduleLoader: FnModuleLoader;\n urlBuilder: FnUrlBuilder;\n}): void {\n moduleLoader = config.moduleLoader;\n urlBuilder = config.urlBuilder;\n}\n\nfunction getModuleLoader(): FnModuleLoader {\n if (!moduleLoader) {\n throw new Error(\n \"attempting to access module loader before initFnRuntime() was called\",\n );\n }\n return moduleLoader;\n}\n\nfunction getUrlBuilder(): FnUrlBuilder {\n if (!urlBuilder) {\n throw new Error(\n \"attempting to access URL builder before initFnRuntime() was called\",\n );\n }\n return urlBuilder;\n}\n\nexport function fnModule<K extends FnKind>(\n kind: K,\n id: string,\n): Promise<FnModule<K>> {\n return getModuleLoader().loadModule(kind, id);\n}\n\nexport function fnUrl<P extends Record<string, unknown>>(\n kind: FnKind,\n id: string,\n props: P,\n bounds: Bounds,\n): Promise<EffieWebUrl> {\n return getUrlBuilder().buildUrl(kind, id, props, bounds);\n}\n\nexport function fnModuleIds(kind: FnKind): string[] {\n return getModuleLoader().listModules(kind);\n}\n\nexport function fnModuleExists(kind: FnKind, id: string): boolean {\n return getModuleLoader().hasModule(kind, id);\n}\n"],"mappings":";;;;;;;AASA,IAAI,eAAsC;AAC1C,IAAI,aAAkC;AAE/B,SAAS,cAAc,QAGrB;AACP,iBAAe,OAAO;AACtB,eAAa,OAAO;AACtB;AAEA,SAAS,kBAAkC;AACzC,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBAA8B;AACrC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,SACd,MACA,IACsB;AACtB,SAAO,gBAAgB,EAAE,WAAW,MAAM,EAAE;AAC9C;AAEO,SAAS,MACd,MACA,IACA,OACA,QACsB;AACtB,SAAO,cAAc,EAAE,SAAS,MAAM,IAAI,OAAO,MAAM;AACzD;AAEO,SAAS,YAAY,MAAwB;AAClD,SAAO,gBAAgB,EAAE,YAAY,IAAI;AAC3C;AAEO,SAAS,eAAe,MAAc,IAAqB;AAChE,SAAO,gBAAgB,EAAE,UAAU,MAAM,EAAE;AAC7C;","names":[]}
@@ -0,0 +1,56 @@
1
+ import { B as Bounds, b as FnKind, a as FnUrlBuilder, F as FnModuleLoader } from '../types-8Q-DJL6t.js';
2
+ import { ServerResponse, RequestListener } from 'node:http';
3
+ import 'zod';
4
+ import '@effing/effie';
5
+
6
+ declare const MAX_DIMENSION = 8192;
7
+ type FnSegmentPayload = {
8
+ id: string;
9
+ props?: Record<string, unknown>;
10
+ bounds: Bounds;
11
+ };
12
+ declare function validateBounds(raw: unknown): Bounds;
13
+ declare function signFnSegment(payload: FnSegmentPayload, secretKey: string): Promise<string>;
14
+ type VerifyFnSegmentOptions = {
15
+ /** When true (default), the segment must include a props object. */
16
+ requireProps?: boolean;
17
+ };
18
+ declare function verifyFnSegment(segment: string, secretKey: string, options?: VerifyFnSegmentOptions): Promise<FnSegmentPayload>;
19
+
20
+ type CreateUrlBuilderOptions = {
21
+ secretKey: string;
22
+ /**
23
+ * Compose the final URL from a kind and a signed segment. Self-host typically
24
+ * returns `${baseUrl}/${kind}/${segment}`; the cloud fn server uses a
25
+ * tenant/project-prefixed shape.
26
+ */
27
+ buildUrl: (kind: FnKind, segment: string) => string;
28
+ };
29
+ declare function createUrlBuilder(options: CreateUrlBuilderOptions): FnUrlBuilder;
30
+ type CreateFlatUrlBuilderOptions = {
31
+ /** Base URL the fn server is reachable at — e.g. `http://localhost:3839` or `https://example.com`. */
32
+ baseUrl: string;
33
+ secretKey: string;
34
+ };
35
+ /** Convenience: builds `${baseUrl}/${kind}/${segment}` URLs. */
36
+ declare function createFlatUrlBuilder(options: CreateFlatUrlBuilderOptions): FnUrlBuilder;
37
+
38
+ /**
39
+ * Send a Web `Response` (as returned by `imageResponse`/`annieResponse`/
40
+ * `effieResponse`) over a Node `ServerResponse`. Headers and status are
41
+ * copied; the body is streamed if present.
42
+ */
43
+ declare function pipeWebResponse(res: ServerResponse, response: Response): Promise<void>;
44
+
45
+ type CreateFnHttpListenerOptions = {
46
+ moduleLoader: FnModuleLoader;
47
+ secretKey: string;
48
+ /**
49
+ * Optional Cache-Control header value for successful responses. Defaults to
50
+ * `undefined` (the underlying response helpers apply their own default).
51
+ */
52
+ cacheControl?: string;
53
+ };
54
+ declare function createFnHttpListener(options: CreateFnHttpListenerOptions): RequestListener;
55
+
56
+ export { type CreateFlatUrlBuilderOptions, type CreateFnHttpListenerOptions, type CreateUrlBuilderOptions, type FnSegmentPayload, MAX_DIMENSION, type VerifyFnSegmentOptions, createFlatUrlBuilder, createFnHttpListener, createUrlBuilder, pipeWebResponse, signFnSegment, validateBounds, verifyFnSegment };
@@ -0,0 +1,204 @@
1
+ import {
2
+ annieResponse,
3
+ effieResponse,
4
+ imageResponse
5
+ } from "../chunk-UHDIQDAF.js";
6
+
7
+ // src/server/segments.ts
8
+ import { deserialize, serialize } from "@effing/serde";
9
+ var MAX_DIMENSION = 8192;
10
+ function validateBounds(raw) {
11
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
12
+ throw new Error('Segment missing "bounds"');
13
+ }
14
+ const { width, height } = raw;
15
+ if (!Number.isInteger(width) || !Number.isInteger(height)) {
16
+ throw new Error("Bounds must be integers");
17
+ }
18
+ const w = width;
19
+ const h = height;
20
+ if (w <= 0 || h <= 0 || w > MAX_DIMENSION || h > MAX_DIMENSION) {
21
+ throw new Error(`Bounds out of range (1..${MAX_DIMENSION}): ${w}x${h}`);
22
+ }
23
+ return { width: w, height: h };
24
+ }
25
+ function signFnSegment(payload, secretKey) {
26
+ return serialize(payload, secretKey);
27
+ }
28
+ async function verifyFnSegment(segment, secretKey, options = {}) {
29
+ const { requireProps = true } = options;
30
+ let payload;
31
+ try {
32
+ payload = await deserialize(segment, secretKey);
33
+ } catch {
34
+ throw new Error("Invalid segment signature");
35
+ }
36
+ if (typeof payload.id !== "string" || payload.id === "") {
37
+ throw new Error('Segment missing "id"');
38
+ }
39
+ let props;
40
+ if (requireProps) {
41
+ if (payload.props === null || typeof payload.props !== "object" || Array.isArray(payload.props)) {
42
+ throw new Error('Segment missing "props"');
43
+ }
44
+ props = payload.props;
45
+ } else if (payload.props !== void 0 && payload.props !== null && typeof payload.props === "object" && !Array.isArray(payload.props)) {
46
+ props = payload.props;
47
+ }
48
+ const bounds = validateBounds(payload.bounds);
49
+ return { id: payload.id, props, bounds };
50
+ }
51
+
52
+ // src/server/url-builder.ts
53
+ import { effieWebUrl } from "@effing/effie";
54
+ function createUrlBuilder(options) {
55
+ const { secretKey, buildUrl } = options;
56
+ return {
57
+ async buildUrl(kind, id, props, bounds) {
58
+ const segment = await signFnSegment({ id, props, bounds }, secretKey);
59
+ return effieWebUrl(buildUrl(kind, segment));
60
+ }
61
+ };
62
+ }
63
+ function createFlatUrlBuilder(options) {
64
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
65
+ return createUrlBuilder({
66
+ secretKey: options.secretKey,
67
+ buildUrl: (kind, segment) => `${baseUrl}/${kind}/${segment}`
68
+ });
69
+ }
70
+
71
+ // src/server/pipe-response.ts
72
+ import { Readable } from "stream";
73
+ async function pipeWebResponse(res, response) {
74
+ res.statusCode = response.status;
75
+ response.headers.forEach((value, key) => {
76
+ res.setHeader(key, value);
77
+ });
78
+ const body = response.body;
79
+ if (!body) {
80
+ res.end();
81
+ return;
82
+ }
83
+ const nodeStream = Readable.fromWeb(
84
+ body
85
+ );
86
+ nodeStream.pipe(res);
87
+ await new Promise((resolve, reject) => {
88
+ nodeStream.on("end", () => resolve());
89
+ nodeStream.on("error", reject);
90
+ res.on("close", () => resolve());
91
+ });
92
+ }
93
+
94
+ // src/server/listener.ts
95
+ var KIND_PATTERNS = [
96
+ { kind: "image", pattern: /^\/image\/([^/?#]+)$/ },
97
+ { kind: "annie", pattern: /^\/annie\/([^/?#]+)$/ },
98
+ { kind: "effie", pattern: /^\/effie\/([^/?#]+)$/ }
99
+ ];
100
+ function matchRoute(url) {
101
+ if (!url) return null;
102
+ const path = url.split("?")[0];
103
+ for (const { kind, pattern } of KIND_PATTERNS) {
104
+ const m = pattern.exec(path);
105
+ if (m) return { kind, segment: decodeURIComponent(m[1]) };
106
+ }
107
+ return null;
108
+ }
109
+ function sendJsonError(res, status, error) {
110
+ if (res.headersSent) {
111
+ if (!res.writableEnded) res.end();
112
+ return;
113
+ }
114
+ res.statusCode = status;
115
+ res.setHeader("Content-Type", "application/json");
116
+ res.end(JSON.stringify({ error }));
117
+ }
118
+ function createFnHttpListener(options) {
119
+ const { moduleLoader, secretKey, cacheControl } = options;
120
+ return (req, res) => {
121
+ handleRequest(req, res, { moduleLoader, secretKey, cacheControl }).catch(
122
+ (err) => {
123
+ const message = err instanceof Error ? err.message : String(err);
124
+ console.error("[fn-server] unhandled error:", message);
125
+ sendJsonError(res, 500, "Internal error");
126
+ }
127
+ );
128
+ };
129
+ }
130
+ async function handleRequest(req, res, options) {
131
+ const { moduleLoader, secretKey, cacheControl } = options;
132
+ if (req.method !== "GET" && req.method !== "HEAD") {
133
+ res.statusCode = 405;
134
+ res.setHeader("Allow", "GET, HEAD");
135
+ res.end();
136
+ return;
137
+ }
138
+ const matched = matchRoute(req.url);
139
+ if (!matched) {
140
+ sendJsonError(res, 404, "Not found");
141
+ return;
142
+ }
143
+ let payload;
144
+ try {
145
+ payload = await verifyFnSegment(matched.segment, secretKey);
146
+ } catch (err) {
147
+ const message = err instanceof Error ? err.message : "Invalid segment";
148
+ sendJsonError(res, 400, message);
149
+ return;
150
+ }
151
+ if (!moduleLoader.hasModule(matched.kind, payload.id)) {
152
+ sendJsonError(res, 404, `No ${matched.kind} found for id '${payload.id}'`);
153
+ return;
154
+ }
155
+ const mod = await moduleLoader.loadModule(matched.kind, payload.id);
156
+ let parsedProps = payload.props ?? {};
157
+ if (mod.propsSchema) {
158
+ try {
159
+ parsedProps = mod.propsSchema.parse(parsedProps);
160
+ } catch (err) {
161
+ const message = err instanceof Error ? err.message : "Invalid props";
162
+ sendJsonError(res, 400, message);
163
+ return;
164
+ }
165
+ }
166
+ const runnerArgs = { props: parsedProps, bounds: payload.bounds };
167
+ if (matched.kind === "image") {
168
+ const bytes = await mod.runner(runnerArgs);
169
+ await pipeWebResponse(res, imageResponse(bytes, { cacheControl }));
170
+ return;
171
+ }
172
+ if (matched.kind === "annie") {
173
+ const frames = mod.runner(runnerArgs);
174
+ const abort = new AbortController();
175
+ res.on("close", () => abort.abort());
176
+ await pipeWebResponse(
177
+ res,
178
+ annieResponse(frames, {
179
+ signal: abort.signal,
180
+ filename: payload.id,
181
+ cacheControl
182
+ })
183
+ );
184
+ return;
185
+ }
186
+ const data = await mod.runner(runnerArgs);
187
+ await pipeWebResponse(
188
+ res,
189
+ effieResponse(data, {
190
+ cacheControl
191
+ })
192
+ );
193
+ }
194
+ export {
195
+ MAX_DIMENSION,
196
+ createFlatUrlBuilder,
197
+ createFnHttpListener,
198
+ createUrlBuilder,
199
+ pipeWebResponse,
200
+ signFnSegment,
201
+ validateBounds,
202
+ verifyFnSegment
203
+ };
204
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/segments.ts","../../src/server/url-builder.ts","../../src/server/pipe-response.ts","../../src/server/listener.ts"],"sourcesContent":["import { deserialize, serialize } from \"@effing/serde\";\nimport type { Bounds } from \"../types\";\n\nexport const MAX_DIMENSION = 8192;\n\nexport type FnSegmentPayload = {\n id: string;\n props?: Record<string, unknown>;\n bounds: Bounds;\n};\n\nexport function validateBounds(raw: unknown): Bounds {\n if (raw === null || typeof raw !== \"object\" || Array.isArray(raw)) {\n throw new Error('Segment missing \"bounds\"');\n }\n const { width, height } = raw as { width?: unknown; height?: unknown };\n if (!Number.isInteger(width) || !Number.isInteger(height)) {\n throw new Error(\"Bounds must be integers\");\n }\n const w = width as number;\n const h = height as number;\n if (w <= 0 || h <= 0 || w > MAX_DIMENSION || h > MAX_DIMENSION) {\n throw new Error(`Bounds out of range (1..${MAX_DIMENSION}): ${w}x${h}`);\n }\n return { width: w, height: h };\n}\n\nexport function signFnSegment(\n payload: FnSegmentPayload,\n secretKey: string,\n): Promise<string> {\n return serialize(payload, secretKey);\n}\n\nexport type VerifyFnSegmentOptions = {\n /** When true (default), the segment must include a props object. */\n requireProps?: boolean;\n};\n\nexport async function verifyFnSegment(\n segment: string,\n secretKey: string,\n options: VerifyFnSegmentOptions = {},\n): Promise<FnSegmentPayload> {\n const { requireProps = true } = options;\n\n let payload: { id?: unknown; props?: unknown; bounds?: unknown };\n try {\n payload = await deserialize<{\n id?: unknown;\n props?: unknown;\n bounds?: unknown;\n }>(segment, secretKey);\n } catch {\n throw new Error(\"Invalid segment signature\");\n }\n\n if (typeof payload.id !== \"string\" || payload.id === \"\") {\n throw new Error('Segment missing \"id\"');\n }\n\n let props: Record<string, unknown> | undefined;\n if (requireProps) {\n if (\n payload.props === null ||\n typeof payload.props !== \"object\" ||\n Array.isArray(payload.props)\n ) {\n throw new Error('Segment missing \"props\"');\n }\n props = payload.props as Record<string, unknown>;\n } else if (\n payload.props !== undefined &&\n payload.props !== null &&\n typeof payload.props === \"object\" &&\n !Array.isArray(payload.props)\n ) {\n props = payload.props as Record<string, unknown>;\n }\n\n const bounds = validateBounds(payload.bounds);\n return { id: payload.id, props, bounds };\n}\n","import { effieWebUrl, type EffieWebUrl } from \"@effing/effie\";\nimport type { Bounds, FnKind, FnUrlBuilder } from \"../types\";\nimport { signFnSegment } from \"./segments\";\n\nexport type CreateUrlBuilderOptions = {\n secretKey: string;\n /**\n * Compose the final URL from a kind and a signed segment. Self-host typically\n * returns `${baseUrl}/${kind}/${segment}`; the cloud fn server uses a\n * tenant/project-prefixed shape.\n */\n buildUrl: (kind: FnKind, segment: string) => string;\n};\n\nexport function createUrlBuilder(\n options: CreateUrlBuilderOptions,\n): FnUrlBuilder {\n const { secretKey, buildUrl } = options;\n return {\n async buildUrl(\n kind: FnKind,\n id: string,\n props: Record<string, unknown>,\n bounds: Bounds,\n ): Promise<EffieWebUrl> {\n const segment = await signFnSegment({ id, props, bounds }, secretKey);\n return effieWebUrl(buildUrl(kind, segment));\n },\n };\n}\n\nexport type CreateFlatUrlBuilderOptions = {\n /** Base URL the fn server is reachable at — e.g. `http://localhost:3839` or `https://example.com`. */\n baseUrl: string;\n secretKey: string;\n};\n\n/** Convenience: builds `${baseUrl}/${kind}/${segment}` URLs. */\nexport function createFlatUrlBuilder(\n options: CreateFlatUrlBuilderOptions,\n): FnUrlBuilder {\n const baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n return createUrlBuilder({\n secretKey: options.secretKey,\n buildUrl: (kind, segment) => `${baseUrl}/${kind}/${segment}`,\n });\n}\n","import type { ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\n\n/**\n * Send a Web `Response` (as returned by `imageResponse`/`annieResponse`/\n * `effieResponse`) over a Node `ServerResponse`. Headers and status are\n * copied; the body is streamed if present.\n */\nexport async function pipeWebResponse(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n const body = response.body;\n if (!body) {\n res.end();\n return;\n }\n\n const nodeStream = Readable.fromWeb(\n body as unknown as import(\"node:stream/web\").ReadableStream<Uint8Array>,\n );\n nodeStream.pipe(res);\n\n await new Promise<void>((resolve, reject) => {\n nodeStream.on(\"end\", () => resolve());\n nodeStream.on(\"error\", reject);\n res.on(\"close\", () => resolve());\n });\n}\n","import type {\n IncomingMessage,\n RequestListener,\n ServerResponse,\n} from \"node:http\";\nimport type { FnKind, FnModuleLoader } from \"../types\";\nimport { imageResponse, annieResponse, effieResponse } from \"../responses\";\nimport { verifyFnSegment } from \"./segments\";\nimport { pipeWebResponse } from \"./pipe-response\";\n\nexport type CreateFnHttpListenerOptions = {\n moduleLoader: FnModuleLoader;\n secretKey: string;\n /**\n * Optional Cache-Control header value for successful responses. Defaults to\n * `undefined` (the underlying response helpers apply their own default).\n */\n cacheControl?: string;\n};\n\nconst KIND_PATTERNS: { kind: FnKind; pattern: RegExp }[] = [\n { kind: \"image\", pattern: /^\\/image\\/([^/?#]+)$/ },\n { kind: \"annie\", pattern: /^\\/annie\\/([^/?#]+)$/ },\n { kind: \"effie\", pattern: /^\\/effie\\/([^/?#]+)$/ },\n];\n\nfunction matchRoute(\n url: string | undefined,\n): { kind: FnKind; segment: string } | null {\n if (!url) return null;\n const path = url.split(\"?\")[0];\n for (const { kind, pattern } of KIND_PATTERNS) {\n const m = pattern.exec(path);\n if (m) return { kind, segment: decodeURIComponent(m[1]) };\n }\n return null;\n}\n\nfunction sendJsonError(\n res: ServerResponse,\n status: number,\n error: string,\n): void {\n if (res.headersSent) {\n if (!res.writableEnded) res.end();\n return;\n }\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error }));\n}\n\nexport function createFnHttpListener(\n options: CreateFnHttpListenerOptions,\n): RequestListener {\n const { moduleLoader, secretKey, cacheControl } = options;\n\n return (req: IncomingMessage, res: ServerResponse) => {\n handleRequest(req, res, { moduleLoader, secretKey, cacheControl }).catch(\n (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n\n console.error(\"[fn-server] unhandled error:\", message);\n sendJsonError(res, 500, \"Internal error\");\n },\n );\n };\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n options: Required<\n Pick<CreateFnHttpListenerOptions, \"moduleLoader\" | \"secretKey\">\n > & {\n cacheControl?: string;\n },\n): Promise<void> {\n const { moduleLoader, secretKey, cacheControl } = options;\n\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n res.statusCode = 405;\n res.setHeader(\"Allow\", \"GET, HEAD\");\n res.end();\n return;\n }\n\n const matched = matchRoute(req.url);\n if (!matched) {\n sendJsonError(res, 404, \"Not found\");\n return;\n }\n\n let payload;\n try {\n payload = await verifyFnSegment(matched.segment, secretKey);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Invalid segment\";\n sendJsonError(res, 400, message);\n return;\n }\n\n if (!moduleLoader.hasModule(matched.kind, payload.id)) {\n sendJsonError(res, 404, `No ${matched.kind} found for id '${payload.id}'`);\n return;\n }\n\n const mod = await moduleLoader.loadModule(matched.kind, payload.id);\n let parsedProps: unknown = payload.props ?? {};\n if (mod.propsSchema) {\n try {\n parsedProps = mod.propsSchema.parse(parsedProps);\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Invalid props\";\n sendJsonError(res, 400, message);\n return;\n }\n }\n\n const runnerArgs = { props: parsedProps, bounds: payload.bounds };\n\n if (matched.kind === \"image\") {\n const bytes = await (\n mod as { runner: (a: typeof runnerArgs) => Promise<Buffer> }\n ).runner(runnerArgs);\n await pipeWebResponse(res, imageResponse(bytes, { cacheControl }));\n return;\n }\n\n if (matched.kind === \"annie\") {\n const frames = (\n mod as { runner: (a: typeof runnerArgs) => AsyncIterable<Buffer> }\n ).runner(runnerArgs);\n const abort = new AbortController();\n res.on(\"close\", () => abort.abort());\n await pipeWebResponse(\n res,\n annieResponse(frames, {\n signal: abort.signal,\n filename: payload.id,\n cacheControl,\n }),\n );\n return;\n }\n\n const data = await (\n mod as { runner: (a: typeof runnerArgs) => Promise<unknown> }\n ).runner(runnerArgs);\n await pipeWebResponse(\n res,\n effieResponse(data as Parameters<typeof effieResponse>[0], {\n cacheControl,\n }),\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ import { EffieSources, EffieData, EffieWebUrl } from '@effing/effie';
3
+
4
+ type Bounds = Readonly<{
5
+ width: number;
6
+ height: number;
7
+ }>;
8
+ type RunnerArgs<P> = {
9
+ props: P;
10
+ bounds: Bounds;
11
+ };
12
+ type FnKind = "image" | "annie" | "effie";
13
+ type BaseFnModule = {
14
+ propsSchema: z.ZodSchema<unknown>;
15
+ previewProps: object;
16
+ };
17
+ type ImageRunnerReturn = Promise<Buffer>;
18
+ type AnnieRunnerReturn = AsyncIterable<Buffer>;
19
+ type EffieRunnerReturn<S extends EffieSources = EffieSources> = Promise<EffieData<S>>;
20
+ type ImageFnModule = BaseFnModule & {
21
+ runner: (args: RunnerArgs<unknown>) => ImageRunnerReturn;
22
+ };
23
+ type AnnieFnModule = BaseFnModule & {
24
+ runner: (args: RunnerArgs<unknown>) => AnnieRunnerReturn;
25
+ };
26
+ type EffieFnModule = BaseFnModule & {
27
+ runner: (args: RunnerArgs<unknown>) => EffieRunnerReturn;
28
+ };
29
+ type FnModule<K extends FnKind> = K extends "image" ? ImageFnModule : K extends "annie" ? AnnieFnModule : EffieFnModule;
30
+ type FnModuleLoader = {
31
+ loadModule<K extends FnKind>(kind: K, id: string): Promise<FnModule<K>>;
32
+ listModules(kind: FnKind): string[];
33
+ hasModule(kind: FnKind, id: string): boolean;
34
+ };
35
+ type FnUrlBuilder = {
36
+ buildUrl<P extends Record<string, unknown>>(kind: FnKind, id: string, props: P, bounds: Bounds): Promise<EffieWebUrl>;
37
+ };
38
+
39
+ export type { AnnieRunnerReturn as A, Bounds as B, EffieRunnerReturn as E, FnModuleLoader as F, ImageRunnerReturn as I, RunnerArgs as R, FnUrlBuilder as a, FnKind as b, FnModule as c, ImageFnModule as d, AnnieFnModule as e, EffieFnModule as f };
package/package.json CHANGED
@@ -1,26 +1,31 @@
1
1
  {
2
2
  "name": "@effing/fn",
3
- "version": "0.33.1",
3
+ "version": "0.34.0",
4
4
  "description": "Effing function runtime — pluggable module loading and URL building",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
8
  "types": "./dist/index.d.ts",
9
9
  "import": "./dist/index.js"
10
+ },
11
+ "./server": {
12
+ "types": "./dist/server/index.d.ts",
13
+ "import": "./dist/server/index.js"
10
14
  }
11
15
  },
12
16
  "files": [
13
17
  "dist"
14
18
  ],
15
19
  "dependencies": {
16
- "@effing/annie": "0.33.1"
20
+ "@effing/annie": "0.34.0",
21
+ "@effing/effie": "0.34.0",
22
+ "@effing/serde": "0.34.0"
17
23
  },
18
24
  "devDependencies": {
19
25
  "tsup": "^8.0.0",
20
26
  "typescript": "^5.9.3",
21
27
  "vitest": "^3.2.4",
22
- "zod": "^3.25.76",
23
- "@effing/effie": "0.33.1"
28
+ "zod": "^3.25.76"
24
29
  },
25
30
  "peerDependencies": {
26
31
  "zod": "^3.0.0"