@pulse-compute/provider-fastly 0.0.0 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +53 -1
  2. package/dist/config-schema.json +77 -0
  3. package/dist/index.d.ts +61 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +59 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +88 -5
  8. package/src/build/canonical-target.js +436 -0
  9. package/src/build/entities-native.js +564 -0
  10. package/src/build/fixed-memory-runtime.as.ts +148 -0
  11. package/src/build/kv-native.as.ts +229 -0
  12. package/src/build/kv-native.js +20 -0
  13. package/src/build/native-http-effects.js +871 -0
  14. package/src/build/native-http-shell.js +703 -0
  15. package/src/build/native-platform-capabilities.js +2196 -0
  16. package/src/build/s3-native.as.ts +215 -0
  17. package/src/build/s3-native.js +30 -0
  18. package/src/compiler/assets-package-out-parity-proof.js +338 -0
  19. package/src/compiler/assets-provider-proof.js +499 -0
  20. package/src/compiler/config-provider-resolution.js +573 -0
  21. package/src/compiler/fastly-adapter.js +564 -0
  22. package/src/compiler/fastly-command-entry.js +390 -0
  23. package/src/compiler/fastly-config-secret-serve.js +323 -0
  24. package/src/compiler/fastly-hostcall-binding.js +598 -0
  25. package/src/compiler/fastly-readiness.js +477 -0
  26. package/src/compiler/index.js +40 -0
  27. package/src/compiler/lifecycle-parity-contract.js +70 -0
  28. package/src/compiler/lifecycle-parity-proof.js +260 -0
  29. package/src/compiler/route-handler-effect-proof.js +255 -0
  30. package/src/compiler/runtime-provider-kv.js +237 -0
  31. package/src/compiler/runtime-provider-shape.js +109 -0
  32. package/src/config-api.js +63 -0
  33. package/src/config-schema.d.ts +13 -0
  34. package/src/config-schema.js +27 -0
  35. package/src/config-schema.json +77 -0
  36. package/src/index.js +3 -0
  37. package/src/index.ts +118 -0
  38. package/src/javascript/assets-adapter.js +122 -0
  39. package/src/javascript/bindings-adapter.js +237 -0
  40. package/src/javascript/errors.js +26 -0
  41. package/src/javascript/fetch-adapter.js +93 -0
  42. package/src/javascript/grip-broadcast.js +261 -0
  43. package/src/javascript/jwt-verifier.js +137 -0
  44. package/src/javascript/lifecycle.js +55 -0
  45. package/src/javascript/local-loader.js +138 -0
  46. package/src/javascript/logging.js +26 -0
  47. package/src/javascript/package-effects.js +68 -0
  48. package/src/javascript/runtime-host.js +108 -0
  49. package/src/javascript/sha256.js +128 -0
  50. package/src/javascript/source-package.js +776 -0
  51. package/src/javascript/support.js +142 -0
  52. package/src/javascript/target-support-policy.js +264 -0
  53. package/src/javascript/target.js +73 -0
  54. package/src/javascript/test-runtime.js +407 -0
  55. package/src/provider-contract.js +79 -0
  56. package/src/runtime/assets-provider.js +464 -0
  57. package/src/runtime/canonical-api-runtime.js +601 -0
  58. package/src/runtime/index.js +28 -0
  59. package/src/runtime/provider-primitives-contract.js +272 -0
  60. package/src/runtime/provider-primitives.js +82 -0
  61. package/src/runtime/route-handler-effects.js +507 -0
  62. package/src/testing/conditional-kv-host.js +109 -0
  63. package/src/testing/fastly-cli.js +538 -0
  64. package/src/testing/native-http-effects-host.js +390 -0
  65. package/src/testing/native-http-shell-host.js +190 -0
  66. package/src/testing/native-platform-capabilities-host.js +676 -0
  67. package/src/toolchain/config.js +131 -0
  68. package/src/toolchain/index.js +345 -0
  69. package/src/toolchain/s3.js +26 -0
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ const runtimeApi = require('@pulse-compute/wasm-contracts/handler/runtime-api');
4
+
5
+ const FASTLY_PROVIDER_CONTRACT_VERSION = 'pulse.fastly-provider-contract.v2';
6
+ const primitiveNames = Object.freeze([
7
+ 'ProviderRequest',
8
+ 'ProviderResponse',
9
+ 'ProviderFetch',
10
+ 'ProviderConfig',
11
+ 'ProviderSecret',
12
+ 'ProviderKV',
13
+ 'ProviderLog'
14
+ ]);
15
+ const ctxSurface = Object.freeze({
16
+ request: Object.freeze(['ctx.req.method', 'ctx.req.url', 'ctx.req.path', 'ctx.req.headers', 'ctx.req.text()', 'ctx.req.json()']),
17
+ response: Object.freeze(['ctx.json()', 'ctx.text()', 'ctx.response()', 'return ctx.fetch(url)']),
18
+ fetch: Object.freeze(['ctx.fetch(url, options)']),
19
+ config: 'ctx.config.get(name)',
20
+ secret: 'ctx.secret.get(name)',
21
+ kv: Object.freeze(['ctx.kv(store).get(key)', 'ctx.kv(store).put(key, value)']),
22
+ log: Object.freeze(['ctx.log.info(message, fields)', 'ctx.log.warn(message, fields)', 'ctx.log.error(message, fields)'])
23
+ });
24
+ const defaults = Object.freeze({
25
+ provider: 'fastly',
26
+ configStore: 'pulse_config',
27
+ secretStore: 'pulse_secrets',
28
+ kvStore: 'pulse_kv',
29
+ allowedMethods: Object.freeze(['GET', 'HEAD', 'POST']),
30
+ defaultTimeoutMs: runtimeApi.limits.fetchTimeoutMs,
31
+ structuredBodyMaxBytes: runtimeApi.limits.structuredBodyBytes,
32
+ opaquePassThrough: true,
33
+ secretRedaction: '[secret:redacted]'
34
+ });
35
+
36
+ function deepFreeze(value) {
37
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
38
+ for (const child of Object.values(value)) deepFreeze(child);
39
+ return Object.freeze(value);
40
+ }
41
+
42
+ function immutableClone(value) {
43
+ return runtimeApi.body.immutableClone(value);
44
+ }
45
+
46
+ function normalizeHeaders(headers) {
47
+ return runtimeApi.body.normalizeHeaders(headers).map(([name, value]) => [String(name).toLowerCase(), String(value)]);
48
+ }
49
+
50
+ function normalizeName(value, fallback = 'VALUE') {
51
+ return String(value || fallback).trim().replace(/[^A-Za-z0-9:_-]+/g, '_') || fallback;
52
+ }
53
+
54
+ function redactSecretValue(name) {
55
+ return `[secret:${normalizeName(name, 'SECRET')}:redacted]`;
56
+ }
57
+
58
+ function redactSecrets(value, secretNames = []) {
59
+ const names = Array.from(new Set(secretNames.map((name) => normalizeName(name, 'SECRET'))));
60
+ if (Array.isArray(value)) return value.map((entry) => redactSecrets(entry, names));
61
+ if (value && typeof value === 'object') {
62
+ const out = {};
63
+ for (const [key, child] of Object.entries(value)) {
64
+ const lower = String(key).toLowerCase();
65
+ if (lower.includes('secret') || lower.includes('token') || names.some((name) => lower.includes(name.toLowerCase()))) out[key] = redactSecretValue(names[0] || key);
66
+ else out[key] = redactSecrets(child, names);
67
+ }
68
+ return out;
69
+ }
70
+ const text = String(value ?? '');
71
+ for (const name of names) if (text.includes(name) || /token|secret/i.test(text)) return redactSecretValue(name);
72
+ return value;
73
+ }
74
+
75
+ function createCapabilityManifest(input = {}) {
76
+ const configStore = normalizeName(input.configStore || defaults.configStore, defaults.configStore);
77
+ const secretStore = normalizeName(input.secretStore || defaults.secretStore, defaults.secretStore);
78
+ const kvStore = normalizeName(input.kvStore || defaults.kvStore, defaults.kvStore);
79
+ return deepFreeze({
80
+ kind: 'provider-capability-manifest',
81
+ provider: 'fastly',
82
+ contractVersion: FASTLY_PROVIDER_CONTRACT_VERSION,
83
+ primitives: primitiveNames,
84
+ ctxSurface,
85
+ bindings: { configStore, secretStore, kvStore },
86
+ config: { surface: ctxSurface.config, exactNameOnly: true, enumerable: false, provider: 'fastly.configStore', store: configStore },
87
+ secret: { surface: ctxSurface.secret, exactNameOnly: true, enumerable: false, provider: 'fastly.secretStore', store: secretStore, traceRedacted: true },
88
+ kv: { surface: ctxSurface.kv, provider: 'fastly.kvStore', store: kvStore, operations: ['get', 'put'], deleteReserved: true },
89
+ fetch: { surface: ctxSurface.fetch, methods: defaults.allowedMethods, timeoutMs: defaults.defaultTimeoutMs, opaquePassThrough: true },
90
+ request: { surface: ctxSurface.request, exposesProviderObject: false },
91
+ response: { surface: ctxSurface.response, exposesProviderObject: false, opaquePassThrough: true },
92
+ log: { surface: ctxSurface.log, secretRedaction: true },
93
+ providerSpecificUserland: false,
94
+ providerSdkWrapper: false,
95
+ capabilityDiscoveryFromUserland: false
96
+ });
97
+ }
98
+
99
+ function createRequestAdapter(input = {}) {
100
+ return deepFreeze({
101
+ kind: 'ProviderRequest',
102
+ provider: 'fastly',
103
+ method: String(input.method || 'GET').toUpperCase(),
104
+ url: String(input.url || 'https://edge.example.test/'),
105
+ path: String(input.path || '/'),
106
+ query: immutableClone(input.query || {}),
107
+ headers: normalizeHeaders(input.headers || []),
108
+ bodyClass: input.bodyClass || 'structured',
109
+ exposesProviderObject: false,
110
+ providerSpecificUserland: false,
111
+ authority: 'ctx-only'
112
+ });
113
+ }
114
+
115
+ function createResponseAdapter(input = {}) {
116
+ const status = Number(input.status || 200);
117
+ const headers = normalizeHeaders(input.headers || [['content-type', input.contentType || 'application/json; charset=utf-8']]);
118
+ const bodyClass = input.bodyClass || 'structured';
119
+ return deepFreeze({
120
+ kind: 'ProviderResponse',
121
+ provider: 'fastly',
122
+ status,
123
+ headers,
124
+ bodyClass,
125
+ bodyKind: input.bodyKind || (bodyClass === 'opaque' ? 'stream' : 'json'),
126
+ preserveStatus: true,
127
+ preserveHeaders: true,
128
+ preserveBodyHandle: bodyClass === 'opaque',
129
+ exposesProviderObject: false,
130
+ providerSpecificUserland: false,
131
+ traceBodyContents: bodyClass !== 'opaque',
132
+ bodyCopiedIntoWasm: bodyClass === 'opaque' ? false : Boolean(input.bodyCopiedIntoWasm)
133
+ });
134
+ }
135
+
136
+ function createFetchAdapter(input = {}) {
137
+ const method = String(input.method || 'GET').toUpperCase();
138
+ if (!defaults.allowedMethods.includes(method)) return deepFreeze({ ok: false, error: runtimeApi.errors.hostEffect, method });
139
+ const headers = normalizeHeaders(input.headers || [['accept', 'application/json']]);
140
+ const timeoutMs = Number(input.timeoutMs || defaults.defaultTimeoutMs);
141
+ return deepFreeze({
142
+ kind: 'ProviderFetch',
143
+ provider: 'fastly',
144
+ effect: {
145
+ ok: true,
146
+ kind: 'fetch-request-effect',
147
+ effectKind: 'backend-fetch',
148
+ method,
149
+ url: String(input.url || 'https://api.example.test/'),
150
+ headers,
151
+ timeoutMs,
152
+ bodyClass: Object.prototype.hasOwnProperty.call(input, 'body') ? 'structured' : 'none',
153
+ body: Object.prototype.hasOwnProperty.call(input, 'body') ? String(input.body ?? '') : undefined,
154
+ providerSpecificUserland: false
155
+ },
156
+ dispatch: 'backend-fetch',
157
+ providerSpecificUserland: false,
158
+ supportsStructuredBodies: true,
159
+ supportsOpaquePassThrough: true,
160
+ timeoutMs,
161
+ methods: defaults.allowedMethods
162
+ });
163
+ }
164
+
165
+ function createConfigAdapter(values = {}) {
166
+ const store = Object.fromEntries(Object.entries(values).map(([key, value]) => [normalizeName(key), String(value)]));
167
+ return Object.freeze({
168
+ kind: 'ProviderConfig',
169
+ provider: 'fastly',
170
+ store: defaults.configStore,
171
+ exactNameOnly: true,
172
+ enumerable: false,
173
+ providerSpecificUserland: false,
174
+ get(name) {
175
+ const key = normalizeName(name);
176
+ return Object.prototype.hasOwnProperty.call(store, key) ? store[key] : undefined;
177
+ },
178
+ list() { return deepFreeze({ ok: false, error: 'CapabilityDiscoveryError', enumerable: false }); },
179
+ snapshot() { return deepFreeze({ keys: Object.keys(store).sort(), valuesVisibleInTrace: false, exactNameOnly: true }); }
180
+ });
181
+ }
182
+
183
+ function createSecretAdapter(values = {}) {
184
+ const store = Object.fromEntries(Object.entries(values).map(([key, value]) => [normalizeName(key), String(value)]));
185
+ return Object.freeze({
186
+ kind: 'ProviderSecret',
187
+ provider: 'fastly',
188
+ store: defaults.secretStore,
189
+ exactNameOnly: true,
190
+ enumerable: false,
191
+ traceRedacted: true,
192
+ providerSpecificUserland: false,
193
+ get(name) {
194
+ const key = normalizeName(name);
195
+ if (!Object.prototype.hasOwnProperty.call(store, key)) return undefined;
196
+ return deepFreeze({ kind: 'secret-value', name: key, value: store[key], redacted: redactSecretValue(key), traceRedacted: true, enumerable: false });
197
+ },
198
+ list() { return deepFreeze({ ok: false, error: 'CapabilityDiscoveryError', enumerable: false, traceRedacted: true }); }
199
+ });
200
+ }
201
+
202
+ function createKvAdapter(stores = {}) {
203
+ const normalizedStores = {};
204
+ for (const [storeName, entries] of Object.entries(stores)) normalizedStores[normalizeName(storeName, 'default')] = { ...(entries || {}) };
205
+ function ensureStore(storeName) {
206
+ const name = normalizeName(storeName, 'default');
207
+ if (!normalizedStores[name]) normalizedStores[name] = {};
208
+ return { name, entries: normalizedStores[name] };
209
+ }
210
+ return Object.freeze({
211
+ kind: 'ProviderKV',
212
+ provider: 'fastly',
213
+ exactStoreBinding: true,
214
+ operations: Object.freeze(['get', 'put']),
215
+ deleteReserved: true,
216
+ providerSpecificUserland: false,
217
+ get(storeName, key) {
218
+ const store = ensureStore(storeName);
219
+ const normalizedKey = String(key || '');
220
+ const hit = Object.prototype.hasOwnProperty.call(store.entries, normalizedKey);
221
+ return deepFreeze({ ok: true, operation: 'get', store: store.name, key: normalizedKey, hit, value: hit ? immutableClone(store.entries[normalizedKey]) : undefined });
222
+ },
223
+ put(storeName, key, value) {
224
+ const store = ensureStore(storeName);
225
+ const normalizedKey = String(key || '');
226
+ store.entries[normalizedKey] = immutableClone(value);
227
+ return deepFreeze({ ok: true, operation: 'put', store: store.name, key: normalizedKey, valueStored: true, generation: `${store.name}:${normalizedKey}:1` });
228
+ },
229
+ delete() { return deepFreeze({ ok: false, operation: 'delete', error: 'CapabilityReservedError', reserved: true }); }
230
+ });
231
+ }
232
+
233
+ function createLogAdapter(options = {}) {
234
+ const secretNames = Array.isArray(options.secretNames) ? options.secretNames : [];
235
+ const adapter = {
236
+ kind: 'ProviderLog',
237
+ provider: 'fastly',
238
+ traceRedacted: true,
239
+ providerSpecificUserland: false,
240
+ event(level, message, fields = {}) {
241
+ const safeLevel = ['debug', 'info', 'warn', 'error'].includes(String(level)) ? String(level) : 'info';
242
+ return deepFreeze({ level: safeLevel, message: String(message || ''), fields: redactSecrets(fields, secretNames), secretRedactionApplied: true });
243
+ },
244
+ info(message, fields) { return adapter.event('info', message, fields); },
245
+ warn(message, fields) { return adapter.event('warn', message, fields); },
246
+ error(message, fields) { return adapter.event('error', message, fields); }
247
+ };
248
+ return Object.freeze(adapter);
249
+ }
250
+
251
+ module.exports = Object.freeze({
252
+ version: FASTLY_PROVIDER_CONTRACT_VERSION,
253
+ primitiveNames,
254
+ ctxSurface,
255
+ ctxSurfaces: ctxSurface,
256
+ defaults,
257
+ capabilityDefaults: defaults,
258
+ deepFreeze,
259
+ immutableClone,
260
+ normalizeHeaders,
261
+ normalizeName,
262
+ redactSecretValue,
263
+ redactSecrets,
264
+ createCapabilityManifest,
265
+ createRequestAdapter,
266
+ createResponseAdapter,
267
+ createFetchAdapter,
268
+ createConfigAdapter,
269
+ createSecretAdapter,
270
+ createKvAdapter,
271
+ createLogAdapter
272
+ });
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const contract = require('./provider-primitives-contract.js');
4
+
5
+ function clone(value) {
6
+ if (Array.isArray(value)) return value.map(clone);
7
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, clone(child)]));
8
+ return value;
9
+ }
10
+
11
+ function createFastlyProviderPrimitiveSurface(options = {}) {
12
+ const manifest = contract.createCapabilityManifest(options);
13
+ const configAdapter = contract.createConfigAdapter(options.configValues || options.configProvider || {});
14
+ const secretAdapter = contract.createSecretAdapter(options.secretValues || options.secretProvider || {});
15
+ const kvAdapter = contract.createKvAdapter(options.kvStores || options.kvValues || {});
16
+ const logAdapter = contract.createLogAdapter({ secretNames: Object.keys(options.secretValues || options.secretProvider || {}) });
17
+ const logs = [];
18
+
19
+ return Object.freeze({
20
+ kind: 'FastlyProviderPrimitiveSurface',
21
+ provider: 'fastly',
22
+ manifest,
23
+ providerSpecificUserland: false,
24
+ providerSdkWrapper: false,
25
+ request(input) {
26
+ return contract.createRequestAdapter(input);
27
+ },
28
+ response(input) {
29
+ return contract.createResponseAdapter(input);
30
+ },
31
+ fetch(input) {
32
+ return contract.createFetchAdapter(input);
33
+ },
34
+ config: Object.freeze({
35
+ get(name) {
36
+ const value = configAdapter.get(name);
37
+ return contract.deepFreeze({ kind: 'ProviderConfigRead', provider: 'fastly', name: contract.normalizeName(name), value, hit: value !== undefined, exactNameOnly: true, enumerable: false, providerSpecificUserland: false });
38
+ },
39
+ list() { return configAdapter.list(); },
40
+ adapter: configAdapter
41
+ }),
42
+ secret: Object.freeze({
43
+ get(name) {
44
+ const secret = secretAdapter.get(name);
45
+ return contract.deepFreeze({ kind: 'ProviderSecretRead', provider: 'fastly', name: contract.normalizeName(name), hit: Boolean(secret), valueRef: secret ? secret.redacted : undefined, traceValue: secret ? secret.redacted : undefined, traceRedacted: true, enumerable: false, providerSpecificUserland: false });
46
+ },
47
+ list() { return secretAdapter.list(); },
48
+ adapter: secretAdapter
49
+ }),
50
+ kv: Object.freeze({
51
+ get(store, key) { return kvAdapter.get(store || manifest.bindings.kvStore, key); },
52
+ put(store, key, value) { return kvAdapter.put(store || manifest.bindings.kvStore, key, value); },
53
+ delete(store, key) { return kvAdapter.delete(store, key); },
54
+ adapter: kvAdapter
55
+ }),
56
+ log: Object.freeze({
57
+ event(level, message, fields) {
58
+ const event = logAdapter.event(level, message, fields);
59
+ logs.push(event);
60
+ return event;
61
+ },
62
+ info(message, fields) { return this.event('info', message, fields); },
63
+ warn(message, fields) { return this.event('warn', message, fields); },
64
+ error(message, fields) { return this.event('error', message, fields); },
65
+ entries() { return logs.map(clone); },
66
+ adapter: logAdapter
67
+ }),
68
+ diagnostics() {
69
+ return contract.deepFreeze({ provider: 'fastly', primitiveCount: contract.primitiveNames.length, logs: logs.length, secretsRedacted: true, providerSpecificUserland: false, providerSdkWrapper: false });
70
+ }
71
+ });
72
+ }
73
+
74
+ function buildFastlyProviderPrimitiveManifest(options = {}) {
75
+ return contract.createCapabilityManifest(options);
76
+ }
77
+
78
+ module.exports = {
79
+ contract,
80
+ createFastlyProviderPrimitiveSurface,
81
+ buildFastlyProviderPrimitiveManifest
82
+ };