@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.
- package/README.md +53 -1
- package/dist/config-schema.json +77 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -5
- package/src/build/canonical-target.js +436 -0
- package/src/build/entities-native.js +564 -0
- package/src/build/fixed-memory-runtime.as.ts +148 -0
- package/src/build/kv-native.as.ts +229 -0
- package/src/build/kv-native.js +20 -0
- package/src/build/native-http-effects.js +871 -0
- package/src/build/native-http-shell.js +703 -0
- package/src/build/native-platform-capabilities.js +2196 -0
- package/src/build/s3-native.as.ts +215 -0
- package/src/build/s3-native.js +30 -0
- package/src/compiler/assets-package-out-parity-proof.js +338 -0
- package/src/compiler/assets-provider-proof.js +499 -0
- package/src/compiler/config-provider-resolution.js +573 -0
- package/src/compiler/fastly-adapter.js +564 -0
- package/src/compiler/fastly-command-entry.js +390 -0
- package/src/compiler/fastly-config-secret-serve.js +323 -0
- package/src/compiler/fastly-hostcall-binding.js +598 -0
- package/src/compiler/fastly-readiness.js +477 -0
- package/src/compiler/index.js +40 -0
- package/src/compiler/lifecycle-parity-contract.js +70 -0
- package/src/compiler/lifecycle-parity-proof.js +260 -0
- package/src/compiler/route-handler-effect-proof.js +255 -0
- package/src/compiler/runtime-provider-kv.js +237 -0
- package/src/compiler/runtime-provider-shape.js +109 -0
- package/src/config-api.js +63 -0
- package/src/config-schema.d.ts +13 -0
- package/src/config-schema.js +27 -0
- package/src/config-schema.json +77 -0
- package/src/index.js +3 -0
- package/src/index.ts +118 -0
- package/src/javascript/assets-adapter.js +122 -0
- package/src/javascript/bindings-adapter.js +237 -0
- package/src/javascript/errors.js +26 -0
- package/src/javascript/fetch-adapter.js +93 -0
- package/src/javascript/grip-broadcast.js +261 -0
- package/src/javascript/jwt-verifier.js +137 -0
- package/src/javascript/lifecycle.js +55 -0
- package/src/javascript/local-loader.js +138 -0
- package/src/javascript/logging.js +26 -0
- package/src/javascript/package-effects.js +68 -0
- package/src/javascript/runtime-host.js +108 -0
- package/src/javascript/sha256.js +128 -0
- package/src/javascript/source-package.js +776 -0
- package/src/javascript/support.js +142 -0
- package/src/javascript/target-support-policy.js +264 -0
- package/src/javascript/target.js +73 -0
- package/src/javascript/test-runtime.js +407 -0
- package/src/provider-contract.js +79 -0
- package/src/runtime/assets-provider.js +464 -0
- package/src/runtime/canonical-api-runtime.js +601 -0
- package/src/runtime/index.js +28 -0
- package/src/runtime/provider-primitives-contract.js +272 -0
- package/src/runtime/provider-primitives.js +82 -0
- package/src/runtime/route-handler-effects.js +507 -0
- package/src/testing/conditional-kv-host.js +109 -0
- package/src/testing/fastly-cli.js +538 -0
- package/src/testing/native-http-effects-host.js +390 -0
- package/src/testing/native-http-shell-host.js +190 -0
- package/src/testing/native-platform-capabilities-host.js +676 -0
- package/src/toolchain/config.js +131 -0
- package/src/toolchain/index.js +345 -0
- package/src/toolchain/s3.js +26 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function loadAssetsContracts() {
|
|
4
|
+
try {
|
|
5
|
+
return require('@pulse-compute/wasm-contracts/assets/contracts');
|
|
6
|
+
} catch (error) {
|
|
7
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
8
|
+
return require('../../../../wasm/packages/contracts/src/assets/contracts.js');
|
|
9
|
+
}
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadDiagnostics() {
|
|
15
|
+
try {
|
|
16
|
+
return require('@pulse-compute/wasm-contracts/diagnostics');
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
|
|
19
|
+
return require('../../../../wasm/packages/contracts/src/diagnostics.js');
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const assetsContracts = loadAssetsContracts();
|
|
26
|
+
const { normalizeDiagnostic } = loadDiagnostics();
|
|
27
|
+
|
|
28
|
+
function clone(value) {
|
|
29
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
30
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clone(entry)]));
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeHeaders(headers) {
|
|
35
|
+
if (!headers) return [];
|
|
36
|
+
if (Array.isArray(headers)) {
|
|
37
|
+
return headers
|
|
38
|
+
.filter((pair) => pair && pair[0] !== undefined && pair[1] !== undefined)
|
|
39
|
+
.map((pair) => [String(pair[0]), String(pair[1])]);
|
|
40
|
+
}
|
|
41
|
+
if (typeof headers[Symbol.iterator] === 'function') {
|
|
42
|
+
return Array.from(headers, ([key, value]) => [String(key), String(value)]);
|
|
43
|
+
}
|
|
44
|
+
return Object.entries(headers).flatMap(([key, value]) => Array.isArray(value)
|
|
45
|
+
? value.map((item) => [String(key), String(item)])
|
|
46
|
+
: [[String(key), String(value)]]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function headerValue(headers, name) {
|
|
50
|
+
const lower = String(name).toLowerCase();
|
|
51
|
+
const found = normalizeHeaders(headers).find(([key]) => String(key).toLowerCase() === lower);
|
|
52
|
+
return found ? found[1] : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setHeader(headers, name, value) {
|
|
56
|
+
const out = normalizeHeaders(headers).filter(([key]) => String(key).toLowerCase() !== String(name).toLowerCase());
|
|
57
|
+
if (value !== undefined && value !== null) out.push([String(name), String(value)]);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function appendMissingHeader(headers, name, value) {
|
|
62
|
+
if (headerValue(headers, name) !== undefined) return headers;
|
|
63
|
+
return headers.concat([[String(name), String(value)]]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeKey(key) {
|
|
67
|
+
const text = String(key || '');
|
|
68
|
+
return text.startsWith('/') ? text : `/${text}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizePlan(input) {
|
|
72
|
+
const artifact = input && input.artifact ? input.artifact : input;
|
|
73
|
+
if (!artifact || typeof artifact !== 'object') return undefined;
|
|
74
|
+
return {
|
|
75
|
+
...artifact,
|
|
76
|
+
entries: Array.isArray(artifact.entries) ? artifact.entries.map(clone) : [],
|
|
77
|
+
payloadModes: artifact.payloadModes ? clone(artifact.payloadModes) : {
|
|
78
|
+
classification: assetsContracts.assetsPayloadModeClassification(),
|
|
79
|
+
responsePolicy: clone(assetsContracts.ASSETS_RESPONSE_POLICY)
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function entriesFromPlan(plan) {
|
|
85
|
+
return (plan && Array.isArray(plan.entries) ? plan.entries : [])
|
|
86
|
+
.filter((entry) => entry && entry.status !== 'unsupported' && entry.lookup && entry.lookup.store && entry.lookup.key)
|
|
87
|
+
.map(clone);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeAssetRecord(value) {
|
|
91
|
+
if (value === undefined || value === null) return undefined;
|
|
92
|
+
if (typeof value === 'string' || Buffer.isBuffer(value)) return { status: 200, body: value, headers: [] };
|
|
93
|
+
if (typeof value === 'object') {
|
|
94
|
+
return {
|
|
95
|
+
status: value.status || 200,
|
|
96
|
+
body: value.body !== undefined ? value.body : value.text !== undefined ? value.text : value.content,
|
|
97
|
+
headers: normalizeHeaders(value.headers),
|
|
98
|
+
cacheControl: value.cacheControl,
|
|
99
|
+
contentType: value.contentType,
|
|
100
|
+
payloadMode: value.payloadMode,
|
|
101
|
+
metadata: clone(value.metadata || {})
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { status: 200, body: String(value), headers: [] };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeStores(stores) {
|
|
108
|
+
const out = new Map();
|
|
109
|
+
for (const [storeName, records] of Object.entries(stores || {})) {
|
|
110
|
+
const byKey = new Map();
|
|
111
|
+
for (const [key, value] of Object.entries(records || {})) byKey.set(normalizeKey(key), normalizeAssetRecord(value));
|
|
112
|
+
out.set(String(storeName), byKey);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function createDefaultStoresFromPlan(plan) {
|
|
118
|
+
const stores = {};
|
|
119
|
+
for (const entry of entriesFromPlan(plan)) {
|
|
120
|
+
const lookup = entry.lookup;
|
|
121
|
+
const store = lookup.store;
|
|
122
|
+
const key = normalizeKey(lookup.key);
|
|
123
|
+
if (!stores[store]) stores[store] = {};
|
|
124
|
+
if (!stores[store][key]) {
|
|
125
|
+
stores[store][key] = {
|
|
126
|
+
status: entry.response && entry.response.status ? entry.response.status : 200,
|
|
127
|
+
body: `fastly-asset:${store}:${key}`,
|
|
128
|
+
headers: [
|
|
129
|
+
['Content-Type', key.endsWith('.js') ? 'application/javascript' : 'text/plain; charset=utf-8'],
|
|
130
|
+
['X-PulseWasm-Fastly-Asset-Store', store],
|
|
131
|
+
['X-PulseWasm-Fastly-Asset-Key', key]
|
|
132
|
+
],
|
|
133
|
+
cacheControl: lookup.cacheControl
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return stores;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function makeDiagnostic(code, message, severity, details) {
|
|
141
|
+
return normalizeDiagnostic({
|
|
142
|
+
phase: 'fastly-assets-provider',
|
|
143
|
+
severity: severity || 'error',
|
|
144
|
+
code,
|
|
145
|
+
message,
|
|
146
|
+
hint: details && details.hint,
|
|
147
|
+
details,
|
|
148
|
+
loc: { file: '<fastly-assets-provider>' }
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function symbolicRefName(value) {
|
|
153
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
154
|
+
if (value.$config) return `$config:${value.$config}`;
|
|
155
|
+
if (value.$secret) return `$secret:${value.$secret}`;
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeResolvedConfig(input) {
|
|
160
|
+
const resolved = input && input.artifact ? input.artifact : input;
|
|
161
|
+
return resolved && typeof resolved === 'object' ? resolved : {};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function plainObject(value) {
|
|
165
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function assetCapabilityConfig(resolvedConfig) {
|
|
169
|
+
const runtime = plainObject(normalizeResolvedConfig(resolvedConfig).runtime);
|
|
170
|
+
return plainObject(plainObject(runtime.capabilities).assets);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function fastlyProviderConfig(resolvedConfig) {
|
|
174
|
+
const runtime = plainObject(normalizeResolvedConfig(resolvedConfig).runtime);
|
|
175
|
+
const provider = plainObject(runtime.provider);
|
|
176
|
+
const platformFastly = plainObject(plainObject(runtime.platform).fastly);
|
|
177
|
+
return {
|
|
178
|
+
kind: provider.kind || 'fastly',
|
|
179
|
+
configStore: provider.configStore || platformFastly.configStore || 'pulse_config',
|
|
180
|
+
secretStore: provider.secretStore || platformFastly.secretStore || 'pulse_secrets'
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function sanitizeBindingName(value) {
|
|
185
|
+
const text = String(value || 'public').replace(/[^A-Za-z0-9_]/g, '_').replace(/^_+|_+$/g, '') || 'public';
|
|
186
|
+
return `pulse_assets_${text}`.toLowerCase();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function uniqueStores(plan) {
|
|
190
|
+
return Array.from(new Set(entriesFromPlan(plan).map((entry) => entry.lookup.store))).sort();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function buildFastlyAssetsProviderConfig(options = {}) {
|
|
194
|
+
const plan = normalizePlan(options.plan || options.assetsLoweringPlan);
|
|
195
|
+
const resolvedConfig = normalizeResolvedConfig(options.resolvedConfig || {});
|
|
196
|
+
const assetConfig = assetCapabilityConfig(resolvedConfig);
|
|
197
|
+
const provider = fastlyProviderConfig(resolvedConfig);
|
|
198
|
+
const stores = uniqueStores(plan).map((logicalName) => ({
|
|
199
|
+
logicalName,
|
|
200
|
+
bindingName: sanitizeBindingName(logicalName),
|
|
201
|
+
mode: 'provider-object-store',
|
|
202
|
+
endpointRef: symbolicRefName(assetConfig.endpoint) || '$config:ASSET_ENDPOINT',
|
|
203
|
+
bucketRef: symbolicRefName(assetConfig.bucket) || '$config:ASSET_BUCKET',
|
|
204
|
+
keyRef: symbolicRefName(assetConfig.key) || '$secret:ASSET_KEY',
|
|
205
|
+
contentAddressed: false,
|
|
206
|
+
objectStoreSemanticsImplemented: false
|
|
207
|
+
}));
|
|
208
|
+
return {
|
|
209
|
+
version: assetsContracts.ASSETS_PROVIDER_CONFIG_VERSION,
|
|
210
|
+
provider: 'fastly',
|
|
211
|
+
mode: options.mode || 'provider-object-store',
|
|
212
|
+
configStore: provider.configStore,
|
|
213
|
+
secretStore: provider.secretStore,
|
|
214
|
+
symbolicRefsOnly: true,
|
|
215
|
+
stores,
|
|
216
|
+
unsupported: {
|
|
217
|
+
uploads: 'unsupported',
|
|
218
|
+
invalidation: 'unsupported',
|
|
219
|
+
mutationApis: 'unsupported',
|
|
220
|
+
fullObjectStoreSemantics: 'unsupported-in-proof'
|
|
221
|
+
},
|
|
222
|
+
summary: {
|
|
223
|
+
stores: stores.length,
|
|
224
|
+
configStore: provider.configStore,
|
|
225
|
+
secretStore: provider.secretStore,
|
|
226
|
+
symbolicRefs: stores.reduce((count, store) => count + [store.endpointRef, store.bucketRef, store.keyRef].filter(Boolean).length, 0)
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function unsupportedMethodResult(method) {
|
|
232
|
+
return {
|
|
233
|
+
status: 405,
|
|
234
|
+
kind: 'fastly-empty',
|
|
235
|
+
headers: [['Allow', assetsContracts.ASSETS_ALLOWED_METHODS.join(', ')]],
|
|
236
|
+
body: undefined,
|
|
237
|
+
diagnostics: [makeDiagnostic(
|
|
238
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.UNSUPPORTED_METHOD,
|
|
239
|
+
`Fastly assets provider proof only accepts ${assetsContracts.ASSETS_ALLOWED_METHODS.join(' / ')} requests.`,
|
|
240
|
+
'error',
|
|
241
|
+
{ method, allowedMethods: assetsContracts.ASSETS_ALLOWED_METHODS }
|
|
242
|
+
)]
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function reservedBinaryResult(entry) {
|
|
247
|
+
return {
|
|
248
|
+
status: 501,
|
|
249
|
+
kind: 'fastly-empty',
|
|
250
|
+
headers: [['Content-Type', 'text/plain; charset=utf-8']],
|
|
251
|
+
body: undefined,
|
|
252
|
+
entryId: entry && entry.id,
|
|
253
|
+
diagnostics: [makeDiagnostic(
|
|
254
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.BINARY_BODY_RESERVED,
|
|
255
|
+
'Fastly assets provider proof explicitly rejects binary-buffer assets until the binary body ABI is implemented.',
|
|
256
|
+
'error',
|
|
257
|
+
{ payloadMode: 'binary-buffer', status: 'reserved-with-diagnostic' }
|
|
258
|
+
)]
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function unsupportedPayloadResult(entry, payloadMode) {
|
|
263
|
+
return {
|
|
264
|
+
status: 415,
|
|
265
|
+
kind: 'fastly-empty',
|
|
266
|
+
headers: [['Content-Type', 'text/plain; charset=utf-8']],
|
|
267
|
+
body: undefined,
|
|
268
|
+
entryId: entry && entry.id,
|
|
269
|
+
diagnostics: [makeDiagnostic(
|
|
270
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.PAYLOAD_MODE_UNSUPPORTED,
|
|
271
|
+
`Fastly assets provider proof cannot serve assets payload mode ${JSON.stringify(payloadMode)}.`,
|
|
272
|
+
'error',
|
|
273
|
+
{ payloadMode }
|
|
274
|
+
)]
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function notFoundResult(entry, request) {
|
|
279
|
+
const lookup = entry && entry.lookup ? entry.lookup : {};
|
|
280
|
+
const passThrough = lookup.passThroughOn404 !== false;
|
|
281
|
+
const headers = [
|
|
282
|
+
['Content-Type', 'text/plain; charset=utf-8'],
|
|
283
|
+
['X-PulseWasm-Fastly-Asset-Miss', '1']
|
|
284
|
+
];
|
|
285
|
+
return {
|
|
286
|
+
status: 404,
|
|
287
|
+
kind: 'fastly-empty',
|
|
288
|
+
headers,
|
|
289
|
+
body: undefined,
|
|
290
|
+
passThroughOn404: passThrough,
|
|
291
|
+
entryId: entry && entry.id,
|
|
292
|
+
diagnostics: [makeDiagnostic(
|
|
293
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.FASTLY_PROVIDER_ASSET_NOT_FOUND,
|
|
294
|
+
'Fastly assets provider proof could not resolve the static store/key from the lowering plan.',
|
|
295
|
+
'info',
|
|
296
|
+
{ store: lookup.store, key: lookup.key, path: request.path, passThroughOn404: passThrough }
|
|
297
|
+
)]
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function resolveAsset(stores, store, key) {
|
|
302
|
+
const byKey = stores.get(String(store));
|
|
303
|
+
if (!byKey) return undefined;
|
|
304
|
+
return byKey.get(normalizeKey(key)) || byKey.get(String(key));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function responseStatus(entry, asset) {
|
|
308
|
+
if (entry && entry.response && entry.response.status) return entry.response.status;
|
|
309
|
+
return asset && asset.status ? asset.status : 200;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function assetHeaders(entry, asset) {
|
|
313
|
+
const lookup = entry.lookup || {};
|
|
314
|
+
let headers = normalizeHeaders(asset && asset.headers);
|
|
315
|
+
headers = appendMissingHeader(headers, 'Content-Type', (asset && asset.contentType) || 'text/plain; charset=utf-8');
|
|
316
|
+
headers = setHeader(headers, 'X-PulseWasm-Fastly-Asset-Store', lookup.store);
|
|
317
|
+
headers = setHeader(headers, 'X-PulseWasm-Fastly-Asset-Key', normalizeKey(lookup.key));
|
|
318
|
+
const cacheControl = lookup.cacheControl || (asset && asset.cacheControl);
|
|
319
|
+
if (cacheControl) headers = setHeader(headers, 'Cache-Control', cacheControl);
|
|
320
|
+
return headers;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function bodyText(asset) {
|
|
324
|
+
const body = asset && asset.body !== undefined ? asset.body : '';
|
|
325
|
+
return Buffer.isBuffer(body) ? body.toString('utf8') : String(body);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function payloadModeForEntry(entry, asset) {
|
|
329
|
+
return (entry && entry.response && entry.response.payloadMode) ||
|
|
330
|
+
(entry && entry.lookup && entry.lookup.payloadMode) ||
|
|
331
|
+
(asset && asset.payloadMode) ||
|
|
332
|
+
'text-response-body';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function matchEntry(entries, request) {
|
|
336
|
+
const method = String(request.method || 'GET').toUpperCase();
|
|
337
|
+
const path = normalizeKey(request.path || '/');
|
|
338
|
+
const candidates = entries.filter((entry) => normalizeKey(entry.lookup.key) === path);
|
|
339
|
+
return candidates.find((entry) => String(entry.lookup.method || 'GET').toUpperCase() === method) ||
|
|
340
|
+
(method === 'HEAD' ? candidates.find((entry) => String(entry.lookup.method || 'GET').toUpperCase() === 'GET') : undefined) ||
|
|
341
|
+
candidates[0];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function normalizeFastlyPath(request) {
|
|
345
|
+
if (!request) return '/';
|
|
346
|
+
if (request.path || request.key) return normalizeKey(request.path || request.key);
|
|
347
|
+
if (request.url) {
|
|
348
|
+
try { return new URL(String(request.url), 'https://pulsewasm.fastly.local').pathname || '/'; } catch (_) { return normalizeKey(String(request.url || '/').split('?')[0]); }
|
|
349
|
+
}
|
|
350
|
+
return '/';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function streamBindingFor(entry, asset, sourceArtifacts) {
|
|
354
|
+
const body = bodyText(asset);
|
|
355
|
+
return {
|
|
356
|
+
kind: 'fastly-stream-binding-plan',
|
|
357
|
+
sourceArtifact: sourceArtifacts.fastlyStreamBindingPlan || null,
|
|
358
|
+
responseRef: `fastly-response-ref:${entry.id || normalizeKey(entry.lookup.key)}`,
|
|
359
|
+
streamRef: `fastly-stream-ref:${entry.id || normalizeKey(entry.lookup.key)}`,
|
|
360
|
+
chunks: [body],
|
|
361
|
+
hostOwnedBytes: true,
|
|
362
|
+
wasmOwnsBytes: false,
|
|
363
|
+
binaryBufferReserved: true
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function sourceArtifactVersions(input = {}) {
|
|
368
|
+
const versionOf = (value) => typeof value === 'string' ? value : value && value.artifact ? value.artifact.version : value && value.version;
|
|
369
|
+
return {
|
|
370
|
+
fastlyStreamBindingPlan: versionOf(input.fastlyStreamBindingPlan),
|
|
371
|
+
fastlyStreamHeaderAdapter: versionOf(input.fastlyStreamHeaderAdapter),
|
|
372
|
+
requestResultHeaders: versionOf(input.requestResultHeaders),
|
|
373
|
+
streamingPassthrough: versionOf(input.streamingPassthrough),
|
|
374
|
+
wasmHostAbi: versionOf(input.wasmHostAbi)
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function createFastlyAssetsProvider(options = {}) {
|
|
379
|
+
const plan = normalizePlan(options.plan || options.assetsLoweringPlan);
|
|
380
|
+
const diagnostics = [];
|
|
381
|
+
if (!plan) {
|
|
382
|
+
diagnostics.push(makeDiagnostic(
|
|
383
|
+
assetsContracts.ASSETS_DIAGNOSTIC_CODES.FASTLY_PROVIDER_PLAN_REQUIRED,
|
|
384
|
+
'Fastly assets provider proof requires an assets-lowering-plan artifact.',
|
|
385
|
+
'error',
|
|
386
|
+
{ hint: 'Emit --assets-lowering-plan before invoking the Fastly assets provider proof.' }
|
|
387
|
+
));
|
|
388
|
+
}
|
|
389
|
+
const entries = entriesFromPlan(plan);
|
|
390
|
+
const stores = normalizeStores(options.stores || createDefaultStoresFromPlan(plan));
|
|
391
|
+
const providerConfig = options.providerConfig || buildFastlyAssetsProviderConfig({ plan, resolvedConfig: options.resolvedConfig, mode: options.mode });
|
|
392
|
+
const sourceArtifacts = sourceArtifactVersions(options.sourceArtifacts || options);
|
|
393
|
+
|
|
394
|
+
function executeRequest(input = {}) {
|
|
395
|
+
const request = {
|
|
396
|
+
method: String(input.method || 'GET').toUpperCase(),
|
|
397
|
+
path: normalizeFastlyPath(input)
|
|
398
|
+
};
|
|
399
|
+
if (!assetsContracts.ASSETS_ALLOWED_METHODS.includes(request.method)) return unsupportedMethodResult(request.method);
|
|
400
|
+
const entry = matchEntry(entries, request);
|
|
401
|
+
if (!entry) return notFoundResult({ lookup: { key: request.path, store: '<none>', passThroughOn404: true } }, request);
|
|
402
|
+
const asset = resolveAsset(stores, entry.lookup.store, entry.lookup.key);
|
|
403
|
+
if (!asset) return notFoundResult(entry, request);
|
|
404
|
+
const payloadMode = payloadModeForEntry(entry, asset);
|
|
405
|
+
if (payloadMode === 'binary-buffer') return reservedBinaryResult(entry);
|
|
406
|
+
const mode = assetsContracts.assetsPayloadMode(payloadMode);
|
|
407
|
+
if (mode && mode.status === 'unsupported') return unsupportedPayloadResult(entry, payloadMode);
|
|
408
|
+
const headers = assetHeaders(entry, asset);
|
|
409
|
+
const status = responseStatus(entry, asset);
|
|
410
|
+
if (request.method === 'HEAD') {
|
|
411
|
+
return { status, kind: 'fastly-empty', headers, body: undefined, entryId: entry.id, lookup: clone(entry.lookup), diagnostics: [] };
|
|
412
|
+
}
|
|
413
|
+
if (payloadMode === 'stream-pass-through-response') {
|
|
414
|
+
const bodyStream = streamBindingFor(entry, asset, sourceArtifacts);
|
|
415
|
+
return {
|
|
416
|
+
status,
|
|
417
|
+
kind: 'fastly-stream',
|
|
418
|
+
headers,
|
|
419
|
+
body: bodyStream.chunks.join(''),
|
|
420
|
+
bodyStream,
|
|
421
|
+
responseRef: bodyStream.responseRef,
|
|
422
|
+
streamRef: bodyStream.streamRef,
|
|
423
|
+
entryId: entry.id,
|
|
424
|
+
lookup: clone(entry.lookup),
|
|
425
|
+
payloadMode,
|
|
426
|
+
fastlyStreamBindingPlanConnected: Boolean(sourceArtifacts.fastlyStreamBindingPlan),
|
|
427
|
+
diagnostics: []
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
status,
|
|
432
|
+
kind: 'fastly-response',
|
|
433
|
+
headers,
|
|
434
|
+
body: bodyText(asset),
|
|
435
|
+
entryId: entry.id,
|
|
436
|
+
lookup: clone(entry.lookup),
|
|
437
|
+
payloadMode,
|
|
438
|
+
fastlyResponsePath: true,
|
|
439
|
+
diagnostics: []
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
version: assetsContracts.ASSETS_FASTLY_PROVIDER_RUNTIME_VERSION,
|
|
445
|
+
provider: 'fastly',
|
|
446
|
+
mode: options.mode || 'provider-object-store',
|
|
447
|
+
plan,
|
|
448
|
+
entries: entries.map(clone),
|
|
449
|
+
providerConfig: clone(providerConfig),
|
|
450
|
+
sourceArtifacts,
|
|
451
|
+
diagnostics,
|
|
452
|
+
executeRequest,
|
|
453
|
+
lookup(store, key) { return resolveAsset(stores, store, key); },
|
|
454
|
+
stores
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
module.exports = {
|
|
459
|
+
createFastlyAssetsProvider,
|
|
460
|
+
createDefaultStoresFromPlan,
|
|
461
|
+
buildFastlyAssetsProviderConfig,
|
|
462
|
+
normalizeHeaders,
|
|
463
|
+
normalizeFastlyPath
|
|
464
|
+
};
|