@neondatabase/functions 0.1.2 → 0.2.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.
package/README.md CHANGED
@@ -10,19 +10,42 @@ npm install @neondatabase/functions
10
10
 
11
11
  ## Usage
12
12
 
13
+ The API mirrors [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package): import `waitUntil` and call it directly with the promise you want to keep alive.
14
+
13
15
  ```ts
14
16
  import { waitUntil } from "@neondatabase/functions/v1";
15
17
 
16
18
  export default {
17
19
  async fetch(req: Request): Promise<Response> {
18
- const defer = waitUntil();
19
20
  // Fire-and-forget background work that should outlive the response.
20
- defer(logRequest(req));
21
+ waitUntil(logRequest(req));
21
22
  return new Response("ok");
22
23
  },
23
24
  };
24
25
  ```
25
26
 
26
- > **Status: not implemented yet.** `waitUntil()` currently returns a no-op function the
27
- > promise you pass is accepted and ignored. Once the Neon Functions runtime ships, it will
28
- > register the promise with the host so the invocation stays alive until it settles.
27
+ `waitUntil(promise)` forwards the promise to the Neon Functions runtime, which keeps
28
+ the invocation alive until the promise settles (up to the 15-minute `waitUntil` limit).
29
+ When no invocation context is in scope local dev, tests, or any non-Neon host — it is
30
+ a **no-op**: the promise is accepted and ignored, so the same code runs everywhere
31
+ without branching. Passing a non-`Promise` throws a `TypeError`.
32
+
33
+ ## Runtime integration
34
+
35
+ The runtime carries the per-invocation context in an `AsyncLocalStorage` and publishes
36
+ an accessor at `globalThis[NEON_FUNCTIONS_CONTEXT]` (a
37
+ `Symbol.for("@neondatabase/functions/request-context")`) shaped like the providers used
38
+ by Vercel and Next.js — a stable object exposing `get()`.
39
+
40
+ To make `waitUntil` resolve to a given invocation, wrap the handler with
41
+ `runWithRequestContext`. This is intended for the Neon Functions runtime; application
42
+ code should not need it.
43
+
44
+ ```ts
45
+ import { runWithRequestContext } from "@neondatabase/functions/v1";
46
+
47
+ runWithRequestContext({ waitUntil: realWaitUntil }, () => handler(req));
48
+ ```
49
+
50
+ Because the context lives in `AsyncLocalStorage`, concurrent invocations in the same
51
+ isolate each see their own context and never clobber one another.
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { WaitUntil, waitUntil } from "./lib/wait-until.js";
2
- export { WaitUntil, waitUntil };
1
+ import { NEON_FUNCTIONS_CONTEXT, NeonFunctionsContext, WaitUntil, runWithRequestContext, waitUntil } from "./lib/wait-until.js";
2
+ export { NEON_FUNCTIONS_CONTEXT, NeonFunctionsContext, WaitUntil, runWithRequestContext, waitUntil };
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- import { waitUntil } from "./lib/wait-until.js";
2
- export { waitUntil };
1
+ import { NEON_FUNCTIONS_CONTEXT, runWithRequestContext, waitUntil } from "./lib/wait-until.js";
2
+ import "./v1.js";
3
+ export { NEON_FUNCTIONS_CONTEXT, runWithRequestContext, waitUntil };
@@ -1,20 +1,33 @@
1
1
  //#region src/lib/wait-until.d.ts
2
+ type WaitUntil = (promise: Promise<unknown>) => void;
2
3
  /**
3
- * `waitUntil` extends the lifetime of a Neon Function invocation so background work
4
- * (logging, cache writes, analytics, …) can finish after the response has been sent.
5
- *
6
- * It mirrors the platform primitive exposed by Cloudflare Workers / Vercel
7
- * (`ctx.waitUntil(promise)`).
4
+ * The slice of the runtime context this package reads. The runtime may attach
5
+ * additional fields; only `waitUntil` is consumed here.
8
6
  */
