@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,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Fixed-memory AssemblyScript runtime for guest-linked Fastly Native modules.
|
|
3
|
+
*
|
|
4
|
+
* The ES256 guest contract reserves [0, 262144) for Rust, [262144, 524288)
|
|
5
|
+
* for primary static data, and [524288, 540928) for the private invocation
|
|
6
|
+
* frame. Dynamic primary allocations use [540928, 2097152). The allocator
|
|
7
|
+
* reclaims and coalesces blocks and asks the mark/sweep runtime to collect
|
|
8
|
+
* before reporting exhaustion. It contains no memory.grow instruction.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
AL_MASK,
|
|
13
|
+
BLOCK_MAXSIZE,
|
|
14
|
+
BLOCK_OVERHEAD,
|
|
15
|
+
} from "~lib/rt/common"
|
|
16
|
+
import "~lib/rt/itcms"
|
|
17
|
+
|
|
18
|
+
const PRIMARY_HEAP_START: usize = 540928
|
|
19
|
+
const PRIMARY_HEAP_END: usize = 2097152
|
|
20
|
+
const FREE: usize = 1
|
|
21
|
+
const MIN_PAYLOAD: usize =
|
|
22
|
+
((1 + BLOCK_OVERHEAD + AL_MASK) & ~AL_MASK) - BLOCK_OVERHEAD
|
|
23
|
+
|
|
24
|
+
@lazy let startOffset: usize =
|
|
25
|
+
((PRIMARY_HEAP_START + BLOCK_OVERHEAD + AL_MASK) & ~AL_MASK) - BLOCK_OVERHEAD
|
|
26
|
+
@lazy let endOffset: usize = startOffset
|
|
27
|
+
@lazy let collecting: bool = false
|
|
28
|
+
|
|
29
|
+
@inline function computeSize(size: usize): usize {
|
|
30
|
+
return ((size + BLOCK_OVERHEAD + AL_MASK) & ~AL_MASK) - BLOCK_OVERHEAD
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@inline function blockSize(block: usize): usize {
|
|
34
|
+
return load<usize>(block) & ~FREE
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@inline function blockIsFree(block: usize): bool {
|
|
38
|
+
return (load<usize>(block) & FREE) != 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function allocateFromFree(block: usize, actualSize: usize, size: usize): usize {
|
|
42
|
+
const remaining = actualSize - size
|
|
43
|
+
if (remaining >= BLOCK_OVERHEAD + MIN_PAYLOAD) {
|
|
44
|
+
const next = block + BLOCK_OVERHEAD + size
|
|
45
|
+
store<usize>(next, (remaining - BLOCK_OVERHEAD) | FREE)
|
|
46
|
+
store<usize>(block, size)
|
|
47
|
+
} else {
|
|
48
|
+
store<usize>(block, actualSize)
|
|
49
|
+
}
|
|
50
|
+
return block + BLOCK_OVERHEAD
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tryAllocate(size: usize): usize {
|
|
54
|
+
let cursor = startOffset
|
|
55
|
+
while (cursor < endOffset) {
|
|
56
|
+
const actualSize = blockSize(cursor)
|
|
57
|
+
if (blockIsFree(cursor) && actualSize >= size) {
|
|
58
|
+
return allocateFromFree(cursor, actualSize, size)
|
|
59
|
+
}
|
|
60
|
+
cursor += BLOCK_OVERHEAD + actualSize
|
|
61
|
+
}
|
|
62
|
+
const nextEnd = endOffset + BLOCK_OVERHEAD + size
|
|
63
|
+
if (nextEnd > PRIMARY_HEAP_END) return 0
|
|
64
|
+
const block = endOffset
|
|
65
|
+
endOffset = nextEnd
|
|
66
|
+
store<usize>(block, size)
|
|
67
|
+
return block + BLOCK_OVERHEAD
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function coalesce(): void {
|
|
71
|
+
let cursor = startOffset
|
|
72
|
+
while (cursor < endOffset) {
|
|
73
|
+
let actualSize = blockSize(cursor)
|
|
74
|
+
let next = cursor + BLOCK_OVERHEAD + actualSize
|
|
75
|
+
if (blockIsFree(cursor)) {
|
|
76
|
+
while (next < endOffset && blockIsFree(next)) {
|
|
77
|
+
actualSize += BLOCK_OVERHEAD + blockSize(next)
|
|
78
|
+
store<usize>(cursor, actualSize | FREE)
|
|
79
|
+
next = cursor + BLOCK_OVERHEAD + actualSize
|
|
80
|
+
}
|
|
81
|
+
if (next == endOffset) {
|
|
82
|
+
endOffset = cursor
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
cursor = next
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@unsafe @global
|
|
91
|
+
export function __alloc(size: usize): usize {
|
|
92
|
+
if (size > BLOCK_MAXSIZE) unreachable()
|
|
93
|
+
const payloadSize = computeSize(size)
|
|
94
|
+
let pointer = tryAllocate(payloadSize)
|
|
95
|
+
if (pointer == 0 && !collecting) {
|
|
96
|
+
collecting = true
|
|
97
|
+
__collect()
|
|
98
|
+
collecting = false
|
|
99
|
+
coalesce()
|
|
100
|
+
pointer = tryAllocate(payloadSize)
|
|
101
|
+
}
|
|
102
|
+
if (pointer == 0) unreachable()
|
|
103
|
+
return pointer
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@unsafe @global
|
|
107
|
+
export function __realloc(pointer: usize, size: usize): usize {
|
|
108
|
+
if (pointer == 0 || (pointer & AL_MASK) != 0) unreachable()
|
|
109
|
+
const block = pointer - BLOCK_OVERHEAD
|
|
110
|
+
const actualSize = blockSize(block)
|
|
111
|
+
const payloadSize = computeSize(size)
|
|
112
|
+
if (payloadSize <= actualSize) {
|
|
113
|
+
const remaining = actualSize - payloadSize
|
|
114
|
+
if (remaining >= BLOCK_OVERHEAD + MIN_PAYLOAD) {
|
|
115
|
+
const next = block + BLOCK_OVERHEAD + payloadSize
|
|
116
|
+
store<usize>(block, payloadSize)
|
|
117
|
+
store<usize>(next, (remaining - BLOCK_OVERHEAD) | FREE)
|
|
118
|
+
coalesce()
|
|
119
|
+
}
|
|
120
|
+
return pointer
|
|
121
|
+
}
|
|
122
|
+
const right = block + BLOCK_OVERHEAD + actualSize
|
|
123
|
+
if (right < endOffset && blockIsFree(right)) {
|
|
124
|
+
const combined = actualSize + BLOCK_OVERHEAD + blockSize(right)
|
|
125
|
+
if (combined >= payloadSize) {
|
|
126
|
+
return allocateFromFree(block, combined, payloadSize)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const newPointer = __alloc(max<usize>(payloadSize, actualSize << 1))
|
|
130
|
+
for (let index: usize = 0; index < actualSize; index += 1) {
|
|
131
|
+
store<u8>(newPointer + index, load<u8>(pointer + index))
|
|
132
|
+
}
|
|
133
|
+
__free(pointer)
|
|
134
|
+
return newPointer
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@unsafe @global
|
|
138
|
+
export function __free(pointer: usize): void {
|
|
139
|
+
if (pointer == 0 || (pointer & AL_MASK) != 0) unreachable()
|
|
140
|
+
const block = pointer - BLOCK_OVERHEAD
|
|
141
|
+
store<usize>(block, blockSize(block) | FREE)
|
|
142
|
+
coalesce()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@unsafe @global
|
|
146
|
+
export function __reset(): void {
|
|
147
|
+
endOffset = startOffset
|
|
148
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Fastly Native realization of the runtime-owned K1/K2 conditional KV contract.
|
|
2
|
+
// Limits are generated from runtime/host. No SDK or unconditional CAS fallback.
|
|
3
|
+
@external('fastly_async_io', 'select') declare function __kv_select(handles: usize, count: i32, timeout: i32, ready: usize): i32
|
|
4
|
+
@external('fastly_http_body', 'close') declare function __kv_close(body: i32): i32
|
|
5
|
+
|
|
6
|
+
@lazy const __kv_deadline = new StaticArray<i64>(PULSE_FASTLY_EFFECT_COUNT)
|
|
7
|
+
@lazy const __kv_dispatched = new StaticArray<bool>(PULSE_FASTLY_EFFECT_COUNT)
|
|
8
|
+
let __kv_request_deadline: i64 = -1;
|
|
9
|
+
// Host embedding seam, in monotonic nanoseconds; never exposed through ctx.
|
|
10
|
+
export function pulse_fastly_kv_request_deadline(deadline: i64): void { __kv_request_deadline = deadline; }
|
|
11
|
+
function __kv_clock(): i64 {
|
|
12
|
+
const out = new StaticArray<i64>(1);
|
|
13
|
+
return wasi_snapshot_preview1_clock_time_get(1, 1, changetype<usize>(out)) == 0 ? out[0] : -1;
|
|
14
|
+
}
|
|
15
|
+
function __kv_remaining(index: i32): i32 {
|
|
16
|
+
const now = __kv_clock();
|
|
17
|
+
if (now < 0) return -1;
|
|
18
|
+
const remaining = __kv_deadline[index] - now;
|
|
19
|
+
return remaining <= 0 ? 0 : i32((remaining + 999999) / 1000000);
|
|
20
|
+
}
|
|
21
|
+
function __kv_set(result: i32, key: string, value: i32): void { host_value_object_set(result, __pulse_fastly_string_value(key), value); }
|
|
22
|
+
function __kv_result(status: string, reason: string = ''): i32 {
|
|
23
|
+
const result = host_value_object(); __kv_set(result, 'status', __pulse_fastly_string_value(status));
|
|
24
|
+
if (reason.length) __kv_set(result, 'reason', __pulse_fastly_string_value(reason));
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function __kv_failure(index: i32, reason: string): i32 {
|
|
28
|
+
return __kv_result(__pulse_fastly_effect_kind(index) == 15 ? 'failed' : __kv_dispatched[index] ? 'unknown' : 'not-stored', reason);
|
|
29
|
+
}
|
|
30
|
+
function __kv_fail(index: i32, reason: string): void { __pulse_fastly_ready_effect(index, __kv_failure(index, reason)); }
|
|
31
|
+
function __kv_expired(index: i32): bool {
|
|
32
|
+
const remaining = __kv_remaining(index);
|
|
33
|
+
if (remaining > 0) return false;
|
|
34
|
+
__kv_fail(index, remaining == 0 ? 'timeout' : 'unavailable'); return true;
|
|
35
|
+
}
|
|
36
|
+
// select readiness guarantees the corresponding I/O action will not block.
|
|
37
|
+
// Zero timeout means infinity in this ABI, so it must never be passed.
|
|
38
|
+
function __kv_ready(index: i32, handle: i32): i32 {
|
|
39
|
+
const remaining = __kv_remaining(index); if (remaining <= 0) return remaining;
|
|
40
|
+
const handles = new StaticArray<i32>(1), out = new StaticArray<i32>(1);
|
|
41
|
+
handles[0] = handle; out[0] = -1;
|
|
42
|
+
if (__kv_select(changetype<usize>(handles), 1, remaining, changetype<usize>(out)) != 0) return -1;
|
|
43
|
+
const after = __kv_remaining(index); if (after <= 0) return after;
|
|
44
|
+
return out[0] == 0 ? 1 : out[0] == -1 ? 0 : -2;
|
|
45
|
+
}
|
|
46
|
+
function __kv_ready_failure(index: i32, ready: i32): i32 { return __kv_failure(index, ready == 0 ? 'timeout' : ready == -2 ? 'protocol' : 'transport'); }
|
|
47
|
+
function __kv_key(text: string): bool {
|
|
48
|
+
if (text == ' ' || text == '\ufffe' || text == '\uffff') return false;
|
|
49
|
+
if (!text.length || text.length > __KV_keyBytes || String.UTF8.byteLength(text) > __KV_keyBytes) return false;
|
|
50
|
+
for (let i = 0; i < text.length; i++) {
|
|
51
|
+
const c = text.charCodeAt(i);
|
|
52
|
+
if (c < 32 || (c >= 127 && c <= 159)) return false;
|
|
53
|
+
if (c >= 0xd800 && c <= 0xdbff) { if (++i >= text.length) return false; const next = text.charCodeAt(i); if (next < 0xdc00 || next > 0xdfff) return false; }
|
|
54
|
+
else if (c >= 0xdc00 && c <= 0xdfff) return false;
|
|
55
|
+
}
|
|
56
|
+
// Fastly's exact-key restrictions; never normalize an application key.
|
|
57
|
+
return text != '.' && text != '..' && !text.startsWith('.well-known/acme-challenge')
|
|
58
|
+
&& text.indexOf('./') < 0 && !text.endsWith('/.') && !text.endsWith('/..')
|
|
59
|
+
&& text.indexOf('#') < 0 && text.indexOf('?') < 0 && text.indexOf('^') < 0
|
|
60
|
+
&& text.indexOf('|') < 0 && text.indexOf(';') < 0 && text.indexOf('\n') < 0 && text.indexOf('\r') < 0;
|
|
61
|
+
}
|
|
62
|
+
function __kv_token_valid(token: string): bool {
|
|
63
|
+
if (token.length != 29 || !token.startsWith('fastly-kv-v1:')) return false;
|
|
64
|
+
for (let i = 13; i < 29; i++) { const c = token.charCodeAt(i); if (!(c >= 48 && c <= 57) && !(c >= 97 && c <= 102)) return false; }
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
function __kv_generation(token: string): u64 {
|
|
68
|
+
let value: u64 = 0;
|
|
69
|
+
for (let i = 13; i < 29; i++) { const c = token.charCodeAt(i); value = (value << 4) | u64(c <= 57 ? c - 48 : c - 87); }
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function __kv_token(value: u64): string {
|
|
73
|
+
let text = 'fastly-kv-v1:';
|
|
74
|
+
for (let i = 15; i >= 0; i--) text += '0123456789abcdef'.charAt(i32((value >> (i * 4)) & 15));
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
function __kv_number(value: f64): string {
|
|
78
|
+
const text = __pulse_fastly_number_string(value);
|
|
79
|
+
// AS prints a trailing .0 for large integral f64s; JSON.stringify does not.
|
|
80
|
+
// Count the portable serialized value budget, including this boundary case.
|
|
81
|
+
return text.endsWith('.0') ? text.substring(0, text.length - 2) : text;
|
|
82
|
+
}
|
|
83
|
+
function __kv_private(text: string): void {
|
|
84
|
+
__pulse_fastly_remember_secret(text);
|
|
85
|
+
const encoded = __pulse_fastly_quote(text);
|
|
86
|
+
__pulse_fastly_remember_secret(encoded.substring(1, encoded.length - 1));
|
|
87
|
+
}
|
|
88
|
+
class __KvEncoder {
|
|
89
|
+
reason: string = ''; entries: i32 = 0; bytes: i32 = 0;
|
|
90
|
+
seen: Set<i32> = new Set<i32>(); parts: Array<string> = new Array<string>();
|
|
91
|
+
add(text: string): void { this.bytes += String.UTF8.byteLength(text); if (this.bytes > __KV_valueBytes) this.reason = 'too-large'; else this.parts.push(text); }
|
|
92
|
+
visit(handle: i32, depth: i32 = 0): void {
|
|
93
|
+
if (this.reason.length) return;
|
|
94
|
+
if (++this.entries > __KV_entries || depth > __KV_depth) { this.reason = 'too-large'; return; }
|
|
95
|
+
const value = __pulse_fastly_value(handle);
|
|
96
|
+
if (value.kind == PULSE_VALUE_NULL) this.add('null');
|
|
97
|
+
else if (value.kind == PULSE_VALUE_BOOLEAN) this.add(value.boolean ? 'true' : 'false');
|
|
98
|
+
else if (value.kind == PULSE_VALUE_STRING) { __kv_private(value.text); this.add(__pulse_fastly_quote(value.text)); }
|
|
99
|
+
else if (value.kind == PULSE_VALUE_NUMBER) { if (!isFinite(value.number)) this.reason = 'invalid-value'; else this.add(value.number == 0 ? '0' : __kv_number(value.number)); }
|
|
100
|
+
else if (value.kind == PULSE_VALUE_ARRAY || value.kind == PULSE_VALUE_OBJECT) {
|
|
101
|
+
if (this.seen.has(handle)) { this.reason = 'invalid-value'; return; } this.seen.add(handle);
|
|
102
|
+
const object = value.kind == PULSE_VALUE_OBJECT; this.add(object ? '{' : '[');
|
|
103
|
+
for (let i = 0; i < value.values.length && !this.reason.length; i++) {
|
|
104
|
+
if (i) this.add(',');
|
|
105
|
+
if (object) { __kv_private(value.keys[i]); this.add(__pulse_fastly_quote(value.keys[i]) + ':'); }
|
|
106
|
+
this.visit(value.values[i], depth + 1);
|
|
107
|
+
}
|
|
108
|
+
this.add(object ? '}' : ']');
|
|
109
|
+
} else this.reason = 'invalid-value';
|
|
110
|
+
}
|
|
111
|
+
encode(handle: i32): string { this.visit(handle); return this.reason.length ? '' : this.parts.join(''); }
|
|
112
|
+
}
|
|
113
|
+
function __pulse_fastly_kv_conditional_begin(index: i32, payload: __PulseFastlyValue): void {
|
|
114
|
+
__kv_dispatched[index] = false;
|
|
115
|
+
const start = __kv_clock(); if (start < 0) { __kv_fail(index, 'unavailable'); return; }
|
|
116
|
+
__kv_deadline[index] = start + i64(__KV_timeoutMs) * 1000000;
|
|
117
|
+
if (__kv_request_deadline >= 0 && __kv_request_deadline < __kv_deadline[index]) __kv_deadline[index] = __kv_request_deadline;
|
|
118
|
+
const kind = __pulse_fastly_effect_kind(index), key = __pulse_fastly_value(__pulse_fastly_payload_field(payload, 'key'));
|
|
119
|
+
if (key.kind != PULSE_VALUE_STRING || !__kv_key(key.text)) { __kv_fail(index, 'invalid-key'); return; }
|
|
120
|
+
__kv_private(key.text);
|
|
121
|
+
let generation: u64 = 0;
|
|
122
|
+
if (kind == 17) {
|
|
123
|
+
const token = __pulse_fastly_value(__pulse_fastly_payload_field(payload, 'generation'));
|
|
124
|
+
if (token.kind != PULSE_VALUE_STRING || !__kv_token_valid(token.text)) { __kv_fail(index, 'invalid-generation'); return; }
|
|
125
|
+
__kv_private(token.text); generation = __kv_generation(token.text);
|
|
126
|
+
}
|
|
127
|
+
let wire = '';
|
|
128
|
+
if (kind != 15) {
|
|
129
|
+
const encoder = new __KvEncoder(); const json = encoder.encode(__pulse_fastly_payload_field(payload, 'value'));
|
|
130
|
+
if (encoder.reason.length) { __kv_fail(index, encoder.reason); return; }
|
|
131
|
+
// Immutable string snapshot exists before any binding/body/dispatch hostcall.
|
|
132
|
+
wire = '{"__pulseKv":1,"value":' + json + '}';
|
|
133
|
+
if (String.UTF8.byteLength(wire) > __KV_wireBytes) { __kv_fail(index, 'too-large'); return; }
|
|
134
|
+
}
|
|
135
|
+
if (__kv_expired(index)) return;
|
|
136
|
+
const name = __pulse_fastly_utf8(__pulse_fastly_kv_store(index)), storeHandle = new StaticArray<i32>(1);
|
|
137
|
+
if (fastly_kv_store_open(changetype<usize>(name), name.byteLength, changetype<usize>(storeHandle)) != 0) { __kv_fail(index, 'configuration'); return; }
|
|
138
|
+
if (__kv_expired(index)) return;
|
|
139
|
+
const keyBytes = __pulse_fastly_utf8(key.text), pending = new StaticArray<i32>(1); let status: i32;
|
|
140
|
+
if (kind == 15) {
|
|
141
|
+
const config = new StaticArray<i32>(1);
|
|
142
|
+
status = fastly_kv_store_lookup(storeHandle[0], changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(pending));
|
|
143
|
+
} else {
|
|
144
|
+
const body = new StaticArray<i32>(1), written = new StaticArray<i32>(1);
|
|
145
|
+
if (fastly_http_body_new(changetype<usize>(body)) != 0) { __kv_fail(index, 'transport'); return; }
|
|
146
|
+
if (__kv_expired(index)) { __kv_close(body[0]); return; }
|
|
147
|
+
const bytes = __pulse_fastly_utf8(wire);
|
|
148
|
+
status = fastly_http_body_write(body[0], changetype<usize>(bytes), bytes.byteLength, 0, changetype<usize>(written));
|
|
149
|
+
if (status != 0 || written[0] != bytes.byteLength) { __kv_close(body[0]); __kv_fail(index, status != 0 ? 'transport' : 'protocol'); return; }
|
|
150
|
+
if (__kv_expired(index)) { __kv_close(body[0]); return; }
|
|
151
|
+
const config = new StaticArray<u64>(4); // 32 zeroed bytes, including padding.
|
|
152
|
+
if (kind == 16) store<u32>(changetype<usize>(config), 1);
|
|
153
|
+
else store<u64>(changetype<usize>(config) + 24, generation);
|
|
154
|
+
__kv_dispatched[index] = true; // Entry is the conservative mutation boundary.
|
|
155
|
+
status = fastly_kv_store_insert(storeHandle[0], changetype<usize>(keyBytes), keyBytes.byteLength, body[0], kind == 17 ? 32 : 0, changetype<usize>(config), changetype<usize>(pending));
|
|
156
|
+
// Insert takes ownership on success. On error the body may already be consumed;
|
|
157
|
+
// best-effort close cannot turn uncertain acceptance into rejection.
|
|
158
|
+
if (status != 0) __kv_close(body[0]);
|
|
159
|
+
}
|
|
160
|
+
if (status != 0) { __kv_fail(index, 'transport'); return; }
|
|
161
|
+
__pulse_fastly_pending[index] = pending[0]; __pulse_fastly_pending_mode[index] = PULSE_FASTLY_PENDING_ASYNC;
|
|
162
|
+
}
|
|
163
|
+
function __kv_utf8(data: Uint8Array, length: i32): bool {
|
|
164
|
+
for (let i = 0; i < length;) {
|
|
165
|
+
const c = data[i++]; if (c <= 127) continue;
|
|
166
|
+
const count = c >= 194 && c <= 223 ? 1 : c >= 224 && c <= 239 ? 2 : c >= 240 && c <= 244 ? 3 : -1;
|
|
167
|
+
if (count < 0 || i + count > length) return false;
|
|
168
|
+
const second = data[i];
|
|
169
|
+
if ((c == 224 && second < 160) || (c == 237 && second > 159) || (c == 240 && second < 144) || (c == 244 && second > 143)) return false;
|
|
170
|
+
for (let j = 0; j < count; j++) { const next = data[i++]; if (next < 128 || next > 191) return false; }
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
function __kv_read(index: i32, body: i32, generation: u64): i32 {
|
|
175
|
+
const buffer = new Uint8Array(__KV_wireBytes + 1), written = new StaticArray<i32>(1); let count = 0;
|
|
176
|
+
while (true) {
|
|
177
|
+
const ready = __kv_ready(index, body); if (ready <= 0) return __kv_ready_failure(index, ready);
|
|
178
|
+
written[0] = 0;
|
|
179
|
+
if (fastly_http_body_read(body, buffer.dataStart + count, buffer.length - count, changetype<usize>(written)) != 0) return __kv_failure(index, 'transport');
|
|
180
|
+
if (written[0] < 0 || written[0] > buffer.length - count) return __kv_failure(index, 'protocol');
|
|
181
|
+
if (!written[0]) break;
|
|
182
|
+
count += written[0]; if (count > __KV_wireBytes) return __kv_failure(index, 'too-large');
|
|
183
|
+
}
|
|
184
|
+
if (!__kv_utf8(buffer, count)) return __kv_failure(index, 'protocol');
|
|
185
|
+
const parser = new __PulseJsonParser(String.UTF8.decodeUnsafe(buffer.dataStart, count, false), true), parsed = parser.parse();
|
|
186
|
+
if (parser.failed || parsed <= 0) return __kv_failure(index, parser.tooLarge ? 'too-large' : 'protocol');
|
|
187
|
+
const outer = __pulse_fastly_value(parsed);
|
|
188
|
+
if (outer.kind != PULSE_VALUE_OBJECT || outer.keys.length != 2 || __pulse_fastly_find(outer, '__pulseKv') < 0 || __pulse_fastly_find(outer, 'value') < 0) return __kv_failure(index, 'protocol');
|
|
189
|
+
const marker = __pulse_fastly_value(__pulse_fastly_payload_field(outer, '__pulseKv'));
|
|
190
|
+
if (marker.kind != PULSE_VALUE_NUMBER || marker.number != 1) return __kv_failure(index, 'protocol');
|
|
191
|
+
const value = __pulse_fastly_payload_field(outer, 'value'), encoder = new __KvEncoder(); encoder.encode(value);
|
|
192
|
+
if (encoder.reason.length) return __kv_failure(index, encoder.reason);
|
|
193
|
+
const remaining = __kv_remaining(index); if (remaining <= 0) return __kv_failure(index, remaining == 0 ? 'timeout' : 'unavailable');
|
|
194
|
+
const result = __kv_result('found'), token = __kv_token(generation); __kv_private(token);
|
|
195
|
+
__kv_set(result, 'generation', __pulse_fastly_string_value(token)); __kv_set(result, 'value', value);
|
|
196
|
+
__pulse_fastly_deep_freeze(result); return result;
|
|
197
|
+
}
|
|
198
|
+
function __kv_wait(index: i32): i32 {
|
|
199
|
+
if (__pulse_fastly_pending_mode[index] == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(index);
|
|
200
|
+
const pending = __pulse_fastly_pending[index];
|
|
201
|
+
__pulse_fastly_pending_mode[index] = PULSE_FASTLY_PENDING_NONE; __pulse_fastly_pending[index] = 0; __pulse_fastly_payloads[index] = 0;
|
|
202
|
+
// No KV cancel hostcall exists. An expired pending handle is left to invocation
|
|
203
|
+
// teardown; never call an unready wait just to drain it, and never retry.
|
|
204
|
+
const ready = __kv_ready(index, pending); if (ready <= 0) return __kv_ready_failure(index, ready);
|
|
205
|
+
const error = new StaticArray<i32>(1), kind = __pulse_fastly_effect_kind(index);
|
|
206
|
+
if (kind != 15) {
|
|
207
|
+
const status = fastly_kv_store_insert_wait(pending, changetype<usize>(error));
|
|
208
|
+
if (status != 0) return __kv_failure(index, 'transport');
|
|
209
|
+
const remaining = __kv_remaining(index); if (remaining <= 0) return __kv_failure(index, remaining == 0 ? 'timeout' : 'unavailable');
|
|
210
|
+
if (error[0] == 1) return __kv_result('stored');
|
|
211
|
+
if (error[0] == 4 || (error[0] == 3 && kind == 17)) return __kv_result('conflict');
|
|
212
|
+
if (error[0] == 2 || error[0] == 5 || error[0] == 7) return __kv_result('not-stored', error[0] == 2 ? 'rejected' : error[0] == 5 ? 'too-large' : 'throttled');
|
|
213
|
+
return __kv_failure(index, error[0] == 6 ? 'unavailable' : 'protocol');
|
|
214
|
+
}
|
|
215
|
+
const body = new StaticArray<i32>(1), metadata = new Uint8Array(2000), written = new StaticArray<i32>(1), generation = new StaticArray<u64>(1);
|
|
216
|
+
const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(body), metadata.dataStart, metadata.length, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(error));
|
|
217
|
+
if (status != 0) return __kv_failure(index, 'transport');
|
|
218
|
+
if (error[0] != 1) {
|
|
219
|
+
const remaining = __kv_remaining(index); if (remaining <= 0) return __kv_failure(index, remaining == 0 ? 'timeout' : 'unavailable');
|
|
220
|
+
if (error[0] == 3) return __kv_result('not-found');
|
|
221
|
+
return __kv_failure(index, error[0] == 5 ? 'too-large' : error[0] == 6 ? 'unavailable' : error[0] == 7 ? 'throttled' : 'protocol');
|
|
222
|
+
}
|
|
223
|
+
const result = written[0] < 0 || written[0] > metadata.length ? __kv_failure(index, 'protocol') : __kv_read(index, body[0], generation[0]);
|
|
224
|
+
__kv_close(body[0]); return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function __pulse_fastly_kv_conditional_wait(index: i32): i32 {
|
|
228
|
+
const result = __kv_wait(index); __pulse_fastly_deep_freeze(result); return result;
|
|
229
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { KV_CONDITIONAL_KINDS, KV_CONDITIONAL_LIMITS } = require('@pulse-compute/runtime/host');
|
|
5
|
+
|
|
6
|
+
const KV_IMPORTS = Object.freeze([
|
|
7
|
+
'fastly_async_io:select', 'fastly_http_body:close', 'wasi_snapshot_preview1:clock_time_get',
|
|
8
|
+
'fastly_kv_store:open'
|
|
9
|
+
]);
|
|
10
|
+
function kvImports(kind) {
|
|
11
|
+
return [...KV_IMPORTS, ...(kind === 'kv.getVersioned'
|
|
12
|
+
? ['fastly_kv_store:lookup', 'fastly_kv_store:lookup_wait_v2', 'fastly_http_body:read']
|
|
13
|
+
: ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait', 'fastly_http_body:new', 'fastly_http_body:write'])];
|
|
14
|
+
}
|
|
15
|
+
function kvNativeSource(plan) {
|
|
16
|
+
if (!plan.effects.some(effect => KV_CONDITIONAL_KINDS.includes(effect.kind))) return '';
|
|
17
|
+
const limits = Object.entries(KV_CONDITIONAL_LIMITS).map(([key, value]) => `const __KV_${key}: i32 = ${value};`).join('\n');
|
|
18
|
+
return limits + '\n' + fs.readFileSync(path.join(__dirname, 'kv-native.as.ts'), 'utf8');
|
|
19
|
+
}
|
|
20
|
+
module.exports = { KV_CONDITIONAL_KINDS, KV_CONDITIONAL_LIMITS, KV_IMPORTS, kvImports, kvNativeSource };
|