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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/API.md +539 -0
  2. package/CHANGELOG.md +47 -0
  3. package/README.md +70 -1
  4. package/bin/pulse.js +15 -0
  5. package/bin/pulsewasm-extract.js +22 -0
  6. package/cli-spec.json +575 -0
  7. package/completions/_pulse +106 -0
  8. package/completions/pulse.bash +31 -0
  9. package/completions/pulse.fish +76 -0
  10. package/docs/README.md +77 -0
  11. package/docs/architecture/current-contracts.md +381 -0
  12. package/docs/architecture/overview.md +93 -0
  13. package/docs/architecture/vision.md +515 -0
  14. package/docs/concepts/bodies.md +186 -0
  15. package/docs/concepts/compilation-and-lowering.md +496 -0
  16. package/docs/concepts/contracts-and-providers.md +158 -0
  17. package/docs/concepts/effects-and-continuations.md +243 -0
  18. package/docs/concepts/entities-and-adapters.md +71 -0
  19. package/docs/concepts/package-owned-lowering.md +175 -0
  20. package/docs/concepts/targets-and-hosts.md +62 -0
  21. package/docs/contributing/README.md +38 -0
  22. package/docs/contributing/adding-core-provider.md +315 -0
  23. package/docs/contributing/adding-first-party-lowerer.md +327 -0
  24. package/docs/contributing/entities-lowering.md +91 -0
  25. package/docs/contributing/package-lowerer-contract.md +383 -0
  26. package/docs/contributing/pulse-aware-packages.md +149 -0
  27. package/docs/examples.md +112 -0
  28. package/docs/fixtures/inspect-fetch-composition.selected.json +181 -0
  29. package/docs/getting-started.md +128 -0
  30. package/docs/guides/compatibility-imports.md +60 -0
  31. package/docs/guides/deploying-fastly.md +188 -0
  32. package/docs/guides/deploying-node.md +143 -0
  33. package/docs/guides/events.md +233 -0
  34. package/docs/guides/fastly-capabilities.md +153 -0
  35. package/docs/guides/fetching-and-composition.md +101 -0
  36. package/docs/guides/grip.md +94 -0
  37. package/docs/guides/json-schemas.md +233 -0
  38. package/docs/guides/migrating-from-express.md +192 -0
  39. package/docs/guides/project-lifecycle.md +143 -0
  40. package/docs/guides/routing.md +184 -0
  41. package/docs/guides/troubleshooting.md +47 -0
  42. package/docs/maintainers/README.md +60 -0
  43. package/docs/maintainers/codex-maintainer.md +71 -0
  44. package/docs/maintainers/documentation-deployment.md +147 -0
  45. package/docs/maintainers/documentation-system.md +58 -0
  46. package/docs/maintainers/documentation-versioning.md +65 -0
  47. package/docs/maintainers/maintainer-charter.md +84 -0
  48. package/docs/maintainers/maintenance-policy.json +778 -0
  49. package/docs/maintainers/maintenance-policy.md +124 -0
  50. package/docs/maintainers/npm-publishing.md +154 -0
  51. package/docs/maintainers/plugin-readiness.json +19 -0
  52. package/docs/maintainers/plugin-readiness.md +31 -0
  53. package/docs/maintainers/public-site.md +158 -0
  54. package/docs/maintainers/release-acceptance.md +250 -0
  55. package/docs/maintainers/release-manifest.md +88 -0
  56. package/docs/maintainers/repository-setup.md +145 -0
  57. package/docs/maintainers/scope-policy.md +93 -0
  58. package/docs/maintainers/support-and-triage.md +74 -0
  59. package/docs/maintainers/testing.md +288 -0
  60. package/docs/packages/README.md +59 -0
  61. package/docs/packages/assets.md +85 -0
  62. package/docs/packages/cli.md +190 -0
  63. package/docs/packages/crypto.md +71 -0
  64. package/docs/packages/entities.md +129 -0
  65. package/docs/packages/grip.md +82 -0
  66. package/docs/packages/implementation-packages.md +113 -0
  67. package/docs/packages/jwt.md +55 -0
  68. package/docs/packages/provider-fastly.md +217 -0
  69. package/docs/packages/pulse.md +159 -0
  70. package/docs/packages/runtime.md +256 -0
  71. package/docs/preview-scope.md +142 -0
  72. package/docs/reference/README.md +33 -0
  73. package/docs/reference/cli-spec.json +575 -0
  74. package/docs/reference/cli.md +457 -0
  75. package/docs/reference/compatibility-matrix.md +169 -0
  76. package/docs/reference/diagnostics.md +1872 -0
  77. package/docs/reference/documentation-versions.json +14 -0
  78. package/docs/reference/environment.md +347 -0
  79. package/docs/reference/handler-authoring.md +264 -0
  80. package/docs/reference/project-config.md +554 -0
  81. package/docs/reference/project-config.schema.json +918 -0
  82. package/docs/reference/release-manifest.json +427 -0
  83. package/docs/reference/shell-completion.md +23 -0
  84. package/documentation-site.json +661 -0
  85. package/documentation-versions.json +14 -0
  86. package/examples/01-hello-json/.pulse/.gitignore +3 -0
  87. package/examples/01-hello-json/.pulse/config.ts +16 -0
  88. package/examples/01-hello-json/README.md +119 -0
  89. package/examples/01-hello-json/package.json +20 -0
  90. package/examples/01-hello-json/src/index.ts +9 -0
  91. package/examples/01-hello-json/tests/pulse.harness.ts +22 -0
  92. package/examples/01-hello-json/tsconfig.json +14 -0
  93. package/examples/02-request-schema/.pulse/.gitignore +3 -0
  94. package/examples/02-request-schema/.pulse/config.ts +17 -0
  95. package/examples/02-request-schema/README.md +145 -0
  96. package/examples/02-request-schema/package.json +20 -0
  97. package/examples/02-request-schema/src/index.ts +18 -0
  98. package/examples/02-request-schema/src/schemas.ts +20 -0
  99. package/examples/02-request-schema/tests/pulse.harness.ts +37 -0
  100. package/examples/02-request-schema/tsconfig.json +14 -0
  101. package/examples/03-fetch-composition/.pulse/.gitignore +3 -0
  102. package/examples/03-fetch-composition/.pulse/config.ts +31 -0
  103. package/examples/03-fetch-composition/README.md +210 -0
  104. package/examples/03-fetch-composition/package.json +20 -0
  105. package/examples/03-fetch-composition/src/index.ts +60 -0
  106. package/examples/03-fetch-composition/tests/pulse.harness.ts +47 -0
  107. package/examples/03-fetch-composition/tsconfig.json +14 -0
  108. package/examples/05-fastly-capabilities/.pulse/.gitignore +3 -0
  109. package/examples/05-fastly-capabilities/.pulse/config.ts +57 -0
  110. package/examples/05-fastly-capabilities/README.md +257 -0
  111. package/examples/05-fastly-capabilities/package.json +22 -0
  112. package/examples/05-fastly-capabilities/src/index.ts +55 -0
  113. package/examples/05-fastly-capabilities/tests/pulse.harness.ts +66 -0
  114. package/examples/05-fastly-capabilities/tsconfig.json +14 -0
  115. package/examples/07-opaque-proxy/.pulse/.gitignore +3 -0
  116. package/examples/07-opaque-proxy/.pulse/config.ts +38 -0
  117. package/examples/07-opaque-proxy/README.md +150 -0
  118. package/examples/07-opaque-proxy/package.json +21 -0
  119. package/examples/07-opaque-proxy/src/index.ts +9 -0
  120. package/examples/07-opaque-proxy/tests/pulse.harness.ts +25 -0
  121. package/examples/07-opaque-proxy/tsconfig.json +14 -0
  122. package/examples/09-router-lowering/.pulse/.gitignore +3 -0
  123. package/examples/09-router-lowering/.pulse/config.ts +22 -0
  124. package/examples/09-router-lowering/README.md +287 -0
  125. package/examples/09-router-lowering/package.json +22 -0
  126. package/examples/09-router-lowering/src/index.ts +65 -0
  127. package/examples/09-router-lowering/tests/pulse.harness.ts +91 -0
  128. package/examples/09-router-lowering/tsconfig.json +14 -0
  129. package/examples/10-entities-tools/.pulse/.gitignore +4 -0
  130. package/examples/10-entities-tools/.pulse/config.ts +23 -0
  131. package/examples/10-entities-tools/README.md +165 -0
  132. package/examples/10-entities-tools/package.json +21 -0
  133. package/examples/10-entities-tools/src/handlers.ts +15 -0
  134. package/examples/10-entities-tools/src/index.ts +31 -0
  135. package/examples/10-entities-tools/src/schemas.ts +18 -0
  136. package/examples/10-entities-tools/tests/pulse.harness.ts +40 -0
  137. package/examples/10-entities-tools/tools-facade.cjs +158 -0
  138. package/examples/10-entities-tools/tsconfig.json +14 -0
  139. package/examples/11-events/.pulse/.gitignore +4 -0
  140. package/examples/11-events/.pulse/config.ts +24 -0
  141. package/examples/11-events/README.md +194 -0
  142. package/examples/11-events/package.json +20 -0
  143. package/examples/11-events/src/index.ts +27 -0
  144. package/examples/11-events/src/schemas.ts +19 -0
  145. package/examples/11-events/tests/pulse.harness.ts +34 -0
  146. package/examples/11-events/tsconfig.json +15 -0
  147. package/examples/12-mcp-proxy/.pulse/.gitignore +3 -0
  148. package/examples/12-mcp-proxy/.pulse/config.ts +25 -0
  149. package/examples/12-mcp-proxy/README.md +149 -0
  150. package/examples/12-mcp-proxy/package.json +20 -0
  151. package/examples/12-mcp-proxy/src/index.ts +17 -0
  152. package/examples/12-mcp-proxy/tests/pulse.harness.ts +29 -0
  153. package/examples/12-mcp-proxy/tsconfig.json +14 -0
  154. package/examples/13-jwt-es256/.pulse/config.ts +19 -0
  155. package/examples/13-jwt-es256/README.md +195 -0
  156. package/examples/13-jwt-es256/package.json +21 -0
  157. package/examples/13-jwt-es256/src/index.ts +21 -0
  158. package/examples/13-jwt-es256/tests/pulse.harness.ts +54 -0
  159. package/examples/13-jwt-es256/tsconfig.json +14 -0
  160. package/examples/README.md +36 -0
  161. package/package.json +66 -6
  162. package/project-config.schema.json +918 -0
  163. package/release-manifest.json +427 -0
  164. package/src/command-spec.js +279 -0
  165. package/src/completion.js +113 -0
  166. package/src/diagnostics.js +350 -0
  167. package/src/documentation.js +45 -0
  168. package/src/index.d.ts +133 -0
  169. package/src/index.js +15 -0
  170. package/src/internal/command-executor.d.ts +29 -0
  171. package/src/internal/command-executor.js +143 -0
  172. package/src/internal/command-plan.d.ts +9 -0
  173. package/src/internal/command-plan.js +73 -0
  174. package/src/internal/command-reporter.d.ts +15 -0
  175. package/src/internal/command-reporter.js +133 -0
  176. package/src/internal/command-request.d.ts +35 -0
  177. package/src/internal/command-request.js +154 -0
  178. package/src/internal/node-http.js +54 -0
  179. package/src/internal/project-context.d.ts +66 -0
  180. package/src/internal/project-context.js +175 -0
  181. package/src/project-config-reference.js +4 -0
  182. package/src/project-config-schema.d.ts +23 -0
  183. package/src/project-config-schema.js +271 -0
  184. package/src/project-config.js +724 -0
  185. package/src/project-execution.js +2930 -0
  186. package/src/provider-drivers.js +3 -0
  187. package/src/target-support.js +3 -0
  188. package/src/typescript-module-loader.js +269 -0
  189. package/src/workflow.js +78 -0
  190. package/src/workspace.js +82 -0
