@pulse-compute/runtime 1.0.0-beta.4 → 1.0.0-beta.6

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
@@ -7,9 +7,9 @@
7
7
  > **Supported entry points:** `@pulse-compute/runtime`<br>
8
8
  > **Stability:** Supported application authoring and execution contract.<br>
9
9
  > **npm:** [`@pulse-compute/runtime`](https://www.npmjs.com/package/@pulse-compute/runtime)<br>
10
- > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.4/packages/runtime/)
10
+ > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.6/packages/runtime/)
11
11
  >
12
- > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.4` package policy.
12
+ > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.6` package policy.
13
13
  <!-- pulse-package-status:end -->
14
14
 
15
15
  `@pulse-compute/runtime` is the low-level, provider-neutral Pulse application contract.
package/docs/API.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # Pulse runtime contract
2
2
 
3
3
  For source eligibility, use [Managed handler TypeScript and
4
- JavaScript](https://pulsecompute.io/v1.0.0-beta.4/reference/handler-authoring/). For provider and target
4
+ JavaScript](https://pulsecompute.io/v1.0.0-beta.6/reference/handler-authoring/). For provider and target
5
5
  differences, use the [compatibility
6
- matrix](https://pulsecompute.io/v1.0.0-beta.4/reference/compatibility-matrix/). For CLI and runtime failures,
6
+ matrix](https://pulsecompute.io/v1.0.0-beta.6/reference/compatibility-matrix/). For CLI and runtime failures,
7
7
  use the stable codes in the [diagnostics
8
- reference](https://pulsecompute.io/v1.0.0-beta.4/reference/diagnostics/).
8
+ reference](https://pulsecompute.io/v1.0.0-beta.6/reference/diagnostics/).
9
9
 
10
10
  This document describes the provider-neutral TypeScript application contract compiled by Pulse. Low-level authoring types and the static `Router` come from `@pulse-compute/runtime`. The public `@pulse-compute/pulse` package owns the conventional `Pulse` application root, deferred project configuration, and schema declarations, while `@pulse-compute/cli` owns workspace orchestration.
11
11
 
12
12
  `@pulse-compute/runtime` is the low-level portable application surface and
13
13
  `@pulse-compute/pulse` is the conventional project surface. Both belong to the
14
- 14-package public release catalog. Native and JavaScript execution remain
14
+ 19-package public release catalog. Native and JavaScript execution remain
15
15
  explicitly selected targets over the same canonical runtime contract.
16
16
 
17
17
  ## Context at a glance
@@ -25,9 +25,11 @@ process, provider SDK, or global network surface behind it.
25
25
  | [`ctx.req.header(name)`](#request-metadata-and-headers) | HTTP handlers and middleware | Case-insensitive first-value header lookup. |
26
26
  | [`ctx.param(name)`](#ctxparam) | Matched route handlers | Named parameters from the static route pattern. |
27
27
  | [`ctx.state`](#ctxstate) | HTTP and event handlers | Invocation-local string state shared across one execution. |
28
+ | [`ctx.time.now()`](#ctxtime) | HTTP and event handlers | Provider wall-clock sample with matching Unix milliseconds and UTC text. |
28
29
  | [`ctx.fetch`](#ctxfetch) | HTTP and event handlers | Explicit outbound HTTP effect and structured or opaque response ownership. |
29
30
  | [`ctx.parallel`](#ctxparallel) | HTTP and event handlers | Statically keyed concurrent Pulse effects. |
30
31
  | [`ctx.encodeJson`](#ctxencodejson) | HTTP and event handlers | Synchronous schema-bound, size-limited JSON text. |
32
+ | [`ctx.decodeJson`](#ctxdecodejson) | HTTP and event handlers | Synchronous schema-bound decode of bounded application text. |
31
33
  | [`ctx.emit`](#ctxemit) | HTTP and event handlers | One-way, schema-bound event acceptance effect. |
32
34
  | [`ctx.log`](#ctxlog) | HTTP and event handlers | Synchronous thresholded logging. |
33
35
  | [`ctx.config`, `ctx.secret`](#config-and-secrets) | HTTP and event handlers | Explicit configured binding reads. |
@@ -50,7 +52,7 @@ Every managed handler is async-shaped:
50
52
  type Handler = (ctx: PulseContext) => Promise<PulseResult | PulseFetchResponse>
51
53
  ```
52
54
 
53
- For native targets the compiler erases the async wrapper. Awaited Pulse effects lower into the existing explicit effect and continuation state machine; no Promise runtime or Asyncify transform is linked. Awaiting a proven synchronous `ctx` expression is redundant and may warn, while arbitrary non-Pulse awaits mark the native eligibility boundary. The canonical [handler authoring reference](https://pulsecompute.io/v1.0.0-beta.4/reference/handler-authoring/) defines the static language subset; the [compatibility matrix](https://pulsecompute.io/v1.0.0-beta.4/reference/compatibility-matrix/) owns the tested four-mode claims.
55
+ For native targets the compiler erases the async wrapper. Awaited Pulse effects lower into the existing explicit effect and continuation state machine; no Promise runtime or Asyncify transform is linked. Awaiting a proven synchronous `ctx` expression is redundant and may warn, while arbitrary non-Pulse awaits mark the native eligibility boundary. The canonical [handler authoring reference](https://pulsecompute.io/v1.0.0-beta.6/reference/handler-authoring/) defines the static language subset; the [compatibility matrix](https://pulsecompute.io/v1.0.0-beta.6/reference/compatibility-matrix/) owns the tested four-mode claims.
54
56
 
55
57
  ## Static `Router`
56
58
 
@@ -138,9 +140,9 @@ eligibility. Fastly targets fail closed because no event ingress/emit adapter is
138
140
  claimed. There is no public event injection command, no event-aware development
139
141
  listener, no HTTP/GRIP translation, and no automatic target fallback.
140
142
 
141
- The [static events guide](https://pulsecompute.io/v1.0.0-beta.4/guides/events/) owns the complete frame,
143
+ The [static events guide](https://pulsecompute.io/v1.0.0-beta.6/guides/events/) owns the complete frame,
142
144
  queue, target-eligibility, diagnostic, and Native-extension contract. The
143
- source-bound [event example](https://pulsecompute.io/v1.0.0-beta.4/examples/11-events/) runs the same mixed project
145
+ source-bound [event example](https://pulsecompute.io/v1.0.0-beta.6/examples/11-events/) runs the same mixed project
144
146
  on Node JavaScript and Node Native.
145
147
 
146
148
  ## `ctx.req`
@@ -152,6 +154,8 @@ interface PulseRequest {
152
154
  readonly path: string
153
155
  readonly headers: readonly [string, string][]
154
156
  header(name: string): string | undefined
157
+ body(): PulseIncomingBody
158
+ readTextChunk(): PulseEffect<{ readonly done: boolean; readonly text: string }>
155
159
  text(): PulseEffect<string>
156
160
  json<T = unknown>(schemaId?: string): PulseEffect<T>
157
161
  }
@@ -159,6 +163,15 @@ interface PulseRequest {
159
163
 
160
164
  Structured request bodies are bounded runtime-owned snapshots. Repeated `text()` and `json()` reads are memoized immutable transforms.
161
165
 
166
+ `body()` is an opaque incoming handle under the Node `bodyForwarding` opt-in;
167
+ it grants no reader or binary inspection. `readTextChunk()` belongs only to the
168
+ experimental Node `bodyTransform` opt-in, with generated output and a finite
169
+ deadline. It excludes forwarding and structured body reads. Both Fastly targets
170
+ reject these capabilities. The [bodies guide](https://pulsecompute.io/v1.0.0-beta.6/concepts/bodies/) owns the
171
+ byte/read/write limits and separates installed output qualification from pending
172
+ installed transform qualification. The production `/server` launcher remains
173
+ qualified for finite HTTP, as described in [Node deployment](https://pulsecompute.io/v1.0.0-beta.6/guides/deploying-node/).
174
+
162
175
  ### Request metadata and headers
163
176
 
164
177
  ```ts
@@ -382,6 +395,16 @@ return ctx.json(output, {
382
395
  })
383
396
  ```
384
397
 
398
+ ## `ctx.time`
399
+
400
+ `await ctx.time.now()` returns one provider-owned wall-clock sample. On success,
401
+ `status` is `'ok'`, `unixEpochMs` is integer Unix milliseconds and `iso8601` is
402
+ its matching UTC string. A failed sample has `status: 'failed'` and reason
403
+ `'unavailable'` or `'invalid-clock'`. Direct awaited calls and keyed parallel
404
+ members are supported on Node/Fastly Native and JavaScript. Wall time can regress
405
+ and is separate from monotonic deadlines and exact commit timestamps. See the
406
+ [complete result contract](https://pulsecompute.io/v1.0.0-beta.6/packages/runtime/#wall-time).
407
+
385
408
  ## Config and secrets
386
409
 
387
410
  ```ts
@@ -453,7 +476,20 @@ projects the value, then returns detached JSON text bounded by `schemas.maxBytes
453
476
  in UTF-8 bytes. It does not create a response or dispatch an effect. Invalid
454
477
  values and oversized text fail before subsequent writes. Declaration order and
455
478
  array order are preserved; cross-target parity is semantic, not a universal
456
- canonical-byte format. See [application-owned encoding](https://pulsecompute.io/v1.0.0-beta.4/guides/json-schemas/#encode-application-owned-text).
479
+ canonical-byte format. See [application-owned encoding](https://pulsecompute.io/v1.0.0-beta.6/guides/json-schemas/#encode-application-owned-text).
480
+
481
+ ## `ctx.decodeJson`
482
+
483
+ ```ts
484
+ const candidate = ctx.decodeJson<Candidate>(stored.text, 'app.Candidate')
485
+ ```
486
+
487
+ Requires a literal registered schema even in non-strict mode. The input must
488
+ be a string within `schemas.maxBytes` UTF-8 bytes. Decoding returns a detached,
489
+ deeply immutable value with declared fields only. It performs no effect and
490
+ does not apply HTTP content-type policy. Keep the original text for hashing,
491
+ verification and retries; decoding does not establish byte canonicalization or
492
+ storage acceptance. See [application text decoding](https://pulsecompute.io/v1.0.0-beta.6/guides/json-schemas/#decode-application-owned-text).
457
493
 
458
494
  ## Explicit JSON schemas
459
495
 
@@ -496,12 +532,12 @@ forms lower into canonical package operations; JavaScript targets execute the
496
532
  real package implementation.
497
533
 
498
534
  Older `/pulsewasm` imports are compatibility-only and are isolated in the
499
- [migration guide](https://pulsecompute.io/v1.0.0-beta.4/guides/compatibility-imports/). See the
500
- [GRIP package guide](https://pulsecompute.io/v1.0.0-beta.4/packages/grip/) for the complete current surface.
535
+ [migration guide](https://pulsecompute.io/v1.0.0-beta.6/guides/compatibility-imports/). See the
536
+ [GRIP package guide](https://pulsecompute.io/v1.0.0-beta.6/packages/grip/) for the complete current surface.
501
537
 
502
538
  ## Entities API
503
539
 
504
- `@pulse-compute/entities` is part of the synchronized `1.0.0-beta.4` package
540
+ `@pulse-compute/entities` is part of the synchronized `1.0.0-beta.6` package
505
541
  set. The application surface has two runtime values:
506
542
 
507
543
  ```ts
@@ -563,9 +599,9 @@ Registrations and the terminal binding must use the supported static form. The
563
599
  first-party JSON-RPC adapter accepts bounded JSON-RPC 2.0 request objects and
564
600
  named params, validates declared schemas, uses stable error framing, and
565
601
  acknowledges notifications with HTTP `204`. See the [package
566
- guide](https://pulsecompute.io/v1.0.0-beta.4/packages/entities/), [entity/adapter
567
- model](https://pulsecompute.io/v1.0.0-beta.4/concepts/entities-and-adapters/), and [executable
568
- example](https://pulsecompute.io/v1.0.0-beta.4/examples/10-entities-tools/).
602
+ guide](https://pulsecompute.io/v1.0.0-beta.6/packages/entities/), [entity/adapter
603
+ model](https://pulsecompute.io/v1.0.0-beta.6/concepts/entities-and-adapters/), and [executable
604
+ example](https://pulsecompute.io/v1.0.0-beta.6/examples/10-entities-tools/).
569
605
 
570
606
  ## Project workflow
571
607
 
@@ -582,5 +618,5 @@ selected profile, handler entry, schema declarations, provider bindings, and
582
618
  output directory through `.pulse/config.ts`. `pulse inspect` is optional
583
619
  observability, and `pulse compile` is the advanced provider-neutral Native
584
620
  artifact command; neither is required before `pulse build`. See the [project
585
- lifecycle guide](https://pulsecompute.io/v1.0.0-beta.4/guides/project-lifecycle/) and [CLI
586
- reference](https://pulsecompute.io/v1.0.0-beta.4/reference/cli/).
621
+ lifecycle guide](https://pulsecompute.io/v1.0.0-beta.6/guides/project-lifecycle/) and [CLI
622
+ reference](https://pulsecompute.io/v1.0.0-beta.6/reference/cli/).
@@ -1,6 +1,6 @@
1
1
  # Static Router authoring
2
2
 
3
- Use `Router` when an application has several method/path entry points or needs compile-time middleware. It is a static authoring marker from `@pulse-compute/runtime`, not a JavaScript runtime dispatcher. The shared async/static restrictions are defined in [Managed handler TypeScript and JavaScript](https://pulsecompute.io/v1.0.0-beta.4/reference/handler-authoring/); target claims live in the [compatibility matrix](https://pulsecompute.io/v1.0.0-beta.4/reference/compatibility-matrix/).
3
+ Use `Router` when an application has several method/path entry points or needs compile-time middleware. It is a static authoring marker from `@pulse-compute/runtime`, not a JavaScript runtime dispatcher. The shared async/static restrictions are defined in [Managed handler TypeScript and JavaScript](https://pulsecompute.io/v1.0.0-beta.6/reference/handler-authoring/); target claims live in the [compatibility matrix](https://pulsecompute.io/v1.0.0-beta.6/reference/compatibility-matrix/).
4
4
 
5
5
  <!-- pulse-doc-source: examples/09-router-lowering/src/index.ts -->
6
6
  ```ts
@@ -105,6 +105,43 @@ acceptance only; they do not implement Catalog persistence or authorization.
105
105
  node wasm/scripts/run-wasm-tests.cjs --task catalog-router-parity --no-report
106
106
  ```
107
107
 
108
+ ## Selected groups
109
+
110
+ Mount a group once after earlier selectors have set request-local state:
111
+
112
+ ```ts
113
+ app.mount('/api', collection, { state: 'read-family', equals: 'collection' })
114
+ ```
115
+
116
+ The optional third argument is an inline object containing exactly `state` and
117
+ `equals`, both string literals. At this mount's ordered position, the request
118
+ path must match and `ctx.state.get('read-family') === 'collection'` must hold.
119
+ An absent or unequal value skips every child owner and effect directly to the
120
+ parent continuation. Selection is not authorization; keep authorization in its
121
+ existing owner.
122
+
123
+ A match enters the child once. Child `next()` still advances to the next sibling,
124
+ and exhaustion joins the parent continuation once. Terminal responses and ordered
125
+ local/parent error handling keep their existing semantics. A suspended effect
126
+ resumes inside the child; changing selection state there does not reevaluate the
127
+ mount. Method, path and parameter scoping are unchanged. An empty selected child
128
+ continues immediately.
129
+
130
+ This form is supported by live JavaScript and canonical Native compilation.
131
+ Eligibility does not accept callbacks, calls, getters, spreads, computed keys,
132
+ descriptor references or dynamic values. Unsupported source receives
133
+ `PULSEWASM_UNSUPPORTED_MOUNT_ELIGIBILITY`; historical table/harness emitters also
134
+ reject selected mounts rather than ignoring them. Two-argument mounts are
135
+ unchanged. The group remains one placement with one existing parent continuation.
136
+
137
+ The `router-selected-groups` task checks exclusion, suspension after a state
138
+ change, terminal/error paths, nested parameters and one common tail on live
139
+ JavaScript and compiled Node/Fastly Native (Fastly uses its local ABI mock host).
140
+
141
+ ```sh
142
+ node wasm/scripts/run-wasm-tests.cjs --task router-selected-groups --no-report
143
+ ```
144
+
108
145
  ## `next()` is a terminal transfer
109
146
 
110
147
  `next()` is not an onion-style callback. It is a compiler-visible control transfer:
@@ -146,6 +183,39 @@ app.error(async (error, ctx, next) => {
146
183
 
147
184
  If the normal lane is exhausted, Pulse returns `404 Not Found`. If the error lane is exhausted, Pulse returns `500 Internal Server Error`.
148
185
 
186
+ Request UTF-8 failures, schema data failures and JWT validation failures enter the next registered
187
+ error handler. Branch on `error.code`; the portable contract does not require
188
+ identical messages, stacks, causes or detail fields across providers.
189
+
190
+ | Boundary | Portable error codes |
191
+ | --- | --- |
192
+ | Request text encoding | `PULSE_REQUEST_BODY_INVALID_UTF8` (Node Native, Node JavaScript, Fastly Native) |
193
+ | Schema data | `PULSE_SCHEMA_DECODE`, `PULSE_SCHEMA_ENCODE`, `PULSE_SCHEMA_JSON_MALFORMED`, `PULSE_SCHEMA_CONTENT_TYPE`, `PULSE_BODY_TOO_LARGE` |
194
+ | JWT input and verification | `PULSE_JWT_TOKEN_REQUIRED`, `PULSE_JWT_BEARER_INVALID`, `PULSE_JWT_MALFORMED`, `PULSE_JWT_LIMIT_EXCEEDED`, `PULSE_JWT_ALGORITHM_NOT_ALLOWED`, `PULSE_JWT_KEY_INVALID`, `PULSE_JWT_SIGNATURE_INVALID` |
195
+ | JWT claims | `PULSE_JWT_CLOCK_INVALID`, `PULSE_JWT_CLAIMS_INVALID`, `PULSE_JWT_CLAIMS_SCHEMA_INVALID` |
196
+
197
+ These admitted `error.code` values remain stable through redaction, including
198
+ when a secret, KV key or array index overlaps their spelling. Messages, stacks,
199
+ causes, details and arbitrary provider error codes remain subject to redaction.
200
+ This preserves the existing recovery catalog; it does not admit new failures.
201
+
202
+ Recovery moves forward in registration order, including through mounted
203
+ routers. A failed handler never resumes. An error handler can return a response,
204
+ forward with `return next(error)`, or clear the error lane with `return next()`.
205
+ A data failure inside an error handler transfers to a later error handler.
206
+
207
+ Already-started members of a failed effect group settle before application
208
+ recovery. Pulse does not retry them, undo completed writes, or imply that a
209
+ failed invocation had no external effects. Conditional KV results such as
210
+ `conflict`, `not-stored` and `unknown` remain ordinary outcomes for the handler to
211
+ inspect. Their meaning does not change when an error handler is registered.
212
+
213
+ Cancellation ends the invocation without an application response. On Native,
214
+ traps, provider protocol failures and unavailable capabilities remain terminal; they
215
+ do not acquire recovery or rollback guarantees. Native authoring still does not
216
+ admit arbitrary `throw` or `try`/`catch`. JavaScript retains its existing
217
+ `PulseUnhandledError` containment for unexpected handler failures.
218
+
149
219
  ## Middleware scope and effects
150
220
 
151
221
  A path-scoped `use('/api', handler)` applies only to that subtree. Middleware declared on a mounted child Router is naturally limited to the mounted subtree.
@@ -1,6 +1,6 @@
1
1
  # Beta scope
2
2
 
3
- Pulse `1.0.0-beta.4` is a Beta of one provider-neutral application contract
3
+ Pulse `1.0.0-beta.6` is a Beta of one provider-neutral application contract
4
4
  with explicit Native and JavaScript execution targets. The intended 1.0
5
5
  surface is present, but deliberate corrections may still occur before the
6
6
  stable `1.0.0` release.
@@ -18,6 +18,8 @@ targets.
18
18
  middleware, GET/HEAD/POST/PUT/PATCH/DELETE routes, exact paths, named parameters, trailing
19
19
  wildcards, and acyclic mounts.
20
20
  - Basic branching and structured object, array, and scalar manipulation.
21
+ - Literal-capped pure `for` loops and zero-argument string `.trim()` across
22
+ Native and JavaScript; see [bounded application values](https://pulsecompute.io/v1.0.0-beta.6/concepts/compilation-and-lowering/#bounded-application-values).
21
23
  - Request method, URL, path, headers, text, and JSON access.
22
24
  - Bounded, memoized structured body decoding.
23
25
  - Explicit TypeScript JSON schema declarations and literal schema IDs.
@@ -75,6 +77,11 @@ JavaScript-selected project into a Native one.
75
77
 
76
78
  ## Deliberately unsupported
77
79
 
80
+ - Unbounded loops, arbitrary callback transformations and Native helper shapes
81
+ outside the supported bounded subset. Effectful loops are limited to the
82
+ experimental finite Node output/transform forms in the [bodies guide](https://pulsecompute.io/v1.0.0-beta.6/concepts/bodies/);
83
+ other loop bodies must stay within the admitted pure forms.
84
+
78
85
  - Automatic fallback from Native lowering to JavaScript execution.
79
86
  - Declaring general target availability without satisfying every declared
80
87
  full-target-support gate.
@@ -88,7 +95,9 @@ JavaScript-selected project into a Native one.
88
95
  - Automatic discovery of arbitrary TypeScript types.
89
96
  - Dynamic schema IDs.
90
97
  - Arbitrary binary body inspection or mutation.
91
- - Userland chunk iteration, transform streams, or manual backpressure.
98
+ - General userland chunk iteration, transform streams, or manual backpressure.
99
+ Experimental finite Node UTF-8 output/transforms are explicit bounded opt-ins,
100
+ not general stream support or production-launcher qualification.
92
101
  - Background tasks and work that outlives the request.
93
102
  - Raw TCP or UDP sockets.
94
103
  - Dynamic GRIP channel, framing-option, or message shapes under Native
@@ -106,13 +115,13 @@ JavaScript-selected project into a Native one.
106
115
  ## Compatibility authority
107
116
 
108
117
  The single source-form, provider-binding, artifact, and deployment-boundary
109
- table is [Provider and target compatibility](https://pulsecompute.io/v1.0.0-beta.4/reference/compatibility-matrix/).
118
+ table is [Provider and target compatibility](https://pulsecompute.io/v1.0.0-beta.6/reference/compatibility-matrix/).
110
119
  It uses one public target order—Node JavaScript, Fastly JavaScript, Node Native,
111
120
  and Fastly Native—and links every row to a focused proof or canonical contract.
112
121
 
113
122
  The exact portable language subset and the JavaScript-only Native eligibility
114
123
  boundaries are defined in
115
- [Managed handler TypeScript and JavaScript](https://pulsecompute.io/v1.0.0-beta.4/reference/handler-authoring/).
124
+ [Managed handler TypeScript and JavaScript](https://pulsecompute.io/v1.0.0-beta.6/reference/handler-authoring/).
116
125
 
117
126
  ## Body model
118
127
 
@@ -136,7 +145,7 @@ pass it through → opaque handle
136
145
  - Package names, release artifacts, and published versions are immutable once
137
146
  released.
138
147
 
139
- The intended npm dist-tag for the Beta is `beta`. It becomes
140
- active only through the atomic documentation-release transaction and an
141
- explicitly authorized publication. The workflow never assigns `latest`
142
- implicitly.
148
+ The release manifest explicitly selects the npm `latest` dist-tag while package
149
+ versions retain the Beta prerelease suffix. The npm tag and Git branch are
150
+ separate names. Publication remains a distinct human-authorized action; this
151
+ support cleanup does not change that policy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-compute/runtime",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": "^22.14.0 || ^24.0.0"
@@ -41,7 +41,7 @@
41
41
  "url": "git+https://github.com/pulse-compute/pulse.git",
42
42
  "directory": "packages/runtime"
43
43
  },
44
- "homepage": "https://pulsecompute.io/v1.0.0-beta.4/packages/runtime/",
44
+ "homepage": "https://pulsecompute.io/v1.0.0-beta.6/packages/runtime/",
45
45
  "bugs": {
46
46
  "url": "https://github.com/pulse-compute/pulse/issues"
47
47
  }
package/src/host.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  Handler,
3
+ PulseTimeResult,
3
4
  PulseEffect,
4
5
  PulseKvGeneration, PulseKvVersionedResult, PulseKvConditionalResult,
5
6
  PulseFetchResponse,
@@ -16,6 +17,8 @@ export interface PulseJavascriptEffectHostExecution {
16
17
  readonly application?: unknown;
17
18
  readonly signal?: AbortSignal;
18
19
  readonly deadlineMonotonicMs?: number;
20
+ /** Host-only request authority; package transports recheck before dispatch. */
21
+ readonly requestBudget?: PulseRequestBudget;
19
22
  /** Adds execution-owned sensitive text to runtime redaction before provider work continues. */
20
23
  registerRedactionValue(value: string | Uint8Array): void;
21
24
  /** Applies one project-owned schema codec directly to an in-memory semantic value. */
@@ -112,6 +115,7 @@ export interface PulseRuntimeHostKvNamespace<T = unknown> {
112
115
  /** Compatibility injection shape retained while providers move to one effect adapter. */
113
116
  export interface PulseRuntimeHostCapabilities {
114
117
  prepareConditionalKv?: PulseConditionalKvPreparation;
118
+ time?(execution?: PulseJavascriptEffectHostExecution): PulseTimeResult | Promise<PulseTimeResult>;
115
119
  fetch?(
116
120
  url: string,
117
121
  init?: unknown,
@@ -173,6 +177,8 @@ export interface PulseJavascriptEffectSummary {
173
177
 
174
178
  /** Maintainer bridge used by the managed Router lifecycle and focused adapter tests. */
175
179
  export interface PulseJavascriptEffectExecution {
180
+ /** Host/runtime admission fence; rejects queued provider dispatch after an ownership conflict. */
181
+ invalidateAdmission(error: unknown): void;
176
182
  /** Execution-owned local projection. It participates in lifecycle containment but is not parallel-eligible. */
177
183
  local<T = unknown>(
178
184
  kind: string,
@@ -199,7 +205,48 @@ export interface PulseJavascriptEffectExecution {
199
205
  close(): Promise<void>;
200
206
  }
201
207
 
208
+ export interface PulseRequestBudget {
209
+ readonly signal: AbortSignal;
210
+ readonly deadlineMonotonicMs?: number;
211
+ readonly clock: PulseKvClock;
212
+ check(): void;
213
+ onAbort(callback: () => void): () => void;
214
+ remainingMs(): number;
215
+ race<T>(value: T | PromiseLike<T>): Promise<T>;
216
+ close(): void;
217
+ }
218
+ export declare function normalizeRequestDuration(value?: number): number | undefined;
219
+ export declare function createRequestBudget(options?: {
220
+ maxDurationMs?: number;
221
+ requestBudget?: PulseRequestBudget;
222
+ requestClock?: PulseKvClock;
223
+ signal?: AbortSignal;
224
+ requestSignal?: AbortSignal;
225
+ }): PulseRequestBudget;
226
+
227
+ export interface PulseGeneratedOutputExecution {
228
+ readonly started: boolean;
229
+ readonly finished: boolean;
230
+ assertEffect(): void;
231
+ dispatch(effect: Readonly<Record<string, unknown>>): Promise<unknown>;
232
+ bindInput(input: PulseIncomingBodyOwnership | undefined): void;
233
+ close(factory?: (options: import('./index').PulseResponseOptions) => unknown): any;
234
+ validateResult(result: unknown): void;
235
+ finish(): Promise<void>;
236
+ cancel(reason?: unknown): void;
237
+ dispose(): void;
238
+ }
202
239
  export interface PulseRuntimeExecutionOptions {
240
+ /** Provider-owned output authority; unavailable to application code. */
241
+ readonly outputExecution?: PulseGeneratedOutputExecution;
242
+ /** Provider-owned incoming stream controller; never an application capability. */
243
+ readonly incomingBody?: PulseIncomingBodyOwnership;
244
+ /** Provider-owned total managed request budget, 1..30000 ms. Omitted preserves existing behavior. */
245
+ readonly maxDurationMs?: number;
246
+ /** Shared host budget; nested adapters must not restart or close an inherited budget. */
247
+ readonly requestBudget?: PulseRequestBudget;
248
+ /** Monotonic host clock/scheduler, never application wall time. */
249
+ readonly requestClock?: PulseKvClock;
203
250
  /** Host-only monotonic clock/scheduler injection for deterministic lifecycle evidence. */
204
251
  readonly kvClock?: PulseKvClock;
205
252
  /** Host monotonic deadline; conditional KV uses the earlier of this and 10 seconds. */
@@ -418,3 +465,31 @@ export declare function admitConditionalKv(input: Readonly<Record<string, unknow
418
465
  export declare function normalizeConditionalKvResult(effect: Readonly<{ kind: string }>, value: unknown, options?: PulseBindingValueLimits): PulseKvVersionedResult<unknown> | PulseKvConditionalResult;
419
466
  export declare function executeConditionalKv(effect: Readonly<Record<string, unknown>>, prepare: PulseConditionalKvPreparation, execution?: PulseConditionalKvExecution, options?: PulseRuntimeExecutionOptions): Promise<PulseKvVersionedResult<unknown> | PulseKvConditionalResult>;
420
467
  export declare function registerKvRedactions(effect: Readonly<Record<string, unknown>>, register?: (value: string) => void): void;
468
+ /** Whether a data failure can transfer to the next Router error handler. */
469
+ export declare function isApplicationError(error: unknown): boolean;
470
+
471
+ /** Host-only wall-clock result contract; the callback samples integer Unix milliseconds. */
472
+ export declare const WALL_TIME_MAX_MS: 253402300799999;
473
+ export declare function readWallTime(clock?: (() => number) | null): PulseTimeResult;
474
+ export declare function normalizeTimeResult(value: unknown): PulseTimeResult;
475
+
476
+ /** Host-only lifecycle authority for one incoming request body. */
477
+ export interface PulseIncomingBodyOwnership {
478
+ marker(): import('./index').PulseIncomingBody;
479
+ bindInvalidation(callback: (error: unknown) => void): void;
480
+ structured(): void;
481
+ readTextChunk(budget: PulseRequestBudget): Promise<{ readonly done: boolean; readonly text: string }>;
482
+ claim(marker: unknown, init: unknown): void;
483
+ forward(url: string, init: unknown, execution: PulseJavascriptEffectHostExecution): Promise<Response>;
484
+ close(): Promise<void>;
485
+ readonly failure: unknown;
486
+ readonly responseSignal?: AbortSignal;
487
+ }
488
+ export declare function createIncomingBodyOwnership(transport: {
489
+ readonly responseSignal?: AbortSignal;
490
+ readTextChunk?(budget: PulseRequestBudget): Promise<{ readonly done: boolean; readonly text: string }>;
491
+ validate(init: unknown): void;
492
+ forward(url: string, init: unknown, execution: PulseJavascriptEffectHostExecution): Promise<Response>;
493
+ cancel(reason?: unknown): unknown;
494
+ }): PulseIncomingBodyOwnership;
495
+ export declare function forwardIncomingBody(marker: unknown, url: string, init: unknown, execution: PulseJavascriptEffectHostExecution): Promise<Response>;
package/src/host.js CHANGED
@@ -78,6 +78,11 @@ async function executeApplication(application, request, options = {}) {
78
78
  }
79
79
 
80
80
  module.exports = Object.freeze({
81
+ createIncomingBodyOwnership: require('./internal/incoming-body.js').createIncomingBodyOwnership,
82
+ forwardIncomingBody: require('./internal/incoming-body.js').forwardIncomingBody,
83
+ ...require('./internal/time.js'),
84
+ ...require('./internal/request-budget.js'),
85
+ isApplicationError: require('./internal/errors.js').isApplicationError,
81
86
  ...require('./internal/conditional-kv.js'),
82
87
  RUNTIME_HOST_API_VERSION,
83
88
  JAVASCRIPT_EFFECT_PROTOCOL_VERSION,
package/src/index.d.ts CHANGED
@@ -2,6 +2,10 @@ export type HeaderPair = readonly [name: string, value: string];
2
2
 
3
3
  declare const pulseEffectBrand: unique symbol;
4
4
  declare const pulseParallelEffectBrand: unique symbol;
5
+ declare const incomingBodyBrand: unique symbol;
6
+
7
+ /** Single-use opaque marker, admitted only inline as a configured Node fetch body. */
8
+ export interface PulseIncomingBody { readonly [incomingBodyBrand]: true; }
5
9
 
6
10
  export interface PulseEffect<T> extends Promise<T> {
7
11
  readonly [pulseEffectBrand]: T;
@@ -23,6 +27,9 @@ export interface PulseRequest {
23
27
  readonly path: string;
24
28
  readonly headers: readonly HeaderPair[];
25
29
  header(name: string): string | undefined;
30
+ body(): PulseIncomingBody;
31
+ /** Experimental finite UTF-8 transform input; Node bodyTransform opt-in required. */
32
+ readTextChunk(): PulseEffect<{ readonly done: boolean; readonly text: string }>;
26
33
  text(): PulseEffect<string>;
27
34
  json<T = unknown>(schemaId?: string): PulseEffect<T>;
28
35
  }
@@ -35,7 +42,7 @@ export interface PulseFetchInitBase {
35
42
 
36
43
  export type PulseFetchInit = PulseFetchInitBase & (
37
44
  | { readonly body?: never; readonly json?: never; readonly schema?: never }
38
- | { readonly body: string; readonly json?: never; readonly schema?: never }
45
+ | { readonly body: string | PulseIncomingBody; readonly json?: never; readonly schema?: never }
39
46
  | { readonly body?: never; readonly json: unknown; readonly schema?: string }
40
47
  );
41
48
 
@@ -133,10 +140,19 @@ export type PulseEmitEvent<Payload = unknown> =
133
140
  | Readonly<{ schema: string; payload: Payload }>
134
141
  | Readonly<{ schema: null; payload?: never }>;
135
142
 
143
+ /** One provider wall-clock sample. This is neither monotonic nor a commit timestamp. */
144
+ export type PulseTimeResult =
145
+ | { readonly status: 'ok'; readonly unixEpochMs: number; readonly iso8601: string }
146
+ | { readonly status: 'failed'; readonly reason: 'unavailable' | 'invalid-clock' };
147
+
136
148
  /** Plane-neutral authority shared by one isolated HTTP request or event invocation. */
137
149
  export interface PulseExecutionContext {
138
150
  /** Validate/project a value through a literal registered schema and return bounded JSON text. */
139
151
  encodeJson(value: unknown, schemaId: string): string;
152
+ /** Decode bounded application-owned JSON text through a literal registered schema. */
153
+ decodeJson<T = unknown>(text: string, schemaId: string): T;
154
+ /** Fresh sample at dispatch, UTC 1970–9999, integer Unix milliseconds. */
155
+ readonly time: { now(): PulseParallelEffect<PulseTimeResult> };
140
156
  readonly state: PulseState;
141
157
  readonly log: PulseLogger;
142
158
  fetch(url: string, init?: PulseFetchInit): PulseFetchOperation;
@@ -157,6 +173,12 @@ export interface PulseExecutionContext {
157
173
 
158
174
  /** HTTP request execution context. */
159
175
  export interface PulseContext extends PulseExecutionContext {
176
+ /** Experimental Node output: opt-in, finite, request-owned UTF-8 writes. */
177
+ readonly output: {
178
+ start(options?: PulseResponseOptions): PulseEffect<void>;
179
+ write(text: string): PulseEffect<void>;
180
+ close(): PulseResult;
181
+ };
160
182
  readonly req: PulseRequest;
161
183
  json(value: unknown, descriptor?: PulseJsonResponseOptions | string): PulseResult;
162
184
  text(value: string, options?: PulseResponseOptions): PulseResult;
@@ -209,7 +231,8 @@ export declare class Router {
209
231
  put(path: string, handler: RouteHandler): this;
210
232
  patch(path: string, handler: RouteHandler): this;
211
233
  delete(path: string, handler: RouteHandler): this;
212
- mount(path: string, router: Router): this;
234
+ /** Enter the child once only when request state strictly equals the literal string; a miss skips the entire child. */
235
+ mount(path: string, router: Router, eligibility?: { readonly state: string; readonly equals: string }): this;
213
236
  error(handler: RouterErrorHandler): this;
214
237
  }
215
238
 
@@ -200,13 +200,22 @@ function createStructuredBodyReader(owner, options = {}) {
200
200
 
201
201
  function bytes() {
202
202
  assertInspectable();
203
- if (!bytesPromise) bytesPromise = readBodyBytes(owner, maxBytes, label, signal);
203
+ if (!bytesPromise) {
204
+ bytesPromise = readBodyBytes(owner, maxBytes, label, signal);
205
+ if (options.onReadSettled) bytesPromise = bytesPromise.finally(options.onReadSettled);
206
+ }
204
207
  return bytesPromise;
205
208
  }
206
209
 
207
210
  function text() {
208
211
  assertInspectable();
209
- if (!textPromise) textPromise = bytes().then((value) => new TextDecoder().decode(value));
212
+ if (!textPromise) textPromise = bytes().then((value) => {
213
+ if (label !== 'request') return new TextDecoder().decode(value);
214
+ try { return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(value); }
215
+ catch (cause) {
216
+ throw bodyError('PULSE_REQUEST_BODY_INVALID_UTF8', 'Pulse request text must be well-formed UTF-8.', {}, cause);
217
+ }
218
+ });
210
219
  return textPromise;
211
220
  }
212
221