@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,122 @@
1
+ 'use strict';
2
+
3
+ const runtimeHost = require('@pulse-compute/runtime/host');
4
+ const { fastlyJavascriptProviderError, isPulseRuntimeError } = require('./errors.js');
5
+
6
+ const FASTLY_JAVASCRIPT_ASSETS_ADAPTER_VERSION = 'pulse.fastly-javascript-assets-adapter.v1';
7
+
8
+ function contentTypeForKey(key) {
9
+ const extension = String(key).toLowerCase().split('.').pop();
10
+ return Object.freeze({
11
+ css: 'text/css; charset=utf-8',
12
+ html: 'text/html; charset=utf-8',
13
+ js: 'text/javascript; charset=utf-8',
14
+ json: 'application/json; charset=utf-8',
15
+ mjs: 'text/javascript; charset=utf-8',
16
+ svg: 'image/svg+xml',
17
+ txt: 'text/plain; charset=utf-8',
18
+ webp: 'image/webp',
19
+ png: 'image/png',
20
+ jpg: 'image/jpeg',
21
+ jpeg: 'image/jpeg',
22
+ gif: 'image/gif',
23
+ ico: 'image/x-icon'
24
+ })[extension] || 'application/octet-stream';
25
+ }
26
+
27
+ function responseHeaders(payload) {
28
+ const headers = new Headers(payload && payload.headers);
29
+ if (!headers.has('content-type')) headers.set('content-type', contentTypeForKey(payload && payload.key));
30
+ if (payload && payload.cacheControl !== undefined) headers.set('cache-control', String(payload.cacheControl));
31
+ return headers;
32
+ }
33
+
34
+ function createFastlyJavascriptAssetsLookup(options = {}) {
35
+ const apis = options.apis || {};
36
+ const bindings = options.bindings && typeof options.bindings === 'object' ? options.bindings : {};
37
+ const kvBindings = bindings.kv && typeof bindings.kv === 'object' ? bindings.kv : {};
38
+ const stores = new Map();
39
+
40
+ function storeFor(logicalStore) {
41
+ const store = typeof logicalStore === 'string' ? logicalStore.trim() : '';
42
+ const resource = store && typeof kvBindings[store] === 'string' ? kvBindings[store].trim() : '';
43
+ if (!store || !resource) {
44
+ throw fastlyJavascriptProviderError(
45
+ 'PULSE_FASTLY_ASSETS_BINDING_REQUIRED',
46
+ 'Fastly JavaScript Assets requires a logical store-to-KV mapping.',
47
+ { store, configuration: store ? `bindings.kv.${store}` : 'bindings.kv' }
48
+ );
49
+ }
50
+ if (!stores.has(resource)) {
51
+ if (typeof apis.KVStore !== 'function') {
52
+ throw fastlyJavascriptProviderError(
53
+ 'PULSE_FASTLY_JAVASCRIPT_API_UNAVAILABLE',
54
+ 'Fastly JavaScript Assets requires the KVStore API.',
55
+ { api: 'KVStore' }
56
+ );
57
+ }
58
+ try { stores.set(resource, new apis.KVStore(resource)); }
59
+ catch (error) {
60
+ throw fastlyJavascriptProviderError(
61
+ 'PULSE_FASTLY_ASSETS_STORE_OPEN_FAILED',
62
+ 'Fastly JavaScript could not open the configured Assets KV Store.',
63
+ { store, resource },
64
+ error
65
+ );
66
+ }
67
+ }
68
+ return Object.freeze({ store, resource, value: stores.get(resource) });
69
+ }
70
+
71
+ return async function fastlyJavascriptAssetsLookup(payload = {}) {
72
+ const opened = storeFor(payload.store);
73
+ const key = typeof payload.key === 'string' ? payload.key : '';
74
+ if (!key) {
75
+ throw fastlyJavascriptProviderError(
76
+ 'PULSE_FASTLY_ASSETS_KEY_INVALID',
77
+ 'Fastly JavaScript Assets requires a non-empty asset key.',
78
+ { store: opened.store }
79
+ );
80
+ }
81
+ let entry;
82
+ try { entry = await opened.value.get(key); }
83
+ catch (error) {
84
+ if (isPulseRuntimeError(error)) throw error;
85
+ throw fastlyJavascriptProviderError(
86
+ 'PULSE_FASTLY_ASSETS_LOOKUP_FAILED',
87
+ 'Fastly JavaScript Assets lookup failed.',
88
+ { store: opened.store, key },
89
+ error
90
+ );
91
+ }
92
+ if (entry === null || entry === undefined) {
93
+ return runtimeHost.markOpaqueResponse(new Response(null, {
94
+ status: 404,
95
+ headers: { 'content-type': 'text/plain; charset=utf-8' }
96
+ }));
97
+ }
98
+ if (!entry || !('body' in Object(entry))) {
99
+ throw fastlyJavascriptProviderError(
100
+ 'PULSE_FASTLY_ASSETS_ENTRY_INVALID',
101
+ 'Fastly JavaScript Assets received an invalid KV entry.',
102
+ { store: opened.store, key }
103
+ );
104
+ }
105
+ const body = entry.body;
106
+ const method = String(payload.method || 'GET').toUpperCase();
107
+ const headers = responseHeaders(payload);
108
+ if (method === 'HEAD') {
109
+ if (body && typeof body.cancel === 'function') {
110
+ try { await body.cancel(); } catch (_) { /* Best-effort host stream cleanup. */ }
111
+ }
112
+ return runtimeHost.markOpaqueResponse(new Response(null, { status: 200, headers }));
113
+ }
114
+ return runtimeHost.markOpaqueResponse(new Response(body, { status: 200, headers }));
115
+ };
116
+ }
117
+
118
+ module.exports = Object.freeze({
119
+ FASTLY_JAVASCRIPT_ASSETS_ADAPTER_VERSION,
120
+ contentTypeForKey,
121
+ createFastlyJavascriptAssetsLookup
122
+ });
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ const runtimeHost = require('@pulse-compute/runtime/host');
4
+ const { fastlyJavascriptProviderError, isPulseRuntimeError } = require('./errors.js');
5
+
6
+ const FASTLY_JAVASCRIPT_BINDINGS_ADAPTER_VERSION = 'pulse.fastly-javascript-bindings-adapter.v1';
7
+
8
+ function requireConstructor(apis, name) {
9
+ const value = apis && apis[name];
10
+ if (typeof value !== 'function') {
11
+ throw fastlyJavascriptProviderError(
12
+ 'PULSE_FASTLY_JAVASCRIPT_API_UNAVAILABLE',
13
+ `Fastly JavaScript ${name} capability is unavailable.`,
14
+ { api: name }
15
+ );
16
+ }
17
+ return value;
18
+ }
19
+
20
+ function requireResourceName(value, field) {
21
+ const normalized = typeof value === 'string' ? value.trim() : '';
22
+ if (!normalized) {
23
+ throw fastlyJavascriptProviderError(
24
+ 'PULSE_FASTLY_JAVASCRIPT_BINDING_REQUIRED',
25
+ `Fastly JavaScript ${field} must name a configured resource.`,
26
+ { configuration: field }
27
+ );
28
+ }
29
+ return normalized;
30
+ }
31
+
32
+ function providerFailure(error, code, message, detail) {
33
+ if (isPulseRuntimeError(error)) throw error;
34
+ throw fastlyJavascriptProviderError(code, message, detail, error);
35
+ }
36
+
37
+ function createFastlyJavascriptBindingCapabilities(options = {}) {
38
+ const apis = options.apis || {};
39
+ const bindings = options.bindings && typeof options.bindings === 'object' ? options.bindings : {};
40
+ const kvBindings = bindings.kv && typeof bindings.kv === 'object' ? bindings.kv : {};
41
+ const limits = {
42
+ maxBindingNameBytes: options.maxBindingNameBytes,
43
+ maxBindingValueBytes: options.maxBindingValueBytes,
44
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
45
+ maxKvKeyBytes: options.maxKvKeyBytes,
46
+ maxKvValueBytes: options.maxKvValueBytes,
47
+ maxKvValueDepth: options.maxKvValueDepth,
48
+ maxKvValueEntries: options.maxKvValueEntries
49
+ };
50
+ let configStore;
51
+ let secretStore;
52
+ const kvStores = new Map();
53
+
54
+ function openConfigStore() {
55
+ if (!configStore) {
56
+ const ConfigStore = requireConstructor(apis, 'ConfigStore');
57
+ const resource = requireResourceName(bindings.configStore, 'bindings.configStore');
58
+ try { configStore = new ConfigStore(resource); }
59
+ catch (error) {
60
+ providerFailure(
61
+ error,
62
+ 'PULSE_FASTLY_CONFIG_STORE_OPEN_FAILED',
63
+ 'Fastly JavaScript could not open the configured Config Store.',
64
+ { configuration: 'bindings.configStore', resource }
65
+ );
66
+ }
67
+ }
68
+ return configStore;
69
+ }
70
+
71
+ function openSecretStore() {
72
+ if (!secretStore) {
73
+ const SecretStore = requireConstructor(apis, 'SecretStore');
74
+ const resource = requireResourceName(bindings.secretStore, 'bindings.secretStore');
75
+ try { secretStore = new SecretStore(resource); }
76
+ catch (error) {
77
+ providerFailure(
78
+ error,
79
+ 'PULSE_FASTLY_SECRET_STORE_OPEN_FAILED',
80
+ 'Fastly JavaScript could not open the configured Secret Store.',
81
+ { configuration: 'bindings.secretStore', resource }
82
+ );
83
+ }
84
+ }
85
+ return secretStore;
86
+ }
87
+
88
+ function physicalKvStore(namespaceInput) {
89
+ const namespace = runtimeHost.normalizeKvNamespace(namespaceInput, limits);
90
+ const resource = typeof kvBindings[namespace] === 'string' ? kvBindings[namespace].trim() : '';
91
+ if (!resource) {
92
+ throw fastlyJavascriptProviderError(
93
+ 'PULSE_FASTLY_KV_BINDING_REQUIRED',
94
+ 'Fastly JavaScript KV requires a logical namespace mapping.',
95
+ { namespace, configuration: `bindings.kv.${namespace}` }
96
+ );
97
+ }
98
+ if (!kvStores.has(resource)) {
99
+ const KVStore = requireConstructor(apis, 'KVStore');
100
+ try { kvStores.set(resource, new KVStore(resource)); }
101
+ catch (error) {
102
+ providerFailure(
103
+ error,
104
+ 'PULSE_FASTLY_KV_STORE_OPEN_FAILED',
105
+ 'Fastly JavaScript could not open a configured KV Store.',
106
+ { namespace, resource }
107
+ );
108
+ }
109
+ }
110
+ return Object.freeze({ namespace, resource, store: kvStores.get(resource) });
111
+ }
112
+
113
+ return Object.freeze({
114
+ version: FASTLY_JAVASCRIPT_BINDINGS_ADAPTER_VERSION,
115
+ config(nameInput) {
116
+ const name = runtimeHost.normalizeBindingName('config', nameInput, limits);
117
+ try {
118
+ const value = openConfigStore().get(name);
119
+ return value === null ? undefined : value;
120
+ } catch (error) {
121
+ providerFailure(
122
+ error,
123
+ 'PULSE_FASTLY_CONFIG_GET_FAILED',
124
+ 'Fastly JavaScript Config Store lookup failed.',
125
+ { name }
126
+ );
127
+ }
128
+ },
129
+ async secret(nameInput) {
130
+ const name = runtimeHost.normalizeBindingName('secret', nameInput, limits);
131
+ let entry;
132
+ try { entry = await openSecretStore().get(name); }
133
+ catch (error) {
134
+ providerFailure(
135
+ error,
136
+ 'PULSE_FASTLY_SECRET_GET_FAILED',
137
+ 'Fastly JavaScript Secret Store lookup failed.',
138
+ { name }
139
+ );
140
+ }
141
+ if (entry === null || entry === undefined) return undefined;
142
+ try {
143
+ if (!entry || typeof entry.plaintext !== 'function') {
144
+ throw fastlyJavascriptProviderError(
145
+ 'PULSE_FASTLY_SECRET_ENTRY_INVALID',
146
+ 'Fastly JavaScript Secret Store returned an invalid entry.',
147
+ { name }
148
+ );
149
+ }
150
+ return entry.plaintext();
151
+ } catch (error) {
152
+ providerFailure(
153
+ error,
154
+ 'PULSE_FASTLY_SECRET_PLAINTEXT_FAILED',
155
+ 'Fastly JavaScript could not read a Secret Store entry.',
156
+ { name }
157
+ );
158
+ }
159
+ },
160
+ kv(namespaceInput) {
161
+ const opened = physicalKvStore(namespaceInput);
162
+ return Object.freeze({
163
+ async get(keyInput) {
164
+ const key = runtimeHost.normalizeKvKey(keyInput, limits);
165
+ let entry;
166
+ try { entry = await opened.store.get(key); }
167
+ catch (error) {
168
+ providerFailure(
169
+ error,
170
+ 'PULSE_FASTLY_KV_GET_FAILED',
171
+ 'Fastly JavaScript KV lookup failed.',
172
+ { namespace: opened.namespace, key }
173
+ );
174
+ }
175
+ if (entry === null || entry === undefined) return undefined;
176
+ try {
177
+ if (!entry || typeof entry.json !== 'function') {
178
+ throw fastlyJavascriptProviderError(
179
+ 'PULSE_FASTLY_KV_ENTRY_INVALID',
180
+ 'Fastly JavaScript KV returned an invalid entry.',
181
+ { namespace: opened.namespace, key }
182
+ );
183
+ }
184
+ return await entry.json();
185
+ } catch (error) {
186
+ providerFailure(
187
+ error,
188
+ 'PULSE_FASTLY_KV_VALUE_INVALID',
189
+ 'Fastly JavaScript KV value is not valid Pulse JSON data.',
190
+ { namespace: opened.namespace, key }
191
+ );
192
+ }
193
+ },
194
+ async put(keyInput, value) {
195
+ const key = runtimeHost.normalizeKvKey(keyInput, limits);
196
+ let body;
197
+ try { body = JSON.stringify(value); }
198
+ catch (error) {
199
+ providerFailure(
200
+ error,
201
+ 'PULSE_FASTLY_KV_VALUE_INVALID',
202
+ 'Fastly JavaScript KV value is not valid Pulse JSON data.',
203
+ { namespace: opened.namespace, key }
204
+ );
205
+ }
206
+ try { await opened.store.put(key, body); }
207
+ catch (error) {
208
+ providerFailure(
209
+ error,
210
+ 'PULSE_FASTLY_KV_PUT_FAILED',
211
+ 'Fastly JavaScript KV write failed.',
212
+ { namespace: opened.namespace, key }
213
+ );
214
+ }
215
+ return true;
216
+ }
217
+ });
218
+ }
219
+ });
220
+ }
221
+
222
+ function withFastlyBindingCapabilities(capabilities, options = {}) {
223
+ const base = capabilities && typeof capabilities === 'object' ? capabilities : {};
224
+ const configured = options.bindingCapabilities || createFastlyJavascriptBindingCapabilities(options);
225
+ return Object.freeze({
226
+ ...base,
227
+ ...(typeof base.config === 'function' ? {} : { config: configured.config }),
228
+ ...(typeof base.secret === 'function' ? {} : { secret: configured.secret }),
229
+ ...(typeof base.kv === 'function' ? {} : { kv: configured.kv })
230
+ });
231
+ }
232
+
233
+ module.exports = Object.freeze({
234
+ FASTLY_JAVASCRIPT_BINDINGS_ADAPTER_VERSION,
235
+ createFastlyJavascriptBindingCapabilities,
236
+ withFastlyBindingCapabilities
237
+ });
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const runtimeHost = require('@pulse-compute/runtime/host');
4
+
5
+ const FASTLY_JAVASCRIPT_PROVIDER_ERROR_VERSION = 'pulse.fastly-javascript-provider-error.v1';
6
+
7
+ function fastlyJavascriptProviderError(code, message, detail = {}, cause) {
8
+ const safeDetail = {
9
+ ...detail,
10
+ ...(cause && typeof cause.name === 'string' ? { causeName: cause.name } : {})
11
+ };
12
+ return new runtimeHost.PulseRuntimeContractError(code, message, {
13
+ detail: Object.freeze(safeDetail)
14
+ });
15
+ }
16
+
17
+ function isPulseRuntimeError(error) {
18
+ return error instanceof runtimeHost.PulseRuntimeContractError
19
+ || Boolean(error && typeof error.code === 'string' && error.code.startsWith('PULSE_'));
20
+ }
21
+
22
+ module.exports = Object.freeze({
23
+ FASTLY_JAVASCRIPT_PROVIDER_ERROR_VERSION,
24
+ fastlyJavascriptProviderError,
25
+ isPulseRuntimeError
26
+ });
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const runtimeHost = require('@pulse-compute/runtime/host');
4
+ const { fastlyJavascriptProviderError, isPulseRuntimeError } = require('./errors.js');
5
+
6
+ const FASTLY_JAVASCRIPT_FETCH_ADAPTER_VERSION = 'pulse.fastly-javascript-fetch-adapter.v1';
7
+
8
+ function normalizeOrigin(url) {
9
+ try {
10
+ const parsed = new URL(String(url));
11
+ if (!['http:', 'https:'].includes(parsed.protocol)) throw new TypeError('unsupported protocol');
12
+ return parsed.origin;
13
+ } catch (error) {
14
+ throw fastlyJavascriptProviderError(
15
+ 'PULSE_FASTLY_FETCH_URL_INVALID',
16
+ 'Fastly JavaScript fetch requires an absolute HTTP or HTTPS URL.',
17
+ { url: String(url) },
18
+ error
19
+ );
20
+ }
21
+ }
22
+
23
+ function createFastlyJavascriptFetchCapability(options = {}) {
24
+ const implementation = options.fetchImplementation;
25
+ const bindings = options.bindings && typeof options.bindings === 'object' ? options.bindings : {};
26
+ const backends = bindings.backends && typeof bindings.backends === 'object' ? bindings.backends : {};
27
+ const dynamicBackends = bindings.dynamicBackends === true;
28
+
29
+ return async function fastlyJavascriptFetch(url, init = {}, execution = {}) {
30
+ if (typeof implementation !== 'function') {
31
+ throw fastlyJavascriptProviderError(
32
+ 'PULSE_FASTLY_FETCH_IMPLEMENTATION_UNAVAILABLE',
33
+ 'Fastly JavaScript fetch is unavailable.',
34
+ { capability: 'fetch' }
35
+ );
36
+ }
37
+ const origin = normalizeOrigin(url);
38
+ const configuredBackend = typeof backends[origin] === 'string' ? backends[origin].trim() : '';
39
+ if (!configuredBackend && !dynamicBackends) {
40
+ throw fastlyJavascriptProviderError(
41
+ 'PULSE_FASTLY_FETCH_BACKEND_REQUIRED',
42
+ 'Fastly JavaScript fetch requires an origin-to-backend mapping unless dynamic backends are enabled.',
43
+ { origin, configuration: `bindings.backends.${origin}`, dynamicBackends: false }
44
+ );
45
+ }
46
+
47
+ const requestInit = {
48
+ method: init.method,
49
+ headers: init.headers,
50
+ body: init.body,
51
+ signal: execution.signal || init.signal,
52
+ redirect: 'follow',
53
+ ...(configuredBackend ? { backend: configuredBackend } : {})
54
+ };
55
+ try {
56
+ const response = await implementation(String(url), requestInit);
57
+ if (!(response instanceof Response) && !runtimeHost.isPulseFetchResponse(response)) {
58
+ throw fastlyJavascriptProviderError(
59
+ 'PULSE_FASTLY_FETCH_RESPONSE_INVALID',
60
+ 'Fastly JavaScript fetch must resolve to a Web Response.',
61
+ { origin, type: typeof response }
62
+ );
63
+ }
64
+ return response;
65
+ } catch (error) {
66
+ if (isPulseRuntimeError(error)) throw error;
67
+ if (requestInit.signal && requestInit.signal.aborted) {
68
+ const reason = requestInit.signal.reason;
69
+ if (reason instanceof Error) throw reason;
70
+ }
71
+ throw fastlyJavascriptProviderError(
72
+ 'PULSE_FASTLY_FETCH_NETWORK',
73
+ 'Fastly JavaScript fetch failed.',
74
+ { origin, method: String(init.method || 'GET').toUpperCase() },
75
+ error
76
+ );
77
+ }
78
+ };
79
+ }
80
+
81
+ function withFastlyFetchCapability(capabilities, options = {}) {
82
+ const base = capabilities && typeof capabilities === 'object' ? capabilities : {};
83
+ return Object.freeze({
84
+ ...base,
85
+ ...(typeof base.fetch === 'function' ? {} : { fetch: createFastlyJavascriptFetchCapability(options) })
86
+ });
87
+ }
88
+
89
+ module.exports = Object.freeze({
90
+ FASTLY_JAVASCRIPT_FETCH_ADAPTER_VERSION,
91
+ createFastlyJavascriptFetchCapability,
92
+ withFastlyFetchCapability
93
+ });