space-data-module-sdk 0.5.19 → 0.5.21
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 +52 -7
- package/package.json +5 -1
- package/src/AGENTS.md +33 -0
- package/src/auth/AGENTS.md +25 -0
- package/src/browser.js +2 -0
- package/src/bundle/AGENTS.md +21 -0
- package/src/compiler/AGENTS.md +35 -0
- package/src/compliance/AGENTS.md +18 -0
- package/src/host/AGENTS.md +39 -0
- package/src/host/isomorphicLoader.js +133 -0
- package/src/host/wasiShim.js +12 -1
- package/src/index.d.ts +100 -0
- package/src/manifest/AGENTS.md +18 -0
- package/src/runtime-host/AGENTS.md +32 -0
- package/src/runtime-host/flatbufferStreamIngestor.js +181 -0
- package/src/runtime-host/flatsqlRuntimeStore.js +120 -49
- package/src/runtime-host/index.js +2 -0
- package/src/testing/AGENTS.md +33 -0
- package/src/testing/index.js +1 -0
- package/src/testing/moduleFlatbufferStreamPump.js +255 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { clonePayloadTypeRef } from "../manifest/typeRefs.js";
|
|
2
|
+
|
|
3
|
+
function assertNonEmptyString(value, label) {
|
|
4
|
+
const normalized = String(value ?? "").trim();
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
throw new TypeError(`${label} is required.`);
|
|
7
|
+
}
|
|
8
|
+
return normalized;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizePositiveInteger(value, fallback) {
|
|
12
|
+
const normalized = Number(value ?? fallback);
|
|
13
|
+
if (!Number.isInteger(normalized) || normalized <= 0) {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toUint8Array(data) {
|
|
20
|
+
if (data instanceof Uint8Array) {
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
if (data instanceof ArrayBuffer) {
|
|
24
|
+
return new Uint8Array(data);
|
|
25
|
+
}
|
|
26
|
+
if (ArrayBuffer.isView(data)) {
|
|
27
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
28
|
+
}
|
|
29
|
+
throw new TypeError(
|
|
30
|
+
"Module FlatBuffer stream pump expects Uint8Array, ArrayBuffer, or ArrayBufferView chunks.",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function concatUint8Arrays(chunks) {
|
|
35
|
+
let totalLength = 0;
|
|
36
|
+
for (const chunk of chunks) {
|
|
37
|
+
totalLength += chunk.byteLength;
|
|
38
|
+
}
|
|
39
|
+
const combined = new Uint8Array(totalLength);
|
|
40
|
+
let offset = 0;
|
|
41
|
+
for (const chunk of chunks) {
|
|
42
|
+
combined.set(chunk, offset);
|
|
43
|
+
offset += chunk.byteLength;
|
|
44
|
+
}
|
|
45
|
+
return combined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readFrameSize(bytes, offset) {
|
|
49
|
+
return (
|
|
50
|
+
bytes[offset] |
|
|
51
|
+
(bytes[offset + 1] << 8) |
|
|
52
|
+
(bytes[offset + 2] << 16) |
|
|
53
|
+
((bytes[offset + 3] << 24) >>> 0)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readFileIdentifier(payload) {
|
|
58
|
+
if (!(payload instanceof Uint8Array) || payload.byteLength < 8) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return String.fromCharCode(payload[4], payload[5], payload[6], payload[7]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeSchemaFileId(fileIdentifier) {
|
|
65
|
+
if (typeof fileIdentifier !== "string") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const normalized = fileIdentifier.trim();
|
|
69
|
+
return normalized.length > 0 ? normalized : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveInvoke(options) {
|
|
73
|
+
if (typeof options.invoke === "function") {
|
|
74
|
+
return options.invoke;
|
|
75
|
+
}
|
|
76
|
+
if (options.harness && typeof options.harness.invoke === "function") {
|
|
77
|
+
return options.harness.invoke.bind(options.harness);
|
|
78
|
+
}
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
"createModuleFlatBufferStreamPump requires an invoke(request) function or harness with invoke().",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function defaultTypeResolver(_payload, context) {
|
|
85
|
+
return {
|
|
86
|
+
schemaName: null,
|
|
87
|
+
fileIdentifier: context.rawFileIdentifier,
|
|
88
|
+
acceptsAnyFlatbuffer: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveFrameTemplate(frameTemplate, payload, context) {
|
|
93
|
+
if (typeof frameTemplate === "function") {
|
|
94
|
+
const resolved = frameTemplate(payload, context);
|
|
95
|
+
return resolved && typeof resolved === "object" ? resolved : {};
|
|
96
|
+
}
|
|
97
|
+
if (frameTemplate && typeof frameTemplate === "object") {
|
|
98
|
+
return frameTemplate;
|
|
99
|
+
}
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createModuleFlatBufferStreamPump(options = {}) {
|
|
104
|
+
const invoke = resolveInvoke(options);
|
|
105
|
+
const methodId = assertNonEmptyString(options.methodId, "methodId");
|
|
106
|
+
const portId = assertNonEmptyString(options.portId, "portId");
|
|
107
|
+
const maxFramesPerInvoke = normalizePositiveInteger(options.maxFramesPerInvoke, 1);
|
|
108
|
+
const streamId = normalizePositiveInteger(options.streamId, 1);
|
|
109
|
+
const typeResolver =
|
|
110
|
+
typeof options.typeResolver === "function"
|
|
111
|
+
? options.typeResolver
|
|
112
|
+
: defaultTypeResolver;
|
|
113
|
+
const onResponse =
|
|
114
|
+
typeof options.onResponse === "function" ? options.onResponse : null;
|
|
115
|
+
|
|
116
|
+
const stats = {
|
|
117
|
+
bytesReceived: 0,
|
|
118
|
+
chunksReceived: 0,
|
|
119
|
+
framesDecoded: 0,
|
|
120
|
+
framesInvoked: 0,
|
|
121
|
+
invokes: 0,
|
|
122
|
+
parseErrors: 0,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let pendingBytes = new Uint8Array(0);
|
|
126
|
+
let pendingFrames = [];
|
|
127
|
+
let nextSequence = normalizePositiveInteger(options.sequenceStart, 1);
|
|
128
|
+
let lastResponse = null;
|
|
129
|
+
|
|
130
|
+
async function flushPendingFrames(isFinalBatch) {
|
|
131
|
+
if (pendingFrames.length === 0) {
|
|
132
|
+
return lastResponse;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const frames = pendingFrames.map((decodedFrame, index) => {
|
|
136
|
+
const context = {
|
|
137
|
+
rawFileIdentifier: decodedFrame.rawFileIdentifier,
|
|
138
|
+
schemaFileId: decodedFrame.schemaFileId,
|
|
139
|
+
methodId,
|
|
140
|
+
portId,
|
|
141
|
+
streamId: decodedFrame.streamId,
|
|
142
|
+
sequence: decodedFrame.sequence,
|
|
143
|
+
stats,
|
|
144
|
+
};
|
|
145
|
+
const resolvedTypeRef = clonePayloadTypeRef(
|
|
146
|
+
typeResolver(decodedFrame.payload, context) ?? defaultTypeResolver(decodedFrame.payload, context),
|
|
147
|
+
);
|
|
148
|
+
if (!resolvedTypeRef.fileIdentifier && decodedFrame.rawFileIdentifier) {
|
|
149
|
+
resolvedTypeRef.fileIdentifier = decodedFrame.rawFileIdentifier;
|
|
150
|
+
}
|
|
151
|
+
if (resolvedTypeRef.acceptsAnyFlatbuffer !== true) {
|
|
152
|
+
resolvedTypeRef.acceptsAnyFlatbuffer = false;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
...resolveFrameTemplate(options.frameTemplate, decodedFrame.payload, context),
|
|
156
|
+
portId,
|
|
157
|
+
typeRef: resolvedTypeRef,
|
|
158
|
+
payload: decodedFrame.payload,
|
|
159
|
+
streamId: decodedFrame.streamId,
|
|
160
|
+
sequence: decodedFrame.sequence,
|
|
161
|
+
endOfStream:
|
|
162
|
+
isFinalBatch === true && index === pendingFrames.length - 1,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
pendingFrames = [];
|
|
167
|
+
const response = await invoke({
|
|
168
|
+
methodId,
|
|
169
|
+
inputs: frames,
|
|
170
|
+
});
|
|
171
|
+
stats.invokes += 1;
|
|
172
|
+
stats.framesInvoked += frames.length;
|
|
173
|
+
lastResponse = response;
|
|
174
|
+
if (onResponse) {
|
|
175
|
+
await onResponse(response, {
|
|
176
|
+
methodId,
|
|
177
|
+
portId,
|
|
178
|
+
frames,
|
|
179
|
+
isFinalBatch: isFinalBatch === true,
|
|
180
|
+
stats,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return response;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function pushBytes(data) {
|
|
187
|
+
const bytes = toUint8Array(data);
|
|
188
|
+
stats.bytesReceived += bytes.byteLength;
|
|
189
|
+
stats.chunksReceived += 1;
|
|
190
|
+
|
|
191
|
+
const combined =
|
|
192
|
+
pendingBytes.byteLength > 0 ? concatUint8Arrays([pendingBytes, bytes]) : bytes;
|
|
193
|
+
|
|
194
|
+
let offset = 0;
|
|
195
|
+
let decodedCount = 0;
|
|
196
|
+
|
|
197
|
+
while (offset + 4 <= combined.byteLength) {
|
|
198
|
+
const frameSize = readFrameSize(combined, offset);
|
|
199
|
+
if (frameSize <= 0) {
|
|
200
|
+
stats.parseErrors += 1;
|
|
201
|
+
throw new Error("Invalid FlatBuffer stream frame size.");
|
|
202
|
+
}
|
|
203
|
+
if (offset + 4 + frameSize > combined.byteLength) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const payload = combined.subarray(offset + 4, offset + 4 + frameSize).slice();
|
|
208
|
+
const rawFileIdentifier = readFileIdentifier(payload);
|
|
209
|
+
const schemaFileId = normalizeSchemaFileId(rawFileIdentifier);
|
|
210
|
+
if (!schemaFileId) {
|
|
211
|
+
stats.parseErrors += 1;
|
|
212
|
+
throw new Error(
|
|
213
|
+
"FlatBuffer stream frame is missing a readable file identifier.",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
pendingFrames.push({
|
|
218
|
+
payload,
|
|
219
|
+
rawFileIdentifier,
|
|
220
|
+
schemaFileId,
|
|
221
|
+
streamId,
|
|
222
|
+
sequence: nextSequence,
|
|
223
|
+
});
|
|
224
|
+
nextSequence += 1;
|
|
225
|
+
stats.framesDecoded += 1;
|
|
226
|
+
decodedCount += 1;
|
|
227
|
+
offset += 4 + frameSize;
|
|
228
|
+
|
|
229
|
+
if (pendingFrames.length >= maxFramesPerInvoke) {
|
|
230
|
+
await flushPendingFrames(false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
pendingBytes =
|
|
235
|
+
offset < combined.byteLength ? combined.slice(offset) : new Uint8Array(0);
|
|
236
|
+
|
|
237
|
+
return decodedCount;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function finish() {
|
|
241
|
+
if (pendingBytes.byteLength > 0) {
|
|
242
|
+
throw new Error("FlatBuffer stream ended with a partial frame.");
|
|
243
|
+
}
|
|
244
|
+
return flushPendingFrames(true);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
stats,
|
|
249
|
+
get lastResponse() {
|
|
250
|
+
return lastResponse;
|
|
251
|
+
},
|
|
252
|
+
pushBytes,
|
|
253
|
+
finish,
|
|
254
|
+
};
|
|
255
|
+
}
|