@abloatai/ablo 0.9.2 → 0.9.4
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 +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +47 -27
- package/dist/BaseSyncedStore.d.ts +7 -38
- package/dist/BaseSyncedStore.js +20 -67
- package/dist/Database.js +7 -1
- package/dist/NetworkMonitor.js +4 -1
- package/dist/SyncClient.d.ts +18 -5
- package/dist/SyncClient.js +72 -1
- package/dist/SyncEngineContext.js +5 -1
- package/dist/auth/index.js +3 -1
- package/dist/cli.cjs +282241 -0
- package/dist/client/Ablo.d.ts +12 -3
- package/dist/client/Ablo.js +36 -3
- package/dist/client/ApiClient.js +39 -6
- package/dist/client/auth.d.ts +1 -1
- package/dist/client/auth.js +14 -5
- package/dist/client/createInternalComponents.js +1 -1
- package/dist/client/createModelProxy.d.ts +9 -0
- package/dist/client/createModelProxy.js +34 -10
- package/dist/client/persistence.d.ts +6 -1
- package/dist/client/persistence.js +1 -1
- package/dist/client/registerDataSource.d.ts +4 -4
- package/dist/client/registerDataSource.js +39 -31
- package/dist/client/writeOptionsSchema.d.ts +50 -0
- package/dist/client/writeOptionsSchema.js +57 -0
- package/dist/core/index.d.ts +18 -26
- package/dist/core/index.js +22 -46
- package/dist/errorCodes.d.ts +13 -0
- package/dist/errorCodes.js +16 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/interfaces/index.d.ts +10 -0
- package/dist/mutators/UndoManager.d.ts +31 -5
- package/dist/mutators/UndoManager.js +113 -1
- package/dist/schema/ddl.js +12 -3
- package/dist/schema/field.js +2 -1
- package/dist/schema/model.d.ts +9 -7
- package/dist/schema/model.js +1 -1
- package/dist/schema/schema.js +7 -1
- package/dist/schema/serialize.js +2 -1
- package/dist/server/storage-mode.d.ts +7 -0
- package/dist/server/storage-mode.js +6 -0
- package/dist/source/adapters/drizzle.js +3 -2
- package/dist/source/adapters/kysely.d.ts +68 -0
- package/dist/source/adapters/kysely.js +210 -0
- package/dist/source/adapters/memory.js +2 -1
- package/dist/source/adapters/prisma.js +3 -2
- package/dist/source/index.js +2 -1
- package/dist/sync/syncPosition.d.ts +78 -0
- package/dist/sync/syncPosition.js +111 -0
- package/dist/transactions/TransactionQueue.d.ts +22 -8
- package/dist/transactions/TransactionQueue.js +76 -34
- package/dist/utils/duration.js +3 -2
- package/docs/api-keys.md +4 -4
- package/docs/cli.md +6 -6
- package/docs/client-behavior.md +1 -1
- package/docs/data-sources.md +61 -42
- package/docs/guarantees.md +2 -2
- package/docs/index.md +2 -2
- package/docs/integration-guide.md +4 -7
- package/docs/mcp.md +1 -1
- package/docs/quickstart.md +84 -37
- package/docs/schema-contract.md +2 -4
- package/llms-full.txt +365 -0
- package/llms.txt +14 -9
- package/package.json +26 -4
package/dist/core/index.d.ts
CHANGED
|
@@ -1,36 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @abloatai/ablo/core — Framework extension
|
|
3
3
|
*
|
|
4
|
-
* Only imported by
|
|
5
|
-
* the
|
|
6
|
-
* Regular model files and components should NOT import from
|
|
4
|
+
* Only imported by the handful of files that extend or orchestrate the
|
|
5
|
+
* sync engine (the app-shell store/provider stack, sync adapters, demo
|
|
6
|
+
* harnesses). Regular model files and components should NOT import from
|
|
7
|
+
* here — the consumer surface is `Ablo({ schema })` on the root.
|
|
8
|
+
*
|
|
9
|
+
* TRIMMED to what framework-level consumers actually import (verified by
|
|
10
|
+
* a monorepo-wide import scan). Everything else the engine defines stays
|
|
11
|
+
* module-private: if a new framework concern genuinely needs another
|
|
12
|
+
* primitive, add the export deliberately — don't re-widen the barrel.
|
|
7
13
|
*/
|
|
8
|
-
export { BaseSyncedStore, type
|
|
9
|
-
export { SyncClient
|
|
10
|
-
export { Database
|
|
14
|
+
export { BaseSyncedStore, type ModelConstructor, type ConcreteModelConstructor, } from '../BaseSyncedStore.js';
|
|
15
|
+
export { SyncClient } from '../SyncClient.js';
|
|
16
|
+
export { Database } from '../Database.js';
|
|
11
17
|
export { ObjectPool, ModelScope } from '../ObjectPool.js';
|
|
12
18
|
export { Model } from '../Model.js';
|
|
13
|
-
export { LazyReferenceCollection, type LazyCollectionOptions } from '../LazyReferenceCollection.js';
|
|
19
|
+
export { LazyReferenceCollection, type LazyCollectionOptions, } from '../LazyReferenceCollection.js';
|
|
20
|
+
export { ModelRegistry, getActiveRegistry, } from '../ModelRegistry.js';
|
|
14
21
|
export { postQuery, type PostQueryOptions } from '../query/client.js';
|
|
15
|
-
export { probeNetwork, type ProbeResult } from '../sync/NetworkProbe.js';
|
|
16
|
-
export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '../sync/ConnectionManager.js';
|
|
17
|
-
export { ModelRegistry, getActiveRegistry, type ExtendedReferenceMetadata, type BackReferenceMetadata } from '../ModelRegistry.js';
|
|
18
22
|
export { computeFKDepthPriority, type InternalAbloOptions } from '../client/Ablo.js';
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export type { SyncEngineConfig, SyncLogger, SyncObservabilityProvider, SyncAnalytics, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, BreadcrumbLevel, SyncBreadcrumbCategory, TransactionFailureDetails, BootstrapFailureDetails, WebSocketErrorDetails, RollbackDetails, SpanAttributes, } from '../interfaces/index.js';
|
|
23
|
-
export { SyncSessionError } from '../errors.js';
|
|
24
|
-
export { QueryProcessor } from './QueryProcessor.js';
|
|
25
|
-
export { QueryView, type QueryViewOptions } from './QueryView.js';
|
|
26
|
-
export { ViewRegistry } from './ViewRegistry.js';
|
|
27
|
-
export { ObjectStore } from '../stores/ObjectStore.js';
|
|
28
|
-
export { NetworkMonitor } from '../NetworkMonitor.js';
|
|
29
|
-
export { SyncWebSocket, type SyncDelta, type VersionVector, type BootstrapHint, type SyncGroupChangePayload, type BootstrapDataEvent, type PresenceUpdateEvent, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
|
|
30
|
-
export { BootstrapHelper, type BootstrapData, type BootstrapOptions, type BootstrapFetchResult } from '../sync/BootstrapHelper.js';
|
|
23
|
+
export type { SyncLogger, SyncObservabilityProvider, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, } from '../interfaces/index.js';
|
|
24
|
+
export { SyncWebSocket, type SyncDelta, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
|
|
25
|
+
export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
31
26
|
export { createIntentStream, type AttachableIntentStream, type IntentStreamConfig, } from '../sync/createIntentStream.js';
|
|
32
27
|
export { awaitIntentGrant, type GrantTransport, } from '../sync/awaitIntentGrant.js';
|
|
33
|
-
export {
|
|
34
|
-
export { PropertyType, LoadStrategy, MutationOperationType } from '../types/index.js';
|
|
35
|
-
export type { PropertyMetadata, ReferenceMetadata, ModelMetadata, SyncAction, DeltaPacket, BootstrapMetadata, DatabaseMetadata, } from '../types/index.js';
|
|
36
|
-
export type { ModelData } from '../BaseSyncedStore.js';
|
|
28
|
+
export { LoadStrategy } from '../types/index.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,52 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @abloatai/ablo/core — Framework extension
|
|
3
3
|
*
|
|
4
|
-
* Only imported by
|
|
5
|
-
* the
|
|
6
|
-
* Regular model files and components should NOT import from
|
|
4
|
+
* Only imported by the handful of files that extend or orchestrate the
|
|
5
|
+
* sync engine (the app-shell store/provider stack, sync adapters, demo
|
|
6
|
+
* harnesses). Regular model files and components should NOT import from
|
|
7
|
+
* here — the consumer surface is `Ablo({ schema })` on the root.
|
|
8
|
+
*
|
|
9
|
+
* TRIMMED to what framework-level consumers actually import (verified by
|
|
10
|
+
* a monorepo-wide import scan). Everything else the engine defines stays
|
|
11
|
+
* module-private: if a new framework concern genuinely needs another
|
|
12
|
+
* primitive, add the export deliberately — don't re-widen the barrel.
|
|
7
13
|
*/
|
|
8
|
-
// Base store class
|
|
9
|
-
export { BaseSyncedStore,
|
|
10
|
-
// Core infrastructure
|
|
14
|
+
// Base store class + the constructor shapes subclasses reference
|
|
15
|
+
export { BaseSyncedStore, } from '../BaseSyncedStore.js';
|
|
16
|
+
// Core infrastructure classes
|
|
11
17
|
export { SyncClient } from '../SyncClient.js';
|
|
12
18
|
export { Database } from '../Database.js';
|
|
13
19
|
export { ObjectPool, ModelScope } from '../ObjectPool.js';
|
|
14
20
|
export { Model } from '../Model.js';
|
|
15
|
-
export { LazyReferenceCollection } from '../LazyReferenceCollection.js';
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// namespace. Direct class access (tests, non-React hosts) imports via
|
|
20
|
-
// the package's internal subpath.
|
|
21
|
-
// Lower-level network primitives — exposed here for the per-app demand
|
|
22
|
-
// loaders. The main barrel hides these so consumer code converges on
|
|
23
|
-
// `ablo.<model>.fetch(...)` for hydration. Loaders that haven't been
|
|
24
|
-
// migrated yet can keep importing from `/core`.
|
|
21
|
+
export { LazyReferenceCollection, } from '../LazyReferenceCollection.js';
|
|
22
|
+
export { ModelRegistry, getActiveRegistry, } from '../ModelRegistry.js';
|
|
23
|
+
// Lower-level network read — for per-app demand loaders that haven't
|
|
24
|
+
// migrated to `ablo.<model>.list(...)` yet.
|
|
25
25
|
export { postQuery } from '../query/client.js';
|
|
26
|
-
export { probeNetwork } from '../sync/NetworkProbe.js';
|
|
27
|
-
export { ConnectionManager, } from '../sync/ConnectionManager.js';
|
|
28
|
-
export { ModelRegistry, getActiveRegistry } from '../ModelRegistry.js';
|
|
29
26
|
// FK-cycle / dependency-order helper — used by schema-aware test
|
|
30
|
-
// fixtures and scaffolding tools to compute commit ordering.
|
|
31
|
-
// here because it traverses model relations and isn't part of the
|
|
32
|
-
// consumer-facing API.
|
|
27
|
+
// fixtures and scaffolding tools to compute commit ordering.
|
|
33
28
|
export { computeFKDepthPriority } from '../client/Ablo.js';
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
// Adapters that the consumer wires into `<AbloProvider>` props
|
|
37
|
-
// (logger, observability, mutation executor, session-error
|
|
38
|
-
// detector, etc.) implement these interfaces. Lives on `/core`
|
|
39
|
-
// because most consumers don't need them; only apps that wrap the
|
|
40
|
-
// provider with custom adapters reach for them.
|
|
41
|
-
export { initSyncEngine, resetSyncEngine, isSyncEngineInitialized } from '../context.js';
|
|
42
|
-
export { noopLogger, noopObservability, noopAnalytics, browserOnlineStatus, defaultSessionErrorDetector, emptyConfig, } from '../SyncEngineContext.js';
|
|
43
|
-
export { SyncSessionError } from '../errors.js';
|
|
44
|
-
export { QueryProcessor } from './QueryProcessor.js';
|
|
45
|
-
export { QueryView } from './QueryView.js';
|
|
46
|
-
export { ViewRegistry } from './ViewRegistry.js';
|
|
47
|
-
export { ObjectStore } from '../stores/ObjectStore.js';
|
|
48
|
-
export { NetworkMonitor } from '../NetworkMonitor.js';
|
|
49
|
-
// Sync layer
|
|
29
|
+
// Sync layer — the wire socket + delta shape, for sync adapters and the
|
|
30
|
+
// multi-agent demo harnesses.
|
|
50
31
|
export { SyncWebSocket, } from '../sync/SyncWebSocket.js';
|
|
51
32
|
export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
52
33
|
// Intent coordination primitives (the lower-level pieces behind the
|
|
@@ -56,11 +37,6 @@ export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
|
56
37
|
// orchestration and e2e harnesses — NOT on the consumer `.` root.
|
|
57
38
|
export { createIntentStream, } from '../sync/createIntentStream.js';
|
|
58
39
|
export { awaitIntentGrant, } from '../sync/awaitIntentGrant.js';
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
// headless-clean import path. Framework-level consumers (the few files
|
|
63
|
-
// that orchestrate sync) import from /core explicitly.
|
|
64
|
-
export { OfflineTransactionStore, offlineTxStore, Priority } from '../sync/OfflineTransactionStore.js';
|
|
65
|
-
// Types used by framework-level code
|
|
66
|
-
export { PropertyType, LoadStrategy, MutationOperationType } from '../types/index.js';
|
|
40
|
+
// Schema/model load strategy enum — referenced by model registration in
|
|
41
|
+
// framework code.
|
|
42
|
+
export { LoadStrategy } from '../types/index.js';
|
package/dist/errorCodes.d.ts
CHANGED
|
@@ -134,9 +134,15 @@ export declare const ERROR_CODES: {
|
|
|
134
134
|
readonly browser_apikey_blocked: ErrorCodeSpec;
|
|
135
135
|
readonly browser_database_url_blocked: ErrorCodeSpec;
|
|
136
136
|
readonly datasource_registration_failed: ErrorCodeSpec;
|
|
137
|
+
readonly datasource_connection_unsupported: ErrorCodeSpec;
|
|
137
138
|
readonly capability_scope_denied: ErrorCodeSpec;
|
|
138
139
|
readonly issuer_register_forbidden: ErrorCodeSpec;
|
|
139
140
|
readonly capability_invalid: ErrorCodeSpec;
|
|
141
|
+
readonly test_database_not_registered: ErrorCodeSpec;
|
|
142
|
+
readonly database_role_cannot_enforce_rls: ErrorCodeSpec;
|
|
143
|
+
readonly database_role_unreadable: ErrorCodeSpec;
|
|
144
|
+
readonly database_tables_unforced_rls: ErrorCodeSpec;
|
|
145
|
+
readonly database_host_not_allowed: ErrorCodeSpec;
|
|
140
146
|
readonly byo_role_cannot_enforce_rls: ErrorCodeSpec;
|
|
141
147
|
readonly byo_role_unreadable: ErrorCodeSpec;
|
|
142
148
|
readonly byo_tenant_tables_unforced_rls: ErrorCodeSpec;
|
|
@@ -152,6 +158,13 @@ export declare const ERROR_CODES: {
|
|
|
152
158
|
readonly stale_context: ErrorCodeSpec;
|
|
153
159
|
readonly idempotency_conflict: ErrorCodeSpec;
|
|
154
160
|
readonly idempotency_key_too_long: ErrorCodeSpec;
|
|
161
|
+
readonly write_options_invalid: ErrorCodeSpec;
|
|
162
|
+
readonly source_operation_id_required: ErrorCodeSpec;
|
|
163
|
+
readonly source_adapter_misconfigured: ErrorCodeSpec;
|
|
164
|
+
readonly source_event_invalid: ErrorCodeSpec;
|
|
165
|
+
readonly duration_invalid: ErrorCodeSpec;
|
|
166
|
+
readonly schema_definition_invalid: ErrorCodeSpec;
|
|
167
|
+
readonly cli_invalid_arguments: ErrorCodeSpec;
|
|
155
168
|
readonly turn_validation_failed: ErrorCodeSpec;
|
|
156
169
|
readonly commit_operation_required: ErrorCodeSpec;
|
|
157
170
|
readonly commit_operation_model_required: ErrorCodeSpec;
|
package/dist/errorCodes.js
CHANGED
|
@@ -123,11 +123,19 @@ export const ERROR_CODES = {
|
|
|
123
123
|
file_upload_auth_required: wire('auth', 401, false, 'File upload requires an authenticated session.'),
|
|
124
124
|
browser_apikey_blocked: client('auth', 'Raw API keys must not be used from a browser context.'),
|
|
125
125
|
browser_database_url_blocked: client('auth', 'A database connection string must not be used from a browser context — it carries DB credentials.'),
|
|
126
|
-
datasource_registration_failed: client('auth', 'Failed to register the provided databaseUrl
|
|
126
|
+
datasource_registration_failed: client('auth', 'Failed to register the provided databaseUrl as a datasource.'),
|
|
127
|
+
datasource_connection_unsupported: wire('validation', 400, false, 'This deployment cannot register a direct (connection string) datasource — use the signed endpoint kind.'),
|
|
127
128
|
// ── permission / capability (403) ──────────────────────────────────
|
|
128
129
|
capability_scope_denied: wire('capability', 403, false, "The connection's resolved scope does not cover the attempted action."),
|
|
129
130
|
issuer_register_forbidden: wire('permission', 403, false, 'Registering a trusted issuer requires a secret (sk_) API key.'),
|
|
130
131
|
capability_invalid: wire('capability', 403, false, 'The capability is unknown, revoked, or expired.'),
|
|
132
|
+
test_database_not_registered: wire('permission', 403, false, 'Test mode requires a registered dev database for this org — run `npx ablo init`, or construct the client with `databaseUrl` using your test key.'),
|
|
133
|
+
database_role_cannot_enforce_rls: wire('permission', 403, false, 'The connected database role cannot enforce row-level security (superuser or BYPASSRLS).'),
|
|
134
|
+
database_role_unreadable: wire('permission', 403, false, 'The connected database role could not be introspected.'),
|
|
135
|
+
database_tables_unforced_rls: wire('permission', 403, false, 'Synced tables in the connected database do not have FORCE ROW LEVEL SECURITY applied.'),
|
|
136
|
+
database_host_not_allowed: wire('permission', 403, false, 'The connected database host resolves to a private, loopback, or link-local address and cannot be used.'),
|
|
137
|
+
// Deprecated spellings of the `database_*` codes above — still emitted by
|
|
138
|
+
// older servers; kept so they classify identically. Do not use in new code.
|
|
131
139
|
byo_role_cannot_enforce_rls: wire('permission', 403, false, 'The direct Postgres connector role cannot enforce row-level security.'),
|
|
132
140
|
byo_role_unreadable: wire('permission', 403, false, 'The direct Postgres connector role could not be introspected.'),
|
|
133
141
|
byo_tenant_tables_unforced_rls: wire('permission', 403, false, 'Tenant tables do not have RLS forced under the direct Postgres connector role.'),
|
|
@@ -146,6 +154,13 @@ export const ERROR_CODES = {
|
|
|
146
154
|
idempotency_conflict: wire('conflict', 409, false, 'The same Idempotency-Key was reused with a different request body.'),
|
|
147
155
|
idempotency_key_too_long: wire('validation', 400, false, 'The supplied Idempotency-Key exceeds the maximum length.'),
|
|
148
156
|
// ── validation (400 / 422) ─────────────────────────────────────────
|
|
157
|
+
write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `intent`) failed validation against the write-options schema.'),
|
|
158
|
+
source_operation_id_required: client('validation', 'A data-source operation arrived without the entity `id` it targets.'),
|
|
159
|
+
source_adapter_misconfigured: client('validation', 'The data-source ORM adapter could not map a schema model onto the backing client (missing delegate or model).'),
|
|
160
|
+
source_event_invalid: client('validation', 'A data-source outbox event could not be built — the operation carries no entity id and none was supplied.'),
|
|
161
|
+
duration_invalid: client('validation', 'A duration value was not a number of seconds or a "500ms" | "30s" | "3m" | "24h" string.'),
|
|
162
|
+
schema_definition_invalid: client('validation', 'A schema definition value was invalid (bad column identifier, non-finite backfill, or unsupported schema-JSON version).'),
|
|
163
|
+
cli_invalid_arguments: client('validation', 'The CLI was invoked with an unknown flag or a malformed flag value.'),
|
|
149
164
|
turn_validation_failed: wire('validation', 422, false, 'The agent turn failed server-side validation.'),
|
|
150
165
|
commit_operation_required: wire('validation', 400, false, 'A commit must carry `operation` or `operations`.'),
|
|
151
166
|
commit_operation_model_required: wire('validation', 400, false, 'A commit operation is missing its `model`.'),
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,9 @@ export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionErr
|
|
|
64
64
|
export type { CommitReceipt, RequiredCapability } from './errors.js';
|
|
65
65
|
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errors.js';
|
|
66
66
|
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from './auth/credentialSource.js';
|
|
67
|
+
export { writeOptionsSchema, onStaleModeSchema, assertWriteOptions, } from './client/writeOptionsSchema.js';
|
|
68
|
+
export type { WriteOptionsInput } from './client/writeOptionsSchema.js';
|
|
69
|
+
export type { WriteOptions, MutationOptions } from './interfaces/index.js';
|
|
67
70
|
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
68
71
|
export type { Register, DefaultSyncShape } from './types/global.js';
|
|
69
72
|
export { defineMutators } from './mutators/defineMutators.js';
|
package/dist/index.js
CHANGED
|
@@ -89,6 +89,13 @@ export { defaultPolicy, capabilityPreemptPolicy } from './policy/index.js';
|
|
|
89
89
|
// `e.type === 'AbloX'`) plus the HTTP-response translator.
|
|
90
90
|
export { SyncSessionError, AbloError, AbloAuthenticationError, AbloPermissionError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloValidationError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, translateHttpError, hasWireCode, errorFromWire, toAbloError, ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errors.js';
|
|
91
91
|
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from './auth/credentialSource.js';
|
|
92
|
+
// THE write-options contract — the one Zod schema for the option bag every
|
|
93
|
+
// write door accepts (`ablo.<model>.create/update/delete`, `commits.create`,
|
|
94
|
+
// the HTTP model routes). The SDK validates against it at each boundary;
|
|
95
|
+
// it's exported so consumers can validate/compose options ahead of a call
|
|
96
|
+
// (e.g. an agent tool's input schema). Runtime twin of `MutationOptions`,
|
|
97
|
+
// drift-guarded at compile time.
|
|
98
|
+
export { writeOptionsSchema, onStaleModeSchema, assertWriteOptions, } from './client/writeOptionsSchema.js';
|
|
92
99
|
// Storage-wedge detection — lets app shells render a recovery screen when the
|
|
93
100
|
// IndexedDB backing store is stuck (see core/openIDBWithTimeout.ts).
|
|
94
101
|
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
@@ -171,6 +171,16 @@ export interface MutationOptions {
|
|
|
171
171
|
*/
|
|
172
172
|
causedByTaskId?: string | null;
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* The `MutationOptions` subset carried per-write through the offline
|
|
176
|
+
* transaction lane (SyncClient → TransactionQueue → wire operation).
|
|
177
|
+
* ONE shared type so the proxy's public params, the queue, and the wire
|
|
178
|
+
* can never narrow each other silently again — `wait` and `intent` are
|
|
179
|
+
* deliberately absent because they resolve client-side before staging
|
|
180
|
+
* (`wait` at the proxy's confirmation await, `intent` server-side via
|
|
181
|
+
* the active lease on the entity).
|
|
182
|
+
*/
|
|
183
|
+
export type WriteOptions = Pick<MutationOptions, 'readAt' | 'onStale' | 'idempotencyKey' | 'label'>;
|
|
174
184
|
/** A single mutation operation in a batch. `options` rides along so the
|
|
175
185
|
* server can cache+replay via `mutation_log`. */
|
|
176
186
|
export interface MutationOperation {
|
|
@@ -52,11 +52,6 @@ export interface UndoScopeOptions {
|
|
|
52
52
|
*/
|
|
53
53
|
recordFromStream?: boolean;
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
* A single undo stack for one surface. Access via `UndoManager.getScope(name)`.
|
|
57
|
-
* Consumers call `record(entry)` after each mutator; `undo()` / `redo()` to
|
|
58
|
-
* traverse the stacks.
|
|
59
|
-
*/
|
|
60
55
|
export declare class UndoScope<S extends Schema> {
|
|
61
56
|
private readonly schema;
|
|
62
57
|
private readonly store;
|
|
@@ -121,6 +116,23 @@ export declare class UndoScope<S extends Schema> {
|
|
|
121
116
|
* response) collapses into ONE Cmd+Z. `endGroup()` flushes it.
|
|
122
117
|
*/
|
|
123
118
|
private group;
|
|
119
|
+
/**
|
|
120
|
+
* ASYNC replay-echo suppression, keyed by `${modelKey}:${id}`.
|
|
121
|
+
*
|
|
122
|
+
* The synchronous {@link replaying} flag only catches echoes delivered INLINE
|
|
123
|
+
* during `applyOps`. The real engine doesn't emit `transaction:created`
|
|
124
|
+
* synchronously: `SyncClient` defers the commit behind `scheduleSync()` +
|
|
125
|
+
* `await persistMutationQueue()` (an IndexedDB write), so a replayed write's
|
|
126
|
+
* echo lands on the stream AFTER `undo()`/`redo()` has already reset
|
|
127
|
+
* `replaying` and pushed the entry. That late echo would be recorded as a
|
|
128
|
+
* NEW edit — and `record()` clears the redo stack, so every undo silently
|
|
129
|
+
* destroyed its own redo. We mark the (modelKey,id) of every op we're about
|
|
130
|
+
* to replay here (synchronously, before the write), and consume one mark when
|
|
131
|
+
* the matching mutation arrives — independent of WHEN it arrives. Entries
|
|
132
|
+
* carry a TTL so a never-arriving echo (offline: the commit is skipped) can't
|
|
133
|
+
* leak and wrongly suppress a much-later genuine edit to the same row.
|
|
134
|
+
*/
|
|
135
|
+
private readonly pendingReplayEchoes;
|
|
124
136
|
constructor(schema: S, store: SyncStoreContract, organizationId: string, options?: UndoScopeOptions);
|
|
125
137
|
/**
|
|
126
138
|
* Open a grouping session: every stream-recorded op until {@link endGroup}
|
|
@@ -131,6 +143,20 @@ export declare class UndoScope<S extends Schema> {
|
|
|
131
143
|
beginGroup(label?: string): void;
|
|
132
144
|
/** Close the grouping session and record the accumulated ops as one entry. */
|
|
133
145
|
endGroup(label?: string): void;
|
|
146
|
+
/** Every `${modelKey}:${id}` a set of ops will touch (all op kinds). */
|
|
147
|
+
private replayEchoKeys;
|
|
148
|
+
/**
|
|
149
|
+
* Arm async-echo suppression for the rows a replay is about to write. Called
|
|
150
|
+
* synchronously, before `applyOps`, so the marks exist no matter how long the
|
|
151
|
+
* engine takes to surface the echo on the stream. See {@link pendingReplayEchoes}.
|
|
152
|
+
*/
|
|
153
|
+
private markReplayEchoes;
|
|
154
|
+
/**
|
|
155
|
+
* If `${schemaKey}:${modelId}` has an armed echo mark, consume one and report
|
|
156
|
+
* that this mutation is our own replay echo (caller drops it). Prunes expired
|
|
157
|
+
* marks opportunistically so a skipped/never-arriving echo can't leak.
|
|
158
|
+
*/
|
|
159
|
+
private consumeReplayEcho;
|
|
134
160
|
/** Resolve a stream mutation's registered name to its schema key, or null. */
|
|
135
161
|
private resolveSchemaKey;
|
|
136
162
|
/**
|
|
@@ -28,6 +28,13 @@ const normalizeModelAlias = (modelName) => modelName.replace('Model', '').toLowe
|
|
|
28
28
|
* Consumers call `record(entry)` after each mutator; `undo()` / `redo()` to
|
|
29
29
|
* traverse the stacks.
|
|
30
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* How long a marked replay-echo stays armed before it's pruned. The real echo
|
|
33
|
+
* arrives within a couple of IndexedDB round-trips (tens of ms); this is a
|
|
34
|
+
* generous safety ceiling so a never-arriving echo (e.g. the commit was skipped
|
|
35
|
+
* offline) can't suppress a genuine later edit to the same row indefinitely.
|
|
36
|
+
*/
|
|
37
|
+
const REPLAY_ECHO_TTL_MS = 5000;
|
|
31
38
|
export class UndoScope {
|
|
32
39
|
schema;
|
|
33
40
|
store;
|
|
@@ -92,6 +99,23 @@ export class UndoScope {
|
|
|
92
99
|
* response) collapses into ONE Cmd+Z. `endGroup()` flushes it.
|
|
93
100
|
*/
|
|
94
101
|
group = null;
|
|
102
|
+
/**
|
|
103
|
+
* ASYNC replay-echo suppression, keyed by `${modelKey}:${id}`.
|
|
104
|
+
*
|
|
105
|
+
* The synchronous {@link replaying} flag only catches echoes delivered INLINE
|
|
106
|
+
* during `applyOps`. The real engine doesn't emit `transaction:created`
|
|
107
|
+
* synchronously: `SyncClient` defers the commit behind `scheduleSync()` +
|
|
108
|
+
* `await persistMutationQueue()` (an IndexedDB write), so a replayed write's
|
|
109
|
+
* echo lands on the stream AFTER `undo()`/`redo()` has already reset
|
|
110
|
+
* `replaying` and pushed the entry. That late echo would be recorded as a
|
|
111
|
+
* NEW edit — and `record()` clears the redo stack, so every undo silently
|
|
112
|
+
* destroyed its own redo. We mark the (modelKey,id) of every op we're about
|
|
113
|
+
* to replay here (synchronously, before the write), and consume one mark when
|
|
114
|
+
* the matching mutation arrives — independent of WHEN it arrives. Entries
|
|
115
|
+
* carry a TTL so a never-arriving echo (offline: the commit is skipped) can't
|
|
116
|
+
* leak and wrongly suppress a much-later genuine edit to the same row.
|
|
117
|
+
*/
|
|
118
|
+
pendingReplayEchoes = new Map();
|
|
95
119
|
constructor(schema, store, organizationId, options = {}) {
|
|
96
120
|
this.schema = schema;
|
|
97
121
|
this.store = store;
|
|
@@ -150,6 +174,80 @@ export class UndoScope {
|
|
|
150
174
|
return;
|
|
151
175
|
this.record({ label: label ?? g.label, inverses, forwards });
|
|
152
176
|
}
|
|
177
|
+
/** Every `${modelKey}:${id}` a set of ops will touch (all op kinds). */
|
|
178
|
+
*replayEchoKeys(ops) {
|
|
179
|
+
for (const op of ops) {
|
|
180
|
+
switch (op.kind) {
|
|
181
|
+
case 'create': {
|
|
182
|
+
const id = op.data.id;
|
|
183
|
+
if (typeof id === 'string')
|
|
184
|
+
yield `${op.modelKey}:${id}`;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case 'update':
|
|
188
|
+
yield `${op.modelKey}:${op.patch.id}`;
|
|
189
|
+
break;
|
|
190
|
+
case 'delete':
|
|
191
|
+
yield `${op.modelKey}:${op.id}`;
|
|
192
|
+
break;
|
|
193
|
+
case 'createMany':
|
|
194
|
+
for (const d of op.data) {
|
|
195
|
+
const id = d.id;
|
|
196
|
+
if (typeof id === 'string')
|
|
197
|
+
yield `${op.modelKey}:${id}`;
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
case 'updateMany':
|
|
201
|
+
for (const p of op.patches)
|
|
202
|
+
yield `${op.modelKey}:${p.id}`;
|
|
203
|
+
break;
|
|
204
|
+
case 'deleteMany':
|
|
205
|
+
for (const id of op.ids)
|
|
206
|
+
yield `${op.modelKey}:${id}`;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Arm async-echo suppression for the rows a replay is about to write. Called
|
|
213
|
+
* synchronously, before `applyOps`, so the marks exist no matter how long the
|
|
214
|
+
* engine takes to surface the echo on the stream. See {@link pendingReplayEchoes}.
|
|
215
|
+
*/
|
|
216
|
+
markReplayEchoes(ops) {
|
|
217
|
+
const expiresAt = Date.now() + REPLAY_ECHO_TTL_MS;
|
|
218
|
+
for (const key of this.replayEchoKeys(ops)) {
|
|
219
|
+
const existing = this.pendingReplayEchoes.get(key);
|
|
220
|
+
if (existing) {
|
|
221
|
+
existing.count += 1;
|
|
222
|
+
existing.expiresAt = expiresAt;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.pendingReplayEchoes.set(key, { count: 1, expiresAt });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* If `${schemaKey}:${modelId}` has an armed echo mark, consume one and report
|
|
231
|
+
* that this mutation is our own replay echo (caller drops it). Prunes expired
|
|
232
|
+
* marks opportunistically so a skipped/never-arriving echo can't leak.
|
|
233
|
+
*/
|
|
234
|
+
consumeReplayEcho(schemaKey, modelId) {
|
|
235
|
+
if (this.pendingReplayEchoes.size === 0)
|
|
236
|
+
return false;
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
for (const [k, v] of this.pendingReplayEchoes) {
|
|
239
|
+
if (v.expiresAt <= now)
|
|
240
|
+
this.pendingReplayEchoes.delete(k);
|
|
241
|
+
}
|
|
242
|
+
const key = `${schemaKey}:${modelId}`;
|
|
243
|
+
const pending = this.pendingReplayEchoes.get(key);
|
|
244
|
+
if (!pending)
|
|
245
|
+
return false;
|
|
246
|
+
pending.count -= 1;
|
|
247
|
+
if (pending.count <= 0)
|
|
248
|
+
this.pendingReplayEchoes.delete(key);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
153
251
|
/** Resolve a stream mutation's registered name to its schema key, or null. */
|
|
154
252
|
resolveSchemaKey(modelName) {
|
|
155
253
|
return (this.schemaKeyByAlias.get(modelName) ??
|
|
@@ -169,6 +267,13 @@ export class UndoScope {
|
|
|
169
267
|
const schemaKey = this.resolveSchemaKey(m.modelName);
|
|
170
268
|
if (!schemaKey)
|
|
171
269
|
return;
|
|
270
|
+
// Drop the ASYNC echo of our own replayed writes. The engine surfaces a
|
|
271
|
+
// replay's `transaction:created` only after an IndexedDB-gated commit, i.e.
|
|
272
|
+
// after `replaying` has already reset — so the synchronous flag above misses
|
|
273
|
+
// it. The (modelKey,id) marks armed in `markReplayEchoes` catch it whenever
|
|
274
|
+
// it lands, which is what stops every undo from wiping its own redo stack.
|
|
275
|
+
if (this.consumeReplayEcho(schemaKey, m.modelId))
|
|
276
|
+
return;
|
|
172
277
|
if (this.tracksModel && !this.tracksModel(schemaKey))
|
|
173
278
|
return;
|
|
174
279
|
const ops = buildUndoOps(m, schemaKey);
|
|
@@ -331,7 +436,10 @@ export class UndoScope {
|
|
|
331
436
|
const tx = createTransaction(this.schema, this.store, this.organizationId);
|
|
332
437
|
const ops = resolveOps(entry.inverses, entry.forwards, this.store, this.conflictPolicy);
|
|
333
438
|
// Suppress our own stream listener so replayed writes don't record as
|
|
334
|
-
// new undo entries.
|
|
439
|
+
// new undo entries. `replaying` covers inline echoes; `markReplayEchoes`
|
|
440
|
+
// covers the engine's async (IDB-gated) echo that lands after this method
|
|
441
|
+
// returns. Cleared in `finally` even if a replay op throws.
|
|
442
|
+
this.markReplayEchoes(ops);
|
|
335
443
|
this.replaying = true;
|
|
336
444
|
try {
|
|
337
445
|
await applyOps(tx, ops);
|
|
@@ -366,6 +474,8 @@ export class UndoScope {
|
|
|
366
474
|
return;
|
|
367
475
|
const tx = createTransaction(this.schema, this.store, this.organizationId);
|
|
368
476
|
const ops = resolveOps(entry.forwards, entry.inverses, this.store, this.conflictPolicy);
|
|
477
|
+
// See undo(): arm async-echo suppression before the replayed writes.
|
|
478
|
+
this.markReplayEchoes(ops);
|
|
369
479
|
this.replaying = true;
|
|
370
480
|
try {
|
|
371
481
|
await applyOps(tx, ops);
|
|
@@ -391,6 +501,7 @@ export class UndoScope {
|
|
|
391
501
|
this.undoStack = [];
|
|
392
502
|
this.redoStack = [];
|
|
393
503
|
this.batch = [];
|
|
504
|
+
this.pendingReplayEchoes.clear();
|
|
394
505
|
this.emitChange();
|
|
395
506
|
}
|
|
396
507
|
/** Introspection — for debug panels / e2e tests. */
|
|
@@ -407,6 +518,7 @@ export class UndoScope {
|
|
|
407
518
|
this.recordListeners.clear();
|
|
408
519
|
this.changeListeners.clear();
|
|
409
520
|
this.batch = [];
|
|
521
|
+
this.pendingReplayEchoes.clear();
|
|
410
522
|
}
|
|
411
523
|
}
|
|
412
524
|
/**
|
package/dist/schema/ddl.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - `generateMigrationPlan` — the destructive-aware counterpart driven by the
|
|
18
18
|
* {@link diffSchema} step list (drops, renames, type casts, backfills).
|
|
19
19
|
*/
|
|
20
|
+
import { AbloValidationError } from '../errors.js';
|
|
20
21
|
import { resolveTenancy, tenancyColumn } from './tenancy.js';
|
|
21
22
|
// ── Identifier safety ────────────────────────────────────────────────────────
|
|
22
23
|
/** Postgres unquoted-identifier-safe slug: lowercase `[a-z0-9_]`, ≤50 chars. */
|
|
@@ -289,7 +290,7 @@ function sqlLiteral(value, fieldType) {
|
|
|
289
290
|
switch (fieldType) {
|
|
290
291
|
case 'number':
|
|
291
292
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
292
|
-
throw new
|
|
293
|
+
throw new AbloValidationError(`backfill for a number field must be a finite number, got ${JSON.stringify(value)}`, { code: 'schema_definition_invalid' });
|
|
293
294
|
}
|
|
294
295
|
return String(value);
|
|
295
296
|
case 'boolean':
|
|
@@ -314,6 +315,14 @@ export function generateMigrationPlan(steps, opts) {
|
|
|
314
315
|
const qs = q(targetSchema);
|
|
315
316
|
const statements = [];
|
|
316
317
|
const concurrent = [];
|
|
318
|
+
// The app schema must exist before any statement targets it. On a fresh
|
|
319
|
+
// org's FIRST push (`prev = null`) the migration plan IS the provisioning —
|
|
320
|
+
// `app_<orgId>` has never been created, and skipping this line made every
|
|
321
|
+
// first push die with `3F000 invalid_schema_name` at statement 0. Idempotent
|
|
322
|
+
// (`IF NOT EXISTS`), so emitting it on every later migration is free.
|
|
323
|
+
if (steps.length > 0 && targetSchema !== 'public') {
|
|
324
|
+
statements.push(`CREATE SCHEMA IF NOT EXISTS ${qs};`);
|
|
325
|
+
}
|
|
317
326
|
const qtFor = (table) => `${qs}.${q(table)}`;
|
|
318
327
|
const tableOfModel = (schema, key) => {
|
|
319
328
|
const m = schema?.models[key];
|
|
@@ -326,8 +335,8 @@ export function generateMigrationPlan(steps, opts) {
|
|
|
326
335
|
switch (step.kind) {
|
|
327
336
|
case 'create_model': {
|
|
328
337
|
// Reuse the provisioner for the full table (base cols + fields + enum
|
|
329
|
-
// checks + RLS), minus its `CREATE SCHEMA` (the
|
|
330
|
-
//
|
|
338
|
+
// checks + RLS), minus its `CREATE SCHEMA` (the plan header above
|
|
339
|
+
// already emitted it once — don't repeat it per model).
|
|
331
340
|
const def = next.models[step.model];
|
|
332
341
|
if (!def)
|
|
333
342
|
break;
|
package/dist/schema/field.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* });
|
|
24
24
|
*/
|
|
25
25
|
import { z } from 'zod';
|
|
26
|
+
import { AbloValidationError } from '../errors.js';
|
|
26
27
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
27
28
|
/** Distinguish a Zod schema from a plain object shape (ZodRawShape). */
|
|
28
29
|
function isZodSchema(value) {
|
|
@@ -179,7 +180,7 @@ export function resolveFieldMeta(schema) {
|
|
|
179
180
|
}
|
|
180
181
|
function assertColumnName(column) {
|
|
181
182
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/.test(column)) {
|
|
182
|
-
throw new
|
|
183
|
+
throw new AbloValidationError(`field.from(): invalid column identifier ${JSON.stringify(column)}`, { code: 'schema_definition_invalid' });
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
/** Add sync-engine chain methods to a Zod schema without disturbing its type. */
|
package/dist/schema/model.d.ts
CHANGED
|
@@ -188,14 +188,16 @@ export interface ModelOptions {
|
|
|
188
188
|
entityRoles?: EntityRole | readonly EntityRole[];
|
|
189
189
|
/**
|
|
190
190
|
* Whether clients may issue CREATE/UPDATE/DELETE mutations for this
|
|
191
|
-
* model via the `commit` wire protocol. Default:
|
|
191
|
+
* model via the `commit` wire protocol. Default: **true** — declaring a
|
|
192
|
+
* model in the schema IS the opt-in; if you put an entity in your synced
|
|
193
|
+
* schema, you almost always want to write it (product decision
|
|
194
|
+
* 2026-06-10, reversing the earlier default-deny that made every fresh
|
|
195
|
+
* quickstart's first write die with `server_execute_unknown_model`).
|
|
192
196
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* incident) OR where internal tables (`sync_deltas`, `presences`,
|
|
198
|
-
* digest/ingestion tables) become writable by accident.
|
|
197
|
+
* Opt OUT for server-managed projections (stats, digests, audit views):
|
|
198
|
+
* `mutable: false`, or the `readOnly.*` sugar which sets it for you.
|
|
199
|
+
* That keeps the 2026-04-20 `AgentJob`-class protection available where
|
|
200
|
+
* it matters, as a deliberate marking instead of a silent default.
|
|
199
201
|
*
|
|
200
202
|
* The server's `buildModelMap` (src/server/commit.ts) derives
|
|
201
203
|
* the mutation allowlist from this flag — no parallel hardcoded list.
|
package/dist/schema/model.js
CHANGED
|
@@ -99,7 +99,7 @@ export function model(shape, relations, options) {
|
|
|
99
99
|
scope: options?.scope,
|
|
100
100
|
grants: options?.grants,
|
|
101
101
|
entityRoles: normalizeEntityRoles(options?.entityRoles),
|
|
102
|
-
mutable: options?.mutable,
|
|
102
|
+
mutable: options?.mutable ?? true,
|
|
103
103
|
lazyObservable: options?.lazyObservable,
|
|
104
104
|
computed: options?.computed,
|
|
105
105
|
autoFill: options?.autoFill,
|
package/dist/schema/schema.js
CHANGED
|
@@ -167,7 +167,13 @@ export function defineSchema(models, options) {
|
|
|
167
167
|
const persist = def.persist
|
|
168
168
|
? { ...def.persist, store: def.persist.store ?? typename }
|
|
169
169
|
: undefined;
|
|
170
|
-
|
|
170
|
+
// Physical table defaults to the schema key — the SAME rule the
|
|
171
|
+
// provisioner/planner use (`tableName ?? key`), resolved here once so the
|
|
172
|
+
// serialized artifact always carries it. Required now that models are
|
|
173
|
+
// mutable by default: the server's `buildModelMap` rejects a mutable
|
|
174
|
+
// model with no `tableName`, which would otherwise break every commit.
|
|
175
|
+
const tableName = def.tableName ?? name;
|
|
176
|
+
resolvedModels[name] = { ...def, typename, tableName, persist };
|
|
171
177
|
}
|
|
172
178
|
validateSyncGroupSchema(resolvedModels);
|
|
173
179
|
return {
|
package/dist/schema/serialize.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* `FieldMeta` (the server does no field-shape validation anyway)
|
|
26
26
|
*/
|
|
27
27
|
import { z } from 'zod';
|
|
28
|
+
import { AbloValidationError } from '../errors.js';
|
|
28
29
|
import { baseFieldsSchema, } from './schema.js';
|
|
29
30
|
/** Current schema-JSON envelope version. Bump on a breaking change to the
|
|
30
31
|
* JSON shape itself (not the user's schema). v2 replaced the per-model
|
|
@@ -201,7 +202,7 @@ export function fromSchemaJSON(json) {
|
|
|
201
202
|
export function parseSchema(json) {
|
|
202
203
|
const parsed = JSON.parse(json);
|
|
203
204
|
if (parsed.v !== SCHEMA_JSON_VERSION) {
|
|
204
|
-
throw new
|
|
205
|
+
throw new AbloValidationError(`parseSchema: unsupported schema-JSON version ${parsed.v} (expected ${SCHEMA_JSON_VERSION})`, { code: 'schema_definition_invalid' });
|
|
205
206
|
}
|
|
206
207
|
return fromSchemaJSON(parsed);
|
|
207
208
|
}
|
|
@@ -7,11 +7,18 @@
|
|
|
7
7
|
* - `hosted` — Ablo's control-plane database.
|
|
8
8
|
* - `selfHosted` — the customer's database, same execution path as hosted.
|
|
9
9
|
* - `source` — a customer-owned endpoint (credentialless ingestion).
|
|
10
|
+
*
|
|
11
|
+
* @internal Deployment topology, not product vocabulary. Customers never see a
|
|
12
|
+
* "storage mode" — their story is `Ablo({ schema, apiKey, databaseUrl })` and
|
|
13
|
+
* one `datasource` resource (docs/plans/sync-engine-stripe-story-scope.md).
|
|
14
|
+
* This export exists for the sync-server host only.
|
|
10
15
|
*/
|
|
11
16
|
import { z } from 'zod';
|
|
17
|
+
/** @internal See module note — host-deployment vocabulary, never customer-facing. */
|
|
12
18
|
export declare const storageModeSchema: z.ZodEnum<{
|
|
13
19
|
source: "source";
|
|
14
20
|
hosted: "hosted";
|
|
15
21
|
selfHosted: "selfHosted";
|
|
16
22
|
}>;
|
|
23
|
+
/** @internal See module note — host-deployment vocabulary, never customer-facing. */
|
|
17
24
|
export type StorageMode = z.infer<typeof storageModeSchema>;
|