@@ -0,0 +1,210 @@
1
+ # Fetch composition
2
+
3
+ Compares one structured origin, sequential composition, and explicit portable
4
+ concurrency in one project. Comments in the handler mark each form.
5
+
6
+ ## Workflow
7
+
8
+ ```bash
9
+ pulse doctor
10
+ pulse inspect
11
+ pulse test
12
+ pulse dev
13
+ pulse build
14
+ ```
15
+
16
+ The documented test result below is executed by the documentation gate.
17
+
18
+ <!-- pulse-doc-run {"project":"examples/03-fetch-composition","args":["test","--json"]} -->
19
+ ```bash
20
+ pulse test --json
21
+ ```
22
+ ```json
23
+ {
24
+ "status": "passed",
25
+ "provider": "node",
26
+ "summary": {
27
+ "total": 3,
28
+ "passed": 3,
29
+ "failed": 0
30
+ },
31
+ "cases": [
32
+ {
33
+ "name": "single",
34
+ "status": "passed",
35
+ "response": {
36
+ "status": 200
37
+ }
38
+ }
39
+ ]
40
+ }
41
+ ```
42
+
43
+ ## Wasm size
44
+
45
+ | Artifact | Default build | `--experimental-native-size` | Reduction |
46
+ |---|---:|---:|---:|
47
+ | `canonical-native.wasm` | 5.0 KiB (5,117 bytes) | 4.2 KiB (4,349 bytes) | 15.0% |
48
+
49
+ The executable documentation gate rebuilds and measures both variants on
50
+ `1.0.0-beta.1`. These are uncompressed on-disk sizes, not transfer sizes or
51
+ platform limits.
52
+
53
+ ## Handler
54
+
55
+ <!-- pulse-doc-source: examples/03-fetch-composition/src/index.ts -->
56
+ ```ts
57
+ import { Pulse } from '@pulse-compute/pulse'
58
+
59
+ interface User {
60
+ id: number
61
+ name: string
62
+ }
63
+
64
+ interface Stats {
65
+ score: number
66
+ }
67
+
68
+ interface Flags {
69
+ enabled: boolean
70
+ }
71
+
72
+ const app = new Pulse({ auto: true })
73
+
74
+ // One structured origin.
75
+ app.get('/user', async (ctx) => {
76
+ const user = await ctx
77
+ .fetch('https://users.example.test/users/123')
78
+ .json<User>()
79
+ return ctx.json({ found: true, user })
80
+ })
81
+
82
+ // Multiple origins with explicit sequential awaits.
83
+ app.get('/user-summary', async (ctx) => {
84
+ const user = await ctx
85
+ .fetch('https://users.example.test/users/123')
86
+ .json<User>()
87
+ const stats = await ctx
88
+ .fetch('https://stats.example.test/users/123')
89
+ .json<Stats>()
90
+ const flags = await ctx
91
+ .fetch('https://flags.example.test/users/123')
92
+ .json<Flags>()
93
+ return ctx.json({
94
+ id: user.id,
95
+ name: user.name,
96
+ score: stats.score,
97
+ enabled: flags.enabled,
98
+ })
99
+ })
100
+
101
+ // Multiple origins with explicit portable concurrency.
102
+ app.get('/user-summary-parallel', async (ctx) => {
103
+ const { user, stats, flags } = await ctx.parallel({
104
+ user: ctx.fetch('https://users.example.test/users/123').json<User>(),
105
+ stats: ctx.fetch('https://stats.example.test/users/123').json<Stats>(),
106
+ flags: ctx.fetch('https://flags.example.test/users/123').json<Flags>(),
107
+ })
108
+ return ctx.json({
109
+ id: user.id,
110
+ name: user.name,
111
+ score: stats.score,
112
+ enabled: flags.enabled,
113
+ })
114
+ })
115
+
116
+ export default app
117
+ ```
118
+ <!-- /pulse-doc-source -->
119
+
120
+ ## Project config
121
+
122
+ <!-- pulse-doc-source: examples/03-fetch-composition/.pulse/config.ts -->
123
+ ```ts
124
+ import { defineConfig } from '@pulse-compute/pulse'
125
+
126
+ export default defineConfig((_scope) => ({
127
+ pulse: {
128
+ entry: 'src/index.ts',
129
+ tests: 'tests/pulse.harness.ts',
130
+ defaultProfile: 'local',
131
+ strict: true,
132
+ },
133
+ local: {
134
+ host: 'node',
135
+ target: 'native',
136
+ outDir: 'dist',
137
+ dev: {
138
+ fetches: {
139
+ 'https://users.example.test/users/123': {
140
+ value: { id: 123, name: 'Ada' },
141
+ delayMs: 15,
142
+ },
143
+ 'https://stats.example.test/users/123': {
144
+ value: { score: 42 },
145
+ delayMs: 5,
146
+ },
147
+ 'https://flags.example.test/users/123': {
148
+ value: { enabled: true },
149
+ delayMs: 1,
150
+ },
151
+ },
152
+ },
153
+ },
154
+ }))
155
+ ```
156
+ <!-- /pulse-doc-source -->
157
+
158
+ ## Test harness
159
+
160
+ <!-- pulse-doc-source: examples/03-fetch-composition/tests/pulse.harness.ts -->
161
+ ```ts
162
+ const fetches = {
163
+ 'https://users.example.test/users/123': {
164
+ value: { id: 123, name: 'Ada' },
165
+ delayMs: 15,
166
+ },
167
+ 'https://stats.example.test/users/123': {
168
+ value: { score: 42 },
169
+ delayMs: 5,
170
+ },
171
+ 'https://flags.example.test/users/123': {
172
+ value: { enabled: true },
173
+ delayMs: 1,
174
+ },
175
+ }
176
+
177
+ const summary = {
178
+ id: 123,
179
+ name: 'Ada',
180
+ score: 42,
181
+ enabled: true,
182
+ }
183
+
184
+ export default {
185
+ cases: [
186
+ {
187
+ name: 'single',
188
+ request: { path: '/user' },
189
+ fetches,
190
+ expect: {
191
+ status: 200,
192
+ json: { found: true, user: { id: 123, name: 'Ada' } },
193
+ },
194
+ },
195
+ {
196
+ name: 'sequential',
197
+ request: { path: '/user-summary' },
198
+ fetches,
199
+ expect: { status: 200, json: summary },
200
+ },
201
+ {
202
+ name: 'parallel',
203
+ request: { path: '/user-summary-parallel' },
204
+ fetches,
205
+ expect: { status: 200, json: summary },
206
+ },
207
+ ],
208
+ }
209
+ ```
210
+ <!-- /pulse-doc-source -->
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@pulse-examples/03-fetch-composition",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "doctor": "pulse doctor",
8
+ "inspect": "pulse inspect",
9
+ "test": "pulse test",
10
+ "dev": "pulse dev",
11
+ "build": "pulse build"
12
+ },
13
+ "dependencies": {
14
+ "@pulse-compute/pulse": "1.0.0-beta.1"
15
+ },
16
+ "devDependencies": {
17
+ "@pulse-compute/cli": "1.0.0-beta.1",
18
+ "typescript": "5.9.3"
19
+ }
20
+ }
@@ -0,0 +1,60 @@
1
+ import { Pulse } from '@pulse-compute/pulse'
2
+
3
+ interface User {
4
+ id: number
5
+ name: string
6
+ }
7
+
8
+ interface Stats {
9
+ score: number
10
+ }
11
+
12
+ interface Flags {
13
+ enabled: boolean
14
+ }
15
+
16
+ const app = new Pulse({ auto: true })
17
+
18
+ // One structured origin.
19
+ app.get('/user', async (ctx) => {
20
+ const user = await ctx
21
+ .fetch('https://users.example.test/users/123')
22
+ .json<User>()
23
+ return ctx.json({ found: true, user })
24
+ })
25
+
26
+ // Multiple origins with explicit sequential awaits.
27
+ app.get('/user-summary', async (ctx) => {
28
+ const user = await ctx
29
+ .fetch('https://users.example.test/users/123')
30
+ .json<User>()
31
+ const stats = await ctx
32
+ .fetch('https://stats.example.test/users/123')
33
+ .json<Stats>()
34
+ const flags = await ctx
35
+ .fetch('https://flags.example.test/users/123')
36
+ .json<Flags>()
37
+ return ctx.json({
38
+ id: user.id,
39
+ name: user.name,
40
+ score: stats.score,
41
+ enabled: flags.enabled,
42
+ })
43
+ })
44
+
45
+ // Multiple origins with explicit portable concurrency.
46
+ app.get('/user-summary-parallel', async (ctx) => {
47
+ const { user, stats, flags } = await ctx.parallel({
48
+ user: ctx.fetch('https://users.example.test/users/123').json<User>(),
49
+ stats: ctx.fetch('https://stats.example.test/users/123').json<Stats>(),
50
+ flags: ctx.fetch('https://flags.example.test/users/123').json<Flags>(),
51
+ })
52
+ return ctx.json({
53
+ id: user.id,
54
+ name: user.name,
55
+ score: stats.score,
56
+ enabled: flags.enabled,
57
+ })
58
+ })
59
+
60
+ export default app
@@ -0,0 +1,47 @@
1
+ const fetches = {
2
+ 'https://users.example.test/users/123': {
3
+ value: { id: 123, name: 'Ada' },
4
+ delayMs: 15,
5
+ },
6
+ 'https://stats.example.test/users/123': {
7
+ value: { score: 42 },
8
+ delayMs: 5,
9
+ },
10
+ 'https://flags.example.test/users/123': {
11
+ value: { enabled: true },
12
+ delayMs: 1,
13
+ },
14
+ }
15
+
16
+ const summary = {
17
+ id: 123,
18
+ name: 'Ada',
19
+ score: 42,
20
+ enabled: true,
21
+ }
22
+
23
+ export default {
24
+ cases: [
25
+ {
26
+ name: 'single',
27
+ request: { path: '/user' },
28
+ fetches,
29
+ expect: {
30
+ status: 200,
31
+ json: { found: true, user: { id: 123, name: 'Ada' } },
32
+ },
33
+ },
34
+ {
35
+ name: 'sequential',
36
+ request: { path: '/user-summary' },
37
+ fetches,
38
+ expect: { status: 200, json: summary },
39
+ },
40
+ {
41
+ name: 'parallel',
42
+ request: { path: '/user-summary-parallel' },
43
+ fetches,
44
+ expect: { status: 200, json: summary },
45
+ },
46
+ ],
47
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noEmit": true
8
+ },
9
+ "include": [
10
+ "src/**/*.ts",
11
+ ".pulse/**/*.ts",
12
+ "tests/**/*.ts"
13
+ ]
14
+ }
@@ -0,0 +1,3 @@
1
+ *
2
+ !.gitignore
3
+ !config.ts
@@ -0,0 +1,57 @@
1
+ import { defineConfig } from '@pulse-compute/pulse'
2
+
3
+ export default defineConfig((scope) => ({
4
+ pulse: {
5
+ entry: 'src/index.ts',
6
+ tests: 'tests/pulse.harness.ts',
7
+ defaultProfile: 'local',
8
+ strict: true,
9
+ },
10
+ local: {
11
+ host: 'fastly',
12
+ target: 'native',
13
+ outDir: 'dist',
14
+ apiBase: scope.config('API_BASE'),
15
+ apiToken: scope.secret('API_TOKEN'),
16
+ dev: {
17
+ config: { API_BASE: 'https://api.example.com' },
18
+ secrets: {
19
+ API_TOKEN: 'local-example-secret',
20
+ GRIP_TOKEN: 'local-grip-secret',
21
+ },
22
+ kv: {
23
+ sessions: { 'session:123': { userId: 123 } },
24
+ },
25
+ fetches: {
26
+ 'https://api.example.com/users/7': {
27
+ value: { id: 7, name: 'Ada' },
28
+ },
29
+ 'POST https://publisher.example.com/publish': {
30
+ status: 202,
31
+ value: { accepted: true, messageId: 'message-1' },
32
+ },
33
+ },
34
+ },
35
+ fastly: {
36
+ bindings: {
37
+ configStore: 'app_config',
38
+ secretStore: 'app_secrets',
39
+ kv: { sessions: 'app_sessions' },
40
+ backends: {
41
+ 'https://api.example.com': 'api_backend',
42
+ 'https://publisher.example.com': 'publisher_backend',
43
+ },
44
+ dynamicBackends: false,
45
+ grip: {
46
+ publishEndpoint: 'https://publisher.example.com/publish',
47
+ publishBackend: 'publisher_backend',
48
+ authentication: {
49
+ scheme: 'bearer',
50
+ secretRef: 'GRIP_TOKEN',
51
+ },
52
+ },
53
+ },
54
+ build: { name: 'pulse-fastly-capabilities-example' },
55
+ },
56
+ },
57
+ }))
@@ -0,0 +1,257 @@
1
+ # Fastly capabilities
2
+
3
+ Combines provider-neutral config, secrets, KV, a named backend, and a
4
+ request-bound GRIP broadcast in one Fastly project. The handler imports GRIP
5
+ from its supported package root; the compatibility-only `/pulsewasm` facade is
6
+ not part of this example.
7
+
8
+ ## Workflow
9
+
10
+ ```bash
11
+ pulse doctor
12
+ pulse inspect
13
+ pulse test
14
+ pulse dev
15
+ pulse build
16
+ ```
17
+
18
+ The documented test result below is executed by the documentation gate.
19
+
20
+ <!-- pulse-doc-run {"project":"examples/05-fastly-capabilities","args":["test","--json"]} -->
21
+ ```bash
22
+ pulse test --json
23
+ ```
24
+ ```json
25
+ {
26
+ "status": "passed",
27
+ "provider": "fastly",
28
+ "summary": {
29
+ "total": 4,
30
+ "passed": 4,
31
+ "failed": 0
32
+ },
33
+ "cases": [
34
+ {
35
+ "name": "configured-fetch",
36
+ "status": "passed",
37
+ "response": {
38
+ "status": 200
39
+ }
40
+ }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ Fastly builds produce a verified `.pulse-docs-build/bin/main.wasm`. Configured
46
+ tests execute through the local-conformance runtime; compiled-target execution
47
+ remains in the explicit reality profile.
48
+
49
+ ## Wasm size
50
+
51
+ | Artifact | Default build | `--experimental-native-size` | Reduction |
52
+ |---|---:|---:|---:|
53
+ | `canonical-native.wasm` | 5.3 KiB (5,454 bytes) | 4.8 KiB (4,888 bytes) | 10.4% |
54
+ | `bin/main.wasm` | 40.4 KiB (41,372 bytes) | 33.7 KiB (34,538 bytes) | 16.5% |
55
+
56
+ The executable documentation gate rebuilds and measures both variants on
57
+ `1.0.0-beta.1`. These are uncompressed on-disk sizes, not transfer sizes or
58
+ platform limits.
59
+
60
+ ## Handler
61
+
62
+ <!-- pulse-doc-source: examples/05-fastly-capabilities/src/index.ts -->
63
+ ```ts
64
+ import { grip } from '@pulse-compute/grip'
65
+ import { Pulse } from '@pulse-compute/pulse'
66
+
67
+ interface User {
68
+ id: number
69
+ name: string
70
+ }
71
+
72
+ interface Session {
73
+ userId: number
74
+ }
75
+
76
+ const app = new Pulse({ auto: true })
77
+
78
+ // Config, secret, and a named Fastly backend.
79
+ app.get('/users/7', async (ctx) => {
80
+ const base = await ctx.config.get('API_BASE')
81
+ const token = await ctx.secret.get('API_TOKEN')
82
+ const user = await ctx
83
+ .fetch('https://api.example.com/users/7', {
84
+ headers: {
85
+ authorization: `Bearer ${token}`,
86
+ accept: 'application/json',
87
+ 'x-api-base': base,
88
+ },
89
+ })
90
+ .json<User>()
91
+ return ctx.json({ user })
92
+ })
93
+
94
+ // Named Fastly KV storage.
95
+ app.get('/session', async (ctx) => {
96
+ const current = await ctx.kv<Session>('sessions').get('session:123')
97
+ if (current === undefined) {
98
+ return ctx.json({ error: 'not_found' }, { status: 404 })
99
+ }
100
+ await ctx.kv<Session>('sessions').put('session:last', current)
101
+ return ctx.json({ session: current })
102
+ })
103
+
104
+ // Request-bound GRIP broadcast through the supported package root.
105
+ app.post('/publish', async (ctx) => {
106
+ const acknowledgement = await grip.broadcast(ctx, {
107
+ channel: 'events:demo',
108
+ event: 'pulse.message',
109
+ id: 'message-1',
110
+ data: { message: 'hello from Pulse' },
111
+ })
112
+ return ctx.json(
113
+ { accepted: acknowledgement.accepted },
114
+ { status: 202 },
115
+ )
116
+ })
117
+
118
+ export default app
119
+ ```
120
+ <!-- /pulse-doc-source -->
121
+
122
+ ## Project config
123
+
124
+ <!-- pulse-doc-source: examples/05-fastly-capabilities/.pulse/config.ts -->
125
+ ```ts
126
+ import { defineConfig } from '@pulse-compute/pulse'
127
+
128
+ export default defineConfig((scope) => ({
129
+ pulse: {
130
+ entry: 'src/index.ts',
131
+ tests: 'tests/pulse.harness.ts',
132
+ defaultProfile: 'local',
133
+ strict: true,
134
+ },
135
+ local: {
136
+ host: 'fastly',
137
+ target: 'native',
138
+ outDir: 'dist',
139
+ apiBase: scope.config('API_BASE'),
140
+ apiToken: scope.secret('API_TOKEN'),
141
+ dev: {
142
+ config: { API_BASE: 'https://api.example.com' },
143
+ secrets: {
144
+ API_TOKEN: 'local-example-secret',
145
+ GRIP_TOKEN: 'local-grip-secret',
146
+ },
147
+ kv: {
148
+ sessions: { 'session:123': { userId: 123 } },
149
+ },
150
+ fetches: {
151
+ 'https://api.example.com/users/7': {
152
+ value: { id: 7, name: 'Ada' },
153
+ },
154
+ 'POST https://publisher.example.com/publish': {
155
+ status: 202,
156
+ value: { accepted: true, messageId: 'message-1' },
157
+ },
158
+ },
159
+ },
160
+ fastly: {
161
+ bindings: {
162
+ configStore: 'app_config',
163
+ secretStore: 'app_secrets',
164
+ kv: { sessions: 'app_sessions' },
165
+ backends: {
166
+ 'https://api.example.com': 'api_backend',
167
+ 'https://publisher.example.com': 'publisher_backend',
168
+ },
169
+ dynamicBackends: false,
170
+ grip: {
171
+ publishEndpoint: 'https://publisher.example.com/publish',
172
+ publishBackend: 'publisher_backend',
173
+ authentication: {
174
+ scheme: 'bearer',
175
+ secretRef: 'GRIP_TOKEN',
176
+ },
177
+ },
178
+ },
179
+ build: { name: 'pulse-fastly-capabilities-example' },
180
+ },
181
+ },
182
+ }))
183
+ ```
184
+ <!-- /pulse-doc-source -->
185
+
186
+ ## Test harness
187
+
188
+ <!-- pulse-doc-source: examples/05-fastly-capabilities/tests/pulse.harness.ts -->
189
+ ```ts
190
+ const config = { API_BASE: 'https://api.example.com' }
191
+ const secrets = {
192
+ API_TOKEN: 'test-example-secret',
193
+ GRIP_TOKEN: 'test-grip-secret',
194
+ }
195
+ const grip = {
196
+ publishEndpoint: 'https://publisher.example.com/publish',
197
+ authentication: {
198
+ scheme: 'bearer',
199
+ secretRef: 'GRIP_TOKEN',
200
+ },
201
+ }
202
+
203
+ export default {
204
+ cases: [
205
+ {
206
+ name: 'configured-fetch',
207
+ request: { path: '/users/7' },
208
+ config,
209
+ secrets,
210
+ fetches: {
211
+ 'https://api.example.com/users/7': {
212
+ value: { id: 7, name: 'Ada' },
213
+ },
214
+ },
215
+ expect: {
216
+ status: 200,
217
+ json: { user: { id: 7, name: 'Ada' } },
218
+ },
219
+ },
220
+ {
221
+ name: 'session-hit',
222
+ request: { path: '/session' },
223
+ kv: { sessions: { 'session:123': { userId: 123 } } },
224
+ expect: {
225
+ status: 200,
226
+ json: { session: { userId: 123 } },
227
+ },
228
+ },
229
+ {
230
+ name: 'session-miss',
231
+ request: { path: '/session' },
232
+ kv: { sessions: {} },
233
+ expect: {
234
+ status: 404,
235
+ json: { error: 'not_found' },
236
+ },
237
+ },
238
+ {
239
+ name: 'grip-broadcast',
240
+ request: { method: 'POST', path: '/publish' },
241
+ secrets,
242
+ grip,
243
+ fetches: {
244
+ 'POST https://publisher.example.com/publish': {
245
+ status: 202,
246
+ value: { accepted: true, messageId: 'message-1' },
247
+ },
248
+ },
249
+ expect: {
250
+ status: 202,
251
+ json: { accepted: true },
252
+ },
253
+ },
254
+ ],
255
+ }
256
+ ```
257
+ <!-- /pulse-doc-source -->
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@pulse-examples/05-fastly-capabilities",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "doctor": "pulse doctor",
8
+ "inspect": "pulse inspect",
9
+ "test": "pulse test",
10
+ "dev": "pulse dev",
11
+ "build": "pulse build"
12
+ },
13
+ "dependencies": {
14
+ "@pulse-compute/grip": "1.0.0-beta.1",
15
+ "@pulse-compute/provider-fastly": "1.0.0-beta.1",
16
+ "@pulse-compute/pulse": "1.0.0-beta.1"
17
+ },
18
+ "devDependencies": {
19
+ "@pulse-compute/cli": "1.0.0-beta.1",
20
+ "typescript": "5.9.3"
21
+ }
22
+ }