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

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 (191) hide show
  1. package/API.md +572 -0
  2. package/CHANGELOG.md +71 -0
  3. package/README.md +71 -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 +470 -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 +300 -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 +70 -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 +202 -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 +100 -0
  48. package/docs/maintainers/maintenance-policy.json +809 -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 +293 -0
  55. package/docs/maintainers/release-manifest.md +89 -0
  56. package/docs/maintainers/repository-setup.md +145 -0
  57. package/docs/maintainers/scope-policy.md +105 -0
  58. package/docs/maintainers/support-and-triage.md +74 -0
  59. package/docs/maintainers/testing.md +334 -0
  60. package/docs/packages/README.md +60 -0
  61. package/docs/packages/assets.md +85 -0
  62. package/docs/packages/cli.md +190 -0
  63. package/docs/packages/crypto.md +78 -0
  64. package/docs/packages/entities.md +129 -0
  65. package/docs/packages/grip.md +82 -0
  66. package/docs/packages/implementation-packages.md +122 -0
  67. package/docs/packages/jwt.md +55 -0
  68. package/docs/packages/provider-fastly.md +243 -0
  69. package/docs/packages/pulse.md +159 -0
  70. package/docs/packages/runtime.md +265 -0
  71. package/docs/packages/s3.md +101 -0
  72. package/docs/preview-scope.md +142 -0
  73. package/docs/reference/README.md +33 -0
  74. package/docs/reference/cli-spec.json +575 -0
  75. package/docs/reference/cli.md +457 -0
  76. package/docs/reference/compatibility-matrix.md +169 -0
  77. package/docs/reference/diagnostics.md +1872 -0
  78. package/docs/reference/documentation-versions.json +22 -0
  79. package/docs/reference/environment.md +347 -0
  80. package/docs/reference/handler-authoring.md +264 -0
  81. package/docs/reference/project-config.md +582 -0
  82. package/docs/reference/project-config.schema.json +981 -0
  83. package/docs/reference/release-manifest.json +441 -0
  84. package/docs/reference/shell-completion.md +23 -0
  85. package/documentation-site.json +668 -0
  86. package/documentation-versions.json +22 -0
  87. package/examples/01-hello-json/.pulse/.gitignore +3 -0
  88. package/examples/01-hello-json/.pulse/config.ts +16 -0
  89. package/examples/01-hello-json/README.md +119 -0
  90. package/examples/01-hello-json/package.json +20 -0
  91. package/examples/01-hello-json/src/index.ts +9 -0
  92. package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
  93. package/examples/01-hello-json/tsconfig.json +14 -0
  94. package/examples/02-request-schema/.pulse/.gitignore +3 -0
  95. package/examples/02-request-schema/.pulse/config.ts +17 -0
  96. package/examples/02-request-schema/README.md +145 -0
  97. package/examples/02-request-schema/package.json +20 -0
  98. package/examples/02-request-schema/src/index.ts +18 -0
  99. package/examples/02-request-schema/src/schemas.ts +20 -0
  100. package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
  101. package/examples/02-request-schema/tsconfig.json +14 -0
  102. package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
  103. package/examples/03-fetch-composition/.pulse/config.ts +31 -0
  104. package/examples/03-fetch-composition/README.md +210 -0
  105. package/examples/03-fetch-composition/package.json +20 -0
  106. package/examples/03-fetch-composition/src/index.ts +60 -0
  107. package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
  108. package/examples/03-fetch-composition/tsconfig.json +14 -0
  109. package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
  110. package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
  111. package/examples/05-fastly-capabilities/README.md +257 -0
  112. package/examples/05-fastly-capabilities/package.json +22 -0
  113. package/examples/05-fastly-capabilities/src/index.ts +55 -0
  114. package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
  115. package/examples/05-fastly-capabilities/tsconfig.json +14 -0
  116. package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
  117. package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
  118. package/examples/07-opaque-proxy/README.md +150 -0
  119. package/examples/07-opaque-proxy/package.json +21 -0
  120. package/examples/07-opaque-proxy/src/index.ts +9 -0
  121. package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
  122. package/examples/07-opaque-proxy/tsconfig.json +14 -0
  123. package/examples/09-router-lowering/.pulse/.gitignore +3 -0
  124. package/examples/09-router-lowering/.pulse/config.ts +22 -0
  125. package/examples/09-router-lowering/README.md +287 -0
  126. package/examples/09-router-lowering/package.json +22 -0
  127. package/examples/09-router-lowering/src/index.ts +65 -0
  128. package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
  129. package/examples/09-router-lowering/tsconfig.json +14 -0
  130. package/examples/10-entities-tools/.pulse/.gitignore +4 -0
  131. package/examples/10-entities-tools/.pulse/config.ts +23 -0
  132. package/examples/10-entities-tools/README.md +165 -0
  133. package/examples/10-entities-tools/package.json +21 -0
  134. package/examples/10-entities-tools/src/handlers.ts +15 -0
  135. package/examples/10-entities-tools/src/index.ts +31 -0
  136. package/examples/10-entities-tools/src/schemas.ts +18 -0
  137. package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
  138. package/examples/10-entities-tools/tools-facade.cjs +158 -0
  139. package/examples/10-entities-tools/tsconfig.json +14 -0
  140. package/examples/11-events/.pulse/.gitignore +4 -0
  141. package/examples/11-events/.pulse/config.ts +24 -0
  142. package/examples/11-events/README.md +194 -0
  143. package/examples/11-events/package.json +20 -0
  144. package/examples/11-events/src/index.ts +27 -0
  145. package/examples/11-events/src/schemas.ts +19 -0
  146. package/examples/11-events/tests/pulse.harness.ts +34 -0
  147. package/examples/11-events/tsconfig.json +15 -0
  148. package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
  149. package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
  150. package/examples/12-mcp-proxy/README.md +149 -0
  151. package/examples/12-mcp-proxy/package.json +20 -0
  152. package/examples/12-mcp-proxy/src/index.ts +17 -0
  153. package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
  154. package/examples/12-mcp-proxy/tsconfig.json +14 -0
  155. package/examples/13-jwt-es256/.pulse/config.ts +19 -0
  156. package/examples/13-jwt-es256/README.md +195 -0
  157. package/examples/13-jwt-es256/package.json +21 -0
  158. package/examples/13-jwt-es256/src/index.ts +21 -0
  159. package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
  160. package/examples/13-jwt-es256/tsconfig.json +14 -0
  161. package/examples/README.md +36 -0
  162. package/package.json +66 -6
  163. package/project-config.schema.json +981 -0
  164. package/release-manifest.json +441 -0
  165. package/src/command-spec.js +279 -0
  166. package/src/completion.js +113 -0
  167. package/src/diagnostics.js +350 -0
  168. package/src/documentation.js +45 -0
  169. package/src/index.d.ts +133 -0
  170. package/src/index.js +15 -0
  171. package/src/internal/command-executor.d.ts +29 -0
  172. package/src/internal/command-executor.js +143 -0
  173. package/src/internal/command-plan.d.ts +9 -0
  174. package/src/internal/command-plan.js +73 -0
  175. package/src/internal/command-reporter.d.ts +15 -0
  176. package/src/internal/command-reporter.js +133 -0
  177. package/src/internal/command-request.d.ts +35 -0
  178. package/src/internal/command-request.js +154 -0
  179. package/src/internal/node-http.js +54 -0
  180. package/src/internal/project-context.d.ts +66 -0
  181. package/src/internal/project-context.js +175 -0
  182. package/src/project-config-reference.js +4 -0
  183. package/src/project-config-schema.d.ts +23 -0
  184. package/src/project-config-schema.js +271 -0
  185. package/src/project-config.js +724 -0
  186. package/src/project-execution.js +2946 -0
  187. package/src/provider-drivers.js +3 -0
  188. package/src/target-support.js +3 -0
  189. package/src/typescript-module-loader.js +269 -0
  190. package/src/workflow.js +78 -0
  191. package/src/workspace.js +82 -0
