@typicalday/firegraph 0.10.0 → 0.11.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/README.md +93 -90
- package/bin/firegraph.mjs +21 -7
- package/dist/{backend-BrqFkbid.d.ts → backend-U-MLShlg.d.ts} +1 -1
- package/dist/{backend-73p5Blx7.d.cts → backend-np4gEVhB.d.cts} +1 -1
- package/dist/backend.d.cts +3 -3
- package/dist/backend.d.ts +3 -3
- package/dist/{chunk-LZOIQHYN.js → chunk-6SB34IPQ.js} +20 -7
- package/dist/chunk-6SB34IPQ.js.map +1 -0
- package/dist/{chunk-SU4FNLC3.js → chunk-EEKWRX5E.js} +1 -1
- package/dist/{chunk-SU4FNLC3.js.map → chunk-EEKWRX5E.js.map} +1 -1
- package/dist/{chunk-YLGXLEUE.js → chunk-GJVVRTQT.js} +5 -14
- package/dist/chunk-GJVVRTQT.js.map +1 -0
- package/dist/cloudflare/index.cjs +151 -27
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +78 -3
- package/dist/cloudflare/index.d.ts +78 -3
- package/dist/cloudflare/index.js +135 -23
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.cjs +4 -13
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/codegen/index.js +1 -1
- package/dist/index.cjs +89 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -21
- package/dist/index.d.ts +106 -21
- package/dist/index.js +70 -116
- package/dist/index.js.map +1 -1
- package/dist/query-client/index.cjs.map +1 -1
- package/dist/query-client/index.js +1 -1
- package/dist/{types-DOemdlVA.d.ts → types-BGWxcpI_.d.cts} +75 -1
- package/dist/{types-DOemdlVA.d.cts → types-BGWxcpI_.d.ts} +75 -1
- package/package.json +35 -27
- package/dist/chunk-LZOIQHYN.js.map +0 -1
- package/dist/chunk-YLGXLEUE.js.map +0 -1
- package/dist/editor/client/assets/index-Bq2bfzeY.js +0 -411
- package/dist/editor/client/assets/index-CJ4m_EOL.css +0 -1
- package/dist/editor/client/index.html +0 -16
- package/dist/editor/server/index.mjs +0 -51566
|
@@ -159,6 +159,7 @@ var cloudflare_exports = {};
|
|
|
159
159
|
__export(cloudflare_exports, {
|
|
160
160
|
DORPCBackend: () => DORPCBackend,
|
|
161
161
|
FiregraphDO: () => FiregraphDO,
|
|
162
|
+
buildDOSchemaStatements: () => buildDOSchemaStatements,
|
|
162
163
|
createDOClient: () => createDOClient,
|
|
163
164
|
createSiblingClient: () => createSiblingClient
|
|
164
165
|
});
|
|
@@ -258,6 +259,116 @@ var GraphTimestampImpl = class _GraphTimestampImpl {
|
|
|
258
259
|
}
|
|
259
260
|
};
|
|
260
261
|
|
|
262
|
+
// src/default-indexes.ts
|
|
263
|
+
var DEFAULT_CORE_INDEXES = Object.freeze([
|
|
264
|
+
{ fields: ["aUid"] },
|
|
265
|
+
{ fields: ["bUid"] },
|
|
266
|
+
{ fields: ["aType"] },
|
|
267
|
+
{ fields: ["bType"] },
|
|
268
|
+
{ fields: ["aUid", "axbType"] },
|
|
269
|
+
{ fields: ["axbType", "bUid"] },
|
|
270
|
+
{ fields: ["aType", "axbType"] },
|
|
271
|
+
{ fields: ["axbType", "bType"] }
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
// src/internal/sqlite-index-ddl.ts
|
|
275
|
+
var IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
276
|
+
var JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
277
|
+
function quoteIdent(name) {
|
|
278
|
+
if (!IDENT_RE.test(name)) {
|
|
279
|
+
throw new FiregraphError(
|
|
280
|
+
`Invalid SQL identifier in index DDL: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,
|
|
281
|
+
"INVALID_INDEX"
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return `"${name}"`;
|
|
285
|
+
}
|
|
286
|
+
function fnv1a32(str) {
|
|
287
|
+
let h = 2166136261;
|
|
288
|
+
for (let i = 0; i < str.length; i++) {
|
|
289
|
+
h ^= str.charCodeAt(i);
|
|
290
|
+
h = Math.imul(h, 16777619);
|
|
291
|
+
}
|
|
292
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
293
|
+
}
|
|
294
|
+
function normalizeFields(fields) {
|
|
295
|
+
return fields.map((f) => {
|
|
296
|
+
if (typeof f === "string") return { path: f, desc: false };
|
|
297
|
+
if (!f.path || typeof f.path !== "string") {
|
|
298
|
+
throw new FiregraphError(
|
|
299
|
+
`IndexSpec field must be a string or { path: string, desc?: boolean }; got ${JSON.stringify(f)}`,
|
|
300
|
+
"INVALID_INDEX"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return { path: f.path, desc: !!f.desc };
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function specFingerprint(spec, leadingColumns) {
|
|
307
|
+
const normalized = {
|
|
308
|
+
lead: leadingColumns,
|
|
309
|
+
fields: normalizeFields(spec.fields),
|
|
310
|
+
where: spec.where ?? ""
|
|
311
|
+
};
|
|
312
|
+
return fnv1a32(JSON.stringify(normalized));
|
|
313
|
+
}
|
|
314
|
+
function compileFieldExpr(path, fieldToColumn) {
|
|
315
|
+
const col = fieldToColumn[path];
|
|
316
|
+
if (col) return quoteIdent(col);
|
|
317
|
+
if (path === "data") {
|
|
318
|
+
return `json_extract("data", '$')`;
|
|
319
|
+
}
|
|
320
|
+
if (path.startsWith("data.")) {
|
|
321
|
+
const suffix = path.slice(5);
|
|
322
|
+
const parts = suffix.split(".");
|
|
323
|
+
for (const part of parts) {
|
|
324
|
+
if (!JSON_PATH_KEY_RE.test(part)) {
|
|
325
|
+
throw new FiregraphError(
|
|
326
|
+
`IndexSpec data path "${path}" has invalid component "${part}". Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,
|
|
327
|
+
"INVALID_INDEX"
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return `json_extract("data", '$.${suffix}')`;
|
|
332
|
+
}
|
|
333
|
+
throw new FiregraphError(
|
|
334
|
+
`IndexSpec field "${path}" is not a known firegraph field. Use a top-level field (aType, aUid, axbType, bType, bUid, createdAt, updatedAt, v) or a dotted data path like 'data.status'.`,
|
|
335
|
+
"INVALID_INDEX"
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
function buildIndexDDL(spec, options) {
|
|
339
|
+
const { table, fieldToColumn, leadingColumns = [] } = options;
|
|
340
|
+
if (!spec.fields || spec.fields.length === 0) {
|
|
341
|
+
throw new FiregraphError("IndexSpec.fields must be a non-empty array", "INVALID_INDEX");
|
|
342
|
+
}
|
|
343
|
+
const normalized = normalizeFields(spec.fields);
|
|
344
|
+
const hash = specFingerprint(spec, leadingColumns);
|
|
345
|
+
const indexName = `${table}_idx_${hash}`;
|
|
346
|
+
const cols = [];
|
|
347
|
+
for (const col of leadingColumns) {
|
|
348
|
+
cols.push(quoteIdent(col));
|
|
349
|
+
}
|
|
350
|
+
for (const f of normalized) {
|
|
351
|
+
const expr = compileFieldExpr(f.path, fieldToColumn);
|
|
352
|
+
cols.push(f.desc ? `${expr} DESC` : expr);
|
|
353
|
+
}
|
|
354
|
+
let ddl = `CREATE INDEX IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(table)}(${cols.join(", ")})`;
|
|
355
|
+
if (spec.where) {
|
|
356
|
+
ddl += ` WHERE ${spec.where}`;
|
|
357
|
+
}
|
|
358
|
+
return ddl;
|
|
359
|
+
}
|
|
360
|
+
function dedupeIndexSpecs(specs, leadingColumns = []) {
|
|
361
|
+
const seen = /* @__PURE__ */ new Set();
|
|
362
|
+
const out = [];
|
|
363
|
+
for (const spec of specs) {
|
|
364
|
+
const fp = specFingerprint(spec, leadingColumns);
|
|
365
|
+
if (seen.has(fp)) continue;
|
|
366
|
+
seen.add(fp);
|
|
367
|
+
out.push(spec);
|
|
368
|
+
}
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
|
|
261
372
|
// src/cloudflare/schema.ts
|
|
262
373
|
var DO_FIELD_TO_COLUMN = {
|
|
263
374
|
aType: "a_type",
|
|
@@ -269,9 +380,9 @@ var DO_FIELD_TO_COLUMN = {
|
|
|
269
380
|
createdAt: "created_at",
|
|
270
381
|
updatedAt: "updated_at"
|
|
271
382
|
};
|
|
272
|
-
var
|
|
383
|
+
var IDENT_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
273
384
|
function validateDOTableName(name) {
|
|
274
|
-
if (!
|
|
385
|
+
if (!IDENT_RE2.test(name)) {
|
|
275
386
|
throw new Error(`Invalid SQL identifier: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);
|
|
276
387
|
}
|
|
277
388
|
}
|
|
@@ -279,9 +390,9 @@ function quoteDOIdent(name) {
|
|
|
279
390
|
validateDOTableName(name);
|
|
280
391
|
return `"${name}"`;
|
|
281
392
|
}
|
|
282
|
-
function buildDOSchemaStatements(table) {
|
|
393
|
+
function buildDOSchemaStatements(table, options = {}) {
|
|
283
394
|
const t = quoteDOIdent(table);
|
|
284
|
-
|
|
395
|
+
const statements = [
|
|
285
396
|
`CREATE TABLE IF NOT EXISTS ${t} (
|
|
286
397
|
doc_id TEXT NOT NULL PRIMARY KEY,
|
|
287
398
|
a_type TEXT NOT NULL,
|
|
@@ -293,13 +404,15 @@ function buildDOSchemaStatements(table) {
|
|
|
293
404
|
v INTEGER,
|
|
294
405
|
created_at INTEGER NOT NULL,
|
|
295
406
|
updated_at INTEGER NOT NULL
|
|
296
|
-
)
|
|
297
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_a_uid`)} ON ${t}(a_uid)`,
|
|
298
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_b_uid`)} ON ${t}(b_uid)`,
|
|
299
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_axb_type_b_uid`)} ON ${t}(axb_type, b_uid)`,
|
|
300
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_a_type`)} ON ${t}(a_type)`,
|
|
301
|
-
`CREATE INDEX IF NOT EXISTS ${quoteDOIdent(`${table}_idx_b_type`)} ON ${t}(b_type)`
|
|
407
|
+
)`
|
|
302
408
|
];
|
|
409
|
+
const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];
|
|
410
|
+
const fromRegistry = options.registry?.entries().flatMap((e) => e.indexes ?? []) ?? [];
|
|
411
|
+
const deduped = dedupeIndexSpecs([...core, ...fromRegistry]);
|
|
412
|
+
for (const spec of deduped) {
|
|
413
|
+
statements.push(buildIndexDDL(spec, { table, fieldToColumn: DO_FIELD_TO_COLUMN }));
|
|
414
|
+
}
|
|
415
|
+
return statements;
|
|
303
416
|
}
|
|
304
417
|
|
|
305
418
|
// src/cloudflare/sql.ts
|
|
@@ -309,12 +422,14 @@ function compileFieldRef(field) {
|
|
|
309
422
|
return { expr: quoteDOIdent(column) };
|
|
310
423
|
}
|
|
311
424
|
if (field.startsWith("data.")) {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
425
|
+
const suffix = field.slice(5);
|
|
426
|
+
for (const part of suffix.split(".")) {
|
|
427
|
+
validateJsonPathKey(part);
|
|
428
|
+
}
|
|
429
|
+
return { expr: `json_extract("data", '$.${suffix}')` };
|
|
315
430
|
}
|
|
316
431
|
if (field === "data") {
|
|
317
|
-
return { expr:
|
|
432
|
+
return { expr: `json_extract("data", '$')` };
|
|
318
433
|
}
|
|
319
434
|
throw new FiregraphError(
|
|
320
435
|
`DO SQLite backend cannot resolve filter field: ${field}`,
|
|
@@ -351,7 +466,7 @@ function bindValue(value) {
|
|
|
351
466
|
}
|
|
352
467
|
return String(value);
|
|
353
468
|
}
|
|
354
|
-
var
|
|
469
|
+
var JSON_PATH_KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
355
470
|
function validateJsonPathKey(key) {
|
|
356
471
|
if (key.length === 0) {
|
|
357
472
|
throw new FiregraphError(
|
|
@@ -359,7 +474,7 @@ function validateJsonPathKey(key) {
|
|
|
359
474
|
"INVALID_QUERY"
|
|
360
475
|
);
|
|
361
476
|
}
|
|
362
|
-
if (!
|
|
477
|
+
if (!JSON_PATH_KEY_RE2.test(key)) {
|
|
363
478
|
throw new FiregraphError(
|
|
364
479
|
`DO SQLite backend: data field path component "${key}" is not a safe JSON-path identifier. Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceData (full-data overwrite) for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,
|
|
365
480
|
"INVALID_QUERY"
|
|
@@ -367,8 +482,7 @@ function validateJsonPathKey(key) {
|
|
|
367
482
|
}
|
|
368
483
|
}
|
|
369
484
|
function compileFilter(filter, params) {
|
|
370
|
-
const { expr
|
|
371
|
-
if (pathParam !== void 0) params.push(pathParam);
|
|
485
|
+
const { expr } = compileFieldRef(filter.field);
|
|
372
486
|
switch (filter.op) {
|
|
373
487
|
case "==":
|
|
374
488
|
params.push(bindValue(filter.value));
|
|
@@ -423,11 +537,10 @@ function asArray(value, op) {
|
|
|
423
537
|
}
|
|
424
538
|
return value;
|
|
425
539
|
}
|
|
426
|
-
function compileOrderBy(options,
|
|
540
|
+
function compileOrderBy(options, _params) {
|
|
427
541
|
if (!options?.orderBy) return "";
|
|
428
542
|
const { field, direction } = options.orderBy;
|
|
429
|
-
const { expr
|
|
430
|
-
if (pathParam !== void 0) params.push(pathParam);
|
|
543
|
+
const { expr } = compileFieldRef(field);
|
|
431
544
|
const dir = direction === "desc" ? "DESC" : "ASC";
|
|
432
545
|
return ` ORDER BY ${expr} ${dir}`;
|
|
433
546
|
}
|
|
@@ -915,9 +1028,7 @@ async function migrateRecord(record, registry, globalWriteBack = "off") {
|
|
|
915
1028
|
};
|
|
916
1029
|
}
|
|
917
1030
|
async function migrateRecords(records, registry, globalWriteBack = "off") {
|
|
918
|
-
return Promise.all(
|
|
919
|
-
records.map((r) => migrateRecord(r, registry, globalWriteBack))
|
|
920
|
-
);
|
|
1031
|
+
return Promise.all(records.map((r) => migrateRecord(r, registry, globalWriteBack)));
|
|
921
1032
|
}
|
|
922
1033
|
|
|
923
1034
|
// src/scope.ts
|
|
@@ -1125,7 +1236,8 @@ function discoveryToEntries(discovery) {
|
|
|
1125
1236
|
subtitleField: entity.subtitleField,
|
|
1126
1237
|
allowedIn: entity.allowedIn,
|
|
1127
1238
|
migrations: entity.migrations,
|
|
1128
|
-
migrationWriteBack: entity.migrationWriteBack
|
|
1239
|
+
migrationWriteBack: entity.migrationWriteBack,
|
|
1240
|
+
indexes: entity.indexes
|
|
1129
1241
|
});
|
|
1130
1242
|
}
|
|
1131
1243
|
for (const [axbType, entity] of discovery.edges) {
|
|
@@ -1153,7 +1265,8 @@ function discoveryToEntries(discovery) {
|
|
|
1153
1265
|
allowedIn: entity.allowedIn,
|
|
1154
1266
|
targetGraph: resolvedTargetGraph,
|
|
1155
1267
|
migrations: entity.migrations,
|
|
1156
|
-
migrationWriteBack: entity.migrationWriteBack
|
|
1268
|
+
migrationWriteBack: entity.migrationWriteBack,
|
|
1269
|
+
indexes: entity.indexes
|
|
1157
1270
|
});
|
|
1158
1271
|
}
|
|
1159
1272
|
}
|
|
@@ -2321,12 +2434,18 @@ var FiregraphDO = class {
|
|
|
2321
2434
|
env;
|
|
2322
2435
|
/** @internal — table name used by every compiled statement. */
|
|
2323
2436
|
table;
|
|
2437
|
+
/** @internal — registry consulted by `runSchema` for per-entry indexes. */
|
|
2438
|
+
registry;
|
|
2439
|
+
/** @internal — overrides `DEFAULT_CORE_INDEXES` when set. */
|
|
2440
|
+
coreIndexes;
|
|
2324
2441
|
constructor(ctx, env, options = {}) {
|
|
2325
2442
|
this.ctx = ctx;
|
|
2326
2443
|
this.env = env;
|
|
2327
2444
|
const table = options.table ?? DEFAULT_OPTIONS.table;
|
|
2328
2445
|
validateDOTableName(table);
|
|
2329
2446
|
this.table = table;
|
|
2447
|
+
this.registry = options.registry;
|
|
2448
|
+
this.coreIndexes = options.coreIndexes;
|
|
2330
2449
|
const autoMigrate = options.autoMigrate ?? DEFAULT_OPTIONS.autoMigrate;
|
|
2331
2450
|
if (autoMigrate) {
|
|
2332
2451
|
void this.ctx.blockConcurrencyWhile(async () => {
|
|
@@ -2516,7 +2635,11 @@ var FiregraphDO = class {
|
|
|
2516
2635
|
// Internals
|
|
2517
2636
|
// ---------------------------------------------------------------------------
|
|
2518
2637
|
runSchema() {
|
|
2519
|
-
|
|
2638
|
+
const statements = buildDOSchemaStatements(this.table, {
|
|
2639
|
+
coreIndexes: this.coreIndexes,
|
|
2640
|
+
registry: this.registry
|
|
2641
|
+
});
|
|
2642
|
+
for (const sql of statements) {
|
|
2520
2643
|
this.ctx.storage.sql.exec(sql).toArray();
|
|
2521
2644
|
}
|
|
2522
2645
|
}
|
|
@@ -2531,6 +2654,7 @@ var FiregraphDO = class {
|
|
|
2531
2654
|
0 && (module.exports = {
|
|
2532
2655
|
DORPCBackend,
|
|
2533
2656
|
FiregraphDO,
|
|
2657
|
+
buildDOSchemaStatements,
|
|
2534
2658
|
createDOClient,
|
|
2535
2659
|
createSiblingClient
|
|
2536
2660
|
});
|