@abloatai/ablo 0.35.0 → 0.36.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/CHANGELOG.md +713 -629
- package/README.md +56 -519
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +24 -4
- package/dist/BaseSyncedStore.js +53 -37
- package/dist/Database.d.ts +8 -20
- package/dist/Database.js +61 -59
- package/dist/InstanceCache.d.ts +6 -2
- package/dist/InstanceCache.js +18 -16
- package/dist/Model.d.ts +17 -7
- package/dist/Model.js +17 -7
- package/dist/ModelRegistry.d.ts +4 -0
- package/dist/ModelRegistry.js +17 -15
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/SyncClient.d.ts +5 -15
- package/dist/SyncClient.js +60 -57
- package/dist/client/Ablo.js +30 -19
- package/dist/client/createInternalComponents.d.ts +4 -0
- package/dist/client/createInternalComponents.js +8 -2
- package/dist/client/createModelProxy.d.ts +21 -1
- package/dist/client/createModelProxy.js +123 -57
- package/dist/client/humans.d.ts +30 -9
- package/dist/client/humans.js +45 -19
- package/dist/client/reactiveEngine.d.ts +16 -11
- package/dist/client/reactiveEngine.js +113 -335
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +23 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/docs/catalog.js +6 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/query/client.d.ts +3 -0
- package/dist/query/client.js +6 -5
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +5 -2
- package/dist/react/context.d.ts +2 -2
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/useAblo.d.ts +9 -2
- package/dist/react/useAblo.js +25 -7
- package/dist/schema/coordination.js +5 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/select.js +3 -0
- package/dist/schema/serialize.js +3 -0
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +13 -12
- package/dist/{core → stores}/StoreManager.d.ts +5 -3
- package/dist/{core → stores}/StoreManager.js +24 -22
- package/dist/stores/SyncActionStore.d.ts +3 -1
- package/dist/stores/SyncActionStore.js +9 -7
- package/dist/sync/BootstrapFetcher.d.ts +4 -0
- package/dist/sync/BootstrapFetcher.js +28 -26
- package/dist/sync/OnDemandLoader.d.ts +3 -0
- package/dist/sync/OnDemandLoader.js +1 -0
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +19 -2
- package/dist/sync/participants.js +3 -1
- package/dist/sync/schemas.d.ts +2 -1
- package/dist/sync/schemas.js +3 -3
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/auth/capability.d.ts +35 -0
- package/dist/transaction/auth/capability.js +25 -0
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/index.d.ts +4 -4
- package/dist/transaction/coordination/index.js +4 -3
- package/dist/transaction/coordination/locator.d.ts +23 -2
- package/dist/transaction/coordination/locator.js +22 -2
- package/dist/transaction/coordination/schema.d.ts +125 -62
- package/dist/transaction/coordination/schema.js +228 -64
- package/dist/transaction/coordination/targetConflict.js +32 -28
- package/dist/transaction/errorCodes.d.ts +2 -2
- package/dist/transaction/errorCodes.js +13 -9
- package/dist/transaction/plugin.d.ts +95 -2
- package/dist/transaction/plugin.js +21 -2
- package/dist/transaction/resources/httpResources.d.ts +57 -2
- package/dist/transaction/resources/modelOperations.d.ts +148 -40
- package/dist/transaction/resources/where.d.ts +16 -0
- package/dist/transaction/resources/where.js +45 -0
- package/dist/transaction/schema/field.d.ts +12 -18
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/openapi.d.ts +16 -15
- package/dist/transaction/schema/openapi.js +186 -25
- package/dist/transaction/schema/relation.d.ts +7 -2
- package/dist/transaction/schema/schema.d.ts +27 -0
- package/dist/transaction/schema/schema.js +20 -0
- package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
- package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
- package/dist/transaction/transport/httpClient.d.ts +9 -1
- package/dist/transaction/transport/httpClient.js +1 -0
- package/dist/transaction/transport/httpTransport.js +156 -44
- package/dist/transaction/transport/wsTransport.d.ts +2 -4
- package/dist/transaction/transport/wsTransport.js +16 -10
- package/dist/transaction/types/streams.d.ts +10 -0
- package/dist/transaction/utils/duration.d.ts +25 -0
- package/dist/transaction/utils/duration.js +32 -0
- package/dist/transaction/wire/accountResponses.d.ts +69 -0
- package/dist/transaction/wire/accountResponses.js +36 -1
- package/dist/transaction/wire/auth.d.ts +9 -2
- package/dist/transaction/wire/auth.js +7 -1
- package/dist/transaction/wire/claims.d.ts +164 -97
- package/dist/transaction/wire/claims.js +126 -28
- package/dist/transaction/wire/commit.d.ts +1 -1
- package/dist/transaction/wire/feedEvent.d.ts +27 -0
- package/dist/transaction/wire/feedEvent.js +27 -1
- package/dist/transaction/wire/frames.d.ts +2 -2
- package/dist/transaction/wire/inboundFrames.d.ts +12 -2
- package/dist/transaction/wire/index.d.ts +10 -6
- package/dist/transaction/wire/index.js +12 -3
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
- package/dist/transactions/mutations/MutationQueue.js +55 -45
- package/dist/transactions/mutations/commitPayload.d.ts +3 -2
- package/dist/transactions/mutations/commitPayload.js +5 -5
- package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
- package/dist/transactions/mutations/deltaConfirmation.js +10 -8
- package/dist/transactions/mutations/replayValidation.d.ts +2 -1
- package/dist/transactions/mutations/replayValidation.js +3 -2
- package/dist/{core → views}/QueryView.d.ts +1 -1
- package/dist/{core → views}/QueryView.js +1 -1
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/docs/agents.md +1 -1
- package/docs/api-keys.md +6 -6
- package/docs/api.md +5 -43
- package/docs/audit.md +4 -3
- package/docs/cli.md +11 -11
- package/docs/client-behavior.md +4 -4
- package/docs/concurrency-convention.md +28 -42
- package/docs/coordination.md +235 -83
- package/docs/data-sources.md +4 -4
- package/docs/debugging.md +34 -12
- package/docs/deployment.md +8 -8
- package/docs/examples/scoped-agent.md +3 -3
- package/docs/groups.md +57 -3
- package/docs/guarantees.md +37 -10
- package/docs/how-it-works.md +29 -5
- package/docs/idempotency.md +6 -6
- package/docs/identity.md +22 -23
- package/docs/index.md +8 -8
- package/docs/integration-guide.md +14 -3
- package/docs/mcp.md +7 -7
- package/docs/migration.md +34 -15
- package/docs/projects.md +1 -1
- package/docs/react.md +19 -8
- package/docs/sessions.md +1 -1
- package/docs/webhooks.md +9 -9
- package/llms.txt +4 -4
- package/package.json +13 -20
- package/dist/cli.cjs +0 -288600
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/httpResponses.d.ts +0 -70
- package/dist/testing/fixtures/httpResponses.js +0 -90
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -186
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -72
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/docs/interaction-model.md +0 -99
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{source → transaction}/footprint.d.ts +0 -0
- /package/dist/{source → transaction}/footprint.js +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -1,138 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The reactive engine
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* commit/claim/session resources —
|
|
2
|
+
* The reactive engine assembly (ADR 0016). `Ablo({ ... })` resolves auth and
|
|
3
|
+
* capabilities; `humans().init` constructs the store cluster; the lifecycle
|
|
4
|
+
* — first mint, identity, ready() — lives in `./storeLifecycle.ts`. What
|
|
5
|
+
* remains here is assembly around those parts: the claim stream and
|
|
6
|
+
* participant manager, options validation, the typed model proxies, and the
|
|
7
|
+
* commit/claim/session resources — composed into the reactive client.
|
|
8
8
|
*
|
|
9
9
|
* Extracted from the factory so the composition root stays a root: resolve,
|
|
10
|
-
* dispatch, return. The
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* dispatch, return. The remaining assembly converts to decoration of a
|
|
11
|
+
* host-built core client with the per-model surface split — the cut's own
|
|
12
|
+
* design step (docs/plans/package-split.md).
|
|
13
13
|
*/
|
|
14
14
|
import { durableCommitOperationSchema, } from '../transaction/transactions/settlement/commitEnvelope.js';
|
|
15
|
-
import { AbloAuthenticationError, AbloConnectionError, AbloValidationError,
|
|
16
|
-
import { modelTarget, streamTarget, subTarget } from '../transaction/coordination/index.js';
|
|
17
|
-
import { initRuntime } from '../context.js';
|
|
18
|
-
import { getActiveRegistry } from '../ModelRegistry.js';
|
|
19
|
-
import { noopObservability, browserOnlineStatus, defaultSessionErrorDetector, noopAnalytics, } from '../RuntimeContext.js';
|
|
20
|
-
import { alwaysOnline } from '../adapters/alwaysOnline.js';
|
|
15
|
+
import { AbloAuthenticationError, AbloConnectionError, AbloValidationError, claimedError } from '../transaction/errors.js';
|
|
16
|
+
import { batchFence, fenceTokenFor, modelTarget, streamTarget, subTarget, } from '../transaction/coordination/index.js';
|
|
21
17
|
import { validateAbloOptions } from './validateAbloOptions.js';
|
|
22
|
-
import {} from '../transaction/auth/index.js';
|
|
23
18
|
import { mintSession } from '../transaction/auth/sessionMint.js';
|
|
24
19
|
import { modelWireNames } from '../transaction/auth/capability.js';
|
|
25
|
-
import {
|
|
26
|
-
import { resolveParticipantIdentity } from '../transaction/auth/identity.js';
|
|
27
|
-
import { BaseSyncedStore } from '../BaseSyncedStore.js';
|
|
20
|
+
import { startStoreLifecycle } from './storeLifecycle.js';
|
|
28
21
|
import { createClaimStream } from '../sync/createClaimStream.js';
|
|
29
22
|
import { awaitClaimGrant } from '../sync/awaitClaimGrant.js';
|
|
30
23
|
import { createSnapshot } from '../sync/createSnapshot.js';
|
|
31
24
|
import { createParticipantManager } from '../sync/participants.js';
|
|
32
25
|
import { resolveApiKeyValue, resolveBootstrapBaseUrl } from '../transaction/auth/apiKey.js';
|
|
33
|
-
import { shouldUseInMemoryPersistence } from '../transaction/persistence.js';
|
|
34
|
-
import { deriveConfigFromSchema } from './schemaConfig.js';
|
|
35
26
|
import { createModelProxy } from './createModelProxy.js';
|
|
36
27
|
import { assertWriteOptions } from '../transaction/resources/writeOptionsSchema.js';
|
|
37
|
-
import { registerModelsFromSchema } from './modelRegistration.js';
|
|
38
28
|
export function buildReactiveEngine(inputs) {
|
|
39
|
-
const { options, internalOptions, url, logger, configuredApiKey, configuredAuthToken, credentialResolver, authCredentials,
|
|
29
|
+
const { options, internalOptions, url, logger, configuredApiKey, configuredAuthToken, credentialResolver, authCredentials, transport, participantId, kind, presence, cluster, createSibling, } = inputs;
|
|
40
30
|
const schema = options.schema;
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
// 3. Initialize SDK context (one call — hides all DI wiring).
|
|
49
|
-
// Each provider can be overridden individually; the noop defaults
|
|
50
|
-
// are preserved for the zero-config consumer path.
|
|
51
|
-
initRuntime({
|
|
52
|
-
logger,
|
|
53
|
-
observability: internalOptions.observability ?? noopObservability,
|
|
54
|
-
analytics: internalOptions.analytics ?? noopAnalytics,
|
|
55
|
-
sessionErrorDetector: internalOptions.sessionErrorDetector ?? defaultSessionErrorDetector,
|
|
56
|
-
onlineStatus: internalOptions.onlineStatus ??
|
|
57
|
-
(shouldUseInMemoryPersistence(options)
|
|
58
|
-
? alwaysOnline()
|
|
59
|
-
: browserOnlineStatus),
|
|
60
|
-
config,
|
|
61
|
-
mutationExecutor: executor,
|
|
62
|
-
getModelMetadata: (name) => getActiveRegistry().getMetadata(name),
|
|
63
|
-
});
|
|
64
|
-
// 4. Create internal components (user never sees these). See
|
|
65
|
-
// `./createInternalComponents.ts` for the construction order
|
|
66
|
-
// and what each component does. Model registration happens
|
|
67
|
-
// here (via `registerModelsFromSchema`, in `./modelRegistration.ts`)
|
|
68
|
-
// because the schema-to-Model-class translation is client-construction
|
|
69
|
-
// wiring that isn't worth pulling into the components module.
|
|
70
|
-
const { modelRegistry, objectPool, bootstrapHelper, database, syncClient, hydration, } = createInternalComponents({
|
|
71
|
-
schema,
|
|
72
|
-
url,
|
|
73
|
-
options: internalOptions,
|
|
74
|
-
auth: authCredentials,
|
|
75
|
-
});
|
|
76
|
-
registerModelsFromSchema(schema, modelRegistry);
|
|
77
|
-
// 5. BaseSyncedStore handles the initialization orchestration
|
|
78
|
-
// (open DB → hydrate IDB → connect WS → fetch bootstrap → hydrate again →
|
|
79
|
-
// ready) and exposes the observable `syncStatus` we expose on the engine.
|
|
80
|
-
//
|
|
81
|
-
// Phase 2: pass the schema into the store so `deriveSyncPlanFromSchema`
|
|
82
|
-
// can auto-populate version vector keys, FK indexes, and enrichment
|
|
83
|
-
// rules from the declarative `belongsTo({ index, enrich })` annotations.
|
|
84
|
-
// Consumers using class-based subclasses with `new SyncedStore(...)`
|
|
85
|
-
// directly can pass explicit config arrays instead.
|
|
86
|
-
const store = new BaseSyncedStore({
|
|
87
|
-
syncClient,
|
|
88
|
-
database,
|
|
89
|
-
objectPool,
|
|
90
|
-
modelRegistry,
|
|
91
|
-
syncWebSocket: transport,
|
|
92
|
-
schema,
|
|
93
|
-
url,
|
|
94
|
-
auth: authCredentials,
|
|
95
|
-
},
|
|
96
|
-
// Collaboration vocabulary is the application's: the SDK subscribes to the
|
|
97
|
-
// event types the caller declares and to nothing by default.
|
|
98
|
-
{ collaborationEvents: internalOptions.collaborationEvents ?? [] });
|
|
99
|
-
// Hand the credential lifecycle to the client (refresher + proactive refresh
|
|
100
|
-
// timer + wake/online/focus re-mint). Installed once here so refresh works for
|
|
101
|
-
// any consumer of `Ablo({ auth })`, not only those who render `<AbloProvider>`.
|
|
102
|
-
// The first mint happens in `ready()` so the first connection carries a token.
|
|
103
|
-
//
|
|
104
|
-
// Long-lived server clients also get the pre-roll timer on windowless hosts
|
|
105
|
-
// (`proactiveInNode`): their socket must renew its `rk_` or `ek_` before the
|
|
106
|
-
// server's keepalive reaper closes it (4001 `credential_expired`). Two signals
|
|
107
|
-
// qualify — an agent or system participant, and an absolute endpoint-string
|
|
108
|
-
// `apiKey` (a relative one can't be fetched in Node, so an absolute URL is
|
|
109
|
-
// unambiguously a deliberate server client). User-kind clients in Node (an
|
|
110
|
-
// SSR/RSC module evaluating scaffolded browser code) stay reactive-only.
|
|
111
|
-
if (credentialResolver) {
|
|
112
|
-
const rawEndpoint = internalOptions.authEndpoint ?? internalOptions.apiKey;
|
|
113
|
-
const absoluteEndpoint = typeof rawEndpoint === 'string' && /^https?:\/\//i.test(rawEndpoint);
|
|
114
|
-
store.startCredentialLifecycle(credentialResolver, {
|
|
115
|
-
/* eslint-disable @typescript-eslint/no-deprecated -- `kind` gates the self-hosted proactive pre-roll; hosted path derives it from the apiKey scope */
|
|
116
|
-
proactiveInNode: internalOptions.kind === 'agent' ||
|
|
117
|
-
internalOptions.kind === 'system' ||
|
|
118
|
-
absoluteEndpoint,
|
|
119
|
-
/* eslint-enable @typescript-eslint/no-deprecated */
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
// Put the lazy-query lane on the same auth-recovery path as the WebSocket probe
|
|
123
|
-
// and the proactive pre-roll: a 401 on `/sync/query` re-mints via the store's
|
|
124
|
-
// single-flight lifecycle and replays once, instead of silently returning empty
|
|
125
|
-
// rows against an expired `ek_` until the next proactive tick. Late-bound
|
|
126
|
-
// because the coordinator is constructed before the store exists.
|
|
127
|
-
hydration.setCredentialRecovery((recovery) => store.recoverFromAuthRejection(recovery));
|
|
128
|
-
// Bind this executor to this client's MutationQueue. Without it, the queue
|
|
129
|
-
// resolves `mutationExecutor` from the module-level `getContext()`, which
|
|
130
|
-
// `initRuntime()` overwrites on every client construction. In multi-client
|
|
131
|
-
// flows (for example a worker plus a per-job peer) the second `initRuntime()`
|
|
132
|
-
// call would silently redirect the first client's queue through the second
|
|
133
|
-
// client's executor closure, so the first client's commits would dispatch
|
|
134
|
-
// over the wrong connection.
|
|
135
|
-
syncClient.getMutationQueue().setMutationExecutor(executor);
|
|
31
|
+
// The store cluster — this client's runtime, the component graph, the
|
|
32
|
+
// registered models, and the store — was constructed by `humans().init`
|
|
33
|
+
// from the widened plugin context (see `./storeCluster.ts`). The engine
|
|
34
|
+
// assembles around it.
|
|
35
|
+
const { runtime, components, store } = cluster;
|
|
36
|
+
const { modelRegistry, objectPool, bootstrapHelper, database, syncClient, hydration, } = components;
|
|
136
37
|
// Self identity, late-bound the same way the connection's values are: the
|
|
137
38
|
// construction-time guess seeds it (correct on the self-hosted path, empty
|
|
138
39
|
// on the hosted path), and `ready()` overwrites it with what identity
|
|
@@ -152,13 +53,6 @@ export function buildReactiveEngine(inputs) {
|
|
|
152
53
|
// locals above.
|
|
153
54
|
const presenceStream = presence;
|
|
154
55
|
const claimStream = createClaimStream({ participantId, logger }, transport);
|
|
155
|
-
const participantManager = createParticipantManager({
|
|
156
|
-
ready,
|
|
157
|
-
transport,
|
|
158
|
-
presence: presenceStream,
|
|
159
|
-
claims: claimStream,
|
|
160
|
-
schema,
|
|
161
|
-
});
|
|
162
56
|
// 6. Validate options up front — fail loudly on obviously wrong inputs so
|
|
163
57
|
// strangers don't get silent empty results. Validation errors are written
|
|
164
58
|
// into `store.syncStatus` (the single source of truth).
|
|
@@ -187,185 +81,47 @@ export function buildReactiveEngine(inputs) {
|
|
|
187
81
|
'for a distinct agent identity). They apply only to the self-hosted ' +
|
|
188
82
|
'`capabilityToken` path.');
|
|
189
83
|
}
|
|
190
|
-
// 7. The
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
/** Resolved account scope — set once identity resolution completes in
|
|
199
|
-
* `ready()`; exposed as the readonly `ablo.organizationId` accessor. */
|
|
84
|
+
// 7. The lifecycle — first mint, identity resolution, credential refresh,
|
|
85
|
+
// and the idempotent ready() driving store.initialize() — is the
|
|
86
|
+
// materialiser's own (`./storeLifecycle.ts`); the engine wires it with
|
|
87
|
+
// the prelude's credential slice and seeds its own state through the
|
|
88
|
+
// callback: the self locals (read by the model proxies' getters) and
|
|
89
|
+
// the streams' own-echo filters, before the store ever connects.
|
|
90
|
+
/** Resolved account scope — seeded once identity resolution completes;
|
|
91
|
+
* exposed as the readonly `ablo.organizationId` accessor. */
|
|
200
92
|
let _resolvedOrganizationId = null;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// Resolve identity against the live token, not the construction-time
|
|
234
|
-
// `configuredAuthToken`. Consumers using a function `apiKey` never pass
|
|
235
|
-
// `authToken` at construction — the lifecycle mints the first `ek_` or
|
|
236
|
-
// `rk_` and calls `setAuthToken()` before `ready()`, which updates the
|
|
237
|
-
// shared credential source. Reading the frozen `configuredAuthToken`
|
|
238
|
-
// here made `/auth/identity` fire with no bearer (returning
|
|
239
|
-
// `no_matching_provider` / `session_expired`) even though the token was
|
|
240
|
-
// present. This reads the shared credential source, like every other
|
|
241
|
-
// transport.
|
|
242
|
-
configuredAuthToken: authCredentials.getAuthToken() ?? configuredAuthToken,
|
|
243
|
-
bootstrapHelper,
|
|
244
|
-
auth: authCredentials,
|
|
245
|
-
logger,
|
|
246
|
-
});
|
|
247
|
-
const { userId, accountScope, teamIds, capabilityToken, syncGroups, participantKind, } = resolved;
|
|
248
|
-
// Fail-loud guard: detect the degenerate "no real sync groups
|
|
249
|
-
// resolved" state before opening the socket. It is the same class of bug as
|
|
250
|
-
// a
|
|
251
|
-
// sensible-looking default that's functionally broken: the
|
|
252
|
-
// SDK ends up subscribing only to the server-side
|
|
253
|
-
// `['default']` fallback, no
|
|
254
|
-
// delta has that tag, live fan-out silently never delivers.
|
|
255
|
-
// For human users (kind:'user') this is almost certainly a
|
|
256
|
-
// misconfiguration upstream — either the caller didn't pass
|
|
257
|
-
// `syncGroups`, or auth resolution didn't derive them, or
|
|
258
|
-
// both. Warn loudly so the next debugging session starts here
|
|
259
|
-
// instead of with "live updates don't work, hard reload fixes
|
|
260
|
-
// it."
|
|
261
|
-
const resolvedSyncGroups = syncGroups ?? [];
|
|
262
|
-
if (participantKind === 'user' &&
|
|
263
|
-
(resolvedSyncGroups.length === 0 ||
|
|
264
|
-
(resolvedSyncGroups.length === 1 && resolvedSyncGroups[0] === 'default'))) {
|
|
265
|
-
// Actionable and not self-healing (no live updates until fixed):
|
|
266
|
-
// kept at warn level for consumers; the low-level diagnostic
|
|
267
|
-
// fields ride the debug log below.
|
|
268
|
-
logger.warn('This client was started without sync groups, so it will not receive ' +
|
|
269
|
-
'live updates. Pass `syncGroups` (for example ' +
|
|
270
|
-
'`["org:<id>", "user:<id>"]`) or check that your auth provider supplies them.');
|
|
271
|
-
logger.debug('degenerate syncGroups — details', { participantKind, resolvedSyncGroups });
|
|
272
|
-
}
|
|
273
|
-
_resolvedOrganizationId = accountScope;
|
|
274
|
-
// Seed the resolved identity into everything that filters or stamps
|
|
275
|
-
// by participant: the presence and claim streams (own-echo filters,
|
|
276
|
-
// the presence `self` entry) and the model proxies' collaboration
|
|
277
|
-
// checks (via the getters over the locals). This runs before the
|
|
278
|
-
// store connects, so no frame is ever filtered against the
|
|
279
|
-
// construction-time guess.
|
|
280
|
-
selfParticipantId = userId;
|
|
281
|
-
selfParticipantKind = participantKind;
|
|
282
|
-
presenceStream.setParticipant({
|
|
283
|
-
id: userId,
|
|
284
|
-
kind: participantKind,
|
|
285
|
-
syncGroups: resolvedSyncGroups,
|
|
286
|
-
});
|
|
287
|
-
claimStream.setParticipant({ id: userId });
|
|
288
|
-
if (resolved.refreshScheduler) {
|
|
289
|
-
_refreshScheduler = resolved.refreshScheduler;
|
|
290
|
-
}
|
|
291
|
-
// Drive the generator to completion. Each yielded promise is awaited
|
|
292
|
-
// then fed back — this is standard generator consumption.
|
|
293
|
-
//
|
|
294
|
-
// The store.initialize() generator updates store.syncStatus as it
|
|
295
|
-
// progresses (syncing → idle on success, error on failure), so the
|
|
296
|
-
// consumer's `sync.syncStatus` observable reflects real-time state.
|
|
297
|
-
// Resolve bootstrap mode: explicit option wins; otherwise
|
|
298
|
-
// agents default to 'none' (transactional participant — see
|
|
299
|
-
// option doc) and everyone else defaults to 'full'.
|
|
300
|
-
const resolvedBootstrapMode = internalOptions.bootstrapMode ?? (participantKind === 'agent' ? 'none' : 'full');
|
|
301
|
-
const gen = store.initialize({
|
|
302
|
-
userId,
|
|
303
|
-
organizationId: accountScope,
|
|
304
|
-
teamIds,
|
|
305
|
-
kind: participantKind,
|
|
306
|
-
capabilityToken,
|
|
307
|
-
syncGroups,
|
|
308
|
-
bootstrapMode: resolvedBootstrapMode,
|
|
309
|
-
});
|
|
310
|
-
let current = gen.next();
|
|
311
|
-
while (!current.done) {
|
|
312
|
-
const yielded = current.value;
|
|
313
|
-
const resolved = yielded instanceof Promise ? await yielded : yielded;
|
|
314
|
-
current = gen.next(resolved);
|
|
315
|
-
}
|
|
316
|
-
const result = current.value;
|
|
317
|
-
if (!result.success) {
|
|
318
|
-
throw result.error
|
|
319
|
-
? toAbloError(result.error)
|
|
320
|
-
: new AbloConnectionError('Sync engine initialization failed', {
|
|
321
|
-
code: 'bootstrap_fetch_timeout',
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
logger.info('Sync engine ready', { models: Object.keys(schema.models).length });
|
|
325
|
-
}
|
|
326
|
-
catch (err) {
|
|
327
|
-
// Coerce so the rejection a consumer awaiting `ready()` catches is
|
|
328
|
-
// always an AbloError — connection setup is held to the same
|
|
329
|
-
// never-leak-untagged contract as the model operations.
|
|
330
|
-
const error = toAbloError(err);
|
|
331
|
-
// Make sure syncStatus reflects the failure for observer() components
|
|
332
|
-
store.syncStatus.state = 'error';
|
|
333
|
-
store.syncStatus.error = error;
|
|
334
|
-
// Log the typed envelope (type + code + status), not just the bare
|
|
335
|
-
// message — so the console line names it as an Ablo error and carries
|
|
336
|
-
// the code (e.g. AbloAuthenticationError/identity_resolve_failed on a
|
|
337
|
-
// 401) instead of reading like an untagged failure.
|
|
338
|
-
logger.error('Sync engine failed to initialize', {
|
|
339
|
-
type: error.type,
|
|
340
|
-
code: error.code,
|
|
341
|
-
httpStatus: error.httpStatus,
|
|
342
|
-
error: error.message,
|
|
343
|
-
});
|
|
344
|
-
// Clear the memo so a future `ready()` re-attempts bootstrap instead of
|
|
345
|
-
// replaying this rejection forever. Bootstrap failures here are transient
|
|
346
|
-
// by nature — offline, an IndexedDB open timeout, a bootstrap fetch
|
|
347
|
-
// hiccup — and the early `if (_readyPromise) return _readyPromise` guard
|
|
348
|
-
// would otherwise hand every later caller this same dead promise, bricking
|
|
349
|
-
// the engine until a full page reload. Nulling it lets the provider's
|
|
350
|
-
// online/wake/retry triggers drive a clean re-bootstrap. (The terminal
|
|
351
|
-
// `_validationError` branch above intentionally stays cached — config
|
|
352
|
-
// can't change without recreating the engine.)
|
|
353
|
-
_readyPromise = null;
|
|
354
|
-
throw error;
|
|
355
|
-
}
|
|
356
|
-
})();
|
|
357
|
-
return _readyPromise;
|
|
358
|
-
}
|
|
359
|
-
// 9. Optional auto-start for convenience. Opt-in because silent background
|
|
360
|
-
// init has historically been the #1 source of "why isn't my data loading"
|
|
361
|
-
// bug reports. Explicit `await sync.ready()` is the default — errors
|
|
362
|
-
// surface immediately instead of being swallowed.
|
|
363
|
-
if (!_validationError && internalOptions.autoStart) {
|
|
364
|
-
void ready().catch(() => {
|
|
365
|
-
// Error is captured in store.syncStatus; consumers should check
|
|
366
|
-
// `sync.syncStatus.state === 'error'` to detect failures.
|
|
367
|
-
});
|
|
368
|
-
}
|
|
93
|
+
const lifecycle = startStoreLifecycle({
|
|
94
|
+
cluster,
|
|
95
|
+
schema,
|
|
96
|
+
internalOptions,
|
|
97
|
+
authCredentials,
|
|
98
|
+
credentialResolver,
|
|
99
|
+
configuredApiKey,
|
|
100
|
+
configuredAuthToken,
|
|
101
|
+
url,
|
|
102
|
+
kind,
|
|
103
|
+
logger,
|
|
104
|
+
validationError: _validationError,
|
|
105
|
+
onIdentityResolved: ({ userId, participantKind, accountScope, syncGroups }) => {
|
|
106
|
+
selfParticipantId = userId;
|
|
107
|
+
selfParticipantKind = participantKind;
|
|
108
|
+
_resolvedOrganizationId = accountScope;
|
|
109
|
+
presenceStream.setParticipant({
|
|
110
|
+
id: userId,
|
|
111
|
+
kind: participantKind,
|
|
112
|
+
syncGroups: [...syncGroups],
|
|
113
|
+
});
|
|
114
|
+
claimStream.setParticipant({ id: userId });
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const ready = lifecycle.ready;
|
|
118
|
+
const participantManager = createParticipantManager({
|
|
119
|
+
ready,
|
|
120
|
+
transport,
|
|
121
|
+
presence: presenceStream,
|
|
122
|
+
claims: claimStream,
|
|
123
|
+
schema,
|
|
124
|
+
});
|
|
369
125
|
// 9b. waitForFlush — drains pending mutations using the store's
|
|
370
126
|
// pendingChanges counter (already maintained by BaseSyncedStore based
|
|
371
127
|
// on MutationQueue events). Polls every 50ms; uses the existing
|
|
@@ -386,7 +142,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
386
142
|
? crypto.randomUUID()
|
|
387
143
|
: `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
388
144
|
}
|
|
389
|
-
function normalizeCommitOperation(op, defaults,
|
|
145
|
+
function normalizeCommitOperation(op, defaults, fence) {
|
|
390
146
|
const type = op.action.toUpperCase();
|
|
391
147
|
const id = op.id ?? '';
|
|
392
148
|
return durableCommitOperationSchema.parse({
|
|
@@ -397,16 +153,14 @@ export function buildReactiveEngine(inputs) {
|
|
|
397
153
|
transactionId: op.transactionId ?? undefined,
|
|
398
154
|
readAt: op.readAt ?? defaults.readAt ?? undefined,
|
|
399
155
|
onStale: op.onStale ?? defaults.onStale ?? undefined,
|
|
400
|
-
|
|
401
|
-
// how it supplies the batch `readAt`.
|
|
402
|
-
fenceToken: op.fenceToken ?? fenceToken ?? undefined,
|
|
156
|
+
fenceToken: op.fenceToken ?? fenceTokenFor(fence, op.model, op.id ?? null) ?? undefined,
|
|
403
157
|
});
|
|
404
158
|
}
|
|
405
|
-
function normalizeCommitOperations(commitOptions,
|
|
159
|
+
function normalizeCommitOperations(commitOptions, fence) {
|
|
406
160
|
if (commitOptions.operations.length === 0) {
|
|
407
161
|
throw new AbloValidationError('Commit requires a non-empty `operations` array.', { code: 'commit_operation_required' });
|
|
408
162
|
}
|
|
409
|
-
return commitOptions.operations.map((op) => normalizeCommitOperation(op, commitOptions,
|
|
163
|
+
return commitOptions.operations.map((op) => normalizeCommitOperation(op, commitOptions, fence));
|
|
410
164
|
}
|
|
411
165
|
function modelClaimFromActive(claim) {
|
|
412
166
|
const target = {
|
|
@@ -543,6 +297,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
543
297
|
({ waited, fenceToken } = await awaitClaimGrant(transport, claim.id, {
|
|
544
298
|
timeoutMs: claimOptions.waitTimeoutMs,
|
|
545
299
|
maxQueueDepth: claimOptions.maxQueueDepth,
|
|
300
|
+
signal: claimOptions.signal,
|
|
546
301
|
logger,
|
|
547
302
|
}));
|
|
548
303
|
}
|
|
@@ -563,6 +318,39 @@ export function buildReactiveEngine(inputs) {
|
|
|
563
318
|
return waitForModelUnclaimed(target, options);
|
|
564
319
|
},
|
|
565
320
|
});
|
|
321
|
+
/**
|
|
322
|
+
* One held claim, as the public claim-state object.
|
|
323
|
+
*
|
|
324
|
+
* The live claim stream only tracks *open* claims; terminal states
|
|
325
|
+
* (committed / expired / canceled) drop out of the list entirely — exactly
|
|
326
|
+
* the ephemeral coordination model — so a present entry is by definition
|
|
327
|
+
* `status: 'active'`.
|
|
328
|
+
*/
|
|
329
|
+
const claimStateOf = (held) => {
|
|
330
|
+
if (!held)
|
|
331
|
+
return null;
|
|
332
|
+
return {
|
|
333
|
+
object: 'claim',
|
|
334
|
+
id: held.id,
|
|
335
|
+
status: 'active',
|
|
336
|
+
target: {
|
|
337
|
+
...streamTarget(held.target),
|
|
338
|
+
...subTarget(held.target),
|
|
339
|
+
},
|
|
340
|
+
description: held.description ?? 'editing',
|
|
341
|
+
heldBy: held.actor,
|
|
342
|
+
participantKind: held.participantKind,
|
|
343
|
+
expiresAt: held.expiresAt,
|
|
344
|
+
// Carried, not dropped: the coordinator writes a heartbeat's `details`
|
|
345
|
+
// into `meta.progress` on the holder's record so an observer can see
|
|
346
|
+
// what a long hold is doing. It reached `ModelClaim` and stopped here,
|
|
347
|
+
// which left the beat writable and unreadable — a channel with a setter
|
|
348
|
+
// and no getter. `target.meta` beside it stays the declared shape; this
|
|
349
|
+
// is the open record, because a declared shape has no member for a key
|
|
350
|
+
// the holder did not write.
|
|
351
|
+
...(held.meta !== undefined ? { meta: held.meta } : {}),
|
|
352
|
+
};
|
|
353
|
+
};
|
|
566
354
|
// Build the typed proxy — one property per model. Done after publicClaims
|
|
567
355
|
// exists so model clients can expose workflow helpers such as
|
|
568
356
|
// `ablo.files.edit(...)` without importing protocol wiring.
|
|
@@ -586,28 +374,16 @@ export function buildReactiveEngine(inputs) {
|
|
|
586
374
|
}),
|
|
587
375
|
queue: (target) => publicClaims.queueFor(streamTarget(target)),
|
|
588
376
|
reorder: (target, order) => { publicClaims.reorder(streamTarget(target), order); },
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
id: held.id,
|
|
600
|
-
status: 'active',
|
|
601
|
-
target: {
|
|
602
|
-
...streamTarget(held.target),
|
|
603
|
-
...subTarget(held.target),
|
|
604
|
-
},
|
|
605
|
-
description: held.description ?? 'editing',
|
|
606
|
-
heldBy: held.actor,
|
|
607
|
-
participantKind: held.participantKind,
|
|
608
|
-
expiresAt: held.expiresAt,
|
|
609
|
-
};
|
|
610
|
-
},
|
|
377
|
+
// One row can have several holders — sub-row claims on disjoint
|
|
378
|
+
// targets are all granted — so `state` and `holders` read the same
|
|
379
|
+
// list and differ only in how much of it they answer with. The
|
|
380
|
+
// projection lives here once: two copies of it would drift the
|
|
381
|
+
// moment a field is added to one caller's answer.
|
|
382
|
+
state: (target) => claimStateOf(publicClaims.list(modelTarget(target))[0]),
|
|
383
|
+
holders: (target) => publicClaims
|
|
384
|
+
.list(modelTarget(target))
|
|
385
|
+
.map((held) => claimStateOf(held))
|
|
386
|
+
.filter((claim) => claim !== null),
|
|
611
387
|
waitFor: (target, waitOptions) => publicClaims.waitFor(modelTarget(target), waitOptions),
|
|
612
388
|
// Getters, not copies: identity is late-bound (seeded in `ready()`),
|
|
613
389
|
// and the contended / heldBy checks must compare against whoever
|
|
@@ -628,9 +404,12 @@ export function buildReactiveEngine(inputs) {
|
|
|
628
404
|
// `ablo.<model>.join(ids, { ttl })` performs a scoped participant join
|
|
629
405
|
// on this model's sync group(s). WebSocket only — `join` throws
|
|
630
406
|
// `AbloConnectionError` if the socket isn't ready.
|
|
407
|
+
// `ttl` passes straight through — both surfaces spell the lease the
|
|
408
|
+
// same way now, so there is no rename here to make a field's name
|
|
409
|
+
// disagree with the value it carries.
|
|
631
410
|
createJoin: (modelKey, ids, options) => participantManager.join({
|
|
632
411
|
scope: { [modelKey]: ids },
|
|
633
|
-
...(options?.ttl !== undefined ? {
|
|
412
|
+
...(options?.ttl !== undefined ? { ttl: options.ttl } : {}),
|
|
634
413
|
}),
|
|
635
414
|
},
|
|
636
415
|
// The client-wide `wait` default; a per-call `wait` still wins.
|
|
@@ -656,7 +435,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
656
435
|
...commitOptions,
|
|
657
436
|
readAt: commitOptions.readAt ?? claim?.readAt ?? null,
|
|
658
437
|
onStale: commitOptions.onStale ?? (claim?.readAt !== undefined ? 'reject' : null),
|
|
659
|
-
}, claim?.fenceToken
|
|
438
|
+
}, batchFence(claim?.target, claim?.fenceToken));
|
|
660
439
|
const wait = commitOptions.wait ?? 'confirmed';
|
|
661
440
|
// Route through the MutationQueue's commit lane so the call
|
|
662
441
|
// tolerates WS disconnects: the envelope stays in memory until
|
|
@@ -828,8 +607,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
828
607
|
},
|
|
829
608
|
},
|
|
830
609
|
async dispose() {
|
|
831
|
-
|
|
832
|
-
_refreshScheduler = null;
|
|
610
|
+
lifecycle.dispose();
|
|
833
611
|
try {
|
|
834
612
|
await store.disconnect();
|
|
835
613
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The store cluster — what `humans()` constructs at `init` now that the
|
|
3
|
+
* plugin context carries the resolved url and the credential source
|
|
4
|
+
* (ADR 0016; docs/plans/package-split.md, sequence step 2): this client's
|
|
5
|
+
* runtime, the internal component graph, the registered model classes, and
|
|
6
|
+
* the `BaseSyncedStore` that orchestrates them. The engine consumes the
|
|
7
|
+
* cluster instead of constructing it; what remains in the composition root
|
|
8
|
+
* (credential lifecycle, ready(), model proxies, resources) migrates in
|
|
9
|
+
* later steps.
|
|
10
|
+
*
|
|
11
|
+
* The cluster rides the humans surface under {@link kStoreCluster}, a
|
|
12
|
+
* symbol: `layerPluginSurface` merges only string-keyed members onto the
|
|
13
|
+
* client, so the handoff never becomes public client API. When the plugin
|
|
14
|
+
* contract adopts context patches (the decoration end-state), this channel
|
|
15
|
+
* dissolves into them.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginContext } from '../transaction/plugin.js';
|
|
18
|
+
import type { Schema, SchemaRecord } from '../transaction/schema/schema.js';
|
|
19
|
+
import { type RuntimeContext } from '../RuntimeContext.js';
|
|
20
|
+
import { BaseSyncedStore } from '../BaseSyncedStore.js';
|
|
21
|
+
import { type DefaultCollaborationEvents } from '../sync/SyncWebSocket.js';
|
|
22
|
+
import { type InternalComponents } from './createInternalComponents.js';
|
|
23
|
+
import type { InternalAbloOptions } from './options.js';
|
|
24
|
+
/**
|
|
25
|
+
* The private handoff slot on the humans surface. Symbol-keyed on purpose:
|
|
26
|
+
* surface members merge onto the client by string key, and the cluster is
|
|
27
|
+
* construction machinery, not client API.
|
|
28
|
+
*/
|
|
29
|
+
export declare const kStoreCluster: unique symbol;
|
|
30
|
+
/** What `humans().init` constructs and the engine consumes. */
|
|
31
|
+
export interface StoreCluster {
|
|
32
|
+
/** This client's runtime — every component below was constructed with it. */
|
|
33
|
+
readonly runtime: RuntimeContext;
|
|
34
|
+
/** The component graph: registry, pool, bootstrap, database, sync client, hydration. */
|
|
35
|
+
readonly components: InternalComponents;
|
|
36
|
+
/** The store orchestrating the graph, holding the host-built connection. */
|
|
37
|
+
readonly store: BaseSyncedStore<DefaultCollaborationEvents, Schema<SchemaRecord>>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Builds the cluster from what the context carries, or returns `null` when
|
|
41
|
+
* it does not carry enough (no connection, no resolved url, no credential
|
|
42
|
+
* source, or no schema on the options bag) — the same tolerance the
|
|
43
|
+
* presence stream established: `init` constructs what the context can
|
|
44
|
+
* support and no more. The reactive factory always supplies all four and
|
|
45
|
+
* treats a missing cluster as a configuration fault.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildStoreCluster(context: PluginContext<InternalAbloOptions<SchemaRecord>>): StoreCluster | null;
|