9
- type WaitUntil = (promise: Promise<unknown>) => void;
7
+ type NeonFunctionsContext = {
8
+ waitUntil?: WaitUntil;
9
+ };
10
10
  /**
11
- * Returns a `waitUntil` function for the current invocation.
11
+ * Well-known `globalThis` symbol under which the per-invocation context provider is
12
+ * published. Mirrors the convention used by Vercel and Next.js.
13
+ */
14
+ declare const NEON_FUNCTIONS_CONTEXT: unique symbol;
15
+ /**
16
+ * Defers async work past the response by forwarding the promise to the Neon Functions
17
+ * runtime, which keeps the invocation alive until the promise settles.
12
18
  *
13
- * NOT IMPLEMENTED YET: this is a no-op placeholder. The returned function accepts a
14
- * promise and ignores it. Once the Neon Functions runtime ships, this will register the
15
- * promise with the host so the invocation stays alive until it settles.
19
+ * The context is resolved at call time from the enclosing invocation, so this stays
20
+ * correct under concurrency. When no invocation context is in scope (local dev, tests,
21
+ * non-Neon hosts), this is a no-op: the promise is accepted and ignored.
22
+ */
23
+ declare function waitUntil(promise: Promise<unknown>): void;
24
+ /**
25
+ * Runtime entry point: binds `context` as the current invocation context for the
26
+ * duration of `fn` (and any async work it spawns), so calls to `waitUntil` inside it
27
+ * forward to `context.waitUntil`. Intended for the Neon Functions runtime to wrap each
28
+ * invocation; application code should not need this.
16
29
  */
17
- declare function waitUntil(): WaitUntil;
30
+ declare function runWithRequestContext<T>(context: NeonFunctionsContext, fn: () => T): T;
18
31
  //#endregion
19
- export { WaitUntil, waitUntil };
32
+ export { NEON_FUNCTIONS_CONTEXT, NeonFunctionsContext, WaitUntil, runWithRequestContext, waitUntil };
20
33
  //# sourceMappingURL=wait-until.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wait-until.d.ts","names":[],"sources":["../../src/lib/wait-until.ts"],"mappings":";;AAOA;AASA;;;;;KATY,SAAA,aAAsB;;;;;;;;iBASlB,SAAA,CAAA,GAAa"}
1
+ {"version":3,"file":"wait-until.d.ts","names":[],"sources":["../../src/lib/wait-until.ts"],"mappings":";KAWY,SAAA,aAAsB;AAAlC;AAMA;AAkBA;AAmDA;AAegB,KApFJ,oBAAA,GAoFyB;EAAA,SAAA,CAAA,EAnFxB,SAmFwB;;;;AAGjC;;cArES;;;;;;;;;iBAmDG,SAAA,UAAmB;;;;;;;iBAenB,kCACN,gCACC,IACR"}
@@ -1,15 +1,64 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  //#region src/lib/wait-until.ts
2
3
  /**
3
- * Returns a `waitUntil` function for the current invocation.
4
+ * `waitUntil` extends the lifetime of a Neon Function invocation so background work
5
+ * (logging, cache writes, analytics, …) can finish after the response has been sent.
4
6
  *
5
- * NOT IMPLEMENTED YET: this is a no-op placeholder. The returned function accepts a
6
- * promise and ignores it. Once the Neon Functions runtime ships, this will register the
7
- * promise with the host so the invocation stays alive until it settles.
7
+ * The public API mirrors Vercel's `@vercel/functions`: import `waitUntil` and call it
8
+ * directly with a promise (`waitUntil(promise)`). Under the hood the per-invocation
9
+ * context is carried by an `AsyncLocalStorage`, so concurrent invocations sharing the
10
+ * same isolate never clobber each other's context.
8
11
  */
