@pulse-compute/cli 0.0.0 → 1.0.0-beta.1

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.
Files changed (190) hide show
  1. package/API.md +539 -0
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +70 -1
  4. package/bin/pulse.js +15 -0
  5. package/bin/pulsewasm-extract.js +22 -0
  6. package/cli-spec.json +575 -0
  7. package/completions/_pulse +106 -0
  8. package/completions/pulse.bash +31 -0
  9. package/completions/pulse.fish +76 -0
  10. package/docs/README.md +77 -0
  11. package/docs/architecture/current-contracts.md +381 -0
  12. package/docs/architecture/overview.md +93 -0
  13. package/docs/architecture/vision.md +515 -0
  14. package/docs/concepts/bodies.md +186 -0
  15. package/docs/concepts/compilation-and-lowering.md +496 -0
  16. package/docs/concepts/contracts-and-providers.md +158 -0
  17. package/docs/concepts/effects-and-continuations.md +243 -0
  18. package/docs/concepts/entities-and-adapters.md +71 -0
  19. package/docs/concepts/package-owned-lowering.md +175 -0
  20. package/docs/concepts/targets-and-hosts.md +62 -0
  21. package/docs/contributing/README.md +38 -0
  22. package/docs/contributing/adding-core-provider.md +315 -0
  23. package/docs/contributing/adding-first-party-lowerer.md +327 -0
  24. package/docs/contributing/entities-lowering.md +91 -0
  25. package/docs/contributing/package-lowerer-contract.md +383 -0
  26. package/docs/contributing/pulse-aware-packages.md +149 -0
  27. package/docs/examples.md +112 -0
  28. package/docs/fixtures/inspect-fetch-composition.selected.json +181 -0
  29. package/docs/getting-started.md +128 -0
  30. package/docs/guides/compatibility-imports.md +60 -0
  31. package/docs/guides/deploying-fastly.md +188 -0
  32. package/docs/guides/deploying-node.md +143 -0
  33. package/docs/guides/events.md +233 -0
  34. package/docs/guides/fastly-capabilities.md +153 -0
  35. package/docs/guides/fetching-and-composition.md +101 -0
  36. package/docs/guides/grip.md +94 -0
  37. package/docs/guides/json-schemas.md +233 -0
  38. package/docs/guides/migrating-from-express.md +192 -0
  39. package/docs/guides/project-lifecycle.md +143 -0
  40. package/docs/guides/routing.md +184 -0
  41. package/docs/guides/troubleshooting.md +47 -0
  42. package/docs/maintainers/README.md +60 -0
  43. package/docs/maintainers/codex-maintainer.md +71 -0
  44. package/docs/maintainers/documentation-deployment.md +147 -0
  45. package/docs/maintainers/documentation-system.md +58 -0
  46. package/docs/maintainers/documentation-versioning.md +65 -0
  47. package/docs/maintainers/maintainer-charter.md +84 -0
  48. package/docs/maintainers/maintenance-policy.json +778 -0
  49. package/docs/maintainers/maintenance-policy.md +124 -0
  50. package/docs/maintainers/npm-publishing.md +154 -0
  51. package/docs/maintainers/plugin-readiness.json +19 -0
  52. package/docs/maintainers/plugin-readiness.md +31 -0
  53. package/docs/maintainers/public-site.md +158 -0
  54. package/docs/maintainers/release-acceptance.md +250 -0
  55. package/docs/maintainers/release-manifest.md +88 -0
  56. package/docs/maintainers/repository-setup.md +145 -0
  57. package/docs/maintainers/scope-policy.md +93 -0
  58. package/docs/maintainers/support-and-triage.md +74 -0
  59. package/docs/maintainers/testing.md +288 -0
  60. package/docs/packages/README.md +59 -0
  61. package/docs/packages/assets.md +85 -0
  62. package/docs/packages/cli.md +190 -0
  63. package/docs/packages/crypto.md +71 -0
  64. package/docs/packages/entities.md +129 -0
  65. package/docs/packages/grip.md +82 -0
  66. package/docs/packages/implementation-packages.md +113 -0
  67. package/docs/packages/jwt.md +55 -0
  68. package/docs/packages/provider-fastly.md +217 -0
  69. package/docs/packages/pulse.md +159 -0
  70. package/docs/packages/runtime.md +256 -0
  71. package/docs/preview-scope.md +142 -0
  72. package/docs/reference/README.md +33 -0
  73. package/docs/reference/cli-spec.json +575 -0
  74. package/docs/reference/cli.md +457 -0
  75. package/docs/reference/compatibility-matrix.md +169 -0
  76. package/docs/reference/diagnostics.md +1872 -0
  77. package/docs/reference/documentation-versions.json +14 -0
  78. package/docs/reference/environment.md +347 -0
  79. package/docs/reference/handler-authoring.md +264 -0
  80. package/docs/reference/project-config.md +554 -0
  81. package/docs/reference/project-config.schema.json +918 -0
  82. package/docs/reference/release-manifest.json +427 -0
  83. package/docs/reference/shell-completion.md +23 -0
  84. package/documentation-site.json +661 -0
  85. package/documentation-versions.json +14 -0
  86. package/examples/01-hello-json/.pulse/.gitignore +3 -0
  87. package/examples/01-hello-json/.pulse/config.ts +16 -0
  88. package/examples/01-hello-json/README.md +119 -0
  89. package/examples/01-hello-json/package.json +20 -0
  90. package/examples/01-hello-json/src/index.ts +9 -0
  91. package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
  92. package/examples/01-hello-json/tsconfig.json +14 -0
  93. package/examples/02-request-schema/.pulse/.gitignore +3 -0
  94. package/examples/02-request-schema/.pulse/config.ts +17 -0
  95. package/examples/02-request-schema/README.md +145 -0
  96. package/examples/02-request-schema/package.json +20 -0
  97. package/examples/02-request-schema/src/index.ts +18 -0
  98. package/examples/02-request-schema/src/schemas.ts +20 -0
  99. package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
  100. package/examples/02-request-schema/tsconfig.json +14 -0
  101. package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
  102. package/examples/03-fetch-composition/.pulse/config.ts +31 -0
  103. package/examples/03-fetch-composition/README.md +210 -0
  104. package/examples/03-fetch-composition/package.json +20 -0
  105. package/examples/03-fetch-composition/src/index.ts +60 -0
  106. package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
  107. package/examples/03-fetch-composition/tsconfig.json +14 -0
  108. package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
  109. package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
  110. package/examples/05-fastly-capabilities/README.md +257 -0
  111. package/examples/05-fastly-capabilities/package.json +22 -0
  112. package/examples/05-fastly-capabilities/src/index.ts +55 -0
  113. package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
  114. package/examples/05-fastly-capabilities/tsconfig.json +14 -0
  115. package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
  116. package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
  117. package/examples/07-opaque-proxy/README.md +150 -0
  118. package/examples/07-opaque-proxy/package.json +21 -0
  119. package/examples/07-opaque-proxy/src/index.ts +9 -0
  120. package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
  121. package/examples/07-opaque-proxy/tsconfig.json +14 -0
  122. package/examples/09-router-lowering/.pulse/.gitignore +3 -0
  123. package/examples/09-router-lowering/.pulse/config.ts +22 -0
  124. package/examples/09-router-lowering/README.md +287 -0
  125. package/examples/09-router-lowering/package.json +22 -0
  126. package/examples/09-router-lowering/src/index.ts +65 -0
  127. package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
  128. package/examples/09-router-lowering/tsconfig.json +14 -0
  129. package/examples/10-entities-tools/.pulse/.gitignore +4 -0
  130. package/examples/10-entities-tools/.pulse/config.ts +23 -0
  131. package/examples/10-entities-tools/README.md +165 -0
  132. package/examples/10-entities-tools/package.json +21 -0
  133. package/examples/10-entities-tools/src/handlers.ts +15 -0
  134. package/examples/10-entities-tools/src/index.ts +31 -0
  135. package/examples/10-entities-tools/src/schemas.ts +18 -0
  136. package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
  137. package/examples/10-entities-tools/tools-facade.cjs +158 -0
  138. package/examples/10-entities-tools/tsconfig.json +14 -0
  139. package/examples/11-events/.pulse/.gitignore +4 -0
  140. package/examples/11-events/.pulse/config.ts +24 -0
  141. package/examples/11-events/README.md +194 -0
  142. package/examples/11-events/package.json +20 -0
  143. package/examples/11-events/src/index.ts +27 -0
  144. package/examples/11-events/src/schemas.ts +19 -0
  145. package/examples/11-events/tests/pulse.harness.ts +34 -0
  146. package/examples/11-events/tsconfig.json +15 -0
  147. package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
  148. package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
  149. package/examples/12-mcp-proxy/README.md +149 -0
  150. package/examples/12-mcp-proxy/package.json +20 -0
  151. package/examples/12-mcp-proxy/src/index.ts +17 -0
  152. package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
  153. package/examples/12-mcp-proxy/tsconfig.json +14 -0
  154. package/examples/13-jwt-es256/.pulse/config.ts +19 -0
  155. package/examples/13-jwt-es256/README.md +195 -0
  156. package/examples/13-jwt-es256/package.json +21 -0
  157. package/examples/13-jwt-es256/src/index.ts +21 -0
  158. package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
  159. package/examples/13-jwt-es256/tsconfig.json +14 -0
  160. package/examples/README.md +36 -0
  161. package/package.json +66 -6
  162. package/project-config.schema.json +918 -0
  163. package/release-manifest.json +427 -0
  164. package/src/command-spec.js +279 -0
  165. package/src/completion.js +113 -0
  166. package/src/diagnostics.js +350 -0
  167. package/src/documentation.js +45 -0
  168. package/src/index.d.ts +133 -0
  169. package/src/index.js +15 -0
  170. package/src/internal/command-executor.d.ts +29 -0
  171. package/src/internal/command-executor.js +143 -0
  172. package/src/internal/command-plan.d.ts +9 -0
  173. package/src/internal/command-plan.js +73 -0
  174. package/src/internal/command-reporter.d.ts +15 -0
  175. package/src/internal/command-reporter.js +133 -0
  176. package/src/internal/command-request.d.ts +35 -0
  177. package/src/internal/command-request.js +154 -0
  178. package/src/internal/node-http.js +54 -0
  179. package/src/internal/project-context.d.ts +66 -0
  180. package/src/internal/project-context.js +175 -0
  181. package/src/project-config-reference.js +4 -0
  182. package/src/project-config-schema.d.ts +23 -0
  183. package/src/project-config-schema.js +271 -0
  184. package/src/project-config.js +724 -0
  185. package/src/project-execution.js +2930 -0
  186. package/src/provider-drivers.js +3 -0
  187. package/src/target-support.js +3 -0
  188. package/src/typescript-module-loader.js +269 -0
  189. package/src/workflow.js +78 -0
  190. package/src/workspace.js +82 -0
