@abloatai/ablo 0.25.0 → 0.26.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/AGENTS.md +5 -3
- package/CHANGELOG.md +20 -0
- package/README.md +3 -3
- package/dist/BaseSyncedStore.d.ts +72 -195
- package/dist/BaseSyncedStore.js +240 -625
- package/dist/Database.d.ts +8 -9
- package/dist/Database.js +9 -14
- package/dist/LazyReferenceCollection.js +5 -0
- package/dist/Model.js +6 -8
- package/dist/ModelRegistry.d.ts +25 -6
- package/dist/ModelRegistry.js +12 -6
- package/dist/NetworkMonitor.js +1 -1
- package/dist/ObjectPool.js +9 -3
- package/dist/SyncClient.d.ts +15 -5
- package/dist/SyncClient.js +152 -66
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/agent/Agent.d.ts +21 -8
- package/dist/agent/Agent.js +26 -13
- package/dist/ai-sdk/coordination-context.js +9 -3
- package/dist/ai-sdk/wrap.d.ts +1 -1
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +10 -2
- package/dist/auth/credentialSource.d.ts +1 -13
- package/dist/auth/credentialSource.js +6 -13
- package/dist/auth/index.js +3 -3
- package/dist/cli.cjs +328 -262
- package/dist/client/Ablo.d.ts +35 -764
- package/dist/client/Ablo.js +64 -688
- package/dist/client/ApiClient.d.ts +14 -1
- package/dist/client/ApiClient.js +63 -9
- package/dist/client/auth.d.ts +8 -17
- package/dist/client/auth.js +42 -6
- package/dist/client/consoleLogger.d.ts +36 -0
- package/dist/client/consoleLogger.js +45 -0
- package/dist/client/createInternalComponents.js +1 -1
- package/dist/client/createModelProxy.js +10 -6
- package/dist/client/credentialEndpoint.d.ts +63 -0
- package/dist/client/credentialEndpoint.js +87 -0
- package/dist/client/hostedEndpoints.d.ts +24 -0
- package/dist/client/hostedEndpoints.js +24 -0
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/identity.js +2 -1
- package/dist/client/modelRegistration.d.ts +14 -0
- package/dist/client/modelRegistration.js +321 -0
- package/dist/client/options.d.ts +396 -0
- package/dist/client/options.js +10 -0
- package/dist/client/resourceTypes.d.ts +344 -0
- package/dist/client/resourceTypes.js +13 -0
- package/dist/client/schemaConfig.d.ts +56 -0
- package/dist/client/schemaConfig.js +188 -0
- package/dist/client/sessionMint.d.ts +1 -1
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +33 -0
- package/dist/client/wsMutationExecutor.js +77 -0
- package/dist/context.js +0 -3
- package/dist/coordination/index.d.ts +8 -1
- package/dist/coordination/index.js +24 -1
- package/dist/coordination/trace.d.ts +0 -1
- package/dist/coordination/trace.js +0 -1
- package/dist/core/DatabaseManager.d.ts +0 -1
- package/dist/core/DatabaseManager.js +26 -24
- package/dist/core/QueryProcessor.js +7 -8
- package/dist/core/QueryView.d.ts +14 -2
- package/dist/core/QueryView.js +4 -5
- package/dist/core/StoreManager.d.ts +2 -2
- package/dist/core/StoreManager.js +5 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/openIDBWithTimeout.js +15 -11
- package/dist/core/query-utils.js +1 -1
- package/dist/core/storeContract.d.ts +143 -0
- package/dist/core/storeContract.js +16 -0
- package/dist/errorCodes.d.ts +11 -2
- package/dist/errorCodes.js +155 -137
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +14 -1
- package/dist/index.js +5 -0
- package/dist/interfaces/index.d.ts +6 -20
- package/dist/keys/index.js +18 -9
- package/dist/mutators/UndoManager.js +6 -1
- package/dist/mutators/defineMutators.d.ts +1 -3
- package/dist/policy/types.d.ts +1 -1
- package/dist/query/client.d.ts +11 -0
- package/dist/query/client.js +81 -52
- package/dist/react/AbloProvider.d.ts +6 -10
- package/dist/react/AbloProvider.js +22 -15
- package/dist/react/context.d.ts +2 -118
- package/dist/react/useAblo.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.js +7 -1
- package/dist/react/useReactive.js +0 -1
- package/dist/react/useUndoScope.js +2 -2
- package/dist/schema/ddlLock.d.ts +39 -0
- package/dist/schema/ddlLock.js +51 -0
- package/dist/schema/field.js +1 -1
- package/dist/schema/index.d.ts +4 -3
- package/dist/schema/index.js +15 -5
- package/dist/schema/model.d.ts +3 -3
- package/dist/schema/model.js +2 -2
- package/dist/schema/openapi.js +5 -1
- package/dist/schema/queries.d.ts +1 -1
- package/dist/schema/queries.js +1 -0
- package/dist/schema/residency.d.ts +35 -0
- package/dist/schema/residency.js +27 -0
- package/dist/schema/roles.d.ts +25 -0
- package/dist/schema/roles.js +37 -0
- package/dist/schema/schema.d.ts +3 -3
- package/dist/schema/schema.js +11 -7
- package/dist/schema/serialize.d.ts +8 -8
- package/dist/schema/sync-delta-row.d.ts +2 -2
- package/dist/schema/sync-delta-row.js +2 -2
- package/dist/server/storage-mode.d.ts +8 -1
- package/dist/server/storage-mode.js +14 -4
- package/dist/source/adapter.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +4 -3
- package/dist/source/adapters/kysely.js +4 -3
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.js +5 -4
- package/dist/source/conformance.js +10 -8
- package/dist/source/connector.js +4 -2
- package/dist/source/factory.d.ts +106 -0
- package/dist/source/factory.js +273 -0
- package/dist/source/index.d.ts +11 -461
- package/dist/source/index.js +14 -416
- package/dist/source/next.d.ts +1 -1
- package/dist/source/next.js +1 -1
- package/dist/source/pushQueue.d.ts +1 -1
- package/dist/source/pushQueue.js +6 -3
- package/dist/source/signing.d.ts +59 -0
- package/dist/source/signing.js +142 -0
- package/dist/source/types.d.ts +334 -0
- package/dist/source/types.js +44 -0
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/stores/ObjectStore.js +23 -23
- package/dist/stores/SyncActionStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.js +70 -35
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +1 -0
- package/dist/sync/AreaOfInterestManager.d.ts +2 -2
- package/dist/sync/AreaOfInterestManager.js +1 -1
- package/dist/sync/BootstrapHelper.d.ts +1 -3
- package/dist/sync/BootstrapHelper.js +29 -8
- package/dist/sync/ConnectionManager.d.ts +8 -0
- package/dist/sync/ConnectionManager.js +19 -4
- package/dist/sync/HydrationCoordinator.d.ts +12 -0
- package/dist/sync/HydrationCoordinator.js +25 -5
- package/dist/sync/NetworkProbe.d.ts +3 -2
- package/dist/sync/NetworkProbe.js +7 -6
- package/dist/sync/SyncWebSocket.d.ts +71 -120
- package/dist/sync/SyncWebSocket.js +324 -630
- package/dist/sync/awaitClaimGrant.js +27 -19
- package/dist/sync/bootstrapApply.d.ts +60 -0
- package/dist/sync/bootstrapApply.js +65 -0
- package/dist/sync/commitFrames.d.ts +43 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.js +3 -2
- package/dist/sync/createSnapshot.d.ts +1 -3
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +329 -0
- package/dist/sync/deltaPipeline.d.ts +111 -0
- package/dist/sync/deltaPipeline.js +262 -0
- package/dist/sync/groupChange.d.ts +111 -0
- package/dist/sync/groupChange.js +246 -0
- package/dist/sync/heartbeat.d.ts +62 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +8 -8
- package/dist/sync/syncCursor.d.ts +44 -0
- package/dist/sync/syncCursor.js +59 -0
- package/dist/sync/syncPlan.d.ts +62 -0
- package/dist/sync/syncPlan.js +51 -0
- package/dist/sync/wsFrameHandlers.d.ts +117 -0
- package/dist/sync/wsFrameHandlers.js +378 -0
- package/dist/testing/fixtures/bootstrap.d.ts +9 -11
- package/dist/testing/fixtures/deltas.d.ts +1 -1
- package/dist/testing/fixtures/models.js +1 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
- package/dist/testing/helpers/sync-engine-harness.js +2 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
- package/dist/testing/mocks/MockSyncContext.js +1 -32
- package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
- package/dist/transactions/TransactionQueue.d.ts +20 -56
- package/dist/transactions/TransactionQueue.js +101 -487
- package/dist/transactions/TransactionStore.d.ts +18 -0
- package/dist/transactions/TransactionStore.js +51 -0
- package/dist/transactions/coalesceRules.d.ts +34 -0
- package/dist/transactions/coalesceRules.js +117 -0
- package/dist/transactions/commitPayload.d.ts +134 -0
- package/dist/transactions/commitPayload.js +152 -0
- package/dist/transactions/deltaConfirmation.d.ts +60 -0
- package/dist/transactions/deltaConfirmation.js +223 -0
- package/dist/transactions/optimistic.d.ts +24 -0
- package/dist/transactions/optimistic.js +45 -0
- package/dist/transactions/persistedReplay.d.ts +93 -0
- package/dist/transactions/persistedReplay.js +105 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +0 -5
- package/dist/types/modelData.d.ts +12 -0
- package/dist/types/modelData.js +11 -0
- package/dist/types/participant.d.ts +21 -0
- package/dist/types/participant.js +12 -0
- package/dist/types/streams.d.ts +8 -14
- package/dist/utils/mobx-setup.d.ts +1 -1
- package/dist/utils/mobx-setup.js +2 -2
- package/dist/wire/errorEnvelope.d.ts +11 -2
- package/dist/wire/errorEnvelope.js +14 -2
- package/dist/wire/frames.d.ts +90 -0
- package/dist/wire/frames.js +55 -1
- package/dist/wire/index.d.ts +4 -1
- package/dist/wire/index.js +20 -1
- package/dist/wire/protocol.d.ts +45 -0
- package/dist/wire/protocol.js +45 -0
- package/dist/wire/protocolVersion.d.ts +56 -0
- package/dist/wire/protocolVersion.js +63 -0
- package/docs/api-keys.md +4 -3
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/package.json +15 -13
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
package/AGENTS.md
CHANGED
|
@@ -6,16 +6,18 @@ Claims don't lock. If another writer holds the row, `claim` waits for them and r
|
|
|
6
6
|
|
|
7
7
|
## Start here — scaffold with `ablo init`
|
|
8
8
|
|
|
9
|
-
Don't hand-write the integration. Run the CLI; it generates the current-API schema, client, Data Source endpoint, and (for Next.js) the browser provider + session route:
|
|
9
|
+
Don't hand-write the integration. Run the CLI; it generates the current-API schema, client, the database connection (logical replication by default, or a signed Data Source endpoint as the fallback), and (for Next.js) the browser provider + session route:
|
|
10
10
|
|
|
11
11
|
- **Scaffold:** `npx ablo init --yes` — flag-driven, never prompts. Override defaults with `--framework <nextjs|vite|remix|vanilla>`, `--auth <apikey|…>`, `--no-agent`, `--no-pull`, `--no-install`, `--no-login`. (Plain `ablo init` needs a TTY and will **HANG** in an agent/CI run — always pass `--yes`.)
|
|
12
12
|
- **Auth:** set `ABLO_API_KEY` in the environment. Do **NOT** run `ablo login` — it opens a browser device flow and blocks an agent.
|
|
13
|
-
- **
|
|
13
|
+
- **Connect your database — logical replication (the primary path):** `npx ablo connect` prints the setup SQL (`wal_level=logical`, a publication, a `REPLICATION` role); `npx ablo connect --register` registers the source with Ablo in one step. Ablo **consumes your Postgres' logical-replication stream** — it never runs DDL on, writes to, owns, or migrates your database, and your application keeps the write path. Registration **is** the enable; there is no tier or flag to pick. (Ablo hosts only the transaction log + coordination, never your rows.)
|
|
14
|
+
- **Fallback — signed Data Source endpoint** (DB can't grant a `REPLICATION` role): the generated `ablo/data-source.ts` exposes one route; Ablo sends signed requests and your app touches its own DB. **Only in this mode** does `npx ablo migrate` provision the adapter's bookkeeping tables (`ablo_outbox`, `ablo_idempotency`) plus your Ablo models — it does **not** touch your other tables. Keep your own migrations (drizzle-kit / prisma migrate) for auth and anything outside the Ablo schema.
|
|
15
|
+
- **No database yet?** A sandbox `sk_test` key holds throwaway **test data** (Stripe-test-mode style) so you can try Ablo before connecting your own Postgres. Test-mode only — in production every row lives in your database.
|
|
14
16
|
- **Adopt an existing DB schema:** `npx ablo pull prisma [path]` / `pull drizzle <module>` (lossless) or `pull` (live DB, lossy). Writes `ablo/schema.ts`.
|
|
15
17
|
- **Push your schema — REQUIRED before any write works.** The server keeps its OWN copy of the schema. After you create or edit `ablo/schema.ts`, run `npx ablo push` (one-shot) — or `npx ablo dev --no-watch`. **Skip this and every write to a new or changed model fails with `server_execute_unknown_model`.** (Plain `ablo dev` watches forever — never run it bare in an agent.)
|
|
16
18
|
- **Other long-running:** `npx ablo logs --no-follow` (default tails forever). `npx ablo mode test|live` ALWAYS pass the argument. `status`, `push`, `pull`, `check`, `generate` are one-shot — safe as-is.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
When you use the signed-endpoint fallback, the generated `ablo/data-source.ts` is the whole endpoint and needs no hand-editing: `dataSourceNext({ schema, apiKey, adapter: prismaDataSource(prisma, schema) })` (or `drizzleDataSource(db, schema)`). The adapter owns commit / idempotency / outbox.
|
|
19
21
|
|
|
20
22
|
## Rule
|
|
21
23
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **`ablo connect --register` works against any deployment.** Registering your database as an Ablo data source over logical replication is now a first-class path everywhere — the gate that could refuse a new connection-string registration is gone. Point `ablo connect --register` at your Postgres and Ablo begins consuming its replication stream while your application keeps owning the write path; registering the source _is_ the enable, with no tier or flag to choose. The signed `dataSource()` endpoint remains the explicit fallback for databases that can't grant replication.
|
|
8
|
+
|
|
9
|
+
- **More engine failures surface as typed errors you can branch on**, instead of opaque `500`s. New `AbloError` codes: `schema_provisioning_forbidden` (`403` — a push tried to create tables in a database where the engine isn't permitted to run DDL), `request_too_large` (`413` — a query or commit body exceeded the size limit), `commit_operation_invalid` (`400` — a malformed commit operation), and `upload_not_configured` (`503` — an upload was attempted with no blob store wired).
|
|
10
|
+
|
|
11
|
+
- **`@abloatai/ablo/wire` now exports the protocol schemas.** `commitOperationSchema`, `commitPayloadSchema`, and the frame schemas let tooling and tests validate client↔engine frames against the same contract the engine enforces.
|
|
12
|
+
|
|
13
|
+
- **Protocol version negotiation is surfaced.** `@abloatai/ablo/wire` exports `PROTOCOL_VERSION`, `MIN_SUPPORTED_PROTOCOL_VERSION`, `WS_CLOSE_PROTOCOL_VERSION`, `PROTOCOL_VERSION_HEADER`, `protocolVersionProblem`, and `ERROR_CONTRACT_VERSION` — the single integer the client and server compare on connect to detect an incompatible peer and fail with a clear reason instead of a cryptic wire error.
|
|
14
|
+
|
|
15
|
+
- **`@abloatai/ablo/coordination` exposes the conflict-policy vocabulary** — `defaultPolicy`, `capabilityPreemptPolicy`, and `interpretConflictAxis` — for server-side consumers building on the claim model. `@abloatai/ablo/schema` gains the role primitives (`identityRole`, `entityRole`, and their types and schemas) for declaring identity- and entity-scoped roles.
|
|
16
|
+
|
|
17
|
+
- **Removed unused API surface.** Dropped the `@abloatai/ablo/server/next` subpath export and the unused `MutationDispatcher` interface (plus its `MockMutationDispatcher` test double) and the offline-mutation-queue internals behind them — none had documented consumers. Every supported entry point (`@abloatai/ablo`, `/ai-sdk`, `/react`, `/source`, `/coordination`, `/schema`, `/keys`, `/auth`, `/wire`, …) is unchanged.
|
|
18
|
+
|
|
19
|
+
- **Internal:** the SDK's largest modules — the `Ablo` client, the data-source runtime, the transaction queue, and the WebSocket sync loop — were decomposed into cohesive leaf modules. No public runtime API or import path changed.
|
|
20
|
+
|
|
21
|
+
- **CLI & docs.** `ablo push` prints a calmer, information-first deploy banner (a production push still requires typing the project name to confirm). The README and `AGENTS.md` now lead with the one-path logical-replication model and clarify that sandbox is test-mode only — in production your rows stay in your database and Ablo holds only the transaction log.
|
|
22
|
+
|
|
3
23
|
## 0.25.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -322,10 +322,10 @@ import Ablo from '@abloatai/ablo';
|
|
|
322
322
|
import { AbloProvider, useAblo } from '@abloatai/ablo/react';
|
|
323
323
|
import { schema } from './ablo/schema';
|
|
324
324
|
|
|
325
|
-
// Build the client once —
|
|
325
|
+
// Build the client once — authEndpoint is your session route; no key in the browser.
|
|
326
326
|
const ablo = Ablo({
|
|
327
327
|
schema,
|
|
328
|
-
|
|
328
|
+
authEndpoint: '/api/ablo-session',
|
|
329
329
|
});
|
|
330
330
|
|
|
331
331
|
function App() {
|
|
@@ -381,7 +381,7 @@ to sync-group strings.
|
|
|
381
381
|
// team membership is asserted server-side when the session route mints the token.
|
|
382
382
|
const ablo = Ablo({
|
|
383
383
|
schema,
|
|
384
|
-
|
|
384
|
+
authEndpoint: '/api/ablo-session',
|
|
385
385
|
});
|
|
386
386
|
|
|
387
387
|
<AbloProvider client={ablo} userId={user.id}>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* implementation stays in the app's SyncedStore.ts until we incrementally
|
|
12
12
|
* pull generic methods into this base class.
|
|
13
13
|
*/
|
|
14
|
+
import type { RecoveryClass } from './errorCodes.js';
|
|
14
15
|
import { ConnectionManager } from './sync/ConnectionManager.js';
|
|
15
16
|
import { AreaOfInterestManager } from './sync/AreaOfInterestManager.js';
|
|
16
17
|
import { type ParticipantScope } from './sync/participants.js';
|
|
@@ -18,19 +19,22 @@ import type { SyncClient } from './SyncClient.js';
|
|
|
18
19
|
import type { Database, BootstrapResult } from './Database.js';
|
|
19
20
|
import type { ObjectPool } from './ObjectPool.js';
|
|
20
21
|
import { ModelRegistry } from './ModelRegistry.js';
|
|
21
|
-
import { SyncWebSocket, type SyncDelta, type SyncGroupChangePayload, type GroupAddedPayload, type GroupRemovedPayload, type
|
|
22
|
+
import { SyncWebSocket, type SyncDelta, type SyncGroupChangePayload, type GroupAddedPayload, type GroupRemovedPayload, type BootstrapHint, type BootstrapDataEvent, type PresenceUpdateEvent, type EventMap, type DefaultCollaborationEvents } from './sync/SyncWebSocket.js';
|
|
22
23
|
import { QueryProcessor } from './core/QueryProcessor.js';
|
|
23
24
|
import { Model } from './Model.js';
|
|
24
25
|
import { ModelScope } from './ObjectPool.js';
|
|
25
26
|
import type { Schema } from './schema/schema.js';
|
|
26
|
-
import type { LocalMutation } from './
|
|
27
|
+
import type { SyncStatus, LocalMutation } from './core/storeContract.js';
|
|
27
28
|
import type { AuthCredentialSource } from './auth/credentialSource.js';
|
|
29
|
+
import type { ModelData } from './types/modelData.js';
|
|
30
|
+
import type { EnrichmentPlanEntry, ForeignKeyIndexSpec } from './sync/syncPlan.js';
|
|
31
|
+
import { type CredentialRefresher } from './sync/credentialLifecycle.js';
|
|
32
|
+
import type { RehydrationStats } from './sync/bootstrapApply.js';
|
|
28
33
|
/** Constructor type for Model subclasses (accepts abstract classes) */
|
|
29
34
|
export type ModelConstructor<T extends Model> = abstract new (...args: never[]) => T;
|
|
30
35
|
/** Concrete constructor type for instantiation */
|
|
31
36
|
export type ConcreteModelConstructor<T extends Model> = new (data?: any) => T;
|
|
32
|
-
|
|
33
|
-
export type ModelData = Record<string, unknown>;
|
|
37
|
+
export type { ModelData } from './types/modelData.js';
|
|
34
38
|
/** Query result interface */
|
|
35
39
|
export interface QueryResult<T extends Model> {
|
|
36
40
|
data: T[];
|
|
@@ -38,55 +42,12 @@ export interface QueryResult<T extends Model> {
|
|
|
38
42
|
hasMore: boolean;
|
|
39
43
|
fromCache?: boolean;
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
export interface ForeignKeyIndexSpec {
|
|
43
|
-
/**
|
|
44
|
-
* The child model name (where the FK field lives) — this is the type
|
|
45
|
-
* that will be passed to `pool.registerForeignKey(modelName, fieldName)`
|
|
46
|
-
* and later to `pool.getByForeignKey(modelName, fieldName, value)`.
|
|
47
|
-
*
|
|
48
|
-
* Use the wire `__typename` casing (e.g., `'SlideLayer'`, not
|
|
49
|
-
* `'slideLayer'`) — that's the value `createFromData` stamps onto
|
|
50
|
-
* models and the pool indexes by.
|
|
51
|
-
*/
|
|
52
|
-
readonly modelName: string;
|
|
53
|
-
/** The FK field name on the child model, e.g. `'slideId'`. */
|
|
54
|
-
readonly fieldName: string;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* A declarative enrichment rule for the delta-apply path.
|
|
58
|
-
*
|
|
59
|
-
* When a delta for `modelName` arrives, after the model is constructed
|
|
60
|
-
* the base store reads `data[foreignKey]` from the payload, looks up
|
|
61
|
-
* the matching parent in the ObjectPool, and attaches it as
|
|
62
|
-
* `data[relationKey]`. Best-effort: if the parent isn't yet in the
|
|
63
|
-
* pool (e.g., arrived later in the same bootstrap batch), enrichment
|
|
64
|
-
* silently no-ops.
|
|
65
|
-
*
|
|
66
|
-
* Replaces the previous pattern of overriding `enrichRelations` on a
|
|
67
|
-
* subclass to hardcode per-model enrichment logic.
|
|
68
|
-
*/
|
|
69
|
-
export interface EnrichmentPlanEntry {
|
|
70
|
-
/** The child model whose incoming deltas should be enriched. */
|
|
71
|
-
readonly modelName: string;
|
|
72
|
-
/** The FK field on the child that points at the parent's id. */
|
|
73
|
-
readonly foreignKey: string;
|
|
74
|
-
/** The property name under which to attach the parent model. */
|
|
75
|
-
readonly relationKey: string;
|
|
76
|
-
}
|
|
45
|
+
export type { ForeignKeyIndexSpec, EnrichmentPlanEntry } from './sync/syncPlan.js';
|
|
77
46
|
/** Configuration for SyncedStore behavior */
|
|
78
47
|
export interface SyncedStoreConfig {
|
|
79
48
|
enableOffline?: boolean;
|
|
80
49
|
enableCache?: boolean;
|
|
81
50
|
enableTelemetry?: boolean;
|
|
82
|
-
/**
|
|
83
|
-
* Initial version vector keys, each seeded to 0. Merged with the
|
|
84
|
-
* schema-derived set (if a schema is provided to the constructor) —
|
|
85
|
-
* explicit keys here layer on top of derived ones. Replaces the
|
|
86
|
-
* subclass pattern of hardcoding `this.versionVector = { tasks: 0, ... }`
|
|
87
|
-
* in the constructor.
|
|
88
|
-
*/
|
|
89
|
-
versionVectorKeys?: readonly string[];
|
|
90
51
|
/**
|
|
91
52
|
* Declarative enrichment plan consumed by `enrichRelations`. Replaces
|
|
92
53
|
* the subclass override of `enrichRelations` for per-model parent
|
|
@@ -104,17 +65,7 @@ export interface SyncedStoreConfig {
|
|
|
104
65
|
*/
|
|
105
66
|
foreignKeyIndexes?: readonly ForeignKeyIndexSpec[];
|
|
106
67
|
}
|
|
107
|
-
|
|
108
|
-
export interface SyncStatus {
|
|
109
|
-
state: 'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting';
|
|
110
|
-
progress: number;
|
|
111
|
-
error?: Error;
|
|
112
|
-
/** When true, the error is a session/auth error requiring re-authentication. */
|
|
113
|
-
isSessionError: boolean;
|
|
114
|
-
lastSyncAt?: Date;
|
|
115
|
-
pendingChanges: number;
|
|
116
|
-
offlineSince?: Date;
|
|
117
|
-
}
|
|
68
|
+
export type { SyncStatus } from './core/storeContract.js';
|
|
118
69
|
/** User context for initialization */
|
|
119
70
|
export interface UserContext {
|
|
120
71
|
userId: string;
|
|
@@ -157,15 +108,7 @@ export interface SmartSyncOptions {
|
|
|
157
108
|
batchingDelay?: number;
|
|
158
109
|
maxBatchSize?: number;
|
|
159
110
|
}
|
|
160
|
-
|
|
161
|
-
export interface RehydrationStats {
|
|
162
|
-
added: number;
|
|
163
|
-
updated: number;
|
|
164
|
-
removed: number;
|
|
165
|
-
skipped: number;
|
|
166
|
-
healed: number;
|
|
167
|
-
elapsedMs: number;
|
|
168
|
-
}
|
|
111
|
+
export type { RehydrationStats } from './sync/bootstrapApply.js';
|
|
169
112
|
/** Bootstrap timeout configuration */
|
|
170
113
|
export declare const BOOTSTRAP_CONFIG: {
|
|
171
114
|
readonly OVERALL_TIMEOUT_MS: 15000;
|
|
@@ -173,7 +116,8 @@ export declare const BOOTSTRAP_CONFIG: {
|
|
|
173
116
|
readonly RETRY_DELAY_MS: 500;
|
|
174
117
|
};
|
|
175
118
|
export { ModelScope };
|
|
176
|
-
export type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload, GroupRemovedPayload,
|
|
119
|
+
export type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload, GroupRemovedPayload, BootstrapHint, BootstrapDataEvent, PresenceUpdateEvent, };
|
|
120
|
+
export { deriveSyncPlanFromSchema } from './sync/syncPlan.js';
|
|
177
121
|
/**
|
|
178
122
|
* BaseSyncedStore — abstract base for app-specific sync stores.
|
|
179
123
|
*
|
|
@@ -202,30 +146,6 @@ export type { SyncDelta, SyncGroupChangePayload, GroupAddedPayload, GroupRemoved
|
|
|
202
146
|
* }
|
|
203
147
|
* }
|
|
204
148
|
*/
|
|
205
|
-
/**
|
|
206
|
-
* Walk a schema and derive the three sync-plan arrays consumed by
|
|
207
|
-
* `BaseSyncedStore`'s constructor: version-vector keys, FK indexes to
|
|
208
|
-
* register on the pool, and the enrichment plan.
|
|
209
|
-
*
|
|
210
|
-
* Version vector keys are derived from each model's `typename` (lowercased
|
|
211
|
-
* to match the server's event-type convention — `'Task'` → `'task'`,
|
|
212
|
-
* `'SlideLayer'` → `'slidelayer'`). A fallback to the schema key applies
|
|
213
|
-
* when `typename` is unset, though `defineSchema()` now always resolves
|
|
214
|
-
* it during assembly so the fallback is defensive-only.
|
|
215
|
-
*
|
|
216
|
-
* FK indexes and enrichment entries are pulled from each `belongsTo`
|
|
217
|
-
* relation where `options.index` / `options.enrich` is set. Relations
|
|
218
|
-
* without those options are skipped — this is an opt-in mechanism so
|
|
219
|
-
* adding a `belongsTo` never silently changes delta or lookup semantics.
|
|
220
|
-
*
|
|
221
|
-
* Pure function: takes a Schema, returns three arrays. No side effects,
|
|
222
|
-
* no class state. Called once at construction time from `BaseSyncedStore`.
|
|
223
|
-
*/
|
|
224
|
-
export declare function deriveSyncPlanFromSchema(schema: Schema): {
|
|
225
|
-
versionVectorKeys: string[];
|
|
226
|
-
enrichmentPlan: EnrichmentPlanEntry[];
|
|
227
|
-
foreignKeyIndexes: ForeignKeyIndexSpec[];
|
|
228
|
-
};
|
|
229
149
|
export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaboration> = DefaultCollaborationEvents, TSchema extends Schema = Schema> {
|
|
230
150
|
syncStatus: SyncStatus;
|
|
231
151
|
protected readonly syncClient: SyncClient;
|
|
@@ -291,18 +211,16 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
291
211
|
unpinScope(scope: ParticipantScope): Promise<void>;
|
|
292
212
|
protected readonly queryProcessor: QueryProcessor;
|
|
293
213
|
/**
|
|
294
|
-
* Runtime behavior flags only — the
|
|
295
|
-
* (`
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
* They don't need to persist on `this.config`.
|
|
214
|
+
* Runtime behavior flags only — the schema/config arrays
|
|
215
|
+
* (`enrichmentPlan`, `foreignKeyIndexes`) are consumed at construction
|
|
216
|
+
* time and stored on the instance as `enrichmentPlan` and
|
|
217
|
+
* pool-registered indexes. They don't need to persist on `this.config`.
|
|
299
218
|
*/
|
|
300
219
|
protected readonly config: Required<Pick<SyncedStoreConfig, 'enableOffline' | 'enableCache' | 'enableTelemetry'>>;
|
|
301
|
-
protected disposers:
|
|
220
|
+
protected disposers: (() => void)[];
|
|
302
221
|
protected initialized: boolean;
|
|
303
222
|
protected dataReady: boolean;
|
|
304
223
|
protected userContext: UserContext | null;
|
|
305
|
-
protected versionVector: VersionVector;
|
|
306
224
|
/**
|
|
307
225
|
* Declarative enrichment plan: "for model X, when a delta arrives,
|
|
308
226
|
* read data[foreignKey] and attach the matching parent from the pool
|
|
@@ -332,11 +250,11 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
332
250
|
modelRegistry: ModelRegistry;
|
|
333
251
|
/**
|
|
334
252
|
* Optional schema. When provided, `deriveSyncPlanFromSchema` walks
|
|
335
|
-
* the schema's models + relations to auto-populate
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
253
|
+
* the schema's models + relations to auto-populate FK indexes and
|
|
254
|
+
* the enrichment plan from declarative annotations. Class-based
|
|
255
|
+
* subclass users (like Ablo's legacy SyncedStore) typically pass
|
|
256
|
+
* explicit `config.foreignKeyIndexes` / `config.enrichmentPlan`
|
|
257
|
+
* instead.
|
|
340
258
|
*/
|
|
341
259
|
schema?: TSchema;
|
|
342
260
|
/** Sync server URL for WebSocket connection. Converted to wss:// automatically. */
|
|
@@ -391,23 +309,15 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
391
309
|
*/
|
|
392
310
|
protected connectionManager: import('./sync/ConnectionManager.js').ConnectionManager | null;
|
|
393
311
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
private credentialRefresher;
|
|
404
|
-
/** Single-flight guard so a wake nudge + an in-flight request + a probe don't
|
|
405
|
-
* all mint at once (the classic "token thrash → random logout" bug). */
|
|
406
|
-
private inFlightCredentialRefresh;
|
|
407
|
-
/** Teardown for the proactive credential lifecycle (refresh timer + wake/
|
|
408
|
-
* online/focus listeners) installed by {@link startCredentialLifecycle};
|
|
409
|
-
* cleared on {@link disconnect}. Null when no resolver is wired. */
|
|
410
|
-
private credentialLifecycleTeardown;
|
|
312
|
+
* Access-credential re-mint + proactive pre-roll — extracted to
|
|
313
|
+
* sync/credentialLifecycle.ts. Owns the refresher hook, the single-flight
|
|
314
|
+
* guard, and the browser-only refresh timer / wake listener; talks back
|
|
315
|
+
* through three lazily-resolved callbacks (the ConnectionManager doesn't
|
|
316
|
+
* exist until `setupWebSocketSync`). The `setCredentialRefresher` /
|
|
317
|
+
* `performCredentialRefresh` / `startCredentialLifecycle` methods below
|
|
318
|
+
* are thin delegates so the store's public surface is unchanged.
|
|
319
|
+
*/
|
|
320
|
+
private readonly credentialLifecycle;
|
|
411
321
|
/**
|
|
412
322
|
* Listeners registered via `subscribeSessionError()`. Fired when the
|
|
413
323
|
* WebSocket closes with a session-invalid code (1008/4001/4003) or a
|
|
@@ -448,7 +358,7 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
448
358
|
waitForConfirmation(modelName: string, modelId: string): Promise<void>;
|
|
449
359
|
/**
|
|
450
360
|
* Observe the LOCAL mutation stream for undo recording (see
|
|
451
|
-
* {@link import('./
|
|
361
|
+
* {@link import('./core/storeContract.js').LocalMutation}). Taps the
|
|
452
362
|
* TransactionQueue's `transaction:created` event — fired once per local
|
|
453
363
|
* create/update/delete/archive with `previousData` already captured.
|
|
454
364
|
* Remote/collaborator deltas apply via `applyDeltaBatchToPool` and never
|
|
@@ -469,28 +379,24 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
469
379
|
/**
|
|
470
380
|
* Register the access-credential re-mint hook. Called by the React provider
|
|
471
381
|
* with a thunk that mints a fresh `ek_`/`rk_` (typically its `getToken`).
|
|
472
|
-
* See {@link
|
|
382
|
+
* See {@link CredentialLifecycle.setRefresher}.
|
|
473
383
|
*/
|
|
474
|
-
setCredentialRefresher(refresher:
|
|
384
|
+
setCredentialRefresher(refresher: CredentialRefresher | null): void;
|
|
475
385
|
/**
|
|
476
386
|
* Re-mint the short-lived access credential and push it into the credential
|
|
477
387
|
* source, reporting a tri-state outcome the {@link ConnectionManager} maps to
|
|
478
|
-
* its FSM.
|
|
479
|
-
*
|
|
480
|
-
* return/throw):
|
|
481
|
-
* - token string → `'refreshed'` (fresh key in place; re-probe & reconnect)
|
|
482
|
-
* - `null` → `'session_error'` (login itself is gone → terminal, sign out)
|
|
483
|
-
* - throw → `'network_error'` (couldn't reach the mint endpoint → transient)
|
|
484
|
-
*
|
|
485
|
-
* SINGLE-FLIGHT: concurrent callers (a wake nudge, an in-flight request, the
|
|
486
|
-
* probe) share one in-flight promise so we never double-mint — the canonical
|
|
487
|
-
* fix for the "every 401 mints a token → thrash → spurious logout" anti-pattern.
|
|
488
|
-
*
|
|
489
|
-
* No refresher wired ⇒ `'refreshed'` (a no-op re-probe): a static-`apiKey`
|
|
490
|
-
* deployment has no session to re-mint from; its credential source refreshes
|
|
491
|
-
* out-of-band, so we just re-probe with whatever it currently holds.
|
|
388
|
+
* its FSM. Single-flight; no refresher wired ⇒ `'refreshed'` (a no-op
|
|
389
|
+
* re-probe). Full contract on {@link CredentialLifecycle.refresh}.
|
|
492
390
|
*/
|
|
493
391
|
performCredentialRefresh(): Promise<'refreshed' | 'session_error' | 'network_error'>;
|
|
392
|
+
/**
|
|
393
|
+
* THE auth-recovery backbone for HTTP transports (lazy query lane etc.):
|
|
394
|
+
* classify-driven single-flight re-mint with the same FSM outcome routing
|
|
395
|
+
* the WS probe and proactive pre-roll use. `'retry'` ⇒ a fresh credential
|
|
396
|
+
* is in the credential source, replay the request ONCE. Full contract on
|
|
397
|
+
* {@link CredentialLifecycle.recoverFromAuthRejection}.
|
|
398
|
+
*/
|
|
399
|
+
recoverFromAuthRejection(recovery: RecoveryClass): Promise<'retry' | 'stop'>;
|
|
494
400
|
/**
|
|
495
401
|
* Nudge the connection FSM to re-probe with the current credential. Idempotent
|
|
496
402
|
* and safe in any state (ignored while `connected`). Call after pushing a
|
|
@@ -500,72 +406,39 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
500
406
|
*/
|
|
501
407
|
nudgeReconnect(): void;
|
|
502
408
|
/**
|
|
503
|
-
* Install the access-credential lifecycle the CLIENT owns
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* timer inside the TTL, plus re-mint on OS wake. The ENTIRE proactive
|
|
509
|
-
* block is browser-gated (`typeof window`): server/SSR has no socket to
|
|
510
|
-
* keep warm and the resolver is browser-oriented, so arming it in Node
|
|
511
|
-
* would fire a relative-URL fetch and throw. (Agents pass a static
|
|
512
|
-
* `apiKey` with no resolver, so this method is never called for them.)
|
|
513
|
-
*
|
|
514
|
-
* Config-driven and invisible, like Supabase's `autoRefreshToken` — consumers
|
|
515
|
-
* never call a refresh method. Idempotent (a second call replaces the first);
|
|
516
|
-
* torn down on {@link disconnect}.
|
|
409
|
+
* Install the access-credential lifecycle the CLIENT owns: register
|
|
410
|
+
* `getToken` as the reactive re-mint hook AND arm the browser-only
|
|
411
|
+
* proactive pre-roll (refresh timer + OS-wake re-mint). Idempotent
|
|
412
|
+
* (a second call replaces the first); torn down on {@link disconnect}.
|
|
413
|
+
* Full rationale on {@link CredentialLifecycle.start}.
|
|
517
414
|
*/
|
|
518
|
-
startCredentialLifecycle(getToken:
|
|
415
|
+
startCredentialLifecycle(getToken: CredentialRefresher, opts?: {
|
|
416
|
+
proactiveInNode?: boolean;
|
|
417
|
+
}): void;
|
|
519
418
|
/** Tear down the proactive credential lifecycle (idempotent). */
|
|
520
419
|
private stopCredentialLifecycle;
|
|
420
|
+
/** Narrow context the group-change leaf talks back through. */
|
|
421
|
+
private groupChangeContext;
|
|
521
422
|
/**
|
|
522
|
-
* Handle an actionType 'G' delta
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* shape:
|
|
526
|
-
*
|
|
527
|
-
* Incremental (EmitGroupAdded): { group, userId }
|
|
528
|
-
* - The recipient was added to a single sync group.
|
|
529
|
-
* - Subsequent 'C' (Covering) deltas deliver each newly-visible entity.
|
|
530
|
-
* - No re-bootstrap — entities arrive via the normal insert path.
|
|
531
|
-
*
|
|
532
|
-
* Legacy (EmitGroupChange): { addedGroups, removedGroups }
|
|
533
|
-
* - Single delta carrying the full group membership diff.
|
|
534
|
-
* - Forces a full re-bootstrap (disconnect + reconnect + fetch all).
|
|
535
|
-
* - Deprecated on the server; kept here for wire-level backward compat.
|
|
423
|
+
* Handle an actionType 'G' delta — incremental `{ group, userId }` or
|
|
424
|
+
* legacy `{ addedGroups, removedGroups }` payloads. Full pathway doc on
|
|
425
|
+
* {@link groupChange.handleSyncGroupChange}.
|
|
536
426
|
*/
|
|
537
427
|
protected handleSyncGroupChange(delta: SyncDelta): Promise<void>;
|
|
538
428
|
/**
|
|
539
|
-
* Handle an incremental GroupAdded delta
|
|
540
|
-
*
|
|
541
|
-
* Adds the new group to the subscription metadata without triggering a
|
|
542
|
-
* re-bootstrap. The server will follow up with 'C' (Covering) deltas for
|
|
543
|
-
* each newly-visible entity, which flow through the normal insert path.
|
|
429
|
+
* Handle an incremental GroupAdded delta — metadata only, no re-bootstrap
|
|
430
|
+
* (covering deltas bring the entities). See {@link groupChange.handleGroupAdded}.
|
|
544
431
|
*/
|
|
545
432
|
protected handleGroupAdded(payload: GroupAddedPayload, syncId: number): Promise<void>;
|
|
546
433
|
/**
|
|
547
|
-
* Handle an actionType 'S' (GroupRemoved) delta
|
|
548
|
-
*
|
|
549
|
-
* Signals that the recipient has lost access to a sync group. Because
|
|
550
|
-
* the client does not track per-entity group membership, we can't
|
|
551
|
-
* selectively purge entities belonging to that group. The safe fallback
|
|
552
|
-
* is the legacy behavior: clear local state and force a re-bootstrap
|
|
553
|
-
* with the updated group list.
|
|
554
|
-
*
|
|
555
|
-
* Future optimization: track group membership in the ObjectPool so 'S'
|
|
556
|
-
* can do a targeted purge instead of a full re-bootstrap.
|
|
434
|
+
* Handle an actionType 'S' (GroupRemoved) delta — SECURITY clear of local
|
|
435
|
+
* state + full re-bootstrap. See {@link groupChange.handleGroupRemoved}.
|
|
557
436
|
*/
|
|
558
437
|
protected handleGroupRemoved(delta: SyncDelta): Promise<void>;
|
|
559
438
|
/** Compute new sync groups after applying additions and removals */
|
|
560
439
|
protected computeUpdatedSyncGroups(payload: SyncGroupChangePayload): string[];
|
|
561
|
-
/** Force a full re-bootstrap via connection lifecycle event
|
|
562
|
-
*
|
|
563
|
-
* No-op for `bootstrapMode: 'none'` participants — they never pull
|
|
564
|
-
* baseline state, so a "force re-bootstrap" trigger (sync-group
|
|
565
|
-
* shrink, scope revocation) instead just flushes the local pool and
|
|
566
|
-
* relies on covering deltas to repopulate the data they actually
|
|
567
|
-
* subscribe to.
|
|
568
|
-
*/
|
|
440
|
+
/** Force a full re-bootstrap via connection lifecycle event (no-op for
|
|
441
|
+
* `bootstrapMode: 'none'` participants — see {@link groupChange.forceFullRebootstrap}). */
|
|
569
442
|
protected forceFullRebootstrap(): void;
|
|
570
443
|
/**
|
|
571
444
|
* Single source of truth for the sync-group list this session is
|
|
@@ -578,6 +451,8 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
578
451
|
protected resolveSyncGroups(context: UserContext): readonly string[];
|
|
579
452
|
/** Check if sync groups shrank since last session — force full bootstrap if so */
|
|
580
453
|
protected checkSyncGroupShrinkage(): Promise<void>;
|
|
454
|
+
/** Narrow context the bootstrap-apply leaf talks back through. */
|
|
455
|
+
private poolContext;
|
|
581
456
|
/** Apply bootstrap data to the ObjectPool with ghost removal */
|
|
582
457
|
/** Apply bootstrap data to the ObjectPool. Delegates pool writes to SyncClient. */
|
|
583
458
|
protected applyBootstrapToPool(bootstrapResult: BootstrapResult, protectedIds?: ReadonlySet<string>): RehydrationStats;
|
|
@@ -595,7 +470,7 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
595
470
|
protected withDeltaQueuing<T>(fn: () => Promise<T>): Promise<T>;
|
|
596
471
|
/** Collect IDs that must survive ghost removal (added by deltas during bootstrap) */
|
|
597
472
|
protected collectDeltaProtectedIds(preBootstrapIds: ReadonlySet<string>): Set<string>;
|
|
598
|
-
/** Replay deltas queued during bootstrap */
|
|
473
|
+
/** Replay deltas queued during bootstrap (atomically, via `applyDeltaFrame`). */
|
|
599
474
|
protected replayQueuedDeltas(): void;
|
|
600
475
|
/**
|
|
601
476
|
* Factory for the internal `ConnectionManager`. Override to return
|
|
@@ -664,11 +539,13 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
|
|
|
664
539
|
*/
|
|
665
540
|
protected waitForWebSocketConnected(timeoutMs: number): Promise<boolean>;
|
|
666
541
|
protected setupWebSocketSync(context: UserContext, lastSyncId: number): void;
|
|
667
|
-
/**
|
|
668
|
-
|
|
542
|
+
/** Memoized pipeline context — `enqueueDelta` runs once per delta, so the
|
|
543
|
+
* accessor object is built once and reused (the get/set accessors always
|
|
544
|
+
* read the live host fields). */
|
|
545
|
+
private _deltaPipelineContext;
|
|
546
|
+
private get deltaPipelineContext();
|
|
669
547
|
/** Get fields that represent meaningful state for deduplication. Override for model-specific fields. */
|
|
670
548
|
protected getStateFields(_modelName: string): string[];
|
|
671
|
-
private isSameState;
|
|
672
549
|
/** Deduplicate deltas to the same entity — keep meaningful state transitions only */
|
|
673
550
|
protected deduplicateDeltas(deltas: SyncDelta[]): SyncDelta[];
|
|
674
551
|
/** Process incoming delta with smart batching */
|