@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.
- package/API.md +539 -0
- package/CHANGELOG.md +47 -0
- package/README.md +70 -1
- package/bin/pulse.js +15 -0
- package/bin/pulsewasm-extract.js +22 -0
- package/cli-spec.json +575 -0
- package/completions/_pulse +106 -0
- package/completions/pulse.bash +31 -0
- package/completions/pulse.fish +76 -0
- package/docs/README.md +77 -0
- package/docs/architecture/current-contracts.md +381 -0
- package/docs/architecture/overview.md +93 -0
- package/docs/architecture/vision.md +515 -0
- package/docs/concepts/bodies.md +186 -0
- package/docs/concepts/compilation-and-lowering.md +496 -0
- package/docs/concepts/contracts-and-providers.md +158 -0
- package/docs/concepts/effects-and-continuations.md +243 -0
- package/docs/concepts/entities-and-adapters.md +71 -0
- package/docs/concepts/package-owned-lowering.md +175 -0
- package/docs/concepts/targets-and-hosts.md +62 -0
- package/docs/contributing/README.md +38 -0
- package/docs/contributing/adding-core-provider.md +315 -0
- package/docs/contributing/adding-first-party-lowerer.md +327 -0
- package/docs/contributing/entities-lowering.md +91 -0
- package/docs/contributing/package-lowerer-contract.md +383 -0
- package/docs/contributing/pulse-aware-packages.md +149 -0
- package/docs/examples.md +112 -0
- package/docs/fixtures/inspect-fetch-composition.selected.json +181 -0
- package/docs/getting-started.md +128 -0
- package/docs/guides/compatibility-imports.md +60 -0
- package/docs/guides/deploying-fastly.md +188 -0
- package/docs/guides/deploying-node.md +143 -0
- package/docs/guides/events.md +233 -0
- package/docs/guides/fastly-capabilities.md +153 -0
- package/docs/guides/fetching-and-composition.md +101 -0
- package/docs/guides/grip.md +94 -0
- package/docs/guides/json-schemas.md +233 -0
- package/docs/guides/migrating-from-express.md +192 -0
- package/docs/guides/project-lifecycle.md +143 -0
- package/docs/guides/routing.md +184 -0
- package/docs/guides/troubleshooting.md +47 -0
- package/docs/maintainers/README.md +60 -0
- package/docs/maintainers/codex-maintainer.md +71 -0
- package/docs/maintainers/documentation-deployment.md +147 -0
- package/docs/maintainers/documentation-system.md +58 -0
- package/docs/maintainers/documentation-versioning.md +65 -0
- package/docs/maintainers/maintainer-charter.md +84 -0
- package/docs/maintainers/maintenance-policy.json +778 -0
- package/docs/maintainers/maintenance-policy.md +124 -0
- package/docs/maintainers/npm-publishing.md +154 -0
- package/docs/maintainers/plugin-readiness.json +19 -0
- package/docs/maintainers/plugin-readiness.md +31 -0
- package/docs/maintainers/public-site.md +158 -0
- package/docs/maintainers/release-acceptance.md +250 -0
- package/docs/maintainers/release-manifest.md +88 -0
- package/docs/maintainers/repository-setup.md +145 -0
- package/docs/maintainers/scope-policy.md +93 -0
- package/docs/maintainers/support-and-triage.md +74 -0
- package/docs/maintainers/testing.md +288 -0
- package/docs/packages/README.md +59 -0
- package/docs/packages/assets.md +85 -0
- package/docs/packages/cli.md +190 -0
- package/docs/packages/crypto.md +71 -0
- package/docs/packages/entities.md +129 -0
- package/docs/packages/grip.md +82 -0
- package/docs/packages/implementation-packages.md +113 -0
- package/docs/packages/jwt.md +55 -0
- package/docs/packages/provider-fastly.md +217 -0
- package/docs/packages/pulse.md +159 -0
- package/docs/packages/runtime.md +256 -0
- package/docs/preview-scope.md +142 -0
- package/docs/reference/README.md +33 -0
- package/docs/reference/cli-spec.json +575 -0
- package/docs/reference/cli.md +457 -0
- package/docs/reference/compatibility-matrix.md +169 -0
- package/docs/reference/diagnostics.md +1872 -0
- package/docs/reference/documentation-versions.json +14 -0
- package/docs/reference/environment.md +347 -0
- package/docs/reference/handler-authoring.md +264 -0
- package/docs/reference/project-config.md +554 -0
- package/docs/reference/project-config.schema.json +918 -0
- package/docs/reference/release-manifest.json +427 -0
- package/docs/reference/shell-completion.md +23 -0
- package/documentation-site.json +661 -0
- package/documentation-versions.json +14 -0
- package/examples/01-hello-json/.pulse/.gitignore +3 -0
- package/examples/01-hello-json/.pulse/config.ts +16 -0
- package/examples/01-hello-json/README.md +119 -0
- package/examples/01-hello-json/package.json +20 -0
- package/examples/01-hello-json/src/index.ts +9 -0
- package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
- package/examples/01-hello-json/tsconfig.json +14 -0
- package/examples/02-request-schema/.pulse/.gitignore +3 -0
- package/examples/02-request-schema/.pulse/config.ts +17 -0
- package/examples/02-request-schema/README.md +145 -0
- package/examples/02-request-schema/package.json +20 -0
- package/examples/02-request-schema/src/index.ts +18 -0
- package/examples/02-request-schema/src/schemas.ts +20 -0
- package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
- package/examples/02-request-schema/tsconfig.json +14 -0
- package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
- package/examples/03-fetch-composition/.pulse/config.ts +31 -0
- package/examples/03-fetch-composition/README.md +210 -0
- package/examples/03-fetch-composition/package.json +20 -0
- package/examples/03-fetch-composition/src/index.ts +60 -0
- package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
- package/examples/03-fetch-composition/tsconfig.json +14 -0
- package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
- package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
- package/examples/05-fastly-capabilities/README.md +257 -0
- package/examples/05-fastly-capabilities/package.json +22 -0
- package/examples/05-fastly-capabilities/src/index.ts +55 -0
- package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
- package/examples/05-fastly-capabilities/tsconfig.json +14 -0
- package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
- package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
- package/examples/07-opaque-proxy/README.md +150 -0
- package/examples/07-opaque-proxy/package.json +21 -0
- package/examples/07-opaque-proxy/src/index.ts +9 -0
- package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
- package/examples/07-opaque-proxy/tsconfig.json +14 -0
- package/examples/09-router-lowering/.pulse/.gitignore +3 -0
- package/examples/09-router-lowering/.pulse/config.ts +22 -0
- package/examples/09-router-lowering/README.md +287 -0
- package/examples/09-router-lowering/package.json +22 -0
- package/examples/09-router-lowering/src/index.ts +65 -0
- package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
- package/examples/09-router-lowering/tsconfig.json +14 -0
- package/examples/10-entities-tools/.pulse/.gitignore +4 -0
- package/examples/10-entities-tools/.pulse/config.ts +23 -0
- package/examples/10-entities-tools/README.md +165 -0
- package/examples/10-entities-tools/package.json +21 -0
- package/examples/10-entities-tools/src/handlers.ts +15 -0
- package/examples/10-entities-tools/src/index.ts +31 -0
- package/examples/10-entities-tools/src/schemas.ts +18 -0
- package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
- package/examples/10-entities-tools/tools-facade.cjs +158 -0
- package/examples/10-entities-tools/tsconfig.json +14 -0
- package/examples/11-events/.pulse/.gitignore +4 -0
- package/examples/11-events/.pulse/config.ts +24 -0
- package/examples/11-events/README.md +194 -0
- package/examples/11-events/package.json +20 -0
- package/examples/11-events/src/index.ts +27 -0
- package/examples/11-events/src/schemas.ts +19 -0
- package/examples/11-events/tests/pulse.harness.ts +34 -0
- package/examples/11-events/tsconfig.json +15 -0
- package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
- package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
- package/examples/12-mcp-proxy/README.md +149 -0
- package/examples/12-mcp-proxy/package.json +20 -0
- package/examples/12-mcp-proxy/src/index.ts +17 -0
- package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
- package/examples/12-mcp-proxy/tsconfig.json +14 -0
- package/examples/13-jwt-es256/.pulse/config.ts +19 -0
- package/examples/13-jwt-es256/README.md +195 -0
- package/examples/13-jwt-es256/package.json +21 -0
- package/examples/13-jwt-es256/src/index.ts +21 -0
- package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
- package/examples/13-jwt-es256/tsconfig.json +14 -0
- package/examples/README.md +36 -0
- package/package.json +66 -6
- package/project-config.schema.json +918 -0
- package/release-manifest.json +427 -0
- package/src/command-spec.js +279 -0
- package/src/completion.js +113 -0
- package/src/diagnostics.js +350 -0
- package/src/documentation.js +45 -0
- package/src/index.d.ts +133 -0
- package/src/index.js +15 -0
- package/src/internal/command-executor.d.ts +29 -0
- package/src/internal/command-executor.js +143 -0
- package/src/internal/command-plan.d.ts +9 -0
- package/src/internal/command-plan.js +73 -0
- package/src/internal/command-reporter.d.ts +15 -0
- package/src/internal/command-reporter.js +133 -0
- package/src/internal/command-request.d.ts +35 -0
- package/src/internal/command-request.js +154 -0
- package/src/internal/node-http.js +54 -0
- package/src/internal/project-context.d.ts +66 -0
- package/src/internal/project-context.js +175 -0
- package/src/project-config-reference.js +4 -0
- package/src/project-config-schema.d.ts +23 -0
- package/src/project-config-schema.js +271 -0
- package/src/project-config.js +724 -0
- package/src/project-execution.js +2930 -0
- package/src/provider-drivers.js +3 -0
- package/src/target-support.js +3 -0
- package/src/typescript-module-loader.js +269 -0
- package/src/workflow.js +78 -0
- 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`, `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 `put`, `patch`, `delete`, 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`, or `POST` 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).
|