@@ -0,0 +1,143 @@
1
+ # Node build and execution
2
+
3
+ Pulse exposes Node Native and Node JavaScript as explicit targets over the same
4
+ provider-neutral application contract. This guide ends at a validated Node
5
+ candidate. The Beta does not install a process manager, create
6
+ infrastructure, or publish a managed Node service.
7
+
8
+ ## Configure the Node provider
9
+
10
+ The hello example is a source-bound Node Native project:
11
+
12
+ <!-- pulse-doc-source: examples/01-hello-json/.pulse/config.ts -->
13
+ ```ts
14
+ import { defineConfig } from '@pulse-compute/pulse'
15
+
16
+ export default defineConfig((_scope) => ({
17
+ pulse: {
18
+ entry: 'src/index.ts',
19
+ tests: 'tests/pulse.harness.ts',
20
+ defaultProfile: 'local',
21
+ strict: true,
22
+ },
23
+ local: {
24
+ host: 'node',
25
+ target: 'native',
26
+ outDir: 'dist',
27
+ dev: { host: '127.0.0.1', port: 8787 },
28
+ },
29
+ }))
30
+ ```
31
+ <!-- /pulse-doc-source -->
32
+
33
+ Select JavaScript deliberately by changing only the target:
34
+
35
+ ```ts
36
+ local: {
37
+ host: 'node',
38
+ target: 'javascript',
39
+ outDir: 'dist',
40
+ }
41
+ ```
42
+
43
+ Target choice belongs to the profile. Pulse does not retry a failed Native
44
+ selection as JavaScript.
45
+
46
+ ## Validate the application
47
+
48
+ Use the normal lifecycle:
49
+
50
+ ```bash
51
+ pulse doctor
52
+ pulse test
53
+ pulse dev
54
+ pulse build
55
+ ```
56
+
57
+ `test` and `dev` execute through the selected Node target's provider-owned local
58
+ lifecycle. They prove the configured application and local inputs; they do not
59
+ create a production process or remote service.
60
+
61
+ ## Node Native candidate
62
+
63
+ With `target: 'native'`, `pulse build` emits:
64
+
65
+ ```text
66
+ canonical-program.json
67
+ canonical-handler.cjs
68
+ canonical-native-plan.json
69
+ canonical-native.wasm
70
+ pulse-build.json
71
+ ```
72
+
73
+ The Wasm imports the provider-neutral `pulse_host` contract. The build manifest
74
+ records the Node Native target, compiler identity, imports/exports,
75
+ optimization profile, provider requirements, and artifact hashes.
76
+
77
+ Use `pulse test` or `pulse dev` for the maintained Node host execution path.
78
+ The built Wasm and manifest are host-integration inputs; the Beta
79
+ does not promise a standalone production Native launcher.
80
+
81
+ ## Node JavaScript candidate
82
+
83
+ With `target: 'javascript'`, `pulse build` emits a deterministic CommonJS
84
+ package containing:
85
+
86
+ ```text
87
+ index.cjs
88
+ package.json
89
+ application/
90
+ pulse-build.json
91
+ pulse-javascript-application-plan.json
92
+ pulse-javascript-source-package.json
93
+ schema-json-registry.json # when schemas are active
94
+ schema-json-codecs.cjs # when schemas are active
95
+ ```
96
+
97
+ The package contains the reachable application graph and exact reachable Pulse
98
+ dependencies. It contains no Pulse Native Wasm, AssemblyScript source, Native
99
+ plan, or automatic fallback.
100
+
101
+ `index.cjs` exports the packaged Pulse application. The maintained local
102
+ execution path is `pulse dev`; embedding the package in a production HTTP
103
+ server requires a separately owned Node host integration. The current
104
+ implementation adapter remains an internal provider surface rather than an
105
+ application compatibility promise.
106
+
107
+ ## Inspect the candidate
108
+
109
+ ```bash
110
+ pulse inspect --artifact ./dist/pulse-build.json --json
111
+ ```
112
+
113
+ Check:
114
+
115
+ - `configuredTarget` and `providerTarget.target`;
116
+ - `automaticFallback: false` for JavaScript;
117
+ - Native import/export and optimization identity, or JavaScript source-package
118
+ identity;
119
+ - schema registry and codec identity when active;
120
+ - the project entry and output root;
121
+ - target-support and project-eligibility evidence.
122
+
123
+ ## Deployment-owner boundary
124
+
125
+ After Pulse produces and validates the candidate, the deployment owner remains
126
+ responsible for:
127
+
128
+ - selecting the Node process/container host;
129
+ - installing the candidate's exact dependencies;
130
+ - wiring HTTP, signals, health checks, credentials, and resource bindings;
131
+ - enforcing operating-system and network policy;
132
+ - retaining build and deployment provenance;
133
+ - deciding activation and rollback.
134
+
135
+ Those decisions do not grant application handlers ambient `process.env`,
136
+ filesystem, socket, or provider-object authority. Handler access remains
137
+ through `ctx` and configured bindings.
138
+
139
+ See [Project lifecycle](./project-lifecycle.md), [Managed handler TypeScript and
140
+ JavaScript](../reference/handler-authoring.md), [Provider and target
141
+ compatibility](../reference/compatibility-matrix.md), [Contracts and
142
+ providers](../concepts/contracts-and-providers.md), and [Release
143
+ acceptance](../maintainers/release-acceptance.md).
@@ -0,0 +1,233 @@
1
+ # Static events and outbound emission
2
+
3
+ Pulse events are a second, provider-neutral application plane beside HTTP.
4
+ `Pulse.on` declares exact inbound event handlers and `ctx.emit` requests
5
+ one-way outbound host acceptance. Neither surface creates a process-global bus,
6
+ a listener, delivery machinery, or reflexive application routing.
7
+
8
+ The current executable realization is deliberately narrow: Node JavaScript and
9
+ Node Native provide a bounded invocation-scoped reference adapter for tests and
10
+ direct parity evidence. Fastly fails closed for event ingress or emit, while
11
+ browser and ESP32 hosts remain unclaimed. Target selection never falls back.
12
+
13
+ ## Declare the topology
14
+
15
+ Event registrations belong only to the resolved `Pulse` application root. The
16
+ type, declaration, and handler are static compiler inputs:
17
+
18
+ <!-- pulse-doc-source: examples/11-events/src/index.ts -->
19
+ ```ts
20
+ import { Pulse } from '@pulse-compute/pulse'
21
+
22
+ type DeviceReading = Readonly<{
23
+ deviceId: string
24
+ temperatureC: number
25
+ }>
26
+
27
+ const app = new Pulse({ auto: true })
28
+
29
+ app.get('/health', async (ctx) => ctx.text('ok'))
30
+
31
+ app.on<DeviceReading>('device.reading', { schema: 'events.DeviceReading' }, async (ctx) => {
32
+ const reading = ctx.event.payload
33
+ ctx.log.info('device reading accepted')
34
+ await ctx.emit('device.reading.accepted', {
35
+ schema: 'events.DeviceReadingAccepted',
36
+ payload: { deviceId: reading.deviceId, accepted: true },
37
+ })
38
+ })
39
+
40
+ app.on('system.tick', { schema: null }, async (ctx) => {
41
+ ctx.log.info('system tick accepted')
42
+ await ctx.emit('system.heartbeat', { schema: null })
43
+ })
44
+
45
+ export default app
46
+ ```
47
+ <!-- /pulse-doc-source -->
48
+
49
+ Each exact event type has one owner. A schema ID must be a literal dotted ID
50
+ present in the project registry. `schema: null` declares an explicit
51
+ no-payload event. Registrations cannot be hidden in helpers, aliased, mounted on
52
+ a `Router`, or created dynamically.
53
+
54
+ ## Event context
55
+
56
+ An event handler receives `PulseEventContext<Payload>` and completes with
57
+ `Promise<void>`:
58
+
59
+ | Available | Deliberately absent |
60
+ |---|---|
61
+ | `ctx.event.type` and immutable `ctx.event.payload` | `ctx.req`, route parameters, and HTTP metadata |
62
+ | execution-local `ctx.state` and synchronous `ctx.log` | response builders or a response result |
63
+ | fetch, config, secret, KV, and `ctx.parallel` | middleware and Router `next()` transfer |
64
+ | one-way `ctx.emit` | provider SDKs, listeners, or transport handles |
65
+
66
+ Schema selection and payload validation complete before handler entry. The
67
+ payload is detached from host input and immutable. State, effects,
68
+ continuations, cancellation, logging, redaction, completion, and disposal are
69
+ owned by one invocation and do not leak into another event or HTTP request.
70
+
71
+ ## Canonical frame and schema rules
72
+
73
+ Ingress and outbound acceptance use `pulse.event-frame.v1` internally:
74
+
75
+ ```ts
76
+ type EventFrame =
77
+ | { version: 'pulse.event-frame.v1'; type: string; schemaId: string; payload: unknown }
78
+ | { version: 'pulse.event-frame.v1'; type: string; schemaId: null }
79
+ ```
80
+
81
+ The public authoring and harness shapes use `schema`, while emitted artifacts
82
+ and host-runtime frames normalize it to `schemaId`. A schema-bound frame must
83
+ contain `payload`; a no-payload frame must omit it. Unknown fields, accessors,
84
+ sparse arrays, cycles, non-finite numbers, symbols, and non-JSON values fail
85
+ before dispatch.
86
+
87
+ Default host bounds are:
88
+
89
+ | Bound | Default |
90
+ |---|---:|
91
+ | Event type | 128 UTF-8 bytes |
92
+ | Schema ID | 256 UTF-8 bytes |
93
+ | Payload | 65,536 UTF-8 JSON bytes |
94
+ | Payload nesting | 32 levels |
95
+ | Payload entries | 4,096 |
96
+ | Static registrations | 256 |
97
+ | Adapter queue depth | 65,536 |
98
+ | Bounded error text | 4,096 UTF-8 bytes |
99
+
100
+ These are containment limits, not an application-facing tuning API. A provider
101
+ or direct host may impose a stricter bound.
102
+
103
+ ## `ctx.emit` means acceptance, not delivery
104
+
105
+ `ctx.emit(type, { schema, payload? })` must use literal event and schema
106
+ identities. It may be awaited directly or supplied as a fresh member of an
107
+ awaited `ctx.parallel({ ... })` group. The effect resolves to `undefined` only
108
+ after the execution-owned adapter accepts a detached, schema-validated frame.
109
+
110
+ Acceptance does not promise:
111
+
112
+ - delivery, persistence, retry, acknowledgement, or a receipt;
113
+ - a correlation ID or remote handler result;
114
+ - automatic invocation of a matching local `Pulse.on` handler;
115
+ - same-stack reentry or an implicit queue consumer;
116
+ - substitution through HTTP, GRIP, logging, or another target.
117
+
118
+ The Node reference adapter maintains separate FIFO ingress and exact outbound
119
+ acceptance ledgers. Outbound frames never feed the ingress queue automatically.
120
+ Queue overflow, cancellation, invalid acceptance results, schema failures, and
121
+ effect-budget exhaustion remain bounded invocation failures.
122
+
123
+ ## Harness event cases
124
+
125
+ `pulse test` uses an explicit discriminant so event input never masquerades as
126
+ an HTTP request:
127
+
128
+ <!-- pulse-doc-source: examples/11-events/tests/pulse.harness.ts -->
129
+ ```ts
130
+ export default { cases: [
131
+ {
132
+ name: 'http health remains separate',
133
+ request: { method: 'GET', path: '/health' },
134
+ expect: { status: 200, text: 'ok' },
135
+ },
136
+ {
137
+ name: 'schema event emits an accepted frame',
138
+ kind: 'event',
139
+ event: {
140
+ type: 'device.reading',
141
+ schema: 'events.DeviceReading',
142
+ payload: { deviceId: 'sensor-7', temperatureC: 21 },
143
+ },
144
+ expect: {
145
+ status: 'completed',
146
+ emitted: [{
147
+ type: 'device.reading.accepted',
148
+ schema: 'events.DeviceReadingAccepted',
149
+ payload: { deviceId: 'sensor-7', accepted: true },
150
+ }],
151
+ },
152
+ },
153
+ {
154
+ name: 'no-payload event emits a no-payload frame',
155
+ kind: 'event',
156
+ event: { type: 'system.tick', schema: null },
157
+ expect: {
158
+ status: 'completed',
159
+ emitted: [{ type: 'system.heartbeat', schema: null }],
160
+ },
161
+ },
162
+ ] }
163
+ ```
164
+ <!-- /pulse-doc-source -->
165
+
166
+ `expect.emitted` is ordered and exact. It verifies host acceptance, not
167
+ transport delivery. `pulse dev` continues to serve the HTTP plane only; there
168
+ is no public event injection command.
169
+
170
+ ## Target eligibility
171
+
172
+ | Selected target | Ingress and emit status | Meaning |
173
+ |---|---|---|
174
+ | Node JavaScript | Eligible | Live application execution through the bounded Node reference adapter. |
175
+ | Node Native | Eligible | Provider-neutral Native event entry driven through the bounded Node reference adapter. |
176
+ | Fastly JavaScript | Blocked | No Fastly event ingress or emit adapter is claimed. |
177
+ | Fastly Native | Blocked | No Fastly event ingress or emit adapter is claimed. |
178
+ | `none` compile-only | Inspection only | Catalog and Native plan may be produced without execution authority. |
179
+ | Browser or ESP32 | Unclaimed | A future host must define and prove its own adapter and queue ownership. |
180
+
181
+ For a blocked Fastly project, `inspect` remains available and `doctor`, `build`,
182
+ and `test` report the exact eligibility boundary. Pulse never produces an
183
+ alternate target artifact.
184
+
185
+ ## Inspection, artifacts, and diagnostics
186
+
187
+ `pulse inspect --json` and `pulse doctor --json` report registrations,
188
+ emission callsites, referenced schemas, `event.ingress`/`event.emit` host
189
+ requirements, command eligibility, and `automaticFallback: false`. Eligible
190
+ builds and compile-only output write deterministic `event-catalog.json` and
191
+ `event-inspection.json` files.
192
+
193
+ Common public workflow diagnostics include:
194
+
195
+ - [`PULSE_TEST_EVENT_INVALID`](../reference/diagnostics.md#pulse-test-event-invalid)
196
+ for a malformed harness frame;
197
+ - [`PULSE_EVENT_TARGET_UNSUPPORTED`](../reference/diagnostics.md#pulse-event-target-unsupported)
198
+ for a target without the required event plane;
199
+ - [`PULSE_FASTLY_EVENT_INGRESS_UNSUPPORTED`](../reference/diagnostics.md#pulse-fastly-event-ingress-unsupported)
200
+ and
201
+ [`PULSE_FASTLY_EVENT_EMIT_UNSUPPORTED`](../reference/diagnostics.md#pulse-fastly-event-emit-unsupported)
202
+ for the explicit Fastly boundary.
203
+
204
+ Compiler diagnostics additionally point to the exact dynamic type, unresolved
205
+ schema, hidden or duplicate registration, missing await, invalid payload, HTTP
206
+ surface in an event handler, or event surface in an HTTP handler.
207
+
208
+ ## Native extension
209
+
210
+ Event-reachable Native artifacts conditionally expose
211
+ `pulse.native-event-abi.v1` through `pulse_event_abi_version()` and
212
+ `pulse_event_start(runtimeId, payloadHandle)`. The host validates the frame and
213
+ schema before module entry. A positive payload handle references an immutable
214
+ host-owned value; `0` means a declared no-payload event.
215
+
216
+ The extension adds no imports. Event-only artifacts expose the event entry;
217
+ mixed artifacts retain independent HTTP and event entries. HTTP-only source
218
+ adds no event imports, exports, catalog data, code, or byte changes. Native
219
+ event effects reuse the ordinary continuation protocol and add no JavaScript,
220
+ Promise, Asyncify, target probing, or fallback.
221
+
222
+ ## No `call` or reflexive routing
223
+
224
+ The event contract intentionally exposes no `ctx.call`, `app.call`, generic
225
+ call effect, request/reply bus, correlation protocol, or compiler/runtime
226
+ reservation for one. `ctx.emit` cannot observe or invoke a local handler. Any
227
+ future call mechanism requires a separately specified host, ownership model,
228
+ failure contract, recursion/reentrancy guard, and explicit authorization; it is
229
+ not latent in this candidate.
230
+
231
+ See the [canonical API](../../API.md), [effects and
232
+ continuations](../concepts/effects-and-continuations.md), [compatibility
233
+ matrix](../reference/compatibility-matrix.md), and [event example](../../examples/11-events/).
@@ -0,0 +1,153 @@
1
+ # Fastly config, secrets, KV, and backends
2
+
3
+ User code stays provider-neutral:
4
+
5
+ <!-- pulse-doc-source: examples/05-fastly-capabilities/src/index.ts -->
6
+ ```ts
7
+ import { grip } from '@pulse-compute/grip'
8
+ import { Pulse } from '@pulse-compute/pulse'
9
+
10
+ interface User {
11
+ id: number
12
+ name: string
13
+ }
14
+
15
+ interface Session {
16
+ userId: number
17
+ }
18
+
19
+ const app = new Pulse({ auto: true })
20
+
21
+ // Config, secret, and a named Fastly backend.
22
+ app.get('/users/7', async (ctx) => {
23
+ const base = await ctx.config.get('API_BASE')
24
+ const token = await ctx.secret.get('API_TOKEN')
25
+ const user = await ctx
26
+ .fetch('https://api.example.com/users/7', {
27
+ headers: {
28
+ authorization: `Bearer ${token}`,
29
+ accept: 'application/json',
30
+ 'x-api-base': base,
31
+ },
32
+ })
33
+ .json<User>()
34
+ return ctx.json({ user })
35
+ })
36
+
37
+ // Named Fastly KV storage.
38
+ app.get('/session', async (ctx) => {
39
+ const current = await ctx.kv<Session>('sessions').get('session:123')
40
+ if (current === undefined) {
41
+ return ctx.json({ error: 'not_found' }, { status: 404 })
42
+ }
43
+ await ctx.kv<Session>('sessions').put('session:last', current)
44
+ return ctx.json({ session: current })
45
+ })
46
+
47
+ // Request-bound GRIP broadcast through the supported package root.
48
+ app.post('/publish', async (ctx) => {
49
+ const acknowledgement = await grip.broadcast(ctx, {
50
+ channel: 'events:demo',
51
+ event: 'pulse.message',
52
+ id: 'message-1',
53
+ data: { message: 'hello from Pulse' },
54
+ })
55
+ return ctx.json(
56
+ { accepted: acknowledgement.accepted },
57
+ { status: 202 },
58
+ )
59
+ })
60
+
61
+ export default app
62
+ ```
63
+ <!-- /pulse-doc-source -->
64
+
65
+ Fastly bindings belong in project configuration:
66
+
67
+ <!-- pulse-doc-source: examples/05-fastly-capabilities/.pulse/config.ts -->
68
+ ```ts
69
+ import { defineConfig } from '@pulse-compute/pulse'
70
+
71
+ export default defineConfig((scope) => ({
72
+ pulse: {
73
+ entry: 'src/index.ts',
74
+ tests: 'tests/pulse.harness.ts',
75
+ defaultProfile: 'local',
76
+ strict: true,
77
+ },
78
+ local: {
79
+ host: 'fastly',
80
+ target: 'native',
81
+ outDir: 'dist',
82
+ apiBase: scope.config('API_BASE'),
83
+ apiToken: scope.secret('API_TOKEN'),
84
+ dev: {
85
+ config: { API_BASE: 'https://api.example.com' },
86
+ secrets: {
87
+ API_TOKEN: 'local-example-secret',
88
+ GRIP_TOKEN: 'local-grip-secret',
89
+ },
90
+ kv: {
91
+ sessions: { 'session:123': { userId: 123 } },
92
+ },
93
+ fetches: {
94
+ 'https://api.example.com/users/7': {
95
+ value: { id: 7, name: 'Ada' },
96
+ },
97
+ 'POST https://publisher.example.com/publish': {
98
+ status: 202,
99
+ value: { accepted: true, messageId: 'message-1' },
100
+ },
101
+ },
102
+ },
103
+ fastly: {
104
+ bindings: {
105
+ configStore: 'app_config',
106
+ secretStore: 'app_secrets',
107
+ kv: { sessions: 'app_sessions' },
108
+ backends: {
109
+ 'https://api.example.com': 'api_backend',
110
+ 'https://publisher.example.com': 'publisher_backend',
111
+ },
112
+ dynamicBackends: false,
113
+ grip: {
114
+ publishEndpoint: 'https://publisher.example.com/publish',
115
+ publishBackend: 'publisher_backend',
116
+ authentication: {
117
+ scheme: 'bearer',
118
+ secretRef: 'GRIP_TOKEN',
119
+ },
120
+ },
121
+ },
122
+ build: { name: 'pulse-fastly-capabilities-example' },
123
+ },
124
+ },
125
+ }))
126
+ ```
127
+ <!-- /pulse-doc-source -->
128
+
129
+ The same example maps its logical `sessions` store and its GRIP publish endpoint
130
+ inside the provider-owned Fastly configuration boundary:
131
+ [`examples/05-fastly-capabilities`](../../examples/05-fastly-capabilities/).
132
+
133
+ Dynamic backends are disabled by default. Map every known origin to a named backend or opt in deliberately.
134
+
135
+ Secret values are never included raw in trace output, errors, snapshots, or CLI project JSON.
136
+
137
+ For `target: 'native'`, `pulse build` compiles the generated Compute package to
138
+ direct-host-ABI `bin/main.wasm`. For `target: 'javascript'`, it emits the
139
+ deterministic source/deployment closure consumed by the pinned Fastly JavaScript
140
+ runtime compiler. Both targets use these same logical bindings.
141
+
142
+ The external reality task records its selected launcher and executes the Native
143
+ candidate through either direct Viceroy:
144
+
145
+ ```bash
146
+ PULSE_VICEROY_BIN=/path/to/viceroy \
147
+ node wasm/scripts/run-wasm-tests.cjs --task provider-fastly-compute-reality --no-report
148
+ ```
149
+
150
+ or `fastly compute serve --file` selected with `PULSE_FASTLY_BIN`. The gate
151
+ sends real HTTP requests through the Native module. It remains separate from
152
+ portable and offline JavaScript-candidate validation and does not deploy a
153
+ service.
@@ -0,0 +1,101 @@
1
+ # Fetching and composing data
2
+
3
+ `ctx.fetch` is the primary host effect for data workflows.
4
+
5
+ ## One project, three composition forms
6
+
7
+ <!-- pulse-doc-source: examples/03-fetch-composition/src/index.ts -->
8
+ ```ts
9
+ import { Pulse } from '@pulse-compute/pulse'
10
+
11
+ interface User {
12
+ id: number
13
+ name: string
14
+ }
15
+
16
+ interface Stats {
17
+ score: number
18
+ }
19
+
20
+ interface Flags {
21
+ enabled: boolean
22
+ }
23
+
24
+ const app = new Pulse({ auto: true })
25
+
26
+ // One structured origin.
27
+ app.get('/user', async (ctx) => {
28
+ const user = await ctx
29
+ .fetch('https://users.example.test/users/123')
30
+ .json<User>()
31
+ return ctx.json({ found: true, user })
32
+ })
33
+
34
+ // Multiple origins with explicit sequential awaits.
35
+ app.get('/user-summary', async (ctx) => {
36
+ const user = await ctx
37
+ .fetch('https://users.example.test/users/123')
38
+ .json<User>()
39
+ const stats = await ctx
40
+ .fetch('https://stats.example.test/users/123')
41
+ .json<Stats>()
42
+ const flags = await ctx
43
+ .fetch('https://flags.example.test/users/123')
44
+ .json<Flags>()
45
+ return ctx.json({
46
+ id: user.id,
47
+ name: user.name,
48
+ score: stats.score,
49
+ enabled: flags.enabled,
50
+ })
51
+ })
52
+
53
+ // Multiple origins with explicit portable concurrency.
54
+ app.get('/user-summary-parallel', async (ctx) => {
55
+ const { user, stats, flags } = await ctx.parallel({
56
+ user: ctx.fetch('https://users.example.test/users/123').json<User>(),
57
+ stats: ctx.fetch('https://stats.example.test/users/123').json<Stats>(),
58
+ flags: ctx.fetch('https://flags.example.test/users/123').json<Flags>(),
59
+ })
60
+ return ctx.json({
61
+ id: user.id,
62
+ name: user.name,
63
+ score: stats.score,
64
+ enabled: flags.enabled,
65
+ })
66
+ })
67
+
68
+ export default app
69
+ ```
70
+ <!-- /pulse-doc-source -->
71
+
72
+ The comments separate one structured origin, explicit sequential composition,
73
+ and `ctx.parallel`. Separate awaits retain sequential JavaScript semantics. The
74
+ explicit keyed group is the portable concurrency contract across JavaScript and
75
+ Native targets. Results retain their source keys even when hosts resolve the
76
+ operations in another order.
77
+
78
+ HTTP 404 and 500 remain ordinary response data. DNS, connection, timeout, and
79
+ host failures fail the effect.
80
+
81
+ ## Dependent requests
82
+
83
+ A request whose URL or options depend on a previous result lowers to a later continuation point. The compiler does not pretend that dependent effects are an independent group.
84
+
85
+ ## Pass-through
86
+
87
+ <!-- pulse-doc-source: examples/07-opaque-proxy/src/index.ts -->
88
+ ```ts
89
+ import { Pulse } from '@pulse-compute/pulse'
90
+
91
+ const app = new Pulse({ auto: true })
92
+
93
+ app.get('/archive', async (ctx) => {
94
+ return ctx.fetch('https://assets.example.com/archive.bin')
95
+ })
96
+
97
+ export default app
98
+ ```
99
+ <!-- /pulse-doc-source -->
100
+
101
+ The direct response path preserves status, headers, repeated headers, and the host-owned body. User code cannot inspect or transform the opaque body.