9
- function waitUntil() {
10
- return (_promise) => {};
12
+ /**
13
+ * Well-known `globalThis` symbol under which the per-invocation context provider is
14
+ * published. Mirrors the convention used by Vercel and Next.js.
15
+ */
16
+ const NEON_FUNCTIONS_CONTEXT = Symbol.for("@neondatabase/functions/request-context");
17
+ /**
18
+ * Holds the per-invocation context. Each `runWithRequestContext(...)` call binds a
19
+ * fresh context for the duration of its callback (and any async work it spawns), so
20
+ * `waitUntil` always resolves to the right invocation even under concurrency.
21
+ */
22
+ const requestContextStore = new AsyncLocalStorage();
23
+ const globalWithContext = globalThis;
24
+ /**
25
+ * Publish a stable provider whose `get()` reads the current store. Installed once;
26
+ * if another copy of this module (or the host runtime) has already published a
27
+ * provider, we leave it in place rather than clobber it.
28
+ */
29
+ globalWithContext[NEON_FUNCTIONS_CONTEXT] ??= { get: () => requestContextStore.getStore() };
30
+ /**
31
+ * Reads the current invocation context off the `globalThis` provider, falling back to
32
+ * an empty context when no provider is published or no invocation is in scope.
33
+ */
34
+ function getContext() {
35
+ return globalWithContext[NEON_FUNCTIONS_CONTEXT]?.get?.() ?? {};
36
+ }
37
+ function isPromise(value) {
38
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
39
+ }
40
+ /**
41
+ * Defers async work past the response by forwarding the promise to the Neon Functions
42
+ * runtime, which keeps the invocation alive until the promise settles.
43
+ *
44
+ * The context is resolved at call time from the enclosing invocation, so this stays
45
+ * correct under concurrency. When no invocation context is in scope (local dev, tests,
46
+ * non-Neon hosts), this is a no-op: the promise is accepted and ignored.
47
+ */
48
+ function waitUntil(promise) {
49
+ if (!isPromise(promise)) throw new TypeError(`waitUntil can only be called with a Promise, got ${typeof promise}`);
50
+ getContext().waitUntil?.(promise);
51
+ }
52
+ /**
53
+ * Runtime entry point: binds `context` as the current invocation context for the
54
+ * duration of `fn` (and any async work it spawns), so calls to `waitUntil` inside it
55
+ * forward to `context.waitUntil`. Intended for the Neon Functions runtime to wrap each
56
+ * invocation; application code should not need this.
57
+ */
58
+ function runWithRequestContext(context, fn) {
59
+ return requestContextStore.run(context, fn);
11
60
  }
12
61
  //#endregion
13
- export { waitUntil };
62
+ export { NEON_FUNCTIONS_CONTEXT, runWithRequestContext, waitUntil };
14
63
 
15
64
  //# sourceMappingURL=wait-until.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wait-until.js","names":[],"sources":["../../src/lib/wait-until.ts"],"sourcesContent":["/**\n * `waitUntil` extends the lifetime of a Neon Function invocation so background work\n * (logging, cache writes, analytics, …) can finish after the response has been sent.\n *\n * It mirrors the platform primitive exposed by Cloudflare Workers / Vercel\n * (`ctx.waitUntil(promise)`).\n */\nexport type WaitUntil = (promise: Promise<unknown>) => void;\n\n/**\n * Returns a `waitUntil` function for the current invocation.\n *\n * NOT IMPLEMENTED YET: this is a no-op placeholder. The returned function accepts a\n * promise and ignores it. Once the Neon Functions runtime ships, this will register the\n * promise with the host so the invocation stays alive until it settles.\n */\nexport function waitUntil(): WaitUntil {\n\treturn (_promise: Promise<unknown>): void => {\n\t\t// no-op: the runtime integration is not implemented yet.\n\t};\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,YAAuB;CACtC,QAAQ,aAAqC,CAE7C;AACD"}
1
+ {"version":3,"file":"wait-until.js","names":[],"sources":["../../src/lib/wait-until.ts"],"sourcesContent":["/**\n * `waitUntil` extends the lifetime of a Neon Function invocation so background work\n * (logging, cache writes, analytics, …) can finish after the response has been sent.\n *\n * The public API mirrors Vercel's `@vercel/functions`: import `waitUntil` and call it\n * directly with a promise (`waitUntil(promise)`). Under the hood the per-invocation\n * context is carried by an `AsyncLocalStorage`, so concurrent invocations sharing the\n * same isolate never clobber each other's context.\n */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nexport type WaitUntil = (promise: Promise<unknown>) => void;\n\n/**\n * The slice of the runtime context this package reads. The runtime may attach\n * additional fields; only `waitUntil` is consumed here.\n */\nexport type NeonFunctionsContext = {\n\twaitUntil?: WaitUntil;\n};\n\n/**\n * The accessor published on `globalThis` so consumers can read the current\n * invocation context without importing the runtime. Mirrors the provider shape\n * used by Vercel (`Symbol.for(\"@vercel/request-context\")`) and Next.js\n * (`Symbol.for(\"@next/request-context\")`): a stable object exposing `get()`.\n */\ntype RequestContextProvider = {\n\tget(): NeonFunctionsContext | undefined;\n};\n\n/**\n * Well-known `globalThis` symbol under which the per-invocation context provider is\n * published. Mirrors the convention used by Vercel and Next.js.\n */\nexport const NEON_FUNCTIONS_CONTEXT: unique symbol = Symbol.for(\n\t\"@neondatabase/functions/request-context\",\n);\n\ntype GlobalWithContext = typeof globalThis & {\n\t[NEON_FUNCTIONS_CONTEXT]?: RequestContextProvider;\n};\n\n/**\n * Holds the per-invocation context. Each `runWithRequestContext(...)` call binds a\n * fresh context for the duration of its callback (and any async work it spawns), so\n * `waitUntil` always resolves to the right invocation even under concurrency.\n */\nconst requestContextStore = new AsyncLocalStorage<NeonFunctionsContext>();\n\nconst globalWithContext: GlobalWithContext = globalThis;\n\n/**\n * Publish a stable provider whose `get()` reads the current store. Installed once;\n * if another copy of this module (or the host runtime) has already published a\n * provider, we leave it in place rather than clobber it.\n */\nglobalWithContext[NEON_FUNCTIONS_CONTEXT] ??= {\n\tget: () => requestContextStore.getStore(),\n};\n\n/**\n * Reads the current invocation context off the `globalThis` provider, falling back to\n * an empty context when no provider is published or no invocation is in scope.\n */\nfunction getContext(): NeonFunctionsContext {\n\treturn globalWithContext[NEON_FUNCTIONS_CONTEXT]?.get?.() ?? {};\n}\n\nfunction isPromise(value: unknown): value is Promise<unknown> {\n\treturn (\n\t\ttypeof value === \"object\" &&\n\t\tvalue !== null &&\n\t\t\"then\" in value &&\n\t\ttypeof value.then === \"function\"\n\t);\n}\n\n/**\n * Defers async work past the response by forwarding the promise to the Neon Functions\n * runtime, which keeps the invocation alive until the promise settles.\n *\n * The context is resolved at call time from the enclosing invocation, so this stays\n * correct under concurrency. When no invocation context is in scope (local dev, tests,\n * non-Neon hosts), this is a no-op: the promise is accepted and ignored.\n */\nexport function waitUntil(promise: Promise<unknown>): void {\n\tif (!isPromise(promise)) {\n\t\tthrow new TypeError(\n\t\t\t`waitUntil can only be called with a Promise, got ${typeof promise}`,\n\t\t);\n\t}\n\tgetContext().waitUntil?.(promise);\n}\n\n/**\n * Runtime entry point: binds `context` as the current invocation context for the\n * duration of `fn` (and any async work it spawns), so calls to `waitUntil` inside it\n * forward to `context.waitUntil`. Intended for the Neon Functions runtime to wrap each\n * invocation; application code should not need this.\n */\nexport function runWithRequestContext<T>(\n\tcontext: NeonFunctionsContext,\n\tfn: () => T,\n): T {\n\treturn requestContextStore.run(context, fn);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmCA,MAAa,yBAAwC,OAAO,IAC3D,yCACD;;;;;;AAWA,MAAM,sBAAsB,IAAI,kBAAwC;AAExE,MAAM,oBAAuC;;;;;;AAO7C,kBAAkB,4BAA4B,EAC7C,WAAW,oBAAoB,SAAS,EACzC;;;;;AAMA,SAAS,aAAmC;CAC3C,OAAO,kBAAkB,uBAAuB,EAAE,MAAM,KAAK,CAAC;AAC/D;AAEA,SAAS,UAAU,OAA2C;CAC7D,OACC,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAO,MAAM,SAAS;AAExB;;;;;;;;;AAUA,SAAgB,UAAU,SAAiC;CAC1D,IAAI,CAAC,UAAU,OAAO,GACrB,MAAM,IAAI,UACT,oDAAoD,OAAO,SAC5D;CAED,WAAW,CAAC,CAAC,YAAY,OAAO;AACjC;;;;;;;AAQA,SAAgB,sBACf,SACA,IACI;CACJ,OAAO,oBAAoB,IAAI,SAAS,EAAE;AAC3C"}
package/dist/v1.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { WaitUntil, waitUntil } from "./lib/wait-until.js";
2
- export { type WaitUntil, waitUntil };
1
+ import { NEON_FUNCTIONS_CONTEXT, NeonFunctionsContext, WaitUntil, runWithRequestContext, waitUntil } from "./lib/wait-until.js";
2
+ export { NEON_FUNCTIONS_CONTEXT, type NeonFunctionsContext, type WaitUntil, runWithRequestContext, waitUntil };
package/dist/v1.js CHANGED
@@ -1,2 +1,2 @@
1
- import { waitUntil } from "./lib/wait-until.js";
2
- export { waitUntil };
1
+ import { NEON_FUNCTIONS_CONTEXT, runWithRequestContext, waitUntil } from "./lib/wait-until.js";
2
+ export { NEON_FUNCTIONS_CONTEXT, runWithRequestContext, waitUntil };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neondatabase/functions",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Runtime helpers for Neon Functions. Currently provides a `waitUntil` primitive for deferring async work past a response.",
5
5
  "keywords": [
6
6
  "neon",