@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,94 @@
1
+ # GRIP and Fanout
2
+
3
+ Pulse treats GRIP as stateless HTTP framing around an external connection owner. Fanout, Pushpin, or another GRIP gateway maintains connections and fan-out state. Pulse handles ordinary requests and responses.
4
+
5
+ The JavaScript target permits ordinary runtime values:
6
+
7
+ ```ts
8
+ import { Router } from '@pulse-compute/runtime'
9
+ import { grip } from '@pulse-compute/grip'
10
+
11
+ const app = new Router()
12
+
13
+ app.get('/events/:topic', async (ctx) => {
14
+ const channel = `topic:${ctx.param('topic')}`
15
+
16
+ if (grip.isWebSocket(ctx.req)) {
17
+ return grip.handoff({ channel })
18
+ }
19
+
20
+ return grip.subscribe(
21
+ new Response(null, { status: 200 }),
22
+ { channel, mode: 'stream', timeoutMs: 30_000 },
23
+ )
24
+ })
25
+
26
+ app.post('/events/:topic', async (ctx) => {
27
+ const data = await ctx.req.json()
28
+ await grip.broadcast(ctx, {
29
+ channel: `topic:${ctx.param('topic')}`,
30
+ event: 'pulse.message',
31
+ data,
32
+ })
33
+ return ctx.json({ accepted: true }, { status: 202 })
34
+ })
35
+
36
+ export default app
37
+ ```
38
+
39
+ `isWebSocket`, `subscribe`, and `handoff` are pure request/response helpers. `broadcast` is the only provider effect, so it participates in normal request cancellation, secret redaction, direct `await`, and keyed `ctx.parallel({ ... })` behavior.
40
+
41
+ The Native target recognizes the same root with supported static framing options
42
+ and broadcast messages:
43
+
44
+ ```ts
45
+ import { grip } from '@pulse-compute/grip'
46
+
47
+ export async function handler(ctx) {
48
+ if (grip.isWebSocket(ctx.req)) {
49
+ return grip.handoff({ channel: 'topic:news', body: 'handoff' })
50
+ }
51
+
52
+ await grip.broadcast(ctx, {
53
+ channel: 'topic:news',
54
+ event: 'pulse.message',
55
+ data: { available: true },
56
+ })
57
+
58
+ return grip.subscribe(new Response(null, { status: 202 }), {
59
+ channels: ['topic:news', 'topic:audit'],
60
+ mode: 'stream',
61
+ timeoutMs: 30_000,
62
+ })
63
+ }
64
+ ```
65
+
66
+ Dynamic Native channel, option, or message shapes fail at the unsupported
67
+ expression. Pulse does not retry another lowering or switch targets.
68
+
69
+ Provider broadcast configuration is distinct from the public gateway URL and
70
+ the application's ingress route. It supplies an explicit publish/control
71
+ endpoint and, on Fastly, its named backend:
72
+
73
+ ```ts
74
+ fastly({
75
+ backends: {
76
+ 'https://publisher.example': 'grip_publisher',
77
+ },
78
+ grip: {
79
+ publishEndpoint: 'https://publisher.example/publish',
80
+ publishBackend: 'grip_publisher',
81
+ authentication: {
82
+ scheme: 'bearer',
83
+ secretRef: 'GRIP_PUBLISH_TOKEN',
84
+ },
85
+ },
86
+ })
87
+ ```
88
+
89
+ Only the secret reference enters project configuration; the provider resolves
90
+ the value inside the shared redaction boundary. There is no ambient endpoint,
91
+ credential, connection state, or automatic fallback. Cross-target conformance
92
+ covers framing, cancellation, bounds, acknowledgements, redaction, and
93
+ deterministic Node/Fastly provider artifacts. GRIP is included in the generally
94
+ available Node JavaScript target in the Beta.
@@ -0,0 +1,233 @@
1
+ # Explicit JSON schemas
2
+
3
+ Schemas are static project declarations, not runtime reflection. The compiler does not execute the registry module. Pulse extracts the default export,
4
+ compiles deterministic JavaScript and Native codecs, and binds literal schema
5
+ IDs at every structured JSON boundary.
6
+
7
+ ## Declare the registry
8
+
9
+ <!-- pulse-doc-source: examples/02-request-schema/src/schemas.ts -->
10
+ ```ts
11
+ import { defineSchemaRegistry, schema } from '@pulse-compute/pulse/schema'
12
+
13
+ export interface CreateUserInput {
14
+ name: string
15
+ active: boolean
16
+ }
17
+
18
+ export interface CreateUserOutput {
19
+ id: number
20
+ name: string
21
+ active: boolean
22
+ sameReference: boolean
23
+ }
24
+
25
+ export default defineSchemaRegistry({
26
+ schemas: {
27
+ 'app.CreateUserInput': schema<CreateUserInput>(),
28
+ 'app.CreateUserOutput': schema<CreateUserOutput>(),
29
+ },
30
+ })
31
+ ```
32
+ <!-- /pulse-doc-source -->
33
+
34
+ The string keys are the contract. TypeScript type names help authoring, but they
35
+ are not discovered automatically and do not become runtime schema IDs.
36
+
37
+ The schema subset is intentionally portable:
38
+
39
+ - an object root with required property signatures;
40
+ - `string`, `boolean`, and finite JSON `number`;
41
+ - `Int32` and `Uint32` marker types imported with `import type`;
42
+ - nested object types and arrays;
43
+ - string-literal enums such as `'admin' | 'member'`;
44
+ - one supported type unioned with `null`.
45
+
46
+ Optional fields, `undefined`, recursive or generic types, interface inheritance,
47
+ arbitrary unions, computed registry keys, runtime registry code, and public
48
+ `json-as` decorators or imports are not supported. Relative type-only imports
49
+ and re-exports can organize the type graph inside the project.
50
+
51
+ ## Add semantic response cases
52
+
53
+ A response case gives one stable ID both a status and an already registered
54
+ schema:
55
+
56
+ ```ts
57
+ import {
58
+ defineSchemaRegistry,
59
+ response,
60
+ schema,
61
+ } from '@pulse-compute/pulse/schema'
62
+ import type { ApiError, CreateUserInput, User } from './models.js'
63
+
64
+ export default defineSchemaRegistry({
65
+ schemas: {
66
+ 'app.CreateUserInput': schema<CreateUserInput>(),
67
+ 'app.User': schema<User>(),
68
+ 'app.ApiError': schema<ApiError>(),
69
+ },
70
+ responses: {
71
+ 'user.created': response(201, 'app.User'),
72
+ 'user.failure': response(400, 'app.ApiError'),
73
+ },
74
+ })
75
+ ```
76
+
77
+ The response status and schema ID must be static literals. A response case
78
+ cannot refer to an undeclared schema.
79
+
80
+ ## Point project configuration at the registry
81
+
82
+ <!-- pulse-doc-source: examples/02-request-schema/.pulse/config.ts -->
83
+ ```ts
84
+ import { defineConfig } from '@pulse-compute/pulse'
85
+
86
+ export default defineConfig((_scope) => ({
87
+ pulse: {
88
+ entry: 'src/index.ts',
89
+ schema: 'src/schemas.ts',
90
+ tests: 'tests/pulse.harness.ts',
91
+ defaultProfile: 'local',
92
+ strict: true,
93
+ },
94
+ local: {
95
+ host: 'node',
96
+ target: 'native',
97
+ outDir: 'dist',
98
+ schemas: { contentTypePolicy: 'require-json', maxBytes: 1024 },
99
+ },
100
+ }))
101
+ ```
102
+ <!-- /pulse-doc-source -->
103
+
104
+ `pulse.schema` owns the registry module. Do not also declare schema identity
105
+ under a provider profile. Provider profiles own decode policy through
106
+ `schemas.contentTypePolicy` and `schemas.maxBytes`:
107
+
108
+ - `schemas.contentTypePolicy: 'accept-json-or-missing'` accepts JSON content
109
+ types and absent content types; it is the default;
110
+ - `schemas.contentTypePolicy: 'require-json'` requires a JSON content type for
111
+ request and fetched-body schema decode;
112
+ - `schemas.maxBytes` bounds structured schema decode and defaults to `65_536`
113
+ bytes;
114
+ - `dev.maxBodyBytes` separately bounds incoming requests in the local
115
+ development server and also defaults to `65_536` bytes.
116
+
117
+ Raise either byte limit deliberately. They are memory and request-amplification
118
+ boundaries, not convenience settings.
119
+
120
+ ## Bind every JSON boundary
121
+
122
+ <!-- pulse-doc-source: examples/02-request-schema/src/index.ts -->
123
+ ```ts
124
+ import { Pulse } from '@pulse-compute/pulse'
125
+ import type { CreateUserInput, CreateUserOutput } from './schemas.js'
126
+
127
+ const app = new Pulse({ auto: true })
128
+
129
+ app.post('/users', async (ctx) => {
130
+ const first = await ctx.req.json<CreateUserInput>('app.CreateUserInput')
131
+ const second = await ctx.req.json<CreateUserInput>('app.CreateUserInput')
132
+ const output: CreateUserOutput = {
133
+ id: 7,
134
+ name: first.name,
135
+ active: first.active,
136
+ sameReference: first === second,
137
+ }
138
+ return ctx.json(output, { status: 201, schema: 'app.CreateUserOutput' })
139
+ })
140
+
141
+ export default app
142
+ ```
143
+ <!-- /pulse-doc-source -->
144
+
145
+ The four schema-boundary forms are:
146
+
147
+ | Boundary | Authoring form | Operation |
148
+ |---|---|---|
149
+ | Incoming request | `await ctx.req.json<T>('app.Input')` | Decode |
150
+ | Fetched response | `await ctx.fetch(url).json<T>('app.Output')` | Decode |
151
+ | Outbound fetch body | `ctx.fetch(url, { json: value, schema: 'app.Input' })` | Encode |
152
+ | Application response | `ctx.json(value, { schema: 'app.Output' })` | Encode |
153
+
154
+ A registered response case is shorthand for response status plus schema:
155
+
156
+ ```ts
157
+ return ctx.json(user, 'user.created')
158
+ ```
159
+
160
+ IDs must be string literals. An outbound `schema` property is valid only with
161
+ the semantic `json` property; a raw string `body` and `json` are mutually
162
+ exclusive.
163
+
164
+ ## Understand strict mode
165
+
166
+ `pulse.strict` defaults to `true`. When the project declares schemas, strict
167
+ mode requires a schema ID for request JSON, fetched-response JSON, outbound
168
+ fetch JSON, and application JSON responses. A response may use either a literal
169
+ `schema` descriptor or a registered response-case ID.
170
+
171
+ Setting `pulse.strict: false` allows schema-less generic JSON at reachable
172
+ request and fetch-response reads and at JSON encode boundaries. It does not
173
+ make an unknown ID valid: supplying an ID always requests that exact compiled
174
+ codec. Pulse does not try schemas in sequence, infer a codec from the value, or
175
+ fall back to generic JSON when an ID is missing from the registry.
176
+
177
+ Generic JSON remains bounded and appears as an explicit host-generic JSON
178
+ capability in Native inspection. It is a deliberate compatibility choice, not
179
+ automatic JavaScript fallback.
180
+
181
+ ## Know the value semantics
182
+
183
+ Schema decoding and encoding are semantic boundaries, not thin calls to a
184
+ provider JSON object:
185
+
186
+ - input values are validated, normalized, deeply immutable, and owned by the
187
+ request after decode;
188
+ - repeated request reads of the same schema reuse the request-local decoded
189
+ value;
190
+ - unknown input fields are removed recursively;
191
+ - every declared field is required;
192
+ - response and fetch encoding emits declared fields only, in declaration order;
193
+ - numeric values must be finite JSON numbers;
194
+ - JavaScript and Native use the same registry contract and semantic trace.
195
+
196
+ The provider never exposes an SDK request or response object to the handler.
197
+ Opaque bodies are not eligible for schema decode; see
198
+ [Structured and opaque bodies](../concepts/bodies.md).
199
+
200
+ ## Inspect and diagnose the contract
201
+
202
+ The CLI compiles the registry and direct codecs with the handler. `doctor`,
203
+ `inspect`, `test`, `dev`, and `build` consume the same project output.
204
+
205
+ ```bash
206
+ pulse inspect examples/02-request-schema --json
207
+ ```
208
+
209
+ Check `schemas.authority`, `schemas.ids`, `schemas.responseCases`, and
210
+ `schemas.codecRealization`, then review the compiler’s schema references and
211
+ provider requirements. The packaged Native build carries the generated schema
212
+ registry and codecs; it does not execute TypeScript or a JavaScript schema
213
+ library at request time.
214
+
215
+ Dynamic IDs, missing declarations, duplicate IDs, unsupported field shapes,
216
+ invalid values, content-type violations, and oversized bodies fail explicitly.
217
+ Common diagnostics include:
218
+
219
+ - [`PULSE_SCHEMA_DECODE`](../reference/diagnostics.md#pulse-schema-decode);
220
+ - [`PULSE_SCHEMA_ENCODE`](../reference/diagnostics.md#pulse-schema-encode);
221
+ - [`PULSE_RESPONSE_ENCODE`](../reference/diagnostics.md#pulse-response-encode);
222
+ - [`PULSE_BODY_TOO_LARGE`](../reference/diagnostics.md#pulse-body-too-large);
223
+ - `PULSE_SCHEMA_REQUIRED`;
224
+ - `PULSE_CANONICAL_SCHEMA_MISSING`;
225
+ - `PULSE_RESPONSE_CASE_MISSING`.
226
+
227
+ ## Related documentation
228
+
229
+ - [Structured and opaque bodies](../concepts/bodies.md)
230
+ - [Project configuration](../reference/project-config.md)
231
+ - [Managed handler TypeScript and JavaScript](../reference/handler-authoring.md)
232
+ - [Provider and target compatibility](../reference/compatibility-matrix.md)
233
+ - [Canonical API](../../API.md)
@@ -0,0 +1,192 @@
1
+ # Migrate an Express service
2
+
3
+ Pulse uses a familiar application, route, middleware, and error-handler shape,
4
+ but it is not an Express-compatible runtime or a drop-in replacement. Express
5
+ owns a live JavaScript server and mutable request/response objects. Pulse owns a
6
+ static application description whose managed handlers compile to the selected
7
+ JavaScript or Native target.
8
+
9
+ Start from the similarity, then make each boundary explicit.
10
+
11
+ ## Map the mental model
12
+
13
+ | Express concept | Pulse equivalent | Important difference |
14
+ |---|---|---|
15
+ | `express()` | `new Pulse({ auto: true })` | The default export is compiled; it is not a live dispatcher. |
16
+ | `req.method`, `req.url`, `req.path` | `ctx.req.method`, `ctx.req.url`, `ctx.req.path` | Request metadata is read-only. |
17
+ | `req.get('name')` | `ctx.req.header('name')` | Headers are normalized by the Pulse request contract. |
18
+ | `req.params.id` | `ctx.param('id')` | Parameters exist only inside a matching static route. |
19
+ | `res.locals` | `ctx.state.get()` / `ctx.state.set()` | State is request-local string storage, not an arbitrary object. |
20
+ | `res.status(201).json(value)` | `return ctx.json(value, { status: 201 })` | Response builders return the terminal result; they do not mutate `res`. |
21
+ | `res.status(204).end()` | `return ctx.response({ status: 204 })` | Every reachable handler branch returns a Pulse result or transfers control. |
22
+ | `next()` | `return next()` | The transfer is terminal; the current handler never resumes. |
23
+ | `next(error)` | `return next(error)` | Error transfer is explicit; authored `throw` is not Router control flow. |
24
+ | Four-argument error middleware | `app.error(async (error, ctx, next) => …)` | Pulse error handlers use the canonical context and terminal transfer. |
25
+ | Body-parser middleware | `await ctx.req.text()` or `await ctx.req.json('schema.id')` | Reads are explicit, bounded, and optionally schema-bound. |
26
+ | `app.listen()` | `pulse dev` / `pulse build` plus a provider | The CLI and selected provider own execution and deployment lifecycle. |
27
+
28
+ ## Translate one route
29
+
30
+ This Express example is illustrative; Express is not a Pulse dependency:
31
+
32
+ ```ts
33
+ import express from 'express'
34
+
35
+ const app = express()
36
+ app.use(express.json())
37
+ app.post('/users/:id', async (req, res, next) => {
38
+ try {
39
+ const user = await saveUser(req.params.id, req.body)
40
+ res.status(201).json(user)
41
+ } catch (error) {
42
+ next(error)
43
+ }
44
+ })
45
+ app.listen(3000)
46
+ ```
47
+
48
+ The Pulse shape moves host work onto `ctx`, makes body decoding explicit, and
49
+ returns the response:
50
+
51
+ ```ts
52
+ import { Pulse } from '@pulse-compute/pulse'
53
+ import type { CreateUserInput, User } from './schemas.js'
54
+
55
+ const app = new Pulse({ auto: true })
56
+
57
+ app.post('/users/:id', async (ctx, next) => {
58
+ const id = ctx.param('id')
59
+ if (!id) return next('missing-user-id')
60
+
61
+ const input = await ctx.req.json<CreateUserInput>('app.CreateUserInput')
62
+ const user = await ctx.fetch('https://users.example.test/users/' + id, {
63
+ method: 'POST',
64
+ json: input,
65
+ schema: 'app.CreateUserInput',
66
+ }).json<User>('app.User')
67
+
68
+ return ctx.json(user, 'user.created')
69
+ })
70
+
71
+ app.error(async (error, ctx, next) => {
72
+ if (error === 'missing-user-id') {
73
+ return ctx.json({
74
+ code: 'missing-user-id',
75
+ message: 'The route did not produce a user ID.',
76
+ }, {
77
+ status: 400,
78
+ schema: 'app.ApiError',
79
+ })
80
+ }
81
+ return next(error)
82
+ })
83
+
84
+ export default app
85
+ ```
86
+
87
+ The snippet assumes the named schemas and `user.created` response case are
88
+ declared in the project schema registry. See
89
+ [Explicit JSON schemas](./json-schemas.md).
90
+
91
+ ## Rewrite middleware as terminal control flow
92
+
93
+ Express middleware can perform work after `await next()` in frameworks with an
94
+ onion model, or depend on the eventual mutable response. Pulse middleware is a
95
+ flat, compiler-owned cursor:
96
+
97
+ ```ts
98
+ app.use('/api', async (ctx, next) => {
99
+ const token = ctx.req.header('authorization')
100
+ if (!token) return ctx.json({ error: 'unauthorized' }, { status: 401 })
101
+
102
+ ctx.state.set('principal', token)
103
+ return next()
104
+ })
105
+ ```
106
+
107
+ `return next()` permanently finishes this middleware. Do not assign its result,
108
+ await it, or place cleanup and response mutation after it. Request state remains
109
+ visible to later middleware, routes, error recovery, and resumed Pulse effects,
110
+ but it is isolated between requests.
111
+
112
+ Use `return next(error)` for a deliberate error-lane transfer. An unhandled
113
+ normal lane ends as `404 Not Found`; an unhandled error lane ends as
114
+ `500 Internal Server Error`.
115
+
116
+ ## Reduce routing to the static topology
117
+
118
+ The Beta Router supports:
119
+
120
+ - `use`, `get`, `head`, `post`, `put`, `patch`, `delete`, `mount`, and `error`;
121
+ - exact paths, named `:parameters`, and a trailing `*` wildcard;
122
+ - statically declared, acyclic mounted routers;
123
+ - first-match order and explicit route fallthrough.
124
+
125
+ It does not support `options`, `trace`, `connect`, regular-expression routes,
126
+ runtime route registration, Express Router plugins, or hidden server lifecycle
127
+ hooks. Keep an unsupported endpoint on its existing service or redesign it
128
+ before moving that endpoint into Pulse; selecting a JavaScript target does not
129
+ widen the Pulse Router API.
130
+
131
+ ## Replace ambient server capabilities
132
+
133
+ Express code often reaches capabilities through Node globals, process state,
134
+ SDK clients, or objects attached by middleware. Managed Pulse handlers use
135
+ request-owned operations instead:
136
+
137
+ | Existing dependency | Pulse boundary |
138
+ |---|---|
139
+ | Ambient `fetch` or HTTP client | `ctx.fetch()` |
140
+ | `process.env` | `ctx.config.get()` or `ctx.secret.get()` |
141
+ | Request-scoped SDK/client attachment | Explicit `ctx` effect or a supported package-root operation |
142
+ | Logging package bound to the process | `ctx.log` |
143
+ | Body-parser buffer or stream | Bounded `ctx.req.text()` / `ctx.req.json()` |
144
+ | Streaming proxy response | Direct opaque response pass-through |
145
+
146
+ Provider SDK objects, sockets, filesystem access, timers, background work, and
147
+ userland body streams are outside the managed handler contract.
148
+
149
+ ## Decide what code can move unchanged
150
+
151
+ Pure TypeScript or JavaScript expressions can move unchanged when they stay
152
+ within the [managed handler language](../reference/handler-authoring.md).
153
+ Imported project helpers also have to use compiler-supported static call shapes
154
+ to remain Native-eligible. An ordinary library may work on an explicitly
155
+ selected JavaScript target when that provider runtime supports the package.
156
+
157
+ Express middleware packages cannot be mounted directly: they expect Express
158
+ `req`, `res`, `next`, server lifecycle, or ambient Node behavior. Rewrite the
159
+ needed policy against `ctx`, and verify its source form in the
160
+ [compatibility matrix](../reference/compatibility-matrix.md). A JavaScript-only
161
+ library or arbitrary library `await` makes the project ineligible for Native;
162
+ Pulse never changes targets or falls back automatically.
163
+
164
+ ## Migrate in bounded slices
165
+
166
+ 1. Inventory routes, methods, middleware order, error paths, body parsing, and
167
+ ambient host dependencies.
168
+ 2. Move one supported `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE` route to a statically declared
169
+ `Pulse` application.
170
+ 3. Replace response mutation with returned `ctx.json`, `ctx.text`, or
171
+ `ctx.response` results.
172
+ 4. Replace body-parser assumptions with bounded text or schema-bound JSON reads.
173
+ 5. Rewrite middleware around terminal `return next()` and explicit
174
+ `return next(error)`.
175
+ 6. Move network, config, secret, KV, logging, and supported package work onto
176
+ explicit `ctx` operations.
177
+ 7. Run `pulse doctor`, `pulse test`, and `pulse inspect` for the configured
178
+ provider/target before `pulse build`.
179
+
180
+ Choose the target deliberately. JavaScript is useful for compatible ordinary
181
+ packages; Native requires the bounded TypeScript/JavaScript subset. Neither
182
+ lane is an Express runtime.
183
+
184
+ ## Continue with
185
+
186
+ - [Static Router authoring](./routing.md)
187
+ - [Explicit JSON schemas](./json-schemas.md)
188
+ - [Structured and opaque bodies](../concepts/bodies.md)
189
+ - [Managed handler TypeScript and JavaScript](../reference/handler-authoring.md)
190
+ - [Provider and target compatibility](../reference/compatibility-matrix.md)
191
+ - [Project lifecycle](./project-lifecycle.md)
192
+ - [Troubleshooting](./troubleshooting.md)
@@ -0,0 +1,143 @@
1
+ # Project lifecycle
2
+
3
+ The normal Pulse application loop is:
4
+
5
+ ```text
6
+ pulse init
7
+ → pulse doctor
8
+ → pulse test
9
+ → pulse dev
10
+ → pulse build
11
+ ```
12
+
13
+ Each command resolves the same project root, `.pulse/config.ts`, selected
14
+ profile, handler graph, schemas, provider bindings, target, and output
15
+ directory. Provider and target selection come from configuration; no command
16
+ silently changes them.
17
+
18
+ ## 1. Initialize
19
+
20
+ ```bash
21
+ pulse init ./my-app
22
+ cd ./my-app
23
+ npm install
24
+ ```
25
+
26
+ `pulse init` writes a conventional application, project configuration, test
27
+ harness, package manifest, and TypeScript configuration. It pins the
28
+ synchronized Pulse package versions but does not run a package manager or make
29
+ an installation network request.
30
+
31
+ ## 2. Diagnose the configured project
32
+
33
+ ```bash
34
+ pulse doctor
35
+ ```
36
+
37
+ `doctor` validates project discovery, configuration, entry and schema
38
+ resolution, output containment, provider support, required bindings, and
39
+ external tool readiness. Run it after changing profiles, targets, bindings, or
40
+ toolchains and before producing a candidate artifact.
41
+
42
+ Use JSON when a script or issue report needs stable diagnostics:
43
+
44
+ ```bash
45
+ pulse doctor --json
46
+ ```
47
+
48
+ ## 3. Execute the test harness
49
+
50
+ ```bash
51
+ pulse test
52
+ ```
53
+
54
+ `test` runs the cases in the configured `tests/pulse.harness.ts` through the
55
+ selected provider's local conformance runtime. It proves the canonical program,
56
+ fixtures, provider mapping, and expected results. It does not prove a remote
57
+ origin, deployed resource, or provider service.
58
+
59
+ Event cases use `kind: 'event'`, a canonical frame, and an ordered exact
60
+ `expect.emitted` list. They are available only when the selected provider and
61
+ target expose the bounded event test adapter.
62
+
63
+ ## 4. Run the development lifecycle
64
+
65
+ ```bash
66
+ pulse dev
67
+ ```
68
+
69
+ `dev` is a foreground process. It watches the reachable handler and schema
70
+ graph, recompiles on change, serves through the selected provider's local
71
+ runtime, and shuts down on normal process signals. Development values and
72
+ fixtures remain local inputs; they do not provision deployment resources.
73
+
74
+ For a mixed HTTP/event project, `dev` serves only HTTP requests. It does not
75
+ open an event listener or injection endpoint; use the harness for bounded event
76
+ execution.
77
+
78
+ ## 5. Build the selected target
79
+
80
+ ```bash
81
+ pulse build
82
+ ```
83
+
84
+ `build` realizes the exact provider and target in the active profile:
85
+
86
+ - Node Native emits Pulse-owned Wasm and the Node host-contract metadata.
87
+ - Node JavaScript emits a deterministic executable CommonJS source package.
88
+ - Fastly Native emits compact direct-host-ABI `bin/main.wasm`.
89
+ - Fastly JavaScript emits a deterministic source/deployment closure and
90
+ downstream runtime candidate.
91
+
92
+ A successful build produces a candidate artifact. It does not publish a
93
+ package, deploy documentation, create provider resources, deploy a service, or
94
+ activate traffic.
95
+
96
+ Continue with the separate [Node build and execution
97
+ guide](./deploying-node.md) or [Fastly deployment-candidate
98
+ guide](./deploying-fastly.md).
99
+
100
+ ## Inspect when you need the plan
101
+
102
+ `pulse inspect` is an observability command, not a required lifecycle stage:
103
+
104
+ ```bash
105
+ pulse inspect --json
106
+ ```
107
+
108
+ Use it to examine target support, project eligibility, the reachable graph,
109
+ effects, continuations, schemas, package-owned lowering, provider requirements,
110
+ bindings, and Native plan identity.
111
+
112
+ ## Compile when you need the provider-neutral boundary
113
+
114
+ `pulse compile` is the advanced provider-neutral Native command:
115
+
116
+ ```bash
117
+ pulse compile
118
+ ```
119
+
120
+ It emits the portable `pulse_host` Wasm contract and its canonical Native plan.
121
+ It does not realize the configured provider. A JavaScript-selected project may
122
+ still use `compile` when its source is Native-eligible, but that separate
123
+ artifact does not change the selected JavaScript target.
124
+
125
+ Use `build` for the normal application artifact. `compile` is not a prerequisite;
126
+ run it only when you specifically need the portable Native boundary, inspection
127
+ evidence, or a host integration input.
128
+
129
+ ## Failure path
130
+
131
+ When a lifecycle command fails:
132
+
133
+ 1. rerun `pulse doctor --json`;
134
+ 2. use `pulse inspect --json` to compare project requirements with target and
135
+ provider decisions;
136
+ 3. resolve the first source-located or binding-specific diagnostic;
137
+ 4. rerun the failed lifecycle step.
138
+
139
+ See [Managed handler TypeScript and JavaScript](../reference/handler-authoring.md),
140
+ [Provider and target compatibility](../reference/compatibility-matrix.md),
141
+ [Static events and outbound emission](./events.md),
142
+ [Troubleshooting](./troubleshooting.md), and [Diagnostics and
143
+ remediation](../reference/diagnostics.md).