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