@craftainer/crud-sdk 0.0.0-stage → 0.2.0
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/LICENSE +674 -0
- package/README.md +207 -2
- package/dist/auth-DwtNEKrw.mjs +22 -0
- package/dist/auth-DwtNEKrw.mjs.map +1 -0
- package/dist/cache/index.d.mts +166 -0
- package/dist/cache/index.mjs +972 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/client-B8fSsx4L.d.mts +316 -0
- package/dist/client-Bv0yCuwC.mjs +923 -0
- package/dist/client-Bv0yCuwC.mjs.map +1 -0
- package/dist/codegen/cli.d.mts +1 -0
- package/dist/codegen/cli.mjs +131 -0
- package/dist/codegen/cli.mjs.map +1 -0
- package/dist/events-Cqj9Rbon.mjs +616 -0
- package/dist/events-Cqj9Rbon.mjs.map +1 -0
- package/dist/events-DpSWzSN9.d.mts +88 -0
- package/dist/index.d.mts +98 -0
- package/dist/index.mjs +3 -0
- package/dist/interfaces-s73cVfIF.d.mts +109 -0
- package/dist/tanstack/index.d.mts +245 -0
- package/dist/tanstack/index.mjs +315 -0
- package/dist/tanstack/index.mjs.map +1 -0
- package/package.json +80 -4
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
//#region src/cache/policy.ts
|
|
2
|
+
function seconds(value) {
|
|
3
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(value, 0) : 0;
|
|
4
|
+
}
|
|
5
|
+
/** Build a policy from a manifest resource object; `overrides` win over the hints. */
|
|
6
|
+
function cachePolicyFromManifest(resource, overrides = {}) {
|
|
7
|
+
const hints = resource.cache ?? {};
|
|
8
|
+
return {
|
|
9
|
+
enabled: overrides.enabled ?? true,
|
|
10
|
+
maxAge: overrides.maxAge ?? seconds(hints.max_age),
|
|
11
|
+
statsTtl: overrides.statsTtl ?? seconds(hints.stats_ttl),
|
|
12
|
+
staleWhileRevalidate: overrides.staleWhileRevalidate ?? seconds(hints.stale_while_revalidate),
|
|
13
|
+
etag: overrides.etag ?? hints.etag !== false,
|
|
14
|
+
revalidateOnly: overrides.revalidateOnly ?? hints.revalidate_only === true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The policy to use under TanStack Query: its `staleTime` decides freshness,
|
|
19
|
+
* so the SDK cache only revalidates (ETag/304) unless told otherwise.
|
|
20
|
+
*/
|
|
21
|
+
function tanstackCachePolicy(resource, overrides = {}) {
|
|
22
|
+
return cachePolicyFromManifest(resource, {
|
|
23
|
+
revalidateOnly: true,
|
|
24
|
+
...overrides
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region node_modules/.pnpm/eventsource-parser@4.1.2/node_modules/eventsource-parser/dist/errors.js
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when encountering an issue during parsing.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
var ParseError = class extends Error {
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "ParseError";
|
|
38
|
+
this.type = options.type;
|
|
39
|
+
this.field = options.field;
|
|
40
|
+
this.value = options.value;
|
|
41
|
+
this.line = options.line;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region node_modules/.pnpm/eventsource-parser@4.1.2/node_modules/eventsource-parser/dist/parse.js
|
|
46
|
+
/**
|
|
47
|
+
* EventSource/Server-Sent Events parser
|
|
48
|
+
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
49
|
+
*/
|
|
50
|
+
const LF = 10;
|
|
51
|
+
const CR = 13;
|
|
52
|
+
const SPACE = 32;
|
|
53
|
+
const MAX_FIELD_PREFIX_LENGTH = 6;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a new EventSource parser.
|
|
56
|
+
*
|
|
57
|
+
* @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})
|
|
58
|
+
* and options like `maxBufferSize` (see {@link ParserConfig}).
|
|
59
|
+
*
|
|
60
|
+
* @returns A new EventSource parser, with `feed` and `reset` methods.
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
function createParser(config) {
|
|
64
|
+
if (typeof config === "function") throw new TypeError("`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?");
|
|
65
|
+
const { maxBufferSize, onComment, onError, onEvent, onId, onRetry } = config;
|
|
66
|
+
const pendingFragments = [];
|
|
67
|
+
let pendingFragmentsLength = 0;
|
|
68
|
+
let bomPrefix = "";
|
|
69
|
+
let id;
|
|
70
|
+
let data = "";
|
|
71
|
+
let dataLines = 0;
|
|
72
|
+
let eventType;
|
|
73
|
+
let terminated = false;
|
|
74
|
+
let skippingLine = false;
|
|
75
|
+
let skipNextLineFeed = false;
|
|
76
|
+
/**
|
|
77
|
+
* Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do
|
|
78
|
+
* not yet form a complete line are held back and prepended to the next chunk,
|
|
79
|
+
* so callers can pass arbitrary slices of the stream without worrying about
|
|
80
|
+
* line boundaries.
|
|
81
|
+
*
|
|
82
|
+
* Per the SSE spec, one leading UTF-8 BOM is stripped before parsing,
|
|
83
|
+
* even when split across chunks. This handles both the raw 3-byte form (0xEF 0xBB
|
|
84
|
+
* 0xBF) and a single decoded U+FEFF, so a leading BOM is ignored regardless of
|
|
85
|
+
* how the caller decoded the bytes.
|
|
86
|
+
*
|
|
87
|
+
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream
|
|
88
|
+
*/
|
|
89
|
+
function feed(chunk) {
|
|
90
|
+
if (terminated) throw new Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.");
|
|
91
|
+
if (bomPrefix !== void 0) {
|
|
92
|
+
chunk = bomPrefix + chunk;
|
|
93
|
+
if (chunk === "" || chunk === "ï" || chunk === "ï»") {
|
|
94
|
+
bomPrefix = chunk;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
bomPrefix = void 0;
|
|
98
|
+
chunk = chunk.replace(/^(?:\uFEFF|\xEF\xBB\xBF)/, "");
|
|
99
|
+
}
|
|
100
|
+
if (skippingLine || skipNextLineFeed) {
|
|
101
|
+
chunk = resumeAfterSkip(chunk);
|
|
102
|
+
if (!chunk) return;
|
|
103
|
+
}
|
|
104
|
+
if (!pendingFragments.length) {
|
|
105
|
+
const trailing = processLines(chunk);
|
|
106
|
+
if (trailing !== "") storeTrailing(trailing);
|
|
107
|
+
checkForBufferOverflow();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (chunk.indexOf("\n") === -1 && chunk.indexOf("\r") === -1) {
|
|
111
|
+
if (pendingFragmentsLength < MAX_FIELD_PREFIX_LENGTH) {
|
|
112
|
+
if (!shouldBufferTrailing(pendingFragments.join("") + chunk.slice(0, MAX_FIELD_PREFIX_LENGTH - pendingFragmentsLength))) {
|
|
113
|
+
pendingFragments.length = 0;
|
|
114
|
+
pendingFragmentsLength = 0;
|
|
115
|
+
skippingLine = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
pendingFragments.push(chunk);
|
|
120
|
+
pendingFragmentsLength += chunk.length;
|
|
121
|
+
checkForBufferOverflow();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
pendingFragments.push(chunk);
|
|
125
|
+
const input = pendingFragments.join("");
|
|
126
|
+
pendingFragments.length = 0;
|
|
127
|
+
pendingFragmentsLength = 0;
|
|
128
|
+
storeTrailing(processLines(input));
|
|
129
|
+
checkForBufferOverflow();
|
|
130
|
+
}
|
|
131
|
+
function resumeAfterSkip(chunk) {
|
|
132
|
+
if (chunk.length === 0) return chunk;
|
|
133
|
+
if (skipNextLineFeed) {
|
|
134
|
+
skipNextLineFeed = false;
|
|
135
|
+
return chunk.charCodeAt(0) === LF ? chunk.slice(1) : chunk;
|
|
136
|
+
}
|
|
137
|
+
const crIndex = chunk.indexOf("\r");
|
|
138
|
+
const lfIndex = chunk.indexOf("\n");
|
|
139
|
+
const lineEnd = crIndex === -1 ? lfIndex : lfIndex === -1 ? crIndex : crIndex < lfIndex ? crIndex : lfIndex;
|
|
140
|
+
if (lineEnd === -1) return "";
|
|
141
|
+
if (lineEnd === chunk.length - 1 && chunk.charCodeAt(lineEnd) === CR) {
|
|
142
|
+
skippingLine = false;
|
|
143
|
+
skipNextLineFeed = true;
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
skippingLine = false;
|
|
147
|
+
return chunk.slice(lineEnd + (chunk.charCodeAt(lineEnd) === CR && chunk.charCodeAt(lineEnd + 1) === LF ? 2 : 1));
|
|
148
|
+
}
|
|
149
|
+
function storeTrailing(trailing) {
|
|
150
|
+
if (!trailing) return;
|
|
151
|
+
if (trailing.charCodeAt(trailing.length - 1) === CR) {
|
|
152
|
+
parseLine(trailing, 0, trailing.length - 1);
|
|
153
|
+
if (checkForBufferOverflow()) return;
|
|
154
|
+
skipNextLineFeed = true;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (shouldBufferTrailing(trailing)) {
|
|
158
|
+
pendingFragments.push(trailing);
|
|
159
|
+
pendingFragmentsLength = trailing.length;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
skippingLine = true;
|
|
163
|
+
}
|
|
164
|
+
function shouldBufferTrailing(trailing) {
|
|
165
|
+
const firstCharCode = trailing.charCodeAt(0);
|
|
166
|
+
return firstCharCode === 58 && !!onComment || firstCharCode === 100 && isPotentialField(trailing, "data") || firstCharCode === 101 && isPotentialField(trailing, "event") || firstCharCode === 105 && isPotentialField(trailing, "id") || firstCharCode === 114 && isPotentialField(trailing, "retry");
|
|
167
|
+
}
|
|
168
|
+
function checkForBufferOverflow() {
|
|
169
|
+
if (terminated) return true;
|
|
170
|
+
if (maxBufferSize === void 0) return false;
|
|
171
|
+
if (pendingFragmentsLength + data.length <= maxBufferSize) return false;
|
|
172
|
+
terminated = true;
|
|
173
|
+
pendingFragments.length = 0;
|
|
174
|
+
pendingFragmentsLength = 0;
|
|
175
|
+
id = void 0;
|
|
176
|
+
data = "";
|
|
177
|
+
dataLines = 0;
|
|
178
|
+
eventType = void 0;
|
|
179
|
+
skippingLine = false;
|
|
180
|
+
skipNextLineFeed = false;
|
|
181
|
+
onError === null || onError === void 0 || onError(new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, { type: "max-buffer-size-exceeded" }));
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Splits `chunk` into SSE lines and dispatches each to the appropriate handler.
|
|
186
|
+
* Returns any trailing bytes that did not terminate with a line break, so the
|
|
187
|
+
* caller can prepend them to the next chunk.
|
|
188
|
+
*
|
|
189
|
+
* The SSE spec permits three line terminators: `\n`, `\r`, and `\r\n`. Real-world
|
|
190
|
+
* streams almost always use plain `\n`, so we take a fast path when no `\r` is
|
|
191
|
+
* present in the chunk. The slow path is spec-correct but does more work per line.
|
|
192
|
+
*/
|
|
193
|
+
function processLines(chunk) {
|
|
194
|
+
let searchIndex = 0;
|
|
195
|
+
if (chunk.indexOf("\r") === -1) {
|
|
196
|
+
let lfIndex = chunk.indexOf("\n", searchIndex);
|
|
197
|
+
while (lfIndex !== -1) {
|
|
198
|
+
if (searchIndex === lfIndex) {
|
|
199
|
+
if (id !== void 0) onId === null || onId === void 0 || onId(id);
|
|
200
|
+
if (dataLines > 0) onEvent === null || onEvent === void 0 || onEvent({
|
|
201
|
+
id,
|
|
202
|
+
event: eventType,
|
|
203
|
+
data
|
|
204
|
+
});
|
|
205
|
+
id = void 0;
|
|
206
|
+
data = "";
|
|
207
|
+
dataLines = 0;
|
|
208
|
+
eventType = void 0;
|
|
209
|
+
searchIndex = lfIndex + 1;
|
|
210
|
+
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const firstCharCode = chunk.charCodeAt(searchIndex);
|
|
214
|
+
if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
|
|
215
|
+
const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5;
|
|
216
|
+
const value = chunk.slice(valueStart, lfIndex);
|
|
217
|
+
if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
|
|
218
|
+
if (id !== void 0) onId === null || onId === void 0 || onId(id);
|
|
219
|
+
onEvent === null || onEvent === void 0 || onEvent({
|
|
220
|
+
id,
|
|
221
|
+
event: eventType,
|
|
222
|
+
data: value
|
|
223
|
+
});
|
|
224
|
+
id = void 0;
|
|
225
|
+
data = "";
|
|
226
|
+
eventType = void 0;
|
|
227
|
+
searchIndex = lfIndex + 2;
|
|
228
|
+
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
data = dataLines === 0 ? value : `${data}\n${value}`;
|
|
232
|
+
dataLines++;
|
|
233
|
+
} else if (isEventPrefix(chunk, searchIndex, firstCharCode)) eventType = chunk.slice(chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6, lfIndex) || void 0;
|
|
234
|
+
else parseLine(chunk, searchIndex, lfIndex);
|
|
235
|
+
if (checkForBufferOverflow()) return "";
|
|
236
|
+
searchIndex = lfIndex + 1;
|
|
237
|
+
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
238
|
+
}
|
|
239
|
+
return chunk.slice(searchIndex);
|
|
240
|
+
}
|
|
241
|
+
while (searchIndex < chunk.length) {
|
|
242
|
+
const crIndex = chunk.indexOf("\r", searchIndex);
|
|
243
|
+
const lfIndex = chunk.indexOf("\n", searchIndex);
|
|
244
|
+
let lineEnd = -1;
|
|
245
|
+
if (crIndex !== -1 && lfIndex !== -1) lineEnd = crIndex < lfIndex ? crIndex : lfIndex;
|
|
246
|
+
else if (crIndex !== -1) {
|
|
247
|
+
if (crIndex === chunk.length - 1) lineEnd = -1;
|
|
248
|
+
else lineEnd = crIndex;
|
|
249
|
+
} else if (lfIndex !== -1) lineEnd = lfIndex;
|
|
250
|
+
if (lineEnd === -1) break;
|
|
251
|
+
parseLine(chunk, searchIndex, lineEnd);
|
|
252
|
+
if (checkForBufferOverflow()) return "";
|
|
253
|
+
searchIndex = lineEnd + 1;
|
|
254
|
+
if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) searchIndex++;
|
|
255
|
+
}
|
|
256
|
+
return chunk.slice(searchIndex);
|
|
257
|
+
}
|
|
258
|
+
function parseLine(chunk, start, end) {
|
|
259
|
+
if (start === end) {
|
|
260
|
+
dispatchEvent();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const firstCharCode = chunk.charCodeAt(start);
|
|
264
|
+
if (isDataPrefix(chunk, start, firstCharCode)) {
|
|
265
|
+
const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5;
|
|
266
|
+
const value = chunk.slice(valueStart, end);
|
|
267
|
+
data = dataLines === 0 ? value : `${data}\n${value}`;
|
|
268
|
+
dataLines++;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (isEventPrefix(chunk, start, firstCharCode)) {
|
|
272
|
+
eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
|
|
276
|
+
const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
|
|
277
|
+
if (!value.includes("\0")) id = value;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (firstCharCode === 58) {
|
|
281
|
+
if (onComment) {
|
|
282
|
+
const line = chunk.slice(start, end);
|
|
283
|
+
onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const line = chunk.slice(start, end);
|
|
288
|
+
const fieldSeparatorIndex = line.indexOf(":");
|
|
289
|
+
if (fieldSeparatorIndex === -1) {
|
|
290
|
+
processField(line, "", line);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const field = line.slice(0, fieldSeparatorIndex);
|
|
294
|
+
const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1;
|
|
295
|
+
processField(field, line.slice(fieldSeparatorIndex + offset), line);
|
|
296
|
+
}
|
|
297
|
+
function processField(field, value, line) {
|
|
298
|
+
switch (field) {
|
|
299
|
+
case "event":
|
|
300
|
+
eventType = value || void 0;
|
|
301
|
+
break;
|
|
302
|
+
case "data":
|
|
303
|
+
data = dataLines === 0 ? value : `${data}\n${value}`;
|
|
304
|
+
dataLines++;
|
|
305
|
+
break;
|
|
306
|
+
case "id":
|
|
307
|
+
if (!value.includes("\0")) id = value;
|
|
308
|
+
break;
|
|
309
|
+
case "retry":
|
|
310
|
+
if (/^\d+$/.test(value)) onRetry === null || onRetry === void 0 || onRetry(parseInt(value, 10));
|
|
311
|
+
else onError === null || onError === void 0 || onError(new ParseError(`Invalid \`retry\` value: "${value}"`, {
|
|
312
|
+
type: "invalid-retry",
|
|
313
|
+
value,
|
|
314
|
+
line
|
|
315
|
+
}));
|
|
316
|
+
break;
|
|
317
|
+
default: onError === null || onError === void 0 || onError(new ParseError(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}…` : field}"`, {
|
|
318
|
+
type: "unknown-field",
|
|
319
|
+
field,
|
|
320
|
+
value,
|
|
321
|
+
line
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function dispatchEvent() {
|
|
326
|
+
if (id !== void 0) onId === null || onId === void 0 || onId(id);
|
|
327
|
+
if (dataLines > 0) onEvent === null || onEvent === void 0 || onEvent({
|
|
328
|
+
id,
|
|
329
|
+
event: eventType,
|
|
330
|
+
data
|
|
331
|
+
});
|
|
332
|
+
id = void 0;
|
|
333
|
+
data = "";
|
|
334
|
+
dataLines = 0;
|
|
335
|
+
eventType = void 0;
|
|
336
|
+
}
|
|
337
|
+
function reset(options = {}) {
|
|
338
|
+
if (options.consume && pendingFragments.length > 0) {
|
|
339
|
+
const incompleteLine = pendingFragments.join("");
|
|
340
|
+
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
341
|
+
}
|
|
342
|
+
bomPrefix = "";
|
|
343
|
+
id = void 0;
|
|
344
|
+
data = "";
|
|
345
|
+
dataLines = 0;
|
|
346
|
+
eventType = void 0;
|
|
347
|
+
pendingFragments.length = 0;
|
|
348
|
+
pendingFragmentsLength = 0;
|
|
349
|
+
terminated = false;
|
|
350
|
+
skippingLine = false;
|
|
351
|
+
skipNextLineFeed = false;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
feed,
|
|
355
|
+
reset
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Checks if `chunk` starts with the literal `data:` at index `i`.
|
|
360
|
+
*
|
|
361
|
+
* Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this
|
|
362
|
+
* hand-unrolled char-code comparison is ~20% faster on common event types.
|
|
363
|
+
* The caller passes `firstCharCode` (the code at `i`) so it can be reused
|
|
364
|
+
* across prefix checks.
|
|
365
|
+
*
|
|
366
|
+
* ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58
|
|
367
|
+
*/
|
|
368
|
+
function isDataPrefix(chunk, i, firstCharCode) {
|
|
369
|
+
return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Checks if `chunk` starts with the literal `event:` at index `i`.
|
|
373
|
+
*
|
|
374
|
+
* See {@link isDataPrefix} for why this is hand-unrolled rather than using
|
|
375
|
+
* `String.prototype.startsWith`.
|
|
376
|
+
*
|
|
377
|
+
* ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58
|
|
378
|
+
*/
|
|
379
|
+
function isEventPrefix(chunk, i, firstCharCode) {
|
|
380
|
+
return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
|
|
381
|
+
}
|
|
382
|
+
function isPotentialField(line, field) {
|
|
383
|
+
let i = 1;
|
|
384
|
+
while (i < line.length && i < field.length) {
|
|
385
|
+
if (line.charCodeAt(i) !== field.charCodeAt(i)) return false;
|
|
386
|
+
i++;
|
|
387
|
+
}
|
|
388
|
+
return line.length <= field.length || line.charCodeAt(field.length) === 58;
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/events.ts
|
|
392
|
+
/**
|
|
393
|
+
* SSE subscriber that turns `GET {base_path}/events` into callbacks.
|
|
394
|
+
*
|
|
395
|
+
* One connection per (fetch, url), reference-counted and fanned out to every
|
|
396
|
+
* subscriber. The caller passes an already authenticated `fetch`: this is
|
|
397
|
+
* fetch streaming, not `EventSource`, because `EventSource` cannot send an
|
|
398
|
+
* Authorization header. Frames carry no `event:` name: the first has
|
|
399
|
+
* `id: 0` and `{"subscriber_id": ...}`; later ones are record events. A
|
|
400
|
+
* reconnect passes `?{resume_param}=<subscriber_id>` explicitly (never
|
|
401
|
+
* `Last-Event-ID`, which the server would read as a subscriber id) when the
|
|
402
|
+
* stream replays, and always tells subscribers to reset, so the gap is
|
|
403
|
+
* covered even without replay. A stream silent for three keep-alive
|
|
404
|
+
* intervals is dead and is reconnected with exponential backoff and jitter.
|
|
405
|
+
* Events invalidate; they never apply snapshots.
|
|
406
|
+
*/
|
|
407
|
+
const BACKOFF_START_MS = 1e3;
|
|
408
|
+
const BACKOFF_MAX_MS = 3e4;
|
|
409
|
+
const DEFAULT_KEEPALIVE_S = 15;
|
|
410
|
+
const DEAD_AFTER_KEEPALIVES = 3;
|
|
411
|
+
const ACTIONS = /* @__PURE__ */ new Set([
|
|
412
|
+
"create",
|
|
413
|
+
"update",
|
|
414
|
+
"delete",
|
|
415
|
+
"restore"
|
|
416
|
+
]);
|
|
417
|
+
const registry = /* @__PURE__ */ new WeakMap();
|
|
418
|
+
function sleep(ms, signal) {
|
|
419
|
+
return new Promise((resolve) => {
|
|
420
|
+
if (signal.aborted) {
|
|
421
|
+
resolve();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const done = () => {
|
|
425
|
+
clearTimeout(timer);
|
|
426
|
+
signal.removeEventListener("abort", done);
|
|
427
|
+
resolve();
|
|
428
|
+
};
|
|
429
|
+
const timer = setTimeout(done, ms);
|
|
430
|
+
signal.addEventListener("abort", done, { once: true });
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
var Hub = class {
|
|
434
|
+
handlers = /* @__PURE__ */ new Set();
|
|
435
|
+
options;
|
|
436
|
+
onEmpty;
|
|
437
|
+
controller;
|
|
438
|
+
connected = false;
|
|
439
|
+
subscriberId;
|
|
440
|
+
attempt = 0;
|
|
441
|
+
constructor(options, onEmpty) {
|
|
442
|
+
this.options = options;
|
|
443
|
+
this.onEmpty = onEmpty;
|
|
444
|
+
}
|
|
445
|
+
subscribe(handlers) {
|
|
446
|
+
const entry = {
|
|
447
|
+
onEvent: (event) => handlers.onEvent(event),
|
|
448
|
+
onReset: () => handlers.onReset?.(),
|
|
449
|
+
onStatus: (connected) => handlers.onStatus?.(connected)
|
|
450
|
+
};
|
|
451
|
+
this.handlers.add(entry);
|
|
452
|
+
if (this.controller === void 0) {
|
|
453
|
+
this.controller = new AbortController();
|
|
454
|
+
this.run(this.controller.signal);
|
|
455
|
+
} else if (this.connected) this.guard(() => entry.onStatus?.(true));
|
|
456
|
+
return () => {
|
|
457
|
+
if (!this.handlers.delete(entry)) return;
|
|
458
|
+
this.guard(() => entry.onStatus?.(false));
|
|
459
|
+
if (this.handlers.size === 0) {
|
|
460
|
+
this.controller?.abort();
|
|
461
|
+
this.controller = void 0;
|
|
462
|
+
this.onEmpty();
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
async guard(call) {
|
|
467
|
+
try {
|
|
468
|
+
await call();
|
|
469
|
+
} catch {}
|
|
470
|
+
}
|
|
471
|
+
async fan(call) {
|
|
472
|
+
for (const handlers of Array.from(this.handlers)) await this.guard(() => call(handlers));
|
|
473
|
+
}
|
|
474
|
+
async setConnected(connected) {
|
|
475
|
+
if (this.connected === connected) return;
|
|
476
|
+
this.connected = connected;
|
|
477
|
+
await this.fan((handlers) => handlers.onStatus?.(connected));
|
|
478
|
+
}
|
|
479
|
+
backoff() {
|
|
480
|
+
const base = Math.min(BACKOFF_START_MS * 2 ** this.attempt, BACKOFF_MAX_MS);
|
|
481
|
+
this.attempt += 1;
|
|
482
|
+
const jitter = (this.options.random ?? Math.random)();
|
|
483
|
+
return Math.min(base * (1 + .5 * jitter), BACKOFF_MAX_MS);
|
|
484
|
+
}
|
|
485
|
+
/** Connect, read and reconnect until aborted. */
|
|
486
|
+
async run(signal) {
|
|
487
|
+
try {
|
|
488
|
+
while (!signal.aborted) {
|
|
489
|
+
try {
|
|
490
|
+
await this.readOnce(signal);
|
|
491
|
+
} catch {}
|
|
492
|
+
await this.setConnected(false);
|
|
493
|
+
await sleep(this.backoff(), signal);
|
|
494
|
+
}
|
|
495
|
+
} finally {
|
|
496
|
+
await this.setConnected(false);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
resumeUrl() {
|
|
500
|
+
const { url, events } = this.options;
|
|
501
|
+
if (events.replay !== "persistent" || this.subscriberId === void 0) return url;
|
|
502
|
+
const param = events.resume_param ?? "subscriber_id";
|
|
503
|
+
return `${url}${url.includes("?") ? "&" : "?"}${encodeURIComponent(param)}=${encodeURIComponent(this.subscriberId)}`;
|
|
504
|
+
}
|
|
505
|
+
async readOnce(signal) {
|
|
506
|
+
const deadMs = (this.options.events.keepalive_seconds ?? DEFAULT_KEEPALIVE_S) * DEAD_AFTER_KEEPALIVES * 1e3;
|
|
507
|
+
const conn = new AbortController();
|
|
508
|
+
const abortConn = () => {
|
|
509
|
+
conn.abort();
|
|
510
|
+
};
|
|
511
|
+
signal.addEventListener("abort", abortConn, { once: true });
|
|
512
|
+
let timer;
|
|
513
|
+
const arm = () => {
|
|
514
|
+
clearTimeout(timer);
|
|
515
|
+
timer = setTimeout(abortConn, deadMs);
|
|
516
|
+
};
|
|
517
|
+
try {
|
|
518
|
+
arm();
|
|
519
|
+
const response = await this.options.fetch(this.resumeUrl(), {
|
|
520
|
+
headers: { accept: "text/event-stream" },
|
|
521
|
+
signal: conn.signal
|
|
522
|
+
});
|
|
523
|
+
if (!response.ok || response.body === null) throw new Error(`event stream refused: HTTP ${response.status}`);
|
|
524
|
+
const reader = response.body.getReader();
|
|
525
|
+
const decoder = new TextDecoder();
|
|
526
|
+
const pending = [];
|
|
527
|
+
const parser = createParser({ onEvent: (message) => {
|
|
528
|
+
pending.push(message);
|
|
529
|
+
} });
|
|
530
|
+
for (;;) {
|
|
531
|
+
const { done, value } = await reader.read();
|
|
532
|
+
if (done) return;
|
|
533
|
+
arm();
|
|
534
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
535
|
+
for (const message of pending.splice(0)) await this.handle(message);
|
|
536
|
+
}
|
|
537
|
+
} finally {
|
|
538
|
+
clearTimeout(timer);
|
|
539
|
+
signal.removeEventListener("abort", abortConn);
|
|
540
|
+
conn.abort();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async handle(message) {
|
|
544
|
+
let data;
|
|
545
|
+
try {
|
|
546
|
+
data = JSON.parse(message.data);
|
|
547
|
+
} catch {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return;
|
|
551
|
+
const record = data;
|
|
552
|
+
if ("subscriber_id" in record) {
|
|
553
|
+
this.subscriberId = String(record["subscriber_id"]);
|
|
554
|
+
this.attempt = 0;
|
|
555
|
+
await this.fan((handlers) => handlers.onReset?.());
|
|
556
|
+
await this.setConnected(true);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (record["resource"] !== this.options.events.resource) return;
|
|
560
|
+
const action = record["action"];
|
|
561
|
+
const recordId = record["record_id"];
|
|
562
|
+
if (!ACTIONS.has(action) || !Number.isInteger(recordId)) {
|
|
563
|
+
await this.fan((handlers) => handlers.onReset?.());
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const snapshot = record["snapshot"];
|
|
567
|
+
const event = {
|
|
568
|
+
resource: String(record["resource"]),
|
|
569
|
+
recordId,
|
|
570
|
+
action,
|
|
571
|
+
snapshot: typeof snapshot === "object" && snapshot !== null ? snapshot : {},
|
|
572
|
+
timestamp: String(record["timestamp"] ?? "")
|
|
573
|
+
};
|
|
574
|
+
await this.fan((handlers) => handlers.onEvent(event));
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
/**
|
|
578
|
+
* The shared hub for `(fetch, url)`: the first subscriber opens the stream,
|
|
579
|
+
* the last one aborts it. A later subscribe after that opens a fresh one.
|
|
580
|
+
*/
|
|
581
|
+
function getEventHub(options) {
|
|
582
|
+
return { subscribe(handlers) {
|
|
583
|
+
let hubs = registry.get(options.fetch);
|
|
584
|
+
if (hubs === void 0) {
|
|
585
|
+
hubs = /* @__PURE__ */ new Map();
|
|
586
|
+
registry.set(options.fetch, hubs);
|
|
587
|
+
}
|
|
588
|
+
const byUrl = hubs;
|
|
589
|
+
let hub = byUrl.get(options.url);
|
|
590
|
+
if (hub === void 0) {
|
|
591
|
+
hub = new Hub(options, () => {
|
|
592
|
+
byUrl.delete(options.url);
|
|
593
|
+
});
|
|
594
|
+
byUrl.set(options.url, hub);
|
|
595
|
+
}
|
|
596
|
+
return hub.subscribe(handlers);
|
|
597
|
+
} };
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Subscribe to one resource's event stream. Returns the unsubscribe; the
|
|
601
|
+
* last unsubscribe aborts the stream. `signal` aborting unsubscribes too.
|
|
602
|
+
*/
|
|
603
|
+
function subscribeEvents(options) {
|
|
604
|
+
const { signal } = options;
|
|
605
|
+
if (signal?.aborted === true) return () => void 0;
|
|
606
|
+
const unsubscribe = getEventHub(options).subscribe(options);
|
|
607
|
+
signal?.addEventListener("abort", unsubscribe, { once: true });
|
|
608
|
+
return () => {
|
|
609
|
+
signal?.removeEventListener("abort", unsubscribe);
|
|
610
|
+
unsubscribe();
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
export { tanstackCachePolicy as i, subscribeEvents as n, cachePolicyFromManifest as r, getEventHub as t };
|
|
615
|
+
|
|
616
|
+
//# sourceMappingURL=events-Cqj9Rbon.mjs.map
|