@@ -0,0 +1,515 @@
1
+ # Pulse Vision
2
+
3
+ > **Status:** North-star architecture and product direction.
4
+ > This document describes the intended shape of Pulse from the Beta
5
+ > toward a compatibility-bearing 1.0. It is directional rather than a
6
+ > compatibility promise for every current release.
7
+
8
+ Pulse is a portable application contract for progressively constrained compute.
9
+
10
+ It lets developers begin with familiar TypeScript, choose an execution target deliberately, and move toward compact native WebAssembly without hiding where the source stops being eligible. Pulse does not attempt to make every host identical, compile the entire JavaScript ecosystem, or silently change deployment architecture. It makes boundaries explicit so teams can ship now and reduce execution debt over time.
11
+
12
+ The long-term goal is not merely “TypeScript to Wasm.” It is a disciplined path from familiar application code to small, host-realizable programs across servers, edge platforms, and constrained devices.
13
+
14
+ ## The problem
15
+
16
+ Application code tends to accumulate host assumptions gradually:
17
+
18
+ - framework-specific request and response objects;
19
+ - implicit event-loop and Promise behavior;
20
+ - direct access to environment variables and secrets;
21
+ - provider SDKs embedded in business logic;
22
+ - large dependencies added for a single feature;
23
+ - opaque JSON contracts;
24
+ - deployment behavior that changes without a clear source-level milestone.
25
+
26
+ Those assumptions are easy to introduce and difficult to remove later. By the time a team wants a smaller artifact, a more constrained host, or a different provider, the application and execution environment may be inseparable.
27
+
28
+ Traditional approaches often force an all-or-nothing choice:
29
+
30
+ 1. stay in a general JavaScript runtime and accept its operational cost; or
31
+ 2. rewrite the application into a constrained language or platform-specific SDK before receiving any native benefit.
32
+
33
+ Pulse aims for a third path.
34
+
35
+ ## The thesis
36
+
37
+ Pulse provides one stable application model and several explicit execution targets.
38
+
39
+ ```text
40
+ familiar TypeScript application
41
+
42
+ Pulse runtime semantics
43
+
44
+ eligibility analysis
45
+
46
+ explicitly selected target
47
+
48
+ native Wasm, JavaScript runtime, or another host realization
49
+ ```
50
+
51
+ The source remains recognizable. The target determines which parts can be lowered and how the host realizes capabilities.
52
+
53
+ Pulse should tell the operator:
54
+
55
+ - what target was selected;
56
+ - what was lowered;
57
+ - what could not be lowered;
58
+ - the first exact boundary where native eligibility ended;
59
+ - which alternate targets are available;
60
+ - what size, validation, or execution tradeoffs the selection introduces.
61
+
62
+ Pulse must not silently fall back from native Wasm to JavaScript. Changing the target is an operator decision expressed in configuration or an explicit CLI invocation.
63
+
64
+ ## Familiar TypeScript is the migration surface
65
+
66
+ Pulse should feel like ordinary TypeScript application code, not a compiler DSL.
67
+
68
+ The intended handler shape is async:
69
+
70
+ ```ts
71
+ app.get('/users/:id', async (ctx) => {
72
+ const user = await ctx
73
+ .fetch(`/origin/users/${ctx.param('id')}`)
74
+ .json<User>('app.User')
75
+
76
+ return ctx.json({ user })
77
+ })
78
+ ```
79
+
80
+ The same source has different mechanics under different targets.
81
+
82
+ ### Native target
83
+
84
+ ```text
85
+ async wrapper
86
+ → erased by lowering
87
+
88
+ await trusted Pulse effect
89
+ → effect emission
90
+ → state-machine suspension
91
+ → host completion
92
+ → continuation resume
93
+
94
+ Promise runtime
95
+ → absent
96
+ ```
97
+
98
+ ### JavaScript target
99
+
100
+ ```text
101
+ async wrapper
102
+ → ordinary async JavaScript
103
+
104
+ await trusted Pulse effect
105
+ → Promise-backed runtime operation
106
+
107
+ continuation
108
+ → JavaScript runtime
109
+ ```
110
+
111
+ The mechanics differ, but Pulse-visible semantics must not:
112
+
113
+ - route and middleware order;
114
+ - capability ordering;
115
+ - error-lane behavior;
116
+ - status and headers;
117
+ - body ownership;
118
+ - schema results;
119
+ - redaction;
120
+ - completion and fallthrough.
121
+
122
+ All Pulse handlers should be async-shaped even when a particular handler contains no `await`. That keeps the source stable when capability work is added later.
123
+
124
+ ## A small control-flow grammar
125
+
126
+ Pulse intentionally keeps routing control separate from capability suspension.
127
+
128
+ ```text
129
+ await effect → suspend for external capability work
130
+ return next() → permanently transfer Router control
131
+ return response → complete the request
132
+ ```
133
+
134
+ Terminal middleware avoids onion-style resumption:
135
+
136
+ ```ts
137
+ app.use(async (ctx, next) => {
138
+ const session = await ctx.kv.get('sessions', ctx.req.header('x-session'))
139
+
140
+ if (!session) {
141
+ return ctx.json({ error: 'unauthorized' }, { status: 401 })
142
+ }
143
+
144
+ return next()
145
+ })
146
+ ```
147
+
148
+ `next()` is not awaited and does not return control to the middleware. This removes suspended middleware frames, post-response mutation, ambiguous cleanup order, and accidental double responses.
149
+
150
+ The constraint simplifies native lowering, JavaScript execution, inspection, and cross-target conformance.
151
+
152
+ ## Explicit execution targets
153
+
154
+ Pulse should classify execution honestly.
155
+
156
+ ```text
157
+ native
158
+ fully lowered into Pulse-owned Wasm
159
+
160
+ javascript
161
+ executed by a JavaScript runtime using the same Pulse semantics
162
+
163
+ sidecar
164
+ executed across an explicit serialized boundary
165
+
166
+ rejected
167
+ unsupported for the selected target
168
+ ```
169
+
170
+ Not every target must exist for every provider.
171
+
172
+ A native build that encounters unsupported code should fail precisely:
173
+
174
+ ```text
175
+ PULSE_NATIVE_AWAIT_UNSUPPORTED
176
+
177
+ Route:
178
+ POST /reports
179
+
180
+ Handler:
181
+ createReport
182
+
183
+ Unsupported boundary:
184
+ await reportingLibrary.render(data)
185
+
186
+ The selected native target supports await only on trusted Pulse effects.
187
+
188
+ Available paths:
189
+ - remove or isolate the dependency;
190
+ - replace it with a Pulse capability;
191
+ - provide package-owned lowering;
192
+ - explicitly select the JavaScript target;
193
+ - move the operation behind a sidecar boundary.
194
+ ```
195
+
196
+ The failure is useful product output. It records the moment architectural debt entered the stack.
197
+
198
+ An operator can then deliberately choose a different target and continue shipping:
199
+
200
+ ```ts
201
+ export default defineConfig({
202
+ execution: {
203
+ target: 'javascript',
204
+ },
205
+ })
206
+ ```
207
+
208
+ Pulse still reports native eligibility while building the JavaScript target, so the path back toward native remains visible.
209
+
210
+ ## Hosts are not equal
211
+
212
+ Pulse can target multiple hosts, but it must not pretend that every host has the same capabilities, execution models, or escape routes.
213
+
214
+ ```text
215
+ Node
216
+ native target: available
217
+ JavaScript target: available
218
+ host strengths: broad local and server integration
219
+
220
+ Fastly
221
+ native target: available
222
+ JavaScript target: available as an explicit larger-runtime choice
223
+ host strengths: edge HTTP, named backends, config, secrets, KV
224
+
225
+ ESP32
226
+ native target: architectural goal / reference host
227
+ JavaScript target: unavailable
228
+ host strengths: GPIO, timers, sensors, constrained event processing
229
+ ```
230
+
231
+ This is not “write once, run anywhere.”
232
+
233
+ It is:
234
+
235
+ > Use one application contract, then let each host state clearly what it can realize.
236
+
237
+ A provider owns the concrete ABI and lifecycle. Pulse owns application semantics.
238
+
239
+ ### Pulse owns
240
+
241
+ - Router topology and matching;
242
+ - terminal middleware;
243
+ - normal and error lanes;
244
+ - effects and continuations;
245
+ - request and response semantics;
246
+ - schema and JSON contracts;
247
+ - capability identities;
248
+ - eligibility diagnostics;
249
+ - portable inspection artifacts.
250
+
251
+ ### The host owns
252
+
253
+ - sockets and HTTP stacks;
254
+ - interrupts and event queues;
255
+ - GPIO and sensors;
256
+ - clocks and timers;
257
+ - storage engines;
258
+ - concrete network APIs;
259
+ - process lifecycle;
260
+ - resource handles;
261
+ - ABI realization.
262
+
263
+ Host sovereignty is a feature. It prevents the runtime contract from becoming a lowest-common-denominator abstraction.
264
+
265
+ ## Containment reduces friction later
266
+
267
+ Pulse is intentionally disciplined at boundaries:
268
+
269
+ - capability access goes through `ctx`;
270
+ - Router control uses terminal transfers;
271
+ - native suspension occurs only at trusted effects;
272
+ - schemas provide runtime truth;
273
+ - provider mechanics stay outside application logic;
274
+ - target changes are explicit;
275
+ - unsupported lowering stops at the first exact boundary.
276
+
277
+ This can feel stricter at the point of authoring, but it reduces friction later.
278
+
279
+ Containment gives the project:
280
+
281
+ - smaller native artifacts;
282
+ - deterministic lowering;
283
+ - simpler host adapters;
284
+ - better cross-target testing;
285
+ - clearer security and secret boundaries;
286
+ - visible dependency debt;
287
+ - less provider lock-in inside business logic;
288
+ - a practical path from general code toward constrained execution.
289
+
290
+ The goal is not maximal permissiveness. The goal is to make the cost and ownership of every escape understandable.
291
+
292
+ ## Schemas are optimization and validation artifacts
293
+
294
+ TypeScript generics help the author but disappear at runtime. Pulse schemas provide runtime identity across targets.
295
+
296
+ ```ts
297
+ const input = await ctx.req.json<CreateUser>('app.CreateUser')
298
+ ```
299
+
300
+ ### Native execution
301
+
302
+ The schema can drive:
303
+
304
+ - specialized decoding;
305
+ - projection;
306
+ - bounded layouts;
307
+ - smaller artifacts;
308
+ - predictable memory use;
309
+ - precise validation.
310
+
311
+ ### JavaScript execution
312
+
313
+ The same schema can drive generated validation after `JSON.parse`.
314
+
315
+ Validation policy should be explicit:
316
+
317
+ ```ts
318
+ export default defineConfig({
319
+ schema: {
320
+ validation: 'strict', // strict | warn | off
321
+ },
322
+ })
323
+ ```
324
+
325
+ - `strict`: reject invalid values;
326
+ - `warn`: continue but record a contract violation;
327
+ - `off`: parse only, while retaining schema visibility for tooling and eligibility analysis.
328
+
329
+ Schemas should specialize and verify JSON; they should not become the price of admission for JSON.
330
+
331
+ Pulse should support a gradient:
332
+
333
+ ```text
334
+ strict projected schema
335
+ → status-indexed schema variants
336
+ → generic native JSON
337
+ → explicit JavaScript parse-only behavior
338
+ ```
339
+
340
+ Response contracts should be status-aware rather than forcing unrelated success and failure payloads into one artificial shape:
341
+
342
+ ```ts
343
+ responses: {
344
+ 200: 'app.User',
345
+ 404: 'app.NotFound',
346
+ '5xx': 'app.ServerError',
347
+ default: 'json',
348
+ }
349
+ ```
350
+
351
+ Tooling should help observe, diff, propose, and verify these contracts over time.
352
+
353
+ ## Eligibility is a first-class artifact
354
+
355
+ A Pulse build should produce more than a deployable binary.
356
+
357
+ It should also produce an architectural ledger:
358
+
359
+ ```json
360
+ {
361
+ "selectedTarget": "javascript",
362
+ "nativeEligibility": {
363
+ "eligible": false,
364
+ "eligibleRoutes": 12,
365
+ "blockedRoutes": 1,
366
+ "firstUnsupportedBoundary": {
367
+ "route": "POST /reports",
368
+ "handler": "createReport",
369
+ "kind": "unsupported-import",
370
+ "specifier": "large-reporting-library"
371
+ }
372
+ }
373
+ }
374
+ ```
375
+
376
+ Teams should be able to see when a release moved from fully native-eligible to partially blocked, why it happened, and what must change to recover eligibility.
377
+
378
+ This turns constraint into feedback rather than punishment.
379
+
380
+ ## Cross-target semantics must be enforced
381
+
382
+ Native and JavaScript targets must share one conformance corpus.
383
+
384
+ The same application cases should run through every supported realization:
385
+
386
+ ```text
387
+ native Node
388
+ native Fastly
389
+ JavaScript Node
390
+ JavaScript Fastly
391
+ ```
392
+
393
+ The contract suite should compare Pulse-visible behavior:
394
+
395
+ - response status;
396
+ - ordered and repeated headers;
397
+ - structured and opaque bodies;
398
+ - route and middleware ordering;
399
+ - terminal `next()` behavior;
400
+ - error-lane transitions;
401
+ - capability ordering;
402
+ - schema success and failure;
403
+ - transport errors versus HTTP errors;
404
+ - redaction;
405
+ - compiler-owned 404 and 500 behavior.
406
+
407
+ Provider mechanics may differ. Pulse semantics may not.
408
+
409
+ Drift should fail CI and block release.
410
+
411
+ ## ESP32 as a boundary witness
412
+
413
+ The ESP32 reference host exists to test whether Pulse is genuinely a constrained-compute model or merely an HTTP compiler.
414
+
415
+ Reference repository:
416
+
417
+ [pulsecompute/pulse-esp32-host](https://github.com/pulsecompute/pulse-esp32-host)
418
+
419
+ A device-oriented application might look like:
420
+
421
+ ```ts
422
+ app.on('gpio:button', async (ctx) => {
423
+ const enabled = await ctx.kv.get<boolean>('device', 'enabled')
424
+
425
+ await ctx.gpio.write('status-led', !enabled)
426
+ await ctx.kv.put('device', 'enabled', !enabled)
427
+ })
428
+ ```
429
+
430
+ On an ESP32 host, those `await` expressions would not require a Promise runtime. They would lower into host operations and continuation states.
431
+
432
+ The event path must remain host-controlled:
433
+
434
+ ```text
435
+ native interrupt service routine
436
+ → host event queue
437
+ → Pulse event frame
438
+ → Wasm handler
439
+ ```
440
+
441
+ Pulse should never make reentrant calls from an interrupt directly into Wasm.
442
+
443
+ The ESP32 host is not a promise that every Pulse application runs on a microcontroller. It demonstrates that the abstractions still make sense when these assumptions are removed:
444
+
445
+ - no JavaScript fallback;
446
+ - no abundant memory;
447
+ - no cloud runtime;
448
+ - no HTTP-centric lifecycle;
449
+ - no generic operating-system services.
450
+
451
+ Fastly proves native edge execution. ESP32 tests the larger architectural claim.
452
+
453
+ ## Product milestones
454
+
455
+ ### Beta: explicit execution fluidity
456
+
457
+ The Beta proves:
458
+
459
+ - the public runtime and conventional Pulse application contracts;
460
+ - Router and terminal middleware;
461
+ - async-shaped handlers and trusted `await` lowering;
462
+ - effects, continuations, schemas, and package-owned capabilities;
463
+ - compact portable and provider-native Wasm;
464
+ - explicit Node and Fastly JavaScript execution and packaging;
465
+ - deliberate target selection with native-eligibility diagnostics;
466
+ - a shared four-mode conformance corpus;
467
+ - explicit failure at unsupported Native boundaries;
468
+ - no silent fallback.
469
+
470
+ The preview remains a deliberate pre-compatibility release. Passing its
471
+ technical gates does not authorize publication or deployment.
472
+
473
+ ### 1.0: compatibility-bearing runtime contract
474
+
475
+ The first stable release begins when the Beta contract has been
476
+ exercised publicly and the runtime, CLI, target-selection, and conformance
477
+ policies are ready to carry compatibility obligations.
478
+
479
+ Sidecar partitioning, richer observability, additional hosts, and higher-order `@pulse-compute/pulse` ergonomics can continue after 1.0.
480
+
481
+ ## What Pulse refuses to promise
482
+
483
+ Pulse does not promise:
484
+
485
+ - that arbitrary JavaScript becomes native Wasm;
486
+ - that every npm package is supported by every target;
487
+ - that every provider has the same capabilities;
488
+ - that JavaScript and native targets have identical size or startup cost;
489
+ - that TypeScript generics provide runtime validation;
490
+ - that unsupported code silently changes the deployment target;
491
+ - that a host abstraction erases host ownership;
492
+ - that all Pulse applications run on every device.
493
+
494
+ These refusals protect the useful promises.
495
+
496
+ ## North star
497
+
498
+ Pulse should let a team say:
499
+
500
+ > We began with a familiar TypeScript application.
501
+ > We shipped it using an explicit execution target.
502
+ > Pulse showed us exactly where native eligibility ended.
503
+ > We removed dependencies, introduced trusted effects and schemas, and progressively constrained the program.
504
+ > The application model stayed stable while the artifact became smaller, more portable, and easier to host.
505
+
506
+ That is the vision:
507
+
508
+ ```text
509
+ familiar TypeScript
510
+ + explicit host capabilities
511
+ + visible eligibility boundaries
512
+ + deliberate execution targets
513
+ + progressive containment
514
+ = constrained compute without an all-or-nothing rewrite
515
+ ```
@@ -0,0 +1,186 @@
1
+ # Structured and opaque bodies
2
+
3
+ Pulse distinguishes bodies that application code may inspect from bodies that must remain host-owned. That distinction is part of the provider-neutral contract and is visible in types, compiler metadata, tests, and diagnostics.
4
+
5
+ A useful rule is:
6
+
7
+ > **Inspect it as a bounded structured value, or pass it through as an opaque response. Do not silently switch between the two.**
8
+
9
+ ## Ownership transitions
10
+
11
+ All network body bytes begin under host or provider ownership. A supported
12
+ Pulse operation either converts them once into bounded request-owned data or
13
+ preserves an opaque host-owned handle. Ownership never moves through a provider
14
+ SDK object in userland.
15
+
16
+ | Boundary | Owner before | Application operation | Owner after |
17
+ |---|---|---|---|
18
+ | Incoming request text/JSON | Request host owns body bytes | `ctx.req.text()` or `ctx.req.json()` | Current request owns the bounded structured value and read cache. |
19
+ | Fetched response text/JSON | Provider adapter owns response bytes | `.text()` or `.json()` on the fetch operation | Current request owns the bounded structured projection. |
20
+ | Outbound fetch JSON | Application owns a supported structured value | `ctx.fetch(url, { json, schema })` | Pulse encodes the semantic value; the provider owns dispatched body bytes. |
21
+ | Application text/JSON response | Application owns a supported structured value | `ctx.text()`, `ctx.json()`, or `ctx.response()` | Pulse returns a terminal result; the provider owns response realization. |
22
+ | Opaque fetch or package response | Provider owns the body handle | Return the response directly | Provider retains ownership through terminal pass-through. |
23
+
24
+ Request-owned values and caches end with that request. They cannot be retained
25
+ for background work. Provider-owned opaque handles stay opaque: they cannot be converted into a structured body. Structured values cannot be promoted into
26
+ a userland stream.
27
+
28
+ ## Structured request bodies
29
+
30
+ `ctx.req.text()` and `ctx.req.json()` read a bounded request body. JSON can be decoded generically or against an explicitly compiled schema. Repeated schema reads are deterministic within one request.
31
+
32
+ The schema example decodes one body twice and proves that the request-local decoded value is reused:
33
+
34
+ <!-- pulse-doc-source: examples/02-request-schema/src/index.ts -->
35
+ ```ts
36
+ import { Pulse } from '@pulse-compute/pulse'
37
+ import type { CreateUserInput, CreateUserOutput } from './schemas.js'
38
+
39
+ const app = new Pulse({ auto: true })
40
+
41
+ app.post('/users', async (ctx) => {
42
+ const first = await ctx.req.json<CreateUserInput>('app.CreateUserInput')
43
+ const second = await ctx.req.json<CreateUserInput>('app.CreateUserInput')
44
+ const output: CreateUserOutput = {
45
+ id: 7,
46
+ name: first.name,
47
+ active: first.active,
48
+ sameReference: first === second,
49
+ }
50
+ return ctx.json(output, { status: 201, schema: 'app.CreateUserOutput' })
51
+ })
52
+
53
+ export default app
54
+ ```
55
+ <!-- /pulse-doc-source -->
56
+
57
+ ```bash
58
+ pulse test examples/02-request-schema --case valid-user --json
59
+ ```
60
+
61
+ Structured body limits are configured through `schemas.maxBytes`, `dev.maxBodyBytes`, or a test case’s `maxBodyBytes`, depending on the read path. Oversized or invalid input fails before unbounded materialization with diagnostics such as:
62
+
63
+ - [`PULSE_REQUEST_BODY_TOO_LARGE`](../reference/diagnostics.md#pulse-request-body-too-large);
64
+ - [`PULSE_BODY_TOO_LARGE`](../reference/diagnostics.md#pulse-body-too-large);
65
+ - [`PULSE_BODY_DECODE`](../reference/diagnostics.md#pulse-body-decode);
66
+ - [`PULSE_SCHEMA_DECODE`](../reference/diagnostics.md#pulse-schema-decode).
67
+
68
+ A successful schema decode returns a normalized, deeply immutable value.
69
+ Repeated request reads with the same schema ID reuse that request-local decoded
70
+ value. Generic JSON is also bounded, but it does not gain a typed schema
71
+ contract.
72
+
73
+ ## Structured fetch responses
74
+
75
+ A fetch response can be inspected through normalized status, headers, `text()`, and `json<T>()`. Once application code asks for text or JSON, the body is treated as a bounded structured value governed by decoding and size policy.
76
+
77
+ ```ts
78
+ const user = await ctx.fetch('https://api.example.test/user').json<{ id: number; name: string }>()
79
+ return ctx.json({ found: true, user })
80
+ ```
81
+
82
+ The application receives portable data, not a provider SDK response object.
83
+
84
+ Schema-bound fetched JSON uses the same codec and content-type policy as
85
+ schema-bound request JSON. A read consumes the response into a structured
86
+ projection for the current request; it does not expose a reusable provider
87
+ stream.
88
+
89
+ ## Opaque response pass-through
90
+
91
+ Some responses should cross Pulse without being copied, decoded, or exposed to userland—archives, media, streaming responses, and provider-owned hold responses are examples. Return the fetch response directly:
92
+
93
+ <!-- pulse-doc-source: examples/07-opaque-proxy/src/index.ts -->
94
+ ```ts
95
+ import { Pulse } from '@pulse-compute/pulse'
96
+
97
+ const app = new Pulse({ auto: true })
98
+
99
+ app.get('/archive', async (ctx) => {
100
+ return ctx.fetch('https://assets.example.com/archive.bin')
101
+ })
102
+
103
+ export default app
104
+ ```
105
+ <!-- /pulse-doc-source -->
106
+
107
+ <!-- pulse-doc-run {"args":["inspect","examples/07-opaque-proxy","--json"],"display":"pulse inspect examples/07-opaque-proxy --json"} -->
108
+ ```bash
109
+ pulse inspect examples/07-opaque-proxy --json
110
+ ```
111
+ ```json
112
+ {
113
+ "status": "ok",
114
+ "provider": {
115
+ "id": "fastly"
116
+ },
117
+ "compiler": {
118
+ "effectCount": 1,
119
+ "continuationCount": 1,
120
+ "opaqueReturnCount": 1,
121
+ "providerLowering": {
122
+ "requirements": [
123
+ "fetch",
124
+ "opaque.pass-through"
125
+ ]
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ Opaque pass-through preserves host ownership. Pulse may carry status and headers needed to complete the response, but application code cannot inspect chunks, decode the body, concatenate it, or retain it beyond the request lifecycle.
132
+
133
+ Attempting to inspect an opaque body fails with [`PULSE_OPAQUE_BODY_INSPECTION`](../reference/diagnostics.md#pulse-opaque-body-inspection). A missing or already-consumed structured body can fail with [`PULSE_BODY_UNAVAILABLE`](../reference/diagnostics.md#pulse-body-unavailable).
134
+
135
+ ## Why the distinction matters
136
+
137
+ The two body classes have different guarantees:
138
+
139
+ | Property | Structured | Opaque |
140
+ |---|---:|---:|
141
+ | Application can read text/JSON | Yes, within limits | No |
142
+ | Application can construct a replacement body | Yes, from supported values | No |
143
+ | Provider object enters userland | No | No |
144
+ | Body may remain host-owned | No | Yes |
145
+ | Suitable for binary/stream pass-through | No | Yes |
146
+ | Userland chunk iteration or transform | No | No |
147
+
148
+ Pulse does not infer that a body is safe to inspect merely because one provider could expose it. The same canonical source must retain equivalent meaning across supported providers.
149
+
150
+ ## Schema encoding
151
+
152
+ `ctx.json(value, { schema: 'namespace.Type' })` validates and encodes a structured response against the compiled schema contract. Schema identifiers must be static and declared in project configuration. Failures use [`PULSE_SCHEMA_ENCODE`](../reference/diagnostics.md#pulse-schema-encode) or [`PULSE_RESPONSE_ENCODE`](../reference/diagnostics.md#pulse-response-encode).
153
+
154
+ `ctx.fetch(url, { json: value, schema: 'namespace.Type' })` applies the same
155
+ semantic encoding boundary to an outbound request. Pulse encodes the value
156
+ before provider dispatch; application code never receives the provider request
157
+ body object.
158
+
159
+ See [Explicit JSON schemas](../guides/json-schemas.md) for the exact request,
160
+ fetch, response, strict-mode, and response-case forms.
161
+
162
+ ## GRIP hold responses are opaque
163
+
164
+ A package-owned `grip.hold(...)` operation returns an opaque response contract. The provider owns the hold/stream realization; canonical handler code may return it but may not inspect or transform its body. This is the same body boundary used by direct fetch pass-through.
165
+
166
+ ## Not supported in the Beta
167
+
168
+ The public contract does not include:
169
+
170
+ - arbitrary binary body inspection;
171
+ - userland stream readers or writers;
172
+ - chunk iteration or transforms;
173
+ - buffering an opaque response into structured memory;
174
+ - provider-specific response objects;
175
+ - background consumption after the request completes.
176
+
177
+ These exclusions are listed in the
178
+ [Beta scope](../preview-scope.md).
179
+
180
+ ## Related documentation
181
+
182
+ - [Canonical API](../../API.md)
183
+ - [Explicit JSON schemas](../guides/json-schemas.md)
184
+ - [Fetching and composing data](../guides/fetching-and-composition.md)
185
+ - [Effects and continuations](./effects-and-continuations.md)
186
+ - [Project configuration](../reference/project-config.md)