@prometheus-ags/entity-graph-core 3.2.0 → 4.0.1

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/dist/index.d.ts CHANGED
@@ -700,11 +700,24 @@ interface PersistGraphToStorageOptions {
700
700
  key: string;
701
701
  store?: GraphStore$1;
702
702
  pendingActions?: GraphActionRecord[];
703
+ /**
704
+ * The runtime whose status this write reports to. Omit for standalone calls,
705
+ * which fall back to the process-wide status store.
706
+ */
707
+ scope?: RuntimeScope;
703
708
  }
704
709
  interface HydrateGraphFromStorageOptions {
705
710
  storage: GraphPersistenceAdapter;
706
711
  key: string;
707
712
  store?: GraphStore$1;
713
+ /**
714
+ * The runtime that owns the pending-action set this hydrate populates.
715
+ *
716
+ * Without a scope, hydrate writes into the process-wide fallback set — which
717
+ * is what made a second runtime's hydrate erase the first runtime's
718
+ * un-settled actions. A runtime always passes its own scope.
719
+ */
720
+ scope?: RuntimeScope;
708
721
  }
709
722
  /**
710
723
  * Retry-with-backoff policy for replaying pending offline actions.
@@ -738,18 +751,58 @@ interface StartLocalFirstGraphOptions {
738
751
  persistDebounceMs?: number;
739
752
  /** Retry-with-backoff policy for offline action replay. */
740
753
  retryPolicy?: ReplayRetryPolicy;
754
+ /**
755
+ * Pre-built scope for this runtime. Omit and one is created — the normal
756
+ * case. Supply one only to share state deliberately (e.g. in a test).
757
+ */
758
+ scope?: RuntimeScope;
741
759
  }
742
760
  interface LocalFirstGraphRuntime {
743
761
  ready: Promise<void>;
744
- dispose: () => void;
762
+ /**
763
+ * Tear down and drain. The returned promise resolves only once every
764
+ * in-flight persist has settled, so `await dispose()` guarantees no further
765
+ * write from this runtime lands (ADR-009 G2).
766
+ *
767
+ * Callers that do not await it get the old fire-and-forget behaviour, which
768
+ * is why the ASO session manager awaits it before opening a replacement.
769
+ */
770
+ dispose: () => Promise<void>;
745
771
  persistNow: () => Promise<void>;
746
772
  hydrate: () => Promise<Awaited<ReturnType<typeof hydrateGraphFromStorage>>>;
747
773
  getStatus: () => GraphSyncStatus;
774
+ /**
775
+ * This runtime's own state. Read `scope.statusStore` for status isolated from
776
+ * every other runtime in the process.
777
+ */
778
+ scope: RuntimeScope;
748
779
  }
749
780
  declare const graphSyncStatusStore: zustand_vanilla.StoreApi<{
750
781
  status: GraphSyncStatus;
751
782
  setStatus: (status: Partial<GraphSyncStatus>) => void;
752
783
  }>;
784
+ /**
785
+ * Per-runtime state: the pending-action set and the status store that runtime
786
+ * publishes to.
787
+ *
788
+ * Both were previously module-level singletons shared by every runtime in the
789
+ * process. That was not merely shared state — `hydrateGraphFromStorage` clears
790
+ * the pending set before repopulating it, so a second runtime hydrating erased
791
+ * the first runtime's un-settled actions. On an account or practice switch that
792
+ * is silent write loss, which is why this is scoped per runtime (ADR-009 G1).
793
+ */
794
+ interface RuntimeScope {
795
+ pendingActions: Map<string, GraphActionRecord>;
796
+ statusStore: GraphSyncStatusStore;
797
+ }
798
+ type GraphSyncStatusStore = ReturnType<typeof createGraphSyncStatusStore>;
799
+ /** Create an independent sync-status store. Each runtime owns one. */
800
+ declare function createGraphSyncStatusStore(): zustand_vanilla.StoreApi<{
801
+ status: GraphSyncStatus;
802
+ setStatus: (status: Partial<GraphSyncStatus>) => void;
803
+ }>;
804
+ /** Create an isolated runtime scope. */
805
+ declare function createRuntimeScope(): RuntimeScope;
753
806
  declare function getGraphSyncStatus(): GraphSyncStatus;
