@auersperg/flare 0.3.9 → 0.3.10
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 +3 -1
- package/dist/cli/{chunk-5s6zv1h9.js → chunk-jrptpjmm.js} +1 -1
- package/dist/cli/{chunk-0s0vrjtw.js → chunk-m0698kap.js} +9 -6
- package/dist/cli/{chunk-5yg1zj5n.js → chunk-znktbexz.js} +6 -6
- package/dist/cli/{chunk-vx7kmczm.js → chunk-zt96ghm7.js} +12 -6
- package/dist/cli/flare.js +5 -5
- package/dist/platform/admin.js +31 -23
- package/dist/platform/dashboard/_astro/{auth-view.EASEF9BN.js → auth-view.BhUFPmNv.js} +1 -1
- package/dist/platform/dashboard/_astro/{cli-approval.DwZVHdyo.js → cli-approval.emPTVRqn.js} +1 -1
- package/dist/platform/dashboard/_astro/client.4z54xJ_z.js +1 -0
- package/dist/platform/dashboard/_astro/{common.TAClb6G9.js → common.9pujiaSZ.js} +1 -1
- package/dist/platform/dashboard/_astro/{data-view.BaHEU4Xa.js → data-view.C-VU8Oxm.js} +1 -1
- package/dist/platform/dashboard/_astro/{functions-view.BGOYuDwu.js → functions-view.3yLrtS0h.js} +1 -1
- package/dist/platform/dashboard/_astro/{index.astro_astro_type_script_index_0_lang.Blg5hU9g.js → index.astro_astro_type_script_index_0_lang.Dsfj1oeZ.js} +2 -2
- package/dist/platform/dashboard/_astro/{input-group.DUp-h0xx.js → input-group.BZ1TZT7L.js} +1 -1
- package/dist/platform/dashboard/_astro/{sdk-view.2ErPn4cO.js → sdk-view.BAiQKNm8.js} +1 -1
- package/dist/platform/dashboard/_astro/{services-view.iZujitxl.js → services-view.BSrqpyr5.js} +1 -1
- package/dist/platform/dashboard/_astro/{settings-view.CqK-mrlB.js → settings-view.C_eYjlIE.js} +1 -1
- package/dist/platform/dashboard/bootstrap/facet.js +64 -46
- package/dist/platform/dashboard/bootstrap/openapi.json +1 -1
- package/dist/platform/dashboard/bootstrap/release.json +2 -2
- package/dist/platform/dashboard/index.html +1 -1
- package/dist/platform/manifest.json +36 -36
- package/dist/platform/runtime.js +99 -66
- package/dist/sdk/backup.js +4 -4
- package/dist/sdk/client.js +22 -41
- package/dist/sdk/cloudflare.js +10 -11
- package/dist/sdk/database.js +45 -27
- package/dist/sdk/http.d.ts +2 -0
- package/dist/sdk/http.js +27 -0
- package/dist/sdk/schema.d.ts +2 -2
- package/dist/sdk/schema.js +7 -6
- package/package.json +2 -2
- package/dist/platform/dashboard/_astro/client.BUUZgEFj.js +0 -1
package/dist/sdk/database.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FlareError, boundedJson, stableJson } from "./protocol.js";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
documentJson,
|
|
4
4
|
fieldSql,
|
|
5
5
|
quote
|
|
6
6
|
} from "./schema.js";
|
|
@@ -64,7 +64,7 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
64
64
|
function query(name) {
|
|
65
65
|
const definition = table(name);
|
|
66
66
|
function make(index, filters, direction) {
|
|
67
|
-
const page = (options) => task(async () => {
|
|
67
|
+
const page = (options, withCursor = true) => task(async () => {
|
|
68
68
|
table(name);
|
|
69
69
|
size(options.limit);
|
|
70
70
|
const fields = [
|
|
@@ -73,9 +73,18 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
73
73
|
"_id"
|
|
74
74
|
];
|
|
75
75
|
const expressions = fields.map(fieldSql);
|
|
76
|
-
const signature = stableJson({ name, index, filters, direction });
|
|
76
|
+
const signature = withCursor ? stableJson({ name, index, filters, direction }) : "";
|
|
77
|
+
const fixed = filters.filter((f) => f.operator === "=");
|
|
77
78
|
const where = filters.map((f) => `${fieldSql(f.field)} ${f.operator === "=" ? "IS" : f.operator} ?`);
|
|
78
79
|
const bindings = filters.map((f) => bind(f.value));
|
|
80
|
+
const rowLimit = options.limit + (withCursor ? 1 : 0);
|
|
81
|
+
const rows = [];
|
|
82
|
+
const read = (terms = [], values = [], orderStart = fixed.length) => {
|
|
83
|
+
if (rows.length >= rowLimit)
|
|
84
|
+
return;
|
|
85
|
+
const conditions = [...terms, ...where];
|
|
86
|
+
rows.push(...sql.exec(`SELECT *${withCursor ? "," + expressions.map((e, i) => `${e} AS __k${i}`).join(",") : ""} FROM ${quote(name)} ${conditions.length ? "WHERE " + conditions.join(" AND ") : ""} ORDER BY ${expressions.slice(orderStart).map((e) => `${e} ${direction}`).join(",")} LIMIT ?`, ...values, ...bindings, rowLimit - rows.length).toArray());
|
|
87
|
+
};
|
|
79
88
|
if (options.cursor != null) {
|
|
80
89
|
let token;
|
|
81
90
|
try {
|
|
@@ -85,23 +94,32 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
85
94
|
} catch {
|
|
86
95
|
throw new FlareError("INVALID_CURSOR", "Invalid cursor");
|
|
87
96
|
}
|
|
88
|
-
if (token.signature !== signature || !Array.isArray(token.values) || token.values.length !== fields.length || !token.values.every(scalar))
|
|
97
|
+
if (!token || token.signature !== signature || !Array.isArray(token.values) || token.values.length !== fields.length || !token.values.every(scalar) || typeof token.values.at(-2) !== "number" || typeof token.values.at(-1) !== "string")
|
|
89
98
|
throw new FlareError("INVALID_CURSOR", "Cursor belongs to a different query");
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
if (fixed.some((f, i) => bind(f.value) !== bind(token.values[i])))
|
|
100
|
+
throw new FlareError("INVALID_CURSOR", "Cursor violates index range");
|
|
101
|
+
const first = fixed.length;
|
|
102
|
+
for (let i = expressions.length - 2;i >= first && rows.length < rowLimit; i--) {
|
|
103
|
+
const prefix = expressions.slice(first, i).map((e) => `${e} IS ?`);
|
|
104
|
+
const values = token.values.slice(first, i).map(bind);
|
|
105
|
+
const expr = expressions[i], value = token.values[i];
|
|
106
|
+
if (i === expressions.length - 2) {
|
|
107
|
+
read([
|
|
108
|
+
...prefix,
|
|
109
|
+
`(${expressions.slice(i).join(",")}) ${direction === "asc" ? ">" : "<"} (?,?)`
|
|
110
|
+
], [...values, ...token.values.slice(i).map(bind)], i);
|
|
111
|
+
} else if (direction === "asc") {
|
|
112
|
+
read([
|
|
113
|
+
...prefix,
|
|
114
|
+
value === null ? `${expr} IS NOT NULL` : `${expr} > ?`
|
|
115
|
+
], value === null ? values : [...values, bind(value)], i);
|
|
116
|
+
} else if (value !== null) {
|
|
117
|
+
read([...prefix, `${expr} < ?`], [...values, bind(value)], i);
|
|
118
|
+
read([...prefix, `${expr} IS NULL`], values, i + 1);
|
|
99
119
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
const rows = sql.exec(`SELECT *,${expressions.map((e, i) => `${e} AS __k${i}`).join(",")} FROM ${quote(name)} ${where.length ? "WHERE " + where.join(" AND ") : ""} ORDER BY ${expressions.map((e) => `${e} ${direction}`).join(",")} LIMIT ?`, ...bindings, options.limit + 1).toArray();
|
|
120
|
+
}
|
|
121
|
+
} else
|
|
122
|
+
read();
|
|
105
123
|
let more = rows.length > options.limit;
|
|
106
124
|
if (more)
|
|
107
125
|
rows.pop();
|
|
@@ -117,7 +135,7 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
117
135
|
bytes += length;
|
|
118
136
|
}
|
|
119
137
|
const last = rows[docs.length - 1];
|
|
120
|
-
const continueCursor = more && last ? btoa(String.fromCharCode(...new TextEncoder().encode(boundedJson({
|
|
138
|
+
const continueCursor = withCursor && more && last ? btoa(String.fromCharCode(...new TextEncoder().encode(boundedJson({
|
|
121
139
|
signature,
|
|
122
140
|
values: expressions.map((_, i) => last[`__k${i}`])
|
|
123
141
|
}, 6144)))) : null;
|
|
@@ -174,14 +192,14 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
174
192
|
paginate: page,
|
|
175
193
|
take(limit) {
|
|
176
194
|
return task(async () => {
|
|
177
|
-
const result = await page({ limit });
|
|
195
|
+
const result = await page({ limit }, false);
|
|
178
196
|
if (!result.isDone && result.page.length < limit)
|
|
179
197
|
throw new FlareError("TOO_LARGE", "Use paginate for large documents");
|
|
180
198
|
return result.page;
|
|
181
199
|
}, false);
|
|
182
200
|
},
|
|
183
201
|
first() {
|
|
184
|
-
return task(async () => (await page({ limit: 1 })).page[0] ?? null, false);
|
|
202
|
+
return task(async () => (await page({ limit: 1 }, false)).page[0] ?? null, false);
|
|
185
203
|
}
|
|
186
204
|
});
|
|
187
205
|
}
|
|
@@ -192,10 +210,10 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
192
210
|
insert(name, input) {
|
|
193
211
|
return task(async () => {
|
|
194
212
|
writing();
|
|
195
|
-
const definition = table(name), value = await
|
|
213
|
+
const definition = table(name), value = await documentJson(definition, input);
|
|
196
214
|
writing();
|
|
197
215
|
const id = crypto.randomUUID();
|
|
198
|
-
sql.exec(`INSERT INTO ${quote(name)} VALUES (?,?,?)`, id, Date.now(),
|
|
216
|
+
sql.exec(`INSERT INTO ${quote(name)} VALUES (?,?,?)`, id, Date.now(), value);
|
|
199
217
|
changed.add(name);
|
|
200
218
|
return id;
|
|
201
219
|
});
|
|
@@ -211,12 +229,12 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
211
229
|
const row = sql.exec(`SELECT _data FROM ${quote(name)} WHERE _id=?`, id).toArray()[0];
|
|
212
230
|
if (!row)
|
|
213
231
|
throw new FlareError("NOT_FOUND", "Document not found");
|
|
214
|
-
const value = await
|
|
232
|
+
const value = await documentJson(definition, {
|
|
215
233
|
...JSON.parse(String(row._data)),
|
|
216
234
|
...patch
|
|
217
235
|
});
|
|
218
236
|
writing();
|
|
219
|
-
sql.exec(`UPDATE ${quote(name)} SET _data=? WHERE _id=?`,
|
|
237
|
+
sql.exec(`UPDATE ${quote(name)} SET _data=? WHERE _id=?`, value, id);
|
|
220
238
|
changed.add(name);
|
|
221
239
|
});
|
|
222
240
|
},
|
|
@@ -225,9 +243,9 @@ export function database(sql, schema, writable, touched = new Set, changed = new
|
|
|
225
243
|
writing();
|
|
226
244
|
const definition = table(name);
|
|
227
245
|
checkedId(id);
|
|
228
|
-
const value = await
|
|
246
|
+
const value = await documentJson(definition, input);
|
|
229
247
|
writing();
|
|
230
|
-
const rows = sql.exec(`UPDATE ${quote(name)} SET _data=? WHERE _id=? RETURNING _id`,
|
|
248
|
+
const rows = sql.exec(`UPDATE ${quote(name)} SET _data=? WHERE _id=? RETURNING _id`, value, id).toArray();
|
|
231
249
|
if (!rows.length)
|
|
232
250
|
throw new FlareError("NOT_FOUND", "Document not found");
|
|
233
251
|
changed.add(name);
|
package/dist/sdk/http.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export async function readBody(body, limit, tooLarge) {
|
|
2
|
+
const reader = body.getReader();
|
|
3
|
+
const chunks = [];
|
|
4
|
+
let size = 0;
|
|
5
|
+
try {
|
|
6
|
+
for (;; ) {
|
|
7
|
+
const { done, value } = await reader.read();
|
|
8
|
+
if (done)
|
|
9
|
+
break;
|
|
10
|
+
size += value.byteLength;
|
|
11
|
+
if (size > limit)
|
|
12
|
+
tooLarge();
|
|
13
|
+
chunks.push(value);
|
|
14
|
+
}
|
|
15
|
+
} finally {
|
|
16
|
+
await reader.cancel().catch(() => {});
|
|
17
|
+
}
|
|
18
|
+
if (chunks.length === 1)
|
|
19
|
+
return chunks[0];
|
|
20
|
+
const bytes = new Uint8Array(size);
|
|
21
|
+
let offset = 0;
|
|
22
|
+
for (const chunk of chunks) {
|
|
23
|
+
bytes.set(chunk, offset);
|
|
24
|
+
offset += chunk.byteLength;
|
|
25
|
+
}
|
|
26
|
+
return bytes;
|
|
27
|
+
}
|
package/dist/sdk/schema.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { StandardSchemaV1, StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import { type Json } from "./protocol.js";
|
|
3
2
|
export type SqlValue = ArrayBuffer | string | number | null;
|
|
4
3
|
/** The small structural portion of native storage needed by the adapter; no Worker globals in public types. */
|
|
5
4
|
export interface NativeSql {
|
|
@@ -39,5 +38,6 @@ export declare function defineTable<V extends Validator>(validator: V): Table<V,
|
|
|
39
38
|
export declare function schemaContract(schema: Schema): SchemaContract;
|
|
40
39
|
export declare function assertSchemaContract(contract: SchemaContract): void;
|
|
41
40
|
export declare function defineSchema<const S extends Schema>(schema: S): S;
|
|
42
|
-
|
|
41
|
+
/** Validate once and return the canonical JSON written by all storage paths. */
|
|
42
|
+
export declare function documentJson(table: TableDefinition, input: unknown): Promise<string>;
|
|
43
43
|
export declare function applySchema(storage: NativeStorage, schema: Schema): Promise<void>;
|
package/dist/sdk/schema.js
CHANGED
|
@@ -92,7 +92,7 @@ function documentSchema(table) {
|
|
|
92
92
|
}
|
|
93
93
|
return doc;
|
|
94
94
|
}
|
|
95
|
-
export async function
|
|
95
|
+
export async function documentJson(table, input) {
|
|
96
96
|
boundedJson(input, 65536);
|
|
97
97
|
const properties = documentSchema(table).properties ?? {};
|
|
98
98
|
if (!input || Array.isArray(input) || typeof input !== "object" || Object.keys(input).some((k) => !Object.hasOwn(properties, k)))
|
|
@@ -100,7 +100,7 @@ export async function documentValue(table, input) {
|
|
|
100
100
|
const result = await table.validator["~standard"].validate(input);
|
|
101
101
|
if (result.issues || !result.value || Array.isArray(result.value) || typeof result.value !== "object" || Object.keys(result.value).some((k) => !Object.hasOwn(properties, k)))
|
|
102
102
|
throw new FlareError("INVALID_INPUT", "Document failed schema validation");
|
|
103
|
-
return
|
|
103
|
+
return boundedJson(result.value, 65536);
|
|
104
104
|
}
|
|
105
105
|
export async function applySchema(storage, schema) {
|
|
106
106
|
const contract = schemaContract(schema), encoded = stableJson(contract);
|
|
@@ -118,13 +118,14 @@ export async function applySchema(storage, schema) {
|
|
|
118
118
|
}
|
|
119
119
|
for (const [name, definition] of Object.entries(contract)) {
|
|
120
120
|
storage.sql.exec(`CREATE TABLE IF NOT EXISTS ${quote(name)} (_id TEXT PRIMARY KEY, _creationTime REAL NOT NULL, _data TEXT NOT NULL CHECK(json_valid(_data)))`);
|
|
121
|
+
const changed = !old[name] || stableJson(old[name].document) !== stableJson(definition.document);
|
|
121
122
|
let cursor = "";
|
|
122
|
-
while (
|
|
123
|
+
while (changed) {
|
|
123
124
|
const rows = storage.sql.exec(`SELECT _id,_data FROM ${quote(name)} WHERE _id > ? ORDER BY _id LIMIT 100`, cursor).toArray();
|
|
124
125
|
for (const row of rows) {
|
|
125
|
-
const value = await
|
|
126
|
-
if (
|
|
127
|
-
storage.sql.exec(`UPDATE ${quote(name)} SET _data=? WHERE _id=?`,
|
|
126
|
+
const value = await documentJson(schema[name], JSON.parse(String(row._data)));
|
|
127
|
+
if (value !== stableJson(JSON.parse(String(row._data))))
|
|
128
|
+
storage.sql.exec(`UPDATE ${quote(name)} SET _data=? WHERE _id=?`, value, row._id);
|
|
128
129
|
}
|
|
129
130
|
if (rows.length < 100)
|
|
130
131
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auersperg/flare",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Flare client, server declarations and development CLI",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@standard-schema/spec": "1.1.0",
|
|
39
|
-
"zod": "4.6.
|
|
39
|
+
"zod": "4.6.6",
|
|
40
40
|
"json-schema-to-typescript": "16.0.0"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Object.freeze({inputBytes:65536,outputBytes:262144,frameBytes:8192,subscriptions:64,fileBytes:10485760}),t=class extends Error{code;ambiguous;constructor(e,t,n=!1){super(t),this.code=e,this.ambiguous=n,this.name=`FlareError`}};function n(e){if(e.length>256||!/^[A-Za-z][A-Za-z0-9_]*(\/[A-Za-z][A-Za-z0-9_]*)*$/.test(e)||e.split(`/`).some(e=>[`__proto__`,`prototype`,`constructor`].includes(e)))throw new t(`INVALID_PATH`,`Invalid function path`)}function r(e,t){return n(e),Object.freeze({path:e,kind:t})}function i(e){let n=new Set;function r(e,i){if(i>40)throw new t(`INVALID_JSON`,`JSON nesting limit exceeded`);if(e===null||typeof e==`string`||typeof e==`boolean`||typeof e==`number`&&Number.isFinite(e))return e;if(typeof e!=`object`||!e||n.has(e))throw new t(`INVALID_JSON`,`Expected lossless JSON`);if(!Array.isArray(e)&&Object.getPrototypeOf(e)!==Object.prototype&&Object.getPrototypeOf(e)!==null)throw new t(`INVALID_JSON`,`Expected plain JSON object`);if(Object.getOwnPropertySymbols(e).length)throw new t(`INVALID_JSON`,`Symbol keys are unsupported`);n.add(e);let a;if(Array.isArray(e))a=Array.from(e,e=>r(e,i+1));else{let n=Object.create(null);for(let a of Object.keys(e).sort()){let o=Object.getOwnPropertyDescriptor(e,a);if(!(`value`in o))throw new t(`INVALID_JSON`,`Getters are unsupported`);n[a]=r(o.value,i+1)}a=n}return n.delete(e),a}return JSON.stringify(r(e,0))}function a(e,n){let r=i(e);if(new TextEncoder().encode(r).byteLength>n)throw new t(`TOO_LARGE`,`JSON payload exceeds limit`);return r}function o(n){if(n.size>e.fileBytes)throw new t(`TOO_LARGE`,`Maximum file size: 10 MiB`);if(!n.name||n.name.length>120||/[\x00-\x1f\x7f/\\]/.test(n.name))throw new t(`INVALID_INPUT`,`File name must be 1–120 characters, without path separators`);return{name:n.name,size:n.size,type:[`text/plain`,`image/png`,`image/jpeg`,`image/webp`,`application/pdf`].includes(n.type)?n.type:`application/octet-stream`}}async function s(n,r,i={}){if(r.size>e.fileBytes)throw new t(`TOO_LARGE`,`Maximum file size: 10 MiB`);let a=new URL(n.url);if(a.username||a.password||a.protocol!==`https:`&&!(a.protocol===`http:`&&[`localhost`,`127.0.0.1`,`[::1]`].includes(a.hostname)))throw new t(`INVALID_URL`,`Expected secure upload URL`);let o;try{o=await(i.fetch??globalThis.fetch)(a.href,{method:`POST`,body:r,redirect:`error`,credentials:`omit`,signal:i.signal??AbortSignal.timeout(i.timeoutMs??12e4)})}catch{throw new t(`UPLOAD_UNKNOWN`,`Connection interrupted or upload timed out; refresh the file list before trying again`,!0)}let s=await o.json().catch(()=>null);if(!o.ok){let e=s&&typeof s==`object`&&`error`in s&&typeof s.error==`string`?s.error:`HTTP_${o.status}`;throw new t(e,`Upload failed: ${e}`,o.status>=500)}if(!s||typeof s!=`object`||!(`id`in s)||typeof s.id!=`string`)throw new t(`INVALID_RESPONSE`,`Upload outcome is unknown; refresh the file list before uploading again`,!0);return{id:s.id}}var c=new Set([`UNAUTHORIZED`,`AUTH_EXPIRED`,`FORBIDDEN`,`AUTH_DISABLED`,`HTTP_401`,`HTTP_403`]);function l(r){let o=new URL(r.url);if(!/^https?:$/.test(o.protocol)||o.username||o.password||o.search||o.hash)throw new t(`INVALID_URL`,`Expected backend HTTP URL`);if(o.protocol===`http:`&&![`localhost`,`127.0.0.1`,`[::1]`].includes(o.hostname))throw new t(`INVALID_URL`,`Remote backends require HTTPS`);o.pathname=o.pathname.replace(/\/$/,``);let s=r.fetch??globalThis.fetch;if(!s)throw new t(`TRANSPORT_UNAVAILABLE`,`Provide a fetch implementation`);let l=new Map,u=!1,d,f=!1,p=0,m=0,h=0,g,_=new Set,v=e=>`${o.href.replace(/\/$/,``)}${e}`,y=(e,t,n)=>{for(let r of e.observers)try{r[t]?.(t===`next`?JSON.parse(i(n)):n)}catch{}};function b(){if(u)throw new t(`DISPOSED`,`Client is disposed`)}async function x(n,i,o){b();let c=a(i,e.inputBytes),l;try{l=await r.getToken?.()??null}catch{throw new t(`AUTH_UNAVAILABLE`,`Cannot obtain access token`)}b();let u=new AbortController,d=setTimeout(()=>u.abort(),r.requestTimeoutMs??3e4);try{let r=await s(v(n),{method:`POST`,headers:{"Content-Type":`application/json`,...l?{Authorization:`Bearer ${l}`}:{}},body:c,signal:u.signal,redirect:`error`});if(!r.body)throw new t(`INVALID_RESPONSE`,`Missing response body`,o);let i=r.body.getReader(),d=0,f=[];try{for(;;){let{done:n,value:r}=await i.read();if(n)break;if(d+=r.byteLength,d>e.outputBytes)throw new t(`INVALID_RESPONSE`,`Response exceeds limit`,o);f.push(r)}}finally{await i.cancel().catch(()=>{})}let p=new Uint8Array(d),m=0;for(let e of f)p.set(e,m),m+=e.byteLength;let h;try{h=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(p))}catch(e){throw r.ok?e:new t(`HTTP_${r.status}`,`Backend request failed`,o&&r.status>=500)}if(!r.ok)throw new t(h&&typeof h==`object`&&`error`in h&&typeof h.error==`string`&&/^[A-Z][A-Z0-9_]{0,63}$/.test(h.error)?h.error:`HTTP_${r.status}`,`Backend request failed`,o&&r.status>=500);if(!h||typeof h!=`object`||!(`result`in h))throw new t(`INVALID_RESPONSE`,`Invalid protocol envelope`,o);return a(h.result,e.outputBytes),h.result}catch(e){throw e instanceof t?e:e instanceof SyntaxError?new t(`INVALID_RESPONSE`,`Invalid JSON response`,o):new t(`TRANSPORT_ERROR`,o?`Write outcome is unknown; do not replay automatically`:`Backend request interrupted`,o)}finally{clearTimeout(d)}}function S(t){d?.send(a(t,e.frameBytes))}function C(e){S({type:`subscribe`,id:e.id,path:e.ref.path,args:e.args})}function w(){if(u||!l.size||g)return;let e=Math.min(3e4,250*2**Math.min(p++,7));g=setTimeout(()=>{g=void 0,T()},e)}async function T(){if(u||f||d||!l.size)return;f=!0;let n=++m;try{let o=await x(`/v1/live/ticket`,{},!1);if(u||n!==m||!l.size)return;if(!o||typeof o.ticket!=`string`||o.ticket.length>1024)throw new t(`INVALID_RESPONSE`,`Invalid live ticket`);let s=new URL(v(`/v1/live`));s.protocol=s.protocol===`https:`?`wss:`:`ws:`,s.searchParams.set(`ticket`,o.ticket);let f=r.createWebSocket?r.createWebSocket(s.href):new globalThis.WebSocket(s.href);d=f;let h=()=>{if(!(d!==f||u)){p=0;for(let e of l.values())e.revision=-1,e.hasValue=!1,e.terminal||C(e)}},g=n=>{if(!(d!==f||u))try{if(typeof n.data!=`string`||new TextEncoder().encode(n.data).byteLength>e.outputBytes+1024)throw Error(`Invalid frame`);let r=JSON.parse(n.data);if(r.type===`ready`)return;let o=[...l.values()].find(e=>e.id===r.id);if(!o||o.terminal)return;if(r.type===`error`){o.terminal=!0,c.has(r.code)&&(o.hasValue=!1,o.value=void 0),y(o,`error`,new t(typeof r.code==`string`?r.code:`LIVE_ERROR`,`Live subscription failed`));return}if(r.type!==`snapshot`||!Number.isSafeInteger(r.revision)||r.revision<0||!(`result`in r))throw Error(`Invalid snapshot`);let s=a(r.result,e.outputBytes);if(r.revision>o.revision){let e=!o.hasValue||i(o.value)!==s;o.revision=r.revision,o.value=r.result,o.hasValue=!0,e&&y(o,`next`,o.value)}S({type:`ack`,id:o.id,revision:o.revision})}catch{f.close(1002,`Invalid protocol frame`)}},b=e=>{if(E(),d===f){d=void 0;for(let n of l.values())e.code===1008&&(n.hasValue=!1,n.value=void 0,n.revision=-1),n.terminal||y(n,`error`,new t(e.code===1008?`AUTH_EXPIRED`:`LIVE_DISCONNECTED`,`Live reconnecting; next snapshot will be current`));w()}},T=()=>f.close(),E=()=>{f.removeEventListener(`open`,h),f.removeEventListener(`message`,g),f.removeEventListener(`close`,b),f.removeEventListener(`error`,T),_.delete(E)};_.add(E),f.addEventListener(`open`,h),f.addEventListener(`message`,g),f.addEventListener(`close`,b),f.addEventListener(`error`,T)}catch(e){if(u||n!==m)return;for(let n of l.values())e instanceof t&&c.has(e.code)&&(n.hasValue=!1,n.value=void 0,n.revision=-1),y(n,`error`,e instanceof t?e:new t(`LIVE_UNAVAILABLE`,`Live connection failed`));w()}finally{f=!1,!u&&n!==m&&!d&&l.size&&w()}}function E(e){return async(r,i)=>{if(n(r.path),r.kind!==e)throw new t(`INVALID_KIND`,`Expected a ${e} function`);return x(`/v1/call`,{path:r.path,kind:r.kind,args:i},r.kind===`mutation`||r.kind===`action`)}}return{query:E(`query`),mutation:E(`mutation`),action:E(`action`),onUpdate(r,o,s,c){let u={next:s,error:c};if(b(),n(r.path),r.kind!==`query`)throw new t(`INVALID_KIND`,`Only queries are subscribable`);a({type:`subscribe`,id:`subscription-9999999999`,path:r.path,args:o},e.frameBytes);let f=i({path:r.path,args:o}),p=l.get(f);if(!p){if(l.size>=e.subscriptions)throw new t(`TOO_MANY_SUBSCRIPTIONS`,`Subscription limit exceeded`);p={id:`subscription-${++h}`,ref:r,args:JSON.parse(i(o)),observers:new Set,revision:-1,hasValue:!1,terminal:!1},l.set(f,p),d?.readyState===1&&C(p)}let v={next:u.next,error:u.error};p.observers.add(v),p.hasValue&&y({...p,observers:new Set([u])},`next`,p.value),T();let x=!1;return()=>{if(!x&&(x=!0,p.observers.delete(v),p.observers.size||(l.delete(f),d?.readyState===1&&S({type:`unsubscribe`,id:p.id})),!l.size)){++m,g&&clearTimeout(g),g=void 0;let e=d;d=void 0;for(let e of[..._])e();e?.close(1e3,`No subscriptions`)}}},refreshAuth(){b(),++m,g&&clearTimeout(g),g=void 0;for(let e of[..._])e();let e=d;d=void 0,e?.close(1e3,`Authorization changed`);for(let e of l.values())e.hasValue=!1,e.value=void 0,e.revision=-1,e.terminal=!1,y(e,`error`,new t(`AUTH_CHANGED`,`Authorization changed; cached value invalidated`));T()},dispose(){if(!u){u=!0,++m,g&&clearTimeout(g),g=void 0;for(let e of[..._])e();l.clear(),d?.close(1e3,`Disposed`),d=void 0}}}}export{r as i,o as n,s as r,l as t};
|