@typicalday/firegraph 0.4.0 → 0.5.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 +114 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/editor/server/index.mjs +755 -44
- package/dist/{index-DR3jF5_b.d.cts → index-B9aodfYD.d.cts} +101 -1
- package/dist/{index-DR3jF5_b.d.ts → index-B9aodfYD.d.ts} +101 -1
- package/dist/index.cjs +794 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +164 -4
- package/dist/index.d.ts +164 -4
- package/dist/index.js +776 -41
- package/dist/index.js.map +1 -1
- package/package.json +27 -24
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Firegraph stores everything as **triples** in a Firestore collection (with optio
|
|
|
68
68
|
- **Edges** are directed relationships between nodes:
|
|
69
69
|
`(tour, Kj7vNq2mP9xR4wL1tY8s3) -[hasDeparture]-> (departure, Xp4nTk8qW2vR7mL9jY5a1)`
|
|
70
70
|
|
|
71
|
-
Every record carries a `data` payload (arbitrary JSON), plus `createdAt` and `updatedAt` server timestamps.
|
|
71
|
+
Every record carries a `data` payload (arbitrary JSON), plus `createdAt` and `updatedAt` server timestamps. Records managed by a schema registry with migrations also carry a `v` field (schema version number, derived from `max(toVersion)` of the migrations array) on the record envelope.
|
|
72
72
|
|
|
73
73
|
### Document IDs
|
|
74
74
|
|
|
@@ -344,6 +344,110 @@ Key behaviors:
|
|
|
344
344
|
|
|
345
345
|
Dynamic registry returns a `DynamicGraphClient` which extends `GraphClient` with `defineNodeType()`, `defineEdgeType()`, and `reloadRegistry()`. Transactions and batches also validate against the compiled dynamic registry.
|
|
346
346
|
|
|
347
|
+
### Schema Versioning & Auto-Migration
|
|
348
|
+
|
|
349
|
+
Firegraph supports schema versioning with automatic migration of records on read. The schema version is derived automatically as `max(toVersion)` from the `migrations` array -- there is no separate `schemaVersion` property to set. When a record's stored version (`v`) is behind the derived version, migration functions run automatically to bring data up to the current version.
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { createRegistry, createGraphClient } from 'firegraph';
|
|
353
|
+
import type { MigrationStep } from 'firegraph';
|
|
354
|
+
|
|
355
|
+
const migrations: MigrationStep[] = [
|
|
356
|
+
{ fromVersion: 0, toVersion: 1, up: (d) => ({ ...d, status: d.status ?? 'draft' }) },
|
|
357
|
+
{ fromVersion: 1, toVersion: 2, up: async (d) => ({ ...d, active: true }) },
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
const registry = createRegistry([
|
|
361
|
+
{
|
|
362
|
+
aType: 'tour',
|
|
363
|
+
axbType: 'is',
|
|
364
|
+
bType: 'tour',
|
|
365
|
+
jsonSchema: tourSchemaV2,
|
|
366
|
+
migrations, // version derived as max(toVersion) = 2
|
|
367
|
+
migrationWriteBack: 'eager',
|
|
368
|
+
},
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
const g = createGraphClient(db, 'graph', { registry });
|
|
372
|
+
|
|
373
|
+
// Reading a v0 record automatically migrates it to v2 in memory
|
|
374
|
+
const tour = await g.getNode(tourId);
|
|
375
|
+
// tour.v === 2, tour.data.status === 'draft', tour.data.active === true
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### How It Works
|
|
379
|
+
|
|
380
|
+
- **Version storage**: The `v` field lives on the record envelope (top-level, alongside `aType`, `data`, etc.), not inside `data`. Records without `v` are treated as version 0 (legacy data).
|
|
381
|
+
- **Read path**: When a record is read and its `v` is behind the derived version (`max(toVersion)` from migrations), migrations run sequentially to bring data up to the current version.
|
|
382
|
+
- **Write path**: When writing via `putNode`/`putEdge`, the record is stamped with `v` equal to the derived version automatically.
|
|
383
|
+
- **`updateNode`**: Does not stamp `v` — it is a raw partial update without schema context. The next read re-triggers migration (which is idempotent).
|
|
384
|
+
|
|
385
|
+
#### Write-Back
|
|
386
|
+
|
|
387
|
+
Write-back controls whether migrated data is persisted back to Firestore after a read-triggered migration:
|
|
388
|
+
|
|
389
|
+
| Mode | Behavior |
|
|
390
|
+
|------|----------|
|
|
391
|
+
| `'off'` | In-memory only; Firestore document unchanged (default) |
|
|
392
|
+
| `'eager'` | Fire-and-forget write after read; inline update in transactions |
|
|
393
|
+
| `'background'` | Same as eager but errors are swallowed with a `console.warn` |
|
|
394
|
+
|
|
395
|
+
Resolution order: `entry.migrationWriteBack > client.migrationWriteBack > 'off'`
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// Global default
|
|
399
|
+
const g = createGraphClient(db, 'graph', {
|
|
400
|
+
registry,
|
|
401
|
+
migrationWriteBack: 'background',
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Entry-level override (takes priority)
|
|
405
|
+
createRegistry([{
|
|
406
|
+
aType: 'tour', axbType: 'is', bType: 'tour',
|
|
407
|
+
migrations,
|
|
408
|
+
migrationWriteBack: 'eager',
|
|
409
|
+
}]);
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### Dynamic Registry Migrations
|
|
413
|
+
|
|
414
|
+
In dynamic mode, migrations are stored as source code strings:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
await g.defineNodeType('tour', tourSchema, 'A tour', {
|
|
418
|
+
migrations: [
|
|
419
|
+
{ fromVersion: 0, toVersion: 1, up: '(d) => ({ ...d, status: "draft" })' },
|
|
420
|
+
],
|
|
421
|
+
migrationWriteBack: 'eager',
|
|
422
|
+
});
|
|
423
|
+
await g.reloadRegistry();
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Stored migration strings must be self-contained — no `import`, `require`, or external references. Firestore special types (`Timestamp`, `GeoPoint`, `VectorValue`, `DocumentReference`) are transparently preserved through the sandbox boundary via tagged serialization. Inside the sandbox, these appear as tagged plain objects (e.g., `{ __firegraph_ser__: 'Timestamp', seconds: N, nanoseconds: N }`) that the migration can read, modify, or create. They are reconstructed into real Firestore types after the migration returns.
|
|
427
|
+
|
|
428
|
+
For custom sandboxing, pass `migrationSandbox` to `createGraphClient()`:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
const g = createGraphClient(db, 'graph', {
|
|
432
|
+
registryMode: { mode: 'dynamic' },
|
|
433
|
+
migrationSandbox: (source) => {
|
|
434
|
+
const compartment = new Compartment({ /* endowments */ });
|
|
435
|
+
return compartment.evaluate(source);
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### Entity Discovery
|
|
441
|
+
|
|
442
|
+
Place a `migrations.ts` file in the entity folder. It must default-export a `MigrationStep[]` array. Optionally set `migrationWriteBack` in `meta.json`. The schema version is derived automatically as `max(toVersion)` from the migrations array.
|
|
443
|
+
|
|
444
|
+
```
|
|
445
|
+
entities/nodes/tour/
|
|
446
|
+
schema.json
|
|
447
|
+
migrations.ts # export default [{ fromVersion: 0, toVersion: 1, up: ... }]
|
|
448
|
+
meta.json # { "migrationWriteBack": "eager" }
|
|
449
|
+
```
|
|
450
|
+
|
|
347
451
|
### Subgraphs
|
|
348
452
|
|
|
349
453
|
Create isolated graph namespaces inside a parent node's Firestore document as subcollections. Each subgraph is a full `GraphClient` scoped to its own collection path.
|
|
@@ -599,6 +703,7 @@ All errors extend `FiregraphError` with a `code` property:
|
|
|
599
703
|
| `ValidationError` | `VALIDATION_ERROR` | Schema validation fails (registry + Zod) |
|
|
600
704
|
| `RegistryViolationError` | `REGISTRY_VIOLATION` | Triple not registered |
|
|
601
705
|
| `RegistryScopeError` | `REGISTRY_SCOPE` | Type not allowed at this subgraph scope |
|
|
706
|
+
| `MigrationError` | `MIGRATION_ERROR` | Migration function fails or chain is incomplete |
|
|
602
707
|
| `DynamicRegistryError` | `DYNAMIC_REGISTRY_ERROR` | Dynamic registry misconfiguration or misuse |
|
|
603
708
|
| `InvalidQueryError` | `INVALID_QUERY` | `findEdges` called with no filters |
|
|
604
709
|
| `QuerySafetyError` | `QUERY_SAFETY` | Query would cause a full collection scan |
|
|
@@ -653,6 +758,13 @@ import type {
|
|
|
653
758
|
NodeTypeData,
|
|
654
759
|
EdgeTypeData,
|
|
655
760
|
|
|
761
|
+
// Migration
|
|
762
|
+
MigrationFn,
|
|
763
|
+
MigrationStep,
|
|
764
|
+
StoredMigrationStep,
|
|
765
|
+
MigrationExecutor,
|
|
766
|
+
MigrationWriteBack,
|
|
767
|
+
|
|
656
768
|
// Traversal
|
|
657
769
|
HopDefinition, // includes targetGraph
|
|
658
770
|
TraversalOptions,
|
|
@@ -680,6 +792,7 @@ All data lives in one Firestore collection. Each document has these fields:
|
|
|
680
792
|
| `bType` | string | Target node type |
|
|
681
793
|
| `bUid` | string | Target node ID |
|
|
682
794
|
| `data` | object | User payload |
|
|
795
|
+
| `v` | number? | Schema version (derived from `max(toVersion)` of migrations; set when entry has migrations) |
|
|
683
796
|
| `createdAt` | Timestamp | Server-set on create |
|
|
684
797
|
| `updatedAt` | Timestamp | Server-set on create/update |
|
|
685
798
|
|
package/dist/codegen/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { p as CodegenOptions, P as generateTypes } from '../index-B9aodfYD.cjs';
|
|
2
2
|
import '@google-cloud/firestore';
|
package/dist/codegen/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { p as CodegenOptions, P as generateTypes } from '../index-B9aodfYD.js';
|
|
2
2
|
import '@google-cloud/firestore';
|