754
807
  declare function persistGraphToStorage(opts: PersistGraphToStorageOptions): Promise<{
755
808
  ok: true;
@@ -1374,6 +1427,22 @@ interface ChangeSet<T = Record<string, unknown>> {
1374
1427
  changes: EntityChange<T>[];
1375
1428
  affectedListKeys?: string[];
1376
1429
  timestamp?: string;
1430
+ /**
1431
+ * The transport's resume position for this batch, when it has one.
1432
+ *
1433
+ * For ElectricSQL this is the shape handle plus the offset of the last
1434
+ * message in the batch — the pair a consumer echoes back to resume. It is
1435
+ * **batch-level, not per-change**: a resume position describes a boundary
1436
+ * between batches, not a point inside one.
1437
+ *
1438
+ * Absent for transports with no resume semantics (a Postgres LISTEN/NOTIFY
1439
+ * frame, for instance, carries no Electric offset). Absent is meaningful —
1440
+ * it means "this batch cannot be checkpointed", not "offset zero".
1441
+ */
1442
+ cursor?: {
1443
+ handle: string;
1444
+ offset: string;
1445
+ };
1377
1446
  }
1378
1447
  type UnsubscribeFn = () => void;
1379
1448
  interface SubscriptionConfig {
@@ -1691,6 +1760,13 @@ interface ShapeStream$1<T = Record<string, unknown>> {
1691
1760
  subscribe(onMsg: (msgs: ShapeMessage$1<T>[]) => void, onErr?: (e: Error) => void): () => void;
1692
1761
  isUpToDate: boolean;
1693
1762
  lastOffset: string;
1763
+ /**
1764
+ * Electric's handle for this shape. Optional because this is a structural
1765
+ * type over the real client and older streams may not expose it — a batch
1766
+ * without a handle yields no cursor rather than a half-formed one, since a
1767
+ * resume needs BOTH handle and offset.
1768
+ */
1769
+ shapeHandle?: string;
1694
1770
  }
1695
1771
  interface ElectricTableConfig<T extends object> {
1696
1772
  type: EntityType;
@@ -1839,6 +1915,63 @@ declare function createTenantScopedElectricAdapter(opts: TenantScopedAdapterOpti
1839
1915
  * not import `@electric-sql/pglite` — consumers pass a PGlite-shaped handle.
1840
1916
  */
1841
1917
 
1918
+ /**
1919
+ * Where a replica resumes from, and which generation it belongs to.
1920
+ *
1921
+ * `handle` and `offset` are ElectricSQL's own values, echoed back verbatim on
1922
+ * resume — they are opaque here. `generation` increments when a snapshot is
1923
+ * rebuilt (Electric's must-refetch), so a checkpoint from a previous generation
1924
+ * is recognisably stale rather than silently resumed against new rows.
1925
+ */
1926
+ interface ReplicaCheckpoint {
1927
+ handle: string;
1928
+ offset: string;
1929
+ generation: number;
1930
+ }
1931
+ /** Checkpoint plus the rows it describes, committed together or not at all. */
1932
+ interface CheckpointedWrite {
1933
+ key: string;
1934
+ value: string;
1935
+ checkpoint: ReplicaCheckpoint;
1936
+ }
1937
+ /** What a caller should do with what it found on disk. */
1938
+ type ResumeDecision = {
1939
+ action: "resume";
1940
+ checkpoint: ReplicaCheckpoint;
1941
+ } | {
1942
+ action: "rebuild";
1943
+ reason: ResumeRejection;
1944
+ };
1945
+ /**
1946
+ * Why a stored replica cannot be resumed.
1947
+ *
1948
+ * Each is a *rebuild*, never a silent continue: resuming against a boundary
1949
+ * that does not describe the rows on disk is how a replica ends up with a
1950
+ * plausible-looking mixture of two generations.
1951
+ */
1952
+ type ResumeRejection =
1953
+ /** Nothing stored — a cold start, not a fault. */
1954
+ "no-value"
1955
+ /** Rows exist but were written by plain `set`, so their position is unknown. */
1956
+ | "no-checkpoint"
1957
+ /** The checkpoint predates a rebuild; its offsets refer to a dead generation. */
1958
+ | "stale-generation"
1959
+ /** The server issued a new shape handle, so old offsets are meaningless. */
1960
+ | "handle-changed";
1961
+ /**
1962
+ * Decide whether a stored replica may be resumed.
1963
+ *
1964
+ * Pure so the decision is testable without a database. `expected` is what the
1965
+ * caller intends to resume as — its current replica generation, and the shape
1966
+ * handle the server most recently gave it.
1967
+ */
1968
+ declare function evaluateResume(stored: {
1969
+ value: string | null;
1970
+ checkpoint: ReplicaCheckpoint | null;
1971
+ }, expected: {
1972
+ generation: number;
1973
+ handle?: string;
1974
+ }): ResumeDecision;
1842
1975
  interface PGlitePersistenceClient {
1843
1976
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{
1844
1977
  rows: T[];
@@ -1849,6 +1982,20 @@ interface CreatePGlitePersistenceAdapterOptions {
1849
1982
  /** Name of the snapshot table. Defaults to `_graph_snapshot`. */
1850
1983
  tableName?: string;
1851
1984
  }
1985
+ /**
1986
+ * A persistence adapter that can also commit a resume checkpoint atomically.
1987
+ *
1988
+ * The plain {@link GraphPersistenceAdapter} contract cannot express this: its
1989
+ * `set` writes a value and returns, so a checkpoint written after it is a
1990
+ * second transaction, and a crash between the two leaves rows whose resume
1991
+ * position is unknown. `setWithCheckpoint` closes that window (ADR-009 G3).
1992
+ */
1993
+ interface CheckpointingPersistenceAdapter extends GraphPersistenceAdapter {
1994
+ /** Commit rows and their resume checkpoint in ONE transaction. */
1995
+ setWithCheckpoint: (write: CheckpointedWrite) => Promise<void>;
1996
+ /** The checkpoint stored for `key`, or null if none. */
1997
+ getCheckpoint: (key: string) => Promise<ReplicaCheckpoint | null>;
1998
+ }
1852
1999
  /**
1853
2000
  * Returns a {@link GraphPersistenceAdapter} that reads/writes the graph
1854
2001
  * snapshot through a PGlite-shaped client.
@@ -1863,7 +2010,7 @@ interface CreatePGlitePersistenceAdapterOptions {
1863
2010
  * const runtime = startLocalFirstGraph({ storage, key: "hotseaters:graph" });
1864
2011
  * ```
1865
2012
  */
1866
- declare function createPGlitePersistenceAdapter(pglite: PGlitePersistenceClient, options?: CreatePGlitePersistenceAdapterOptions): Promise<GraphPersistenceAdapter>;
2013
+ declare function createPGlitePersistenceAdapter(pglite: PGlitePersistenceClient, options?: CreatePGlitePersistenceAdapterOptions): Promise<CheckpointingPersistenceAdapter>;
1867
2014
 
1868
2015
  /**
1869
2016
  * adapters/tauri-sql-persistence.ts
@@ -2380,4 +2527,4 @@ declare function getFacetedMinMaxValues<TData>(rowModel: RowModel<TData>, column
2380
2527
  */
2381
2528
  declare function getFacetedRowModel<TData>(preFilteredRowModel: RowModel<TData>, columnId: string, allFilteredRowModel: RowModel<TData>): RowModel<TData>;
2382
2529
 
2383
- export { type AccessorFn, type AdapterStatus, type AgUiStateDeltaEvent, type AgUiStateMapping, type AgUiStateSnapshotEvent, type AggregationFn, type ApplyAgUiOptions, type BelongsToRelation, type CascadeContext, type Cell, type CellContext, type CellRenderer, type ChangeEvent, type ChangeOp, type ChangeOperation, type ChangeSet, type ChannelConfig, type Column, type ColumnDef, type ColumnFiltersState, type ColumnMeta, type ColumnOrderState, type ColumnPinningState, type ColumnSizingInfoState, type ColumnSizingState, type ColumnVisibilityState, type CreateFlintAdapterOptions, type CreatePGlitePersistenceAdapterOptions, type CreateTauriSqlPersistenceAdapterOptions, type DevtoolsEvent, type DevtoolsEventBus, type DevtoolsEventBusOptions, type DevtoolsSourceFn, type ElectricAdapterOptions, type ElectricTableConfig, type EngineOptions, type EntityChange, EntityId, type EntityJsonSchemaConfig, type EntityQueryOptions, type EntitySchema, EntitySnapshot, EntitySyncMetadata, type EntityTransport, EntityType, type ExpandedState, type FilterFn, FilterSpec, type FlintCheckpointStore, type FlintClientLike, type FlintEntityEvent, type FlintEntityQuery, type FlintEntityRecord, type GetEntityJsonSchemaOptions, type GraphActionEvent, type GraphActionOptions, type GraphActionRecord$1 as GraphActionRecord, type GraphEffectEvent, type GraphEffectHandle, type GraphEffectOptions, type GraphIncludeMap, type GraphIncludeRelation, type GraphPersistenceAdapter, type GraphQueryOptions, type GraphSnapshotExportOptions, type GraphSnapshotPayload, type GraphSnapshotWithSchemasOptions, GraphStore$1 as GraphStore, type GraphSyncStatus, type GraphToolContext, type GraphTransaction, type GroupingState, type HasManyRelation, type Header, type HeaderContext, type HeaderGroup, type HeaderRenderer, IncrementalView, type IncrementalViewOptions, type JsonPatchOp, type JsonSchemaObject, type ListFetchParams, type ListQuery, type ListQueryOptions, type ListResponse, type ListResult, type LocalFirstGraphRuntime, type LoroModuleLoader, type MakeRestTransportOptions, type ManagerOptions, type ManyToManyRelation, type MergeContext, type MergeStrategy, type PGlitePersistenceClient, type PaginationState, type ColumnDef as PureColumnDef, type ColumnMeta as PureColumnMeta, QueryKey, type RealtimeAdapter, RealtimeManager, type RegisterEntityFromSqlOptions, type RegisteredStore, type RelationDescriptor, type ReplayRetryPolicy, type Row, type RowModel, type RowSelectionState, type SchemaGraphToolContext, SortSpec, type SortingFn, type SortingState, type StartLocalFirstGraphOptions, type SubscriptionConfig, type SupabaseAdapterOptions, type SupabaseLike, type SupabaseQueryBuilderLike, type SurrealCheckpointStore, type SurrealLike, type SurrealLiveAction, type SurrealLiveAdapterOptions, type SurrealTableConfig, type SyncAdapter, SyncOrigin, type TableInstance, type TableOptions, type TableState, type TauriSqlClient, type TenantClaim, type TenantScopedAdapterOptions, type TenantScopedTableConfig, type TimeTravelSnapshot, type TimeTravelState, type UnsubscribeFn, type Updater, type ViewMode, type WebSocketAdapterOptions, __resetActiveGraphStore, __resetEntityTransports, __resetStoreRegistry, applyAgUiDelta, applyAgUiSnapshot, applyJsonPatch, applyView, attachGlobalListeners, buildTenantWhere, cascadeInvalidation, checkCompleteness, collectDirtyPaths, compareEntities, configureEngine, configureTimeTravel, createConvexAdapter, createDevtoolsEventBus, createElectricAdapter, createFlintAdapter, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphTool, createGraphTransaction, createLoroMergeStrategy, createPGlitePersistenceAdapter, createRow, createSchemaGraphTool, createSupabaseRealtimeAdapter, createSurrealLiveAdapter, createTauriSqlPersistenceAdapter, createTenantScopedElectricAdapter, createWebSocketAdapter, dedupe, escapeHtml, exportGraphSnapshot, exportGraphSnapshotWithSchemas, fetchEntity, fetchList, findInsertionIndex, getActiveSubscriberCount, getCoreRowModel, getRealtimeManager as getElectricRealtimeManager, getEngineOptions, getEntityJsonSchema, getEntityTransport, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGraphSyncStatus, getGroupedRowModel, getMergeStrategy, getPaginatedRowModel, getRealtimeManager, getRegisteredEntityTypes, getRegisteredStores, getSchema, getSelectedRowModel, getSortedRowModel, getTimeTravelState, getValueAtPath, graphStore, graphSyncStatusStore, hasMergeStrategy, humanizeFieldName, hydrateGraphFromStorage, inferSchemaFieldType, lwwStrategy, makeRestTransport, matchesFilter, matchesSearch, parseCreateTable, persistGraphToStorage, prepareGraphStoreScope, publishFlintMutation, queryOnce, readRelations, recordGraphSnapshot, registerEntityFromSql, registerEntityJsonSchema, registerEntityTransport, registerMergeStrategy, registerRuntimeSchema, registerSchema, registerStore, registerSubscriber, registryKey, renderMarkdownToHtml, replayActionWithRetry, resetRealtimeManager, resolveActiveGraphStore, restoreGraphSnapshot, restoreGraphSnapshotBySeq, runWithGraphStore, selectGraph, serializeKey, setActiveGraphStore, setDefaultMergeStrategy, setValueAtPath, sqlTypeToJsonSchema, startGarbageCollector, startLocalFirstGraph, stepTimeTravel, stopGarbageCollector, subscribeDevtoolsEvent, subscribeSubscriberStats, subscribeTimeTravel, toPrismaOrderBy, toPrismaWhere, unregisterSubscriber, useGraphStore };
2530
+ export { type AccessorFn, type AdapterStatus, type AgUiStateDeltaEvent, type AgUiStateMapping, type AgUiStateSnapshotEvent, type AggregationFn, type ApplyAgUiOptions, type BelongsToRelation, type CascadeContext, type Cell, type CellContext, type CellRenderer, type ChangeEvent, type ChangeOp, type ChangeOperation, type ChangeSet, type ChannelConfig, type CheckpointedWrite, type CheckpointingPersistenceAdapter, type Column, type ColumnDef, type ColumnFiltersState, type ColumnMeta, type ColumnOrderState, type ColumnPinningState, type ColumnSizingInfoState, type ColumnSizingState, type ColumnVisibilityState, type CreateFlintAdapterOptions, type CreatePGlitePersistenceAdapterOptions, type CreateTauriSqlPersistenceAdapterOptions, type DevtoolsEvent, type DevtoolsEventBus, type DevtoolsEventBusOptions, type DevtoolsSourceFn, type ElectricAdapterOptions, type ElectricTableConfig, type EngineOptions, type EntityChange, EntityId, type EntityJsonSchemaConfig, type EntityQueryOptions, type EntitySchema, EntitySnapshot, EntitySyncMetadata, type EntityTransport, EntityType, type ExpandedState, type FilterFn, FilterSpec, type FlintCheckpointStore, type FlintClientLike, type FlintEntityEvent, type FlintEntityQuery, type FlintEntityRecord, type GetEntityJsonSchemaOptions, type GraphActionEvent, type GraphActionOptions, type GraphActionRecord$1 as GraphActionRecord, type GraphEffectEvent, type GraphEffectHandle, type GraphEffectOptions, type GraphIncludeMap, type GraphIncludeRelation, type GraphPersistenceAdapter, type GraphQueryOptions, type GraphSnapshotExportOptions, type GraphSnapshotPayload, type GraphSnapshotWithSchemasOptions, GraphStore$1 as GraphStore, type GraphSyncStatus, type GraphSyncStatusStore, type GraphToolContext, type GraphTransaction, type GroupingState, type HasManyRelation, type Header, type HeaderContext, type HeaderGroup, type HeaderRenderer, IncrementalView, type IncrementalViewOptions, type JsonPatchOp, type JsonSchemaObject, type ListFetchParams, type ListQuery, type ListQueryOptions, type ListResponse, type ListResult, type LocalFirstGraphRuntime, type LoroModuleLoader, type MakeRestTransportOptions, type ManagerOptions, type ManyToManyRelation, type MergeContext, type MergeStrategy, type PGlitePersistenceClient, type PaginationState, type ColumnDef as PureColumnDef, type ColumnMeta as PureColumnMeta, QueryKey, type RealtimeAdapter, RealtimeManager, type RegisterEntityFromSqlOptions, type RegisteredStore, type RelationDescriptor, type ReplayRetryPolicy, type ReplicaCheckpoint, type ResumeDecision, type ResumeRejection, type Row, type RowModel, type RowSelectionState, type RuntimeScope, type SchemaGraphToolContext, SortSpec, type SortingFn, type SortingState, type StartLocalFirstGraphOptions, type SubscriptionConfig, type SupabaseAdapterOptions, type SupabaseLike, type SupabaseQueryBuilderLike, type SurrealCheckpointStore, type SurrealLike, type SurrealLiveAction, type SurrealLiveAdapterOptions, type SurrealTableConfig, type SyncAdapter, SyncOrigin, type TableInstance, type TableOptions, type TableState, type TauriSqlClient, type TenantClaim, type TenantScopedAdapterOptions, type TenantScopedTableConfig, type TimeTravelSnapshot, type TimeTravelState, type UnsubscribeFn, type Updater, type ViewMode, type WebSocketAdapterOptions, __resetActiveGraphStore, __resetEntityTransports, __resetStoreRegistry, applyAgUiDelta, applyAgUiSnapshot, applyJsonPatch, applyView, attachGlobalListeners, buildTenantWhere, cascadeInvalidation, checkCompleteness, collectDirtyPaths, compareEntities, configureEngine, configureTimeTravel, createConvexAdapter, createDevtoolsEventBus, createElectricAdapter, createFlintAdapter, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphSyncStatusStore, createGraphTool, createGraphTransaction, createLoroMergeStrategy, createPGlitePersistenceAdapter, createRow, createRuntimeScope, createSchemaGraphTool, createSupabaseRealtimeAdapter, createSurrealLiveAdapter, createTauriSqlPersistenceAdapter, createTenantScopedElectricAdapter, createWebSocketAdapter, dedupe, escapeHtml, evaluateResume, exportGraphSnapshot, exportGraphSnapshotWithSchemas, fetchEntity, fetchList, findInsertionIndex, getActiveSubscriberCount, getCoreRowModel, getRealtimeManager as getElectricRealtimeManager, getEngineOptions, getEntityJsonSchema, getEntityTransport, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGraphSyncStatus, getGroupedRowModel, getMergeStrategy, getPaginatedRowModel, getRealtimeManager, getRegisteredEntityTypes, getRegisteredStores, getSchema, getSelectedRowModel, getSortedRowModel, getTimeTravelState, getValueAtPath, graphStore, graphSyncStatusStore, hasMergeStrategy, humanizeFieldName, hydrateGraphFromStorage, inferSchemaFieldType, lwwStrategy, makeRestTransport, matchesFilter, matchesSearch, parseCreateTable, persistGraphToStorage, prepareGraphStoreScope, publishFlintMutation, queryOnce, readRelations, recordGraphSnapshot, registerEntityFromSql, registerEntityJsonSchema, registerEntityTransport, registerMergeStrategy, registerRuntimeSchema, registerSchema, registerStore, registerSubscriber, registryKey, renderMarkdownToHtml, replayActionWithRetry, resetRealtimeManager, resolveActiveGraphStore, restoreGraphSnapshot, restoreGraphSnapshotBySeq, runWithGraphStore, selectGraph, serializeKey, setActiveGraphStore, setDefaultMergeStrategy, setValueAtPath, sqlTypeToJsonSchema, startGarbageCollector, startLocalFirstGraph, stepTimeTravel, stopGarbageCollector, subscribeDevtoolsEvent, subscribeSubscriberStats, subscribeTimeTravel, toPrismaOrderBy, toPrismaWhere, unregisterSubscriber, useGraphStore };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { graphStore, useGraphStore, subscribeDevtoolsEvent, getGraphDevtoolsCompatibilityDelegate, subscribeGraphDevtoolsCompatibility, matchesFilter, matchesSearch, compareEntities, emitLegacyDevtoolsEvent, __require } from './chunk-BE3BMRKF.mjs';
2
- export { EMPTY_ENTITY_STATE, EMPTY_IDS, EMPTY_LIST_STATE, EMPTY_SYNC_METADATA, applyView, attachGlobalListeners, cascadeInvalidation, checkCompleteness, compareEntities, configureEngine, createGraphStore, dedupe, fetchEntity, fetchList, findInsertionIndex, getActiveSubscriberCount, getEngineOptions, getMergeStrategy, getSchema, graphStore, hasMergeStrategy, lwwStrategy, matchesFilter, matchesSearch, readRelations, registerMergeStrategy, registerSchema, registerSubscriber, serializeKey, setDefaultMergeStrategy, startGarbageCollector, stopGarbageCollector, subscribeDevtoolsEvent, subscribeSubscriberStats, unregisterSubscriber, useGraphStore } from './chunk-BE3BMRKF.mjs';
1
+ import { graphStore, useGraphStore, subscribeDevtoolsEvent, getGraphDevtoolsCompatibilityDelegate, subscribeGraphDevtoolsCompatibility, matchesFilter, matchesSearch, compareEntities, emitLegacyDevtoolsEvent, __require } from './chunk-H7NBREZV.mjs';
2
+ export { EMPTY_ENTITY_STATE, EMPTY_IDS, EMPTY_LIST_STATE, EMPTY_SYNC_METADATA, applyView, attachGlobalListeners, cascadeInvalidation, checkCompleteness, compareEntities, configureEngine, createGraphStore, dedupe, fetchEntity, fetchList, findInsertionIndex, getActiveSubscriberCount, getEngineOptions, getMergeStrategy, getSchema, graphStore, hasMergeStrategy, lwwStrategy, matchesFilter, matchesSearch, readRelations, registerMergeStrategy, registerSchema, registerSubscriber, serializeKey, setDefaultMergeStrategy, startGarbageCollector, stopGarbageCollector, subscribeDevtoolsEvent, subscribeSubscriberStats, unregisterSubscriber, useGraphStore } from './chunk-H7NBREZV.mjs';
3
3
  import { createStore } from 'zustand/vanilla';
4
4
 
5
5
  // src/errors.ts
@@ -968,21 +968,49 @@ var graphSyncStatusStore = createStore()((set) => ({
968
968
  }
969
969
  }))
970
970
  }));
971
- var pendingActions = /* @__PURE__ */ new Map();
971
+ function createGraphSyncStatusStore() {
972
+ return createStore()((set) => ({
973
+ status: {
974
+ phase: "idle",
975
+ isOnline: true,
976
+ isSynced: true,
977
+ pendingActions: 0,
978
+ lastHydratedAt: null,
979
+ lastPersistedAt: null,
980
+ storageKey: null,
981
+ error: null
982
+ },
983
+ setStatus: (status) => set((state) => ({ status: { ...state.status, ...status } }))
984
+ }));
985
+ }
986
+ function createRuntimeScope() {
987
+ return {
988
+ pendingActions: /* @__PURE__ */ new Map(),
989
+ statusStore: createGraphSyncStatusStore()
990
+ };
991
+ }
992
+ var fallbackScope = {
993
+ pendingActions: /* @__PURE__ */ new Map(),
994
+ statusStore: graphSyncStatusStore
995
+ };
996
+ function resolveScope(scope) {
997
+ return scope ?? fallbackScope;
998
+ }
972
999
  function getGraphSyncStatus() {
973
1000
  return graphSyncStatusStore.getState().status;
974
1001
  }
975
1002
  async function persistGraphToStorage(opts) {
976
1003
  const storeApi = opts.store ?? graphStore;
1004
+ const scope = resolveScope(opts.scope);
977
1005
  const payload = {
978
1006
  version: 1,
979
1007
  snapshot: cloneGraphSnapshot(storeApi),
980
- pendingActions: opts.pendingActions ?? Array.from(pendingActions.values())
1008
+ pendingActions: opts.pendingActions ?? Array.from(scope.pendingActions.values())
981
1009
  };
982
1010
  const json = JSON.stringify(payload);
983
1011
  await opts.storage.set(opts.key, json);
984
1012
  const persistedAt = (/* @__PURE__ */ new Date()).toISOString();
985
- graphSyncStatusStore.getState().setStatus({
1013
+ scope.statusStore.getState().setStatus({
986
1014
  lastPersistedAt: persistedAt,
987
1015
  storageKey: opts.key,
988
1016
  pendingActions: payload.pendingActions.length
@@ -996,6 +1024,7 @@ async function persistGraphToStorage(opts) {
996
1024
  }
997
1025
  async function hydrateGraphFromStorage(opts) {
998
1026
  const storeApi = opts.store ?? graphStore;
1027
+ const scope = resolveScope(opts.scope);
999
1028
  const raw = await opts.storage.get(opts.key);
1000
1029
  if (!raw) {
1001
1030
  return {
@@ -1009,13 +1038,13 @@ async function hydrateGraphFromStorage(opts) {
1009
1038
  try {
1010
1039
  const parsed = JSON.parse(raw);
1011
1040
  storeApi.setState(parsed.snapshot);
1012
- pendingActions.clear();
1013
- for (const action of parsed.pendingActions ?? []) pendingActions.set(action.id, action);
1041
+ scope.pendingActions.clear();
1042
+ for (const action of parsed.pendingActions ?? []) scope.pendingActions.set(action.id, action);
1014
1043
  const hydratedAt = (/* @__PURE__ */ new Date()).toISOString();
1015
- graphSyncStatusStore.getState().setStatus({
1044
+ scope.statusStore.getState().setStatus({
1016
1045
  lastHydratedAt: hydratedAt,
1017
1046
  storageKey: opts.key,
1018
- pendingActions: pendingActions.size,
1047
+ pendingActions: scope.pendingActions.size,
1019
1048
  error: null
1020
1049
  });
1021
1050
  return {
@@ -1025,11 +1054,11 @@ async function hydrateGraphFromStorage(opts) {
1025
1054
  entityCounts: Object.fromEntries(
1026
1055
  Object.entries(parsed.snapshot.entities).map(([type, entities]) => [type, Object.keys(entities).length])
1027
1056
  ),
1028
- pendingActions: Array.from(pendingActions.values())
1057
+ pendingActions: Array.from(scope.pendingActions.values())
1029
1058
  };
1030
1059
  } catch (error) {
1031
1060
  const message = error instanceof Error ? error.message : String(error);
1032
- graphSyncStatusStore.getState().setStatus({
1061
+ scope.statusStore.getState().setStatus({
1033
1062
  phase: "error",
1034
1063
  error: message,
1035
1064
  storageKey: opts.key
@@ -1047,8 +1076,15 @@ function startLocalFirstGraph(opts) {
1047
1076
  const storeApi = opts.store ?? graphStore;
1048
1077
  const key = opts.key ?? DEFAULT_STORAGE_KEY;
1049
1078
  const persistDebounceMs = opts.persistDebounceMs ?? 50;
1050
- const statusStore = graphSyncStatusStore.getState();
1051
- statusStore.setStatus({
1079
+ const scope = opts.scope ?? createRuntimeScope();
1080
+ const { pendingActions } = scope;
1081
+ const setStatus = (status) => {
1082
+ scope.statusStore.getState().setStatus(status);
1083
+ if (scope.statusStore !== graphSyncStatusStore) {
1084
+ graphSyncStatusStore.getState().setStatus(status);
1085
+ }
1086
+ };
1087
+ setStatus({
1052
1088
  phase: "hydrating",
1053
1089
  storageKey: key,
1054
1090
  isOnline: opts.onlineSource?.getIsOnline() ?? getDefaultOnlineSource().getIsOnline(),
@@ -1056,10 +1092,22 @@ function startLocalFirstGraph(opts) {
1056
1092
  error: null
1057
1093
  });
1058
1094
  let persistTimer = null;
1095
+ const inFlight = /* @__PURE__ */ new Set();
1096
+ let disposePromise = null;
1097
+ let disposed = false;
1098
+ const runPersist = () => {
1099
+ const write = persistGraphToStorage({ storage: opts.storage, key, store: storeApi, scope }).catch(() => void 0).finally(() => {
1100
+ inFlight.delete(write);
1101
+ });
1102
+ inFlight.add(write);
1103
+ return write;
1104
+ };
1059
1105
  const schedulePersist = () => {
1106
+ if (disposed) return;
1060
1107
  if (persistTimer) clearTimeout(persistTimer);
1061
1108
  persistTimer = setTimeout(() => {
1062
- void persistGraphToStorage({ storage: opts.storage, key, store: storeApi });
1109
+ persistTimer = null;
1110
+ void runPersist();
1063
1111
  }, persistDebounceMs);
1064
1112
  };
1065
1113
  const graphUnsub = storeApi.subscribe(() => {
@@ -1068,7 +1116,7 @@ function startLocalFirstGraph(opts) {
1068
1116
  const actionUnsub = subscribeGraphActionEvents((event) => {
1069
1117
  if (event.type === "enqueued") pendingActions.set(event.record.id, event.record);
1070
1118
  if (event.type === "settled") pendingActions.delete(event.record.id);
1071
- graphSyncStatusStore.getState().setStatus({
1119
+ setStatus({
1072
1120
  pendingActions: pendingActions.size,
1073
1121
  isSynced: pendingActions.size === 0
1074
1122
  });
@@ -1076,15 +1124,15 @@ function startLocalFirstGraph(opts) {
1076
1124
  });
1077
1125
  const onlineSource = opts.onlineSource ?? getDefaultOnlineSource();
1078
1126
  const onlineUnsub = onlineSource.subscribe((online) => {
1079
- graphSyncStatusStore.getState().setStatus({
1127
+ setStatus({
1080
1128
  isOnline: online,
1081
1129
  phase: online ? "ready" : "offline"
1082
1130
  });
1083
1131
  });
1084
1132
  const ready = (async () => {
1085
- const hydrated = await hydrateGraphFromStorage({ storage: opts.storage, key, store: storeApi });
1133
+ const hydrated = await hydrateGraphFromStorage({ storage: opts.storage, key, store: storeApi, scope });
1086
1134
  if (opts.replayPendingActions && hydrated.ok && pendingActions.size > 0) {
1087
- graphSyncStatusStore.getState().setStatus({
1135
+ setStatus({
1088
1136
  phase: "syncing",
1089
1137
  isSynced: false
1090
1138
  });
@@ -1093,10 +1141,10 @@ function startLocalFirstGraph(opts) {
1093
1141
  await replayActionWithRetry(action, policy, storeApi);
1094
1142
  pendingActions.delete(action.id);
1095
1143
  }
1096
- await persistGraphToStorage({ storage: opts.storage, key, store: storeApi });
1144
+ await persistGraphToStorage({ storage: opts.storage, key, store: storeApi, scope });
1097
1145
  }
1098
1146
  const online = onlineSource.getIsOnline();
1099
- graphSyncStatusStore.getState().setStatus({
1147
+ setStatus({
1100
1148
  phase: online ? "ready" : "offline",
1101
1149
  isOnline: online,
1102
1150
  isSynced: pendingActions.size === 0,
@@ -1105,21 +1153,39 @@ function startLocalFirstGraph(opts) {
1105
1153
  })();
1106
1154
  return {
1107
1155
  ready,
1156
+ /**
1157
+ * Tear down, and do not resolve until this runtime is quiescent.
1158
+ *
1159
+ * Unsubscribes first so no further work is queued, cancels a scheduled
1160
+ * write, then awaits every write already in flight. Callers that await
1161
+ * this are guaranteed no persist from this runtime lands afterwards.
1162
+ *
1163
+ * Idempotent: a second call returns the same promise rather than starting
1164
+ * a second teardown, which is what makes a StrictMode double-unmount safe.
1165
+ */
1108
1166
  dispose() {
1167
+ if (disposePromise) return disposePromise;
1168
+ disposed = true;
1109
1169
  graphUnsub();
1110
1170
  actionUnsub();
1111
1171
  onlineUnsub();
1112
- if (persistTimer) clearTimeout(persistTimer);
1172
+ if (persistTimer) {
1173
+ clearTimeout(persistTimer);
1174
+ persistTimer = null;
1175
+ }
1176
+ disposePromise = Promise.all([...inFlight]).then(() => void 0);
1177
+ return disposePromise;
1113
1178
  },
1114
1179
  async persistNow() {
1115
- await persistGraphToStorage({ storage: opts.storage, key, store: storeApi });
1180
+ await runPersist();
1116
1181
  },
1117
1182
  hydrate() {
1118
- return hydrateGraphFromStorage({ storage: opts.storage, key, store: storeApi });
1183
+ return hydrateGraphFromStorage({ storage: opts.storage, key, store: storeApi, scope });
1119
1184
  },
1120
1185
  getStatus() {
1121
- return graphSyncStatusStore.getState().status;
1122
- }
1186
+ return scope.statusStore.getState().status;
1187
+ },
1188
+ scope
1123
1189
  };
1124
1190
  }
1125
1191
  function cloneGraphSnapshot(storeApi) {
@@ -2500,7 +2566,10 @@ function createElectricAdapter(opts) {
2500
2566
  shapeUnsubs.push(tc.shapeStream.subscribe((msgs) => {
2501
2567
  if (!globalHandler) return;
2502
2568
  const changes = msgs.map((m) => toChange(tc, m)).filter((c) => c !== null);
2503
- if (changes.length > 0) globalHandler({ changes, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2569
+ const last = msgs.at(-1);
2570
+ const handle = tc.shapeStream.shapeHandle;
2571
+ const cursor = handle && last?.offset ? { handle, offset: last.offset } : void 0;
2572
+ if (changes.length > 0) globalHandler({ changes, timestamp: (/* @__PURE__ */ new Date()).toISOString(), cursor });
2504
2573
  if (tc.shapeStream.isUpToDate) {
2505
2574
  syncedTables.add(tc.table);
2506
2575
  checkAllSynced();
@@ -2605,6 +2674,17 @@ function createTenantScopedElectricAdapter(opts) {
2605
2674
  }
2606
2675
 
2607
2676
  // src/adapters/pglite-persistence.ts
2677
+ function evaluateResume(stored, expected) {
2678
+ if (stored.value === null) return { action: "rebuild", reason: "no-value" };
2679
+ if (!stored.checkpoint) return { action: "rebuild", reason: "no-checkpoint" };
2680
+ if (stored.checkpoint.generation !== expected.generation) {
2681
+ return { action: "rebuild", reason: "stale-generation" };
2682
+ }
2683
+ if (expected.handle && stored.checkpoint.handle !== expected.handle) {
2684
+ return { action: "rebuild", reason: "handle-changed" };
2685
+ }
2686
+ return { action: "resume", checkpoint: stored.checkpoint };
2687
+ }
2608
2688
  var DEFAULT_TABLE = "_graph_snapshot";
2609
2689
  async function createPGlitePersistenceAdapter(pglite, options = {}) {
2610
2690
  const tableName = options.tableName ?? DEFAULT_TABLE;
@@ -2618,6 +2698,12 @@ async function createPGlitePersistenceAdapter(pglite, options = {}) {
2618
2698
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
2619
2699
  )`
2620
2700
  );
2701
+ await pglite.exec(
2702
+ `ALTER TABLE ${tableName}
2703
+ ADD COLUMN IF NOT EXISTS checkpoint_handle TEXT,
2704
+ ADD COLUMN IF NOT EXISTS checkpoint_offset TEXT,
2705
+ ADD COLUMN IF NOT EXISTS checkpoint_generation INTEGER`
2706
+ );
2621
2707
  return {
2622
2708
  async get(key) {
2623
2709
  const result = await pglite.query(
@@ -2639,6 +2725,42 @@ async function createPGlitePersistenceAdapter(pglite, options = {}) {
2639
2725
  },
2640
2726
  async remove(key) {
2641
2727
  await pglite.query(`DELETE FROM ${tableName} WHERE key = $1`, [key]);
2728
+ },
2729
+ /**
2730
+ * Commit rows and their resume checkpoint together.
2731
+ *
2732
+ * One statement, so the two cannot be separated by a crash: either the row
2733
+ * carries the new value AND the new checkpoint, or it carries neither. On
2734
+ * resume, `getCheckpoint` therefore describes exactly the value returned by
2735
+ * `get` — the property ADR-009 G3 requires.
2736
+ */
2737
+ async setWithCheckpoint({ key, value, checkpoint }) {
2738
+ await pglite.query(
2739
+ `INSERT INTO ${tableName}
2740
+ (key, value, updated_at, checkpoint_handle, checkpoint_offset, checkpoint_generation)
2741
+ VALUES ($1, $2, now(), $3, $4, $5)
2742
+ ON CONFLICT (key) DO UPDATE
2743
+ SET value = EXCLUDED.value,
2744
+ updated_at = now(),
2745
+ checkpoint_handle = EXCLUDED.checkpoint_handle,
2746
+ checkpoint_offset = EXCLUDED.checkpoint_offset,
2747
+ checkpoint_generation = EXCLUDED.checkpoint_generation`,
2748
+ [key, value, checkpoint.handle, checkpoint.offset, checkpoint.generation]
2749
+ );
2750
+ },
2751
+ async getCheckpoint(key) {
2752
+ const result = await pglite.query(
2753
+ `SELECT checkpoint_handle, checkpoint_offset, checkpoint_generation
2754
+ FROM ${tableName} WHERE key = $1`,
2755
+ [key]
2756
+ );
2757
+ const row = result.rows[0];
2758
+ if (!row?.checkpoint_handle || row.checkpoint_offset === null) return null;
2759
+ return {
2760
+ handle: row.checkpoint_handle,
2761
+ offset: row.checkpoint_offset,
2762
+ generation: row.checkpoint_generation ?? 0
2763
+ };
2642
2764
  }
2643
2765
  };
2644
2766
  }
@@ -3163,6 +3285,6 @@ function getFacetedRowModel(preFilteredRowModel, columnId, allFilteredRowModel)
3163
3285
  return preFilteredRowModel;
3164
3286
  }
3165
3287
 
3166
- export { IncrementalView, RealtimeManager, TerminalError, TransientError, __resetActiveGraphStore, __resetEntityTransports, __resetStoreRegistry, applyAgUiDelta, applyAgUiSnapshot, applyJsonPatch, buildTenantWhere, collectDirtyPaths, configureTimeTravel, createConvexAdapter, createDevtoolsEventBus, createElectricAdapter, createFlintAdapter, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphTool, createGraphTransaction, createLoroMergeStrategy, createPGlitePersistenceAdapter, createRow, createSchemaGraphTool, createSupabaseRealtimeAdapter, createSurrealLiveAdapter, createTauriSqlPersistenceAdapter, createTenantScopedElectricAdapter, createWebSocketAdapter, escapeHtml, exportGraphSnapshot, exportGraphSnapshotWithSchemas, flattenClauses, getCoreRowModel, getRealtimeManager as getElectricRealtimeManager, getEntityJsonSchema, getEntityTransport, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGraphSyncStatus, getGroupedRowModel, getPaginatedRowModel, getRealtimeManager, getRegisteredEntityTypes, getRegisteredStores, getSelectedRowModel, getSortedRowModel, getTimeTravelState, getValueAtPath, graphSyncStatusStore, hasCustomPredicates, humanizeFieldName, hydrateGraphFromStorage, inferSchemaFieldType, makeRestTransport, parseCreateTable, persistGraphToStorage, prepareGraphStoreScope, publishFlintMutation, queryOnce, recordGraphSnapshot, registerEntityFromSql, registerEntityJsonSchema, registerEntityTransport, registerRuntimeSchema, registerStore, registryKey, renderMarkdownToHtml, replayActionWithRetry, resetRealtimeManager, resolveActiveGraphStore, restoreGraphSnapshot, restoreGraphSnapshotBySeq, runWithGraphStore, selectGraph, setActiveGraphStore, setValueAtPath, sqlTypeToJsonSchema, startLocalFirstGraph, stepTimeTravel, subscribeTimeTravel, toEntityError, toGraphQLVariables, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses };
3288
+ export { IncrementalView, RealtimeManager, TerminalError, TransientError, __resetActiveGraphStore, __resetEntityTransports, __resetStoreRegistry, applyAgUiDelta, applyAgUiSnapshot, applyJsonPatch, buildTenantWhere, collectDirtyPaths, configureTimeTravel, createConvexAdapter, createDevtoolsEventBus, createElectricAdapter, createFlintAdapter, createGraphAction, createGraphEffect, createGraphQLSubscriptionAdapter, createGraphSyncStatusStore, createGraphTool, createGraphTransaction, createLoroMergeStrategy, createPGlitePersistenceAdapter, createRow, createRuntimeScope, createSchemaGraphTool, createSupabaseRealtimeAdapter, createSurrealLiveAdapter, createTauriSqlPersistenceAdapter, createTenantScopedElectricAdapter, createWebSocketAdapter, escapeHtml, evaluateResume, exportGraphSnapshot, exportGraphSnapshotWithSchemas, flattenClauses, getCoreRowModel, getRealtimeManager as getElectricRealtimeManager, getEntityJsonSchema, getEntityTransport, getExpandedRowModel, getFacetedMinMaxValues, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getGraphSyncStatus, getGroupedRowModel, getPaginatedRowModel, getRealtimeManager, getRegisteredEntityTypes, getRegisteredStores, getSelectedRowModel, getSortedRowModel, getTimeTravelState, getValueAtPath, graphSyncStatusStore, hasCustomPredicates, humanizeFieldName, hydrateGraphFromStorage, inferSchemaFieldType, makeRestTransport, parseCreateTable, persistGraphToStorage, prepareGraphStoreScope, publishFlintMutation, queryOnce, recordGraphSnapshot, registerEntityFromSql, registerEntityJsonSchema, registerEntityTransport, registerRuntimeSchema, registerStore, registryKey, renderMarkdownToHtml, replayActionWithRetry, resetRealtimeManager, resolveActiveGraphStore, restoreGraphSnapshot, restoreGraphSnapshotBySeq, runWithGraphStore, selectGraph, setActiveGraphStore, setValueAtPath, sqlTypeToJsonSchema, startLocalFirstGraph, stepTimeTravel, subscribeTimeTravel, toEntityError, toGraphQLVariables, toPrismaOrderBy, toPrismaWhere, toRestParams, toSQLClauses };
3167
3289
  //# sourceMappingURL=index.mjs.map
3168
3290
  //# sourceMappingURL=index.mjs.map