@abloatai/ablo 0.29.2 → 0.30.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 +27 -0
- package/README.md +3 -2
- package/dist/BaseSyncedStore.js +3 -1
- package/dist/Database.js +29 -2
- package/dist/SyncClient.d.ts +5 -3
- package/dist/SyncClient.js +8 -4
- package/dist/ai-sdk/coordinatedTool.d.ts +0 -1
- package/dist/ai-sdk/coordinatedTool.js +0 -1
- package/dist/ai-sdk/coordinationContext.js +1 -5
- package/dist/cli.cjs +561 -90
- package/dist/client/Ablo.d.ts +1 -1
- package/dist/client/Ablo.js +24 -36
- package/dist/client/auth.d.ts +8 -18
- package/dist/client/auth.js +16 -96
- package/dist/client/createModelProxy.d.ts +16 -11
- package/dist/client/createModelProxy.js +162 -20
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/httpTransport.js +263 -89
- package/dist/client/options.d.ts +0 -15
- package/dist/client/resourceTypes.d.ts +13 -26
- package/dist/client/schemaConfig.js +5 -0
- package/dist/client/writeOptionsSchema.d.ts +1 -0
- package/dist/client/writeOptionsSchema.js +11 -2
- package/dist/client/wsMutationExecutor.d.ts +3 -5
- package/dist/coordination/schema.d.ts +17 -13
- package/dist/coordination/schema.js +36 -10
- package/dist/errorCodes.d.ts +9 -4
- package/dist/errorCodes.js +10 -5
- package/dist/errors.js +4 -9
- package/dist/index.d.ts +4 -0
- package/dist/interfaces/index.d.ts +31 -19
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/schema.d.ts +13 -0
- package/dist/schema/select.js +7 -0
- package/dist/schema/syncDeltaRow.d.ts +31 -13
- package/dist/schema/syncDeltaRow.js +33 -15
- package/dist/schema/tenancy.d.ts +52 -16
- package/dist/schema/tenancy.js +86 -25
- package/dist/server/commit.d.ts +8 -24
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/readConfig.d.ts +10 -0
- package/dist/source/adapter.d.ts +26 -15
- package/dist/source/adapter.js +5 -3
- package/dist/source/adapters/drizzle.js +35 -9
- package/dist/source/adapters/kysely.d.ts +33 -61
- package/dist/source/adapters/kysely.js +170 -177
- package/dist/source/adapters/kyselyMutationCore.d.ts +76 -0
- package/dist/source/adapters/kyselyMutationCore.js +125 -0
- package/dist/source/adapters/memory.d.ts +1 -1
- package/dist/source/adapters/memory.js +24 -10
- package/dist/source/adapters/prisma.js +35 -8
- package/dist/source/conformance.d.ts +26 -28
- package/dist/source/conformance.js +142 -69
- package/dist/source/contract.d.ts +46 -4
- package/dist/source/contract.js +66 -5
- package/dist/source/factory.d.ts +3 -3
- package/dist/source/factory.js +24 -3
- package/dist/source/idempotency.d.ts +46 -0
- package/dist/source/idempotency.js +129 -0
- package/dist/source/index.d.ts +6 -4
- package/dist/source/index.js +5 -3
- package/dist/source/migrations.d.ts +9 -5
- package/dist/source/migrations.js +39 -6
- package/dist/source/types.d.ts +88 -14
- package/dist/source/types.js +16 -0
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +0 -1
- package/dist/sync/BootstrapFetcher.js +7 -0
- package/dist/sync/SyncWebSocket.d.ts +5 -27
- package/dist/sync/awaitClaimGrant.d.ts +6 -0
- package/dist/sync/awaitClaimGrant.js +12 -2
- package/dist/sync/commitFrames.d.ts +2 -4
- package/dist/sync/commitFrames.js +1 -0
- package/dist/sync/createClaimStream.d.ts +1 -1
- package/dist/sync/createClaimStream.js +13 -15
- package/dist/sync/deltaPipeline.d.ts +1 -1
- package/dist/sync/deltaPipeline.js +1 -1
- package/dist/sync/participants.d.ts +2 -3
- package/dist/sync/participants.js +2 -2
- package/dist/sync/wsFrameHandlers.js +35 -15
- package/dist/testing/mocks/MockMutationExecutor.d.ts +21 -0
- package/dist/testing/mocks/MockMutationExecutor.js +53 -1
- package/dist/transactions/TransactionQueue.d.ts +61 -9
- package/dist/transactions/TransactionQueue.js +502 -114
- package/dist/transactions/commitEnvelope.d.ts +4 -0
- package/dist/transactions/commitEnvelope.js +25 -0
- package/dist/transactions/commitPayload.d.ts +23 -5
- package/dist/transactions/commitPayload.js +3 -0
- package/dist/transactions/deltaConfirmation.d.ts +2 -1
- package/dist/transactions/deltaConfirmation.js +22 -4
- package/dist/transactions/durableWriteStore.d.ts +8 -1
- package/dist/transactions/httpCommitEnvelope.d.ts +3 -1
- package/dist/transactions/httpCommitEnvelope.js +30 -0
- package/dist/transactions/replayValidation.d.ts +4 -0
- package/dist/transactions/replayValidation.js +6 -0
- package/dist/types/streams.d.ts +29 -17
- package/dist/wire/commit.d.ts +671 -0
- package/dist/wire/commit.js +300 -0
- package/dist/wire/delta.d.ts +4 -0
- package/dist/wire/delta.js +9 -0
- package/dist/wire/frames.d.ts +17 -45
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/index.js +4 -0
- package/docs/agent-messaging.md +4 -5
- package/docs/agents.md +1 -2
- package/docs/api-keys.md +1 -2
- package/docs/client-behavior.md +4 -6
- package/docs/coordination.md +64 -9
- package/docs/data-sources.md +4 -7
- package/docs/integration-guide.md +6 -8
- package/docs/migration.md +0 -3
- package/docs/quickstart.md +7 -8
- package/docs/schema-contract.md +7 -8
- package/package.json +4 -4
- package/dist/client/registerDataSource.d.ts +0 -19
- package/dist/client/registerDataSource.js +0 -66
package/dist/client/Ablo.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { type AbloHttpClient, type AbloHttpClientOptions } from './httpClient.js
|
|
|
30
30
|
import type { AbloOptions } from './options.js';
|
|
31
31
|
import type { AbloSession, CommitResource, CreateAgentClientParams, CreateSessionParams } from './resourceTypes.js';
|
|
32
32
|
export type { ApiKeySetter, AbloOptions, InternalAbloOptions } from './options.js';
|
|
33
|
-
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, ModelOperations, ModelOperationAction, CommitWait, IfClaimedPolicy, ClaimedOptions, ClaimWaitOptions, ModelReadOptions, ClaimCreateOptions, CommitOperationInput, CommitCreateOptions, CommitReceipt, CommitResource, ClaimResource, ModelMutationOptions, HttpClaimApi, SessionOperation, CreateUserSessionParams, CreateAgentSessionParams, CreateSessionParams, CreateAgentClientParams, AbloSession, } from './resourceTypes.js';
|
|
33
|
+
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, ModelOperations, ModelOperationAction, CommitWait, IfClaimedPolicy, ClaimedOptions, ClaimWaitOptions, ModelReadOptions, ClaimCreateOptions, CommitOperationInput, CommitCreateOptions, CommitReceipt, CommitResource, ClaimResource, ModelMutationOptions, HttpClaimApi, SessionOperation, CreateUserSessionParams, CreateAgentSessionParams, CreateSessionParams, CreateAgentClientParams, AbloSession, } from './resourceTypes.js';
|
|
34
34
|
export { computeFKDepthPriority } from './schemaConfig.js';
|
|
35
35
|
import type { ModelOperations } from './createModelProxy.js';
|
|
36
36
|
/**
|
package/dist/client/Ablo.js
CHANGED
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { durableCommitOperationSchema, } from '../transactions/commitEnvelope.js';
|
|
22
22
|
import { AbloAuthenticationError, AbloConnectionError, AbloValidationError, toAbloError, claimedError } from '../errors.js';
|
|
23
|
-
import { descriptionFromMeta } from '../coordination/schema.js';
|
|
24
23
|
import { initSyncEngine } from '../context.js';
|
|
25
24
|
import { noopObservability, browserOnlineStatus, defaultSessionErrorDetector, noopAnalytics, } from '../SyncEngineContext.js';
|
|
26
25
|
import { alwaysOnline } from '../adapters/alwaysOnline.js';
|
|
@@ -40,8 +39,7 @@ import { createParticipantManager } from '../sync/participants.js';
|
|
|
40
39
|
// Value import is cycle-safe: httpClient.js and httpTransport.js take the client
|
|
41
40
|
// types from the `options`/`resourceTypes` leaves, never from this module.
|
|
42
41
|
import { createAbloHttpClient, } from './httpClient.js';
|
|
43
|
-
import { assertBrowserSafety, readProcessEnv, resolveApiKey, resolveApiKeyValue, resolveAuthToken, resolveBaseURL, resolveBootstrapBaseUrl,
|
|
44
|
-
import { registerDataSource } from './registerDataSource.js';
|
|
42
|
+
import { assertBrowserSafety, readProcessEnv, resolveApiKey, resolveApiKeyValue, resolveAuthToken, resolveBaseURL, resolveBootstrapBaseUrl, rejectRemovedDatabaseUrlOption, warnIfCliKeyMismatch, } from './auth.js';
|
|
45
43
|
import { shouldUseInMemoryPersistence } from './persistence.js';
|
|
46
44
|
import { deriveConfigFromSchema } from './schemaConfig.js';
|
|
47
45
|
import { registerModelsFromSchema } from './modelRegistration.js';
|
|
@@ -89,20 +87,15 @@ export function Ablo(options) {
|
|
|
89
87
|
const authCredentials = createAuthCredentialSource(
|
|
90
88
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- load-bearing on the self-hosted path; server-internal cap-mint (Phase 3) not shipped
|
|
91
89
|
internalOptions.capabilityToken ?? configuredAuthToken);
|
|
92
|
-
|
|
90
|
+
rejectRemovedDatabaseUrlOption(options);
|
|
93
91
|
assertBrowserSafety({
|
|
94
92
|
apiKey: configuredApiKey,
|
|
95
|
-
databaseUrl: configuredDatabaseUrl,
|
|
96
93
|
dangerouslyAllowBrowser: options.dangerouslyAllowBrowser,
|
|
97
94
|
});
|
|
98
95
|
// Custom logger wins; otherwise build the default `[Ablo]` logger at the level
|
|
99
96
|
// resolved from `debug`/`logLevel`/`ABLO_LOG_LEVEL` (default `warn`).
|
|
100
97
|
const logger = internalOptions.logger ??
|
|
101
98
|
createConsoleLogger(resolveLogLevel({ debug: options.debug, logLevel: options.logLevel }));
|
|
102
|
-
// Nudge (once) if a stray DATABASE_URL is in the env but `databaseUrl` wasn't
|
|
103
|
-
// passed — the env value is no longer auto-adopted (see resolveDatabaseUrl).
|
|
104
|
-
warnIfDatabaseUrlEnvIgnored(authInput, (m) => { logger.warn(m); });
|
|
105
|
-
warnIfDatabaseUrlDeprecated(authInput, (m) => { logger.warn(m); });
|
|
106
99
|
void warnIfCliKeyMismatch(authInput, (m) => { logger.warn(m); });
|
|
107
100
|
const schema = options.schema;
|
|
108
101
|
const url = resolveBaseURL(authInput);
|
|
@@ -317,19 +310,6 @@ export function Ablo(options) {
|
|
|
317
310
|
}
|
|
318
311
|
authCredentials.setAuthToken(token);
|
|
319
312
|
}
|
|
320
|
-
// Register the caller's own database for write-back before bootstrap, so
|
|
321
|
-
// the server resolves this org's data plane to the customer's DB rather
|
|
322
|
-
// than serving an empty/wrong store. The org is derived server-side from
|
|
323
|
-
// the API key. Idempotent server-side (register-or-update). Skipped when
|
|
324
|
-
// no `databaseUrl` was configured (Ablo-managed storage).
|
|
325
|
-
if (configuredDatabaseUrl) {
|
|
326
|
-
await registerDataSource({
|
|
327
|
-
baseUrl: resolveBootstrapBaseUrl({ url }),
|
|
328
|
-
apiKey: await resolveApiKeyValue(configuredApiKey),
|
|
329
|
-
databaseUrl: configuredDatabaseUrl,
|
|
330
|
-
...(internalOptions.fetch ? { fetchImpl: internalOptions.fetch } : {}),
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
313
|
// Resolve participant identity + scope. Three branches —
|
|
334
314
|
// hosted-cloud apiKey exchange, self-derived from capability
|
|
335
315
|
// token, or legacy explicit options. See `./identity.ts`.
|
|
@@ -490,7 +470,7 @@ export function Ablo(options) {
|
|
|
490
470
|
? crypto.randomUUID()
|
|
491
471
|
: `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
492
472
|
}
|
|
493
|
-
function normalizeCommitOperation(op, defaults) {
|
|
473
|
+
function normalizeCommitOperation(op, defaults, fenceToken) {
|
|
494
474
|
const type = op.action.toUpperCase();
|
|
495
475
|
const id = op.id ?? '';
|
|
496
476
|
return durableCommitOperationSchema.parse({
|
|
@@ -501,22 +481,23 @@ export function Ablo(options) {
|
|
|
501
481
|
transactionId: op.transactionId ?? undefined,
|
|
502
482
|
readAt: op.readAt ?? defaults.readAt ?? undefined,
|
|
503
483
|
onStale: op.onStale ?? defaults.onStale ?? undefined,
|
|
484
|
+
// The batch's claim (if any) supplies one token for every op, mirroring
|
|
485
|
+
// how it supplies the batch `readAt`.
|
|
486
|
+
fenceToken: op.fenceToken ?? fenceToken ?? undefined,
|
|
504
487
|
});
|
|
505
488
|
}
|
|
506
|
-
function normalizeCommitOperations(commitOptions) {
|
|
489
|
+
function normalizeCommitOperations(commitOptions, fenceToken) {
|
|
507
490
|
if (commitOptions.operations.length === 0) {
|
|
508
491
|
throw new AbloValidationError('Commit requires a non-empty `operations` array.', { code: 'commit_operation_required' });
|
|
509
492
|
}
|
|
510
|
-
return commitOptions.operations.map((op) => normalizeCommitOperation(op, commitOptions));
|
|
493
|
+
return commitOptions.operations.map((op) => normalizeCommitOperation(op, commitOptions, fenceToken));
|
|
511
494
|
}
|
|
512
495
|
function modelClaimFromActive(claim) {
|
|
513
|
-
const description = descriptionFromMeta(claim.target.meta);
|
|
514
496
|
return {
|
|
515
497
|
id: claim.id,
|
|
516
498
|
actor: claim.heldBy ?? "",
|
|
517
499
|
participantKind: claim.participantKind ?? "user",
|
|
518
|
-
|
|
519
|
-
...(description ? { description } : {}),
|
|
500
|
+
description: claim.description,
|
|
520
501
|
field: claim.target.field,
|
|
521
502
|
status: 'active',
|
|
522
503
|
expiresAt: claim.expiresAt ?? 0,
|
|
@@ -595,16 +576,21 @@ export function Ablo(options) {
|
|
|
595
576
|
}
|
|
596
577
|
});
|
|
597
578
|
}
|
|
598
|
-
function wrapClaimHandle(claim, waited = false) {
|
|
579
|
+
function wrapClaimHandle(claim, waited = false, fenceToken) {
|
|
599
580
|
const release = async () => {
|
|
600
581
|
claim.revoke?.();
|
|
601
582
|
};
|
|
583
|
+
// The token is server-stamped and arrives on the grant frame, so prefer
|
|
584
|
+
// the one `awaitClaimGrant` read there; fall back to any the local handle
|
|
585
|
+
// already carried (immediate, non-queued grants).
|
|
586
|
+
const resolvedFenceToken = fenceToken ?? claim.fenceToken;
|
|
602
587
|
return {
|
|
603
588
|
object: 'claim',
|
|
604
589
|
id: claim.id,
|
|
605
|
-
|
|
590
|
+
description: claim.description,
|
|
606
591
|
target: claim.target,
|
|
607
592
|
waited,
|
|
593
|
+
...(resolvedFenceToken !== undefined ? { fenceToken: resolvedFenceToken } : {}),
|
|
608
594
|
release,
|
|
609
595
|
revoke: claim.revoke,
|
|
610
596
|
// The lease-control members are forwarded explicitly — this wrapper
|
|
@@ -625,7 +611,7 @@ export function Ablo(options) {
|
|
|
625
611
|
field: claimOptions.target.field,
|
|
626
612
|
meta: claimOptions.target.meta,
|
|
627
613
|
}, {
|
|
628
|
-
|
|
614
|
+
description: claimOptions.description,
|
|
629
615
|
ttl: claimOptions.ttl,
|
|
630
616
|
queue: claimOptions.queue,
|
|
631
617
|
});
|
|
@@ -635,11 +621,12 @@ export function Ablo(options) {
|
|
|
635
621
|
// callers — chiefly `ablo.<model>.claim` — get a handle that already
|
|
636
622
|
// holds the lease, never a half-claimed one racing the queue.
|
|
637
623
|
let waited = false;
|
|
624
|
+
let fenceToken;
|
|
638
625
|
if (claimOptions.queue) {
|
|
639
626
|
const ws = store.getSyncWebSocket();
|
|
640
627
|
if (ws) {
|
|
641
628
|
try {
|
|
642
|
-
({ waited } = await awaitClaimGrant(ws, claim.id, {
|
|
629
|
+
({ waited, fenceToken } = await awaitClaimGrant(ws, claim.id, {
|
|
643
630
|
timeoutMs: claimOptions.waitTimeoutMs,
|
|
644
631
|
maxQueueDepth: claimOptions.maxQueueDepth,
|
|
645
632
|
}));
|
|
@@ -653,7 +640,7 @@ export function Ablo(options) {
|
|
|
653
640
|
}
|
|
654
641
|
}
|
|
655
642
|
}
|
|
656
|
-
return wrapClaimHandle(claim, waited);
|
|
643
|
+
return wrapClaimHandle(claim, waited, fenceToken);
|
|
657
644
|
},
|
|
658
645
|
list(target) {
|
|
659
646
|
return listModelClaims(target);
|
|
@@ -708,7 +695,7 @@ export function Ablo(options) {
|
|
|
708
695
|
...(held.target.field ? { field: held.target.field } : {}),
|
|
709
696
|
...(held.target.meta ? { meta: held.target.meta } : {}),
|
|
710
697
|
},
|
|
711
|
-
|
|
698
|
+
description: held.description ?? 'editing',
|
|
712
699
|
heldBy: held.actor,
|
|
713
700
|
participantKind: held.participantKind,
|
|
714
701
|
expiresAt: held.expiresAt,
|
|
@@ -757,7 +744,7 @@ export function Ablo(options) {
|
|
|
757
744
|
...commitOptions,
|
|
758
745
|
readAt: commitOptions.readAt ?? claim?.readAt ?? null,
|
|
759
746
|
onStale: commitOptions.onStale ?? (claim?.readAt !== undefined ? 'reject' : null),
|
|
760
|
-
});
|
|
747
|
+
}, claim?.fenceToken ?? null);
|
|
761
748
|
const wait = commitOptions.wait ?? 'confirmed';
|
|
762
749
|
// Route through the TransactionQueue's commit lane so the call
|
|
763
750
|
// tolerates WS disconnects: the envelope stays in memory until
|
|
@@ -775,12 +762,13 @@ export function Ablo(options) {
|
|
|
775
762
|
if (wait === 'queued') {
|
|
776
763
|
return { id: clientTxId, status: 'queued' };
|
|
777
764
|
}
|
|
778
|
-
const { lastSyncId, notifications } = await queue.waitForCommitReceipt(clientTxId);
|
|
765
|
+
const { lastSyncId, notifications, missingIds } = await queue.waitForCommitReceipt(clientTxId);
|
|
779
766
|
return {
|
|
780
767
|
id: clientTxId,
|
|
781
768
|
status: 'confirmed',
|
|
782
769
|
lastSyncId,
|
|
783
770
|
...(notifications && notifications.length > 0 ? { notifications } : {}),
|
|
771
|
+
...(missingIds && missingIds.length > 0 ? { missingIds } : {}),
|
|
784
772
|
};
|
|
785
773
|
},
|
|
786
774
|
};
|
package/dist/client/auth.d.ts
CHANGED
|
@@ -30,7 +30,6 @@ export interface AuthResolveInput {
|
|
|
30
30
|
readonly authEndpoint?: string | ApiKeySetter | null;
|
|
31
31
|
readonly authToken?: string | null;
|
|
32
32
|
readonly baseURL?: string | null;
|
|
33
|
-
readonly databaseUrl?: string | null;
|
|
34
33
|
readonly dangerouslyAllowBrowser?: boolean;
|
|
35
34
|
};
|
|
36
35
|
readonly env: Record<string, string | undefined>;
|
|
@@ -66,22 +65,6 @@ export interface CliKeyMismatch {
|
|
|
66
65
|
/** Infer the sandbox or production mode from an Ablo key's prefix. */
|
|
67
66
|
export declare function modeFromApiKey(key: string): CliMode | undefined;
|
|
68
67
|
export declare function describeCliKeyMismatch(configured: StaticApiKey, cli: CliCredentialSnapshot): CliKeyMismatch | null;
|
|
69
|
-
/**
|
|
70
|
-
* Resolves the Postgres connection string for the direct-connection option, or
|
|
71
|
-
* `null` when none was given.
|
|
72
|
-
*
|
|
73
|
-
* `databaseUrl` is opt-in: the client registers a dedicated database only when
|
|
74
|
-
* the caller passes it explicitly. It is never read from
|
|
75
|
-
* `process.env.DATABASE_URL`, because this module treats `ABLO_API_KEY` as the
|
|
76
|
-
* one environment fallback — an app's `DATABASE_URL`, commonly set for other
|
|
77
|
-
* tools, must not silently switch the client into connection-string mode. The
|
|
78
|
-
* default path leaves `DATABASE_URL` untouched and reads through `dataSource(...)`
|
|
79
|
-
* instead, so this returns `null`. {@link warnIfDatabaseUrlEnvIgnored} nudges a
|
|
80
|
-
* caller who set the environment variable but omitted the option.
|
|
81
|
-
*/
|
|
82
|
-
export declare function resolveDatabaseUrl(input: AuthResolveInput): string | null;
|
|
83
|
-
export declare function warnIfDatabaseUrlEnvIgnored(input: AuthResolveInput, warn?: (message: string) => void): void;
|
|
84
|
-
export declare function warnIfDatabaseUrlDeprecated(input: AuthResolveInput, warn?: (message: string) => void): void;
|
|
85
68
|
export declare function warnIfCliKeyMismatch(input: AuthResolveInput, warn?: (message: string) => void): Promise<void>;
|
|
86
69
|
export { ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, ABLO_DEFAULT_BASE_URL } from './hostedEndpoints.js';
|
|
87
70
|
/**
|
|
@@ -99,9 +82,16 @@ export declare function resolveBaseURL(input: AuthResolveInput): string;
|
|
|
99
82
|
* server proxy. Throws {@link AbloAuthenticationError} when a secret key is
|
|
100
83
|
* detected in a browser without opt-in.
|
|
101
84
|
*/
|
|
85
|
+
/**
|
|
86
|
+
* Rejects the REMOVED `databaseUrl` dial-in option loudly. The option is gone
|
|
87
|
+
* from the types, but an untyped or stale caller could still pass it — and
|
|
88
|
+
* silently ignoring it would reroute their writes to Ablo-hosted storage,
|
|
89
|
+
* the opposite of what that option used to promise. A construction-time throw
|
|
90
|
+
* turns a wrong-storage surprise into a clear migration instruction.
|
|
91
|
+
*/
|
|
92
|
+
export declare function rejectRemovedDatabaseUrlOption(options: object): void;
|
|
102
93
|
export declare function assertBrowserSafety(input: {
|
|
103
94
|
apiKey: string | ApiKeySetter | null;
|
|
104
|
-
databaseUrl?: string | null;
|
|
105
95
|
dangerouslyAllowBrowser: boolean | undefined;
|
|
106
96
|
}): void;
|
|
107
97
|
/**
|
package/dist/client/auth.js
CHANGED
|
@@ -223,94 +223,6 @@ export function describeCliKeyMismatch(configured, cli) {
|
|
|
223
223
|
}
|
|
224
224
|
return null;
|
|
225
225
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Resolves the Postgres connection string for the direct-connection option, or
|
|
228
|
-
* `null` when none was given.
|
|
229
|
-
*
|
|
230
|
-
* `databaseUrl` is opt-in: the client registers a dedicated database only when
|
|
231
|
-
* the caller passes it explicitly. It is never read from
|
|
232
|
-
* `process.env.DATABASE_URL`, because this module treats `ABLO_API_KEY` as the
|
|
233
|
-
* one environment fallback — an app's `DATABASE_URL`, commonly set for other
|
|
234
|
-
* tools, must not silently switch the client into connection-string mode. The
|
|
235
|
-
* default path leaves `DATABASE_URL` untouched and reads through `dataSource(...)`
|
|
236
|
-
* instead, so this returns `null`. {@link warnIfDatabaseUrlEnvIgnored} nudges a
|
|
237
|
-
* caller who set the environment variable but omitted the option.
|
|
238
|
-
*/
|
|
239
|
-
export function resolveDatabaseUrl(input) {
|
|
240
|
-
return input.options.databaseUrl ?? null;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Warns once when `DATABASE_URL` is set in the environment but `databaseUrl` was
|
|
244
|
-
* not passed as an option.
|
|
245
|
-
*
|
|
246
|
-
* The client does not adopt `process.env.DATABASE_URL` on its own, because that
|
|
247
|
-
* value is commonly set for other tools and switching the client into
|
|
248
|
-
* connection-string mode behind the caller's back is surprising — and on
|
|
249
|
-
* localhost it would try to register a database the hosted service cannot reach.
|
|
250
|
-
* This warning points the developer at the explicit option instead. It fires at
|
|
251
|
-
* most once per process and falls back to `console.warn` when no logger is
|
|
252
|
-
* supplied.
|
|
253
|
-
*
|
|
254
|
-
* The warning is skipped entirely when an `apiKey` resolves (from the option or
|
|
255
|
-
* `ABLO_API_KEY`): that caller has chosen the hosted, token-based transport,
|
|
256
|
-
* which is separate from the direct `databaseUrl` connection. A `DATABASE_URL`
|
|
257
|
-
* present in that environment belongs to unrelated infrastructure, not an omitted
|
|
258
|
-
* option, so warning would be a false positive.
|
|
259
|
-
*/
|
|
260
|
-
let warnedDatabaseUrlEnvIgnored = false;
|
|
261
|
-
export function warnIfDatabaseUrlEnvIgnored(input, warn) {
|
|
262
|
-
if (warnedDatabaseUrlEnvIgnored)
|
|
263
|
-
return;
|
|
264
|
-
if (input.options.databaseUrl != null)
|
|
265
|
-
return;
|
|
266
|
-
// Hosted/token path → DATABASE_URL is unrelated infra, not an omitted option.
|
|
267
|
-
if (resolveApiKey(input) != null)
|
|
268
|
-
return;
|
|
269
|
-
const envUrl = input.env.DATABASE_URL;
|
|
270
|
-
if (typeof envUrl !== 'string' || envUrl.length === 0)
|
|
271
|
-
return;
|
|
272
|
-
warnedDatabaseUrlEnvIgnored = true;
|
|
273
|
-
const message = 'Found DATABASE_URL in the environment but `databaseUrl` was not passed to Ablo(...). ' +
|
|
274
|
-
'Ablo no longer auto-adopts DATABASE_URL — the environment value is ignored. ' +
|
|
275
|
-
'To register your Postgres directly, pass `databaseUrl: process.env.DATABASE_URL` explicitly; ' +
|
|
276
|
-
'otherwise ignore this (the hosted sandbox and signed Data Source endpoints need no databaseUrl).';
|
|
277
|
-
if (warn)
|
|
278
|
-
warn(message);
|
|
279
|
-
else if (typeof console !== 'undefined')
|
|
280
|
-
console.warn('[Ablo]', message);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Warns once when the deprecated `databaseUrl` option is used.
|
|
284
|
-
*
|
|
285
|
-
* Passing `databaseUrl` opens a connection pool directly into your Postgres and
|
|
286
|
-
* writes to it. That option is deprecated. Ablo is designed to host only the
|
|
287
|
-
* ordered transaction log (the `sync_deltas` table) and coordination state,
|
|
288
|
-
* never your rows — your data stays in your own database. The supported path is
|
|
289
|
-
* a signed data-source endpoint (`dataSource(...)`), where your app owns the
|
|
290
|
-
* write and your database credentials never leave it.
|
|
291
|
-
*
|
|
292
|
-
* The option still works at runtime so existing integrations keep running. This
|
|
293
|
-
* warning fires at most once per process and falls back to `console.warn` when
|
|
294
|
-
* no logger is supplied.
|
|
295
|
-
*/
|
|
296
|
-
let warnedDatabaseUrlDeprecated = false;
|
|
297
|
-
export function warnIfDatabaseUrlDeprecated(input, warn) {
|
|
298
|
-
if (warnedDatabaseUrlDeprecated)
|
|
299
|
-
return;
|
|
300
|
-
if (input.options.databaseUrl == null)
|
|
301
|
-
return;
|
|
302
|
-
warnedDatabaseUrlDeprecated = true;
|
|
303
|
-
const message = '`databaseUrl` (the direct connector) is deprecated and will be removed from ' +
|
|
304
|
-
'the supported path. It lets Ablo dial into your database; we are moving off ' +
|
|
305
|
-
'that. Ablo hosts only the transaction log — your data stays in your DB. Expose ' +
|
|
306
|
-
'a signed Data Source endpoint (`dataSource(...)`) so your app owns the write, ' +
|
|
307
|
-
'or self-host the engine to keep the log in your infra too. ' +
|
|
308
|
-
'See docs/plans/stripe-shaped-storage-posture.md.';
|
|
309
|
-
if (warn)
|
|
310
|
-
warn(message);
|
|
311
|
-
else if (typeof console !== 'undefined')
|
|
312
|
-
console.warn('[Ablo]', message);
|
|
313
|
-
}
|
|
314
226
|
let warnedCliKeyMismatch = false;
|
|
315
227
|
export async function warnIfCliKeyMismatch(input, warn) {
|
|
316
228
|
if (warnedCliKeyMismatch)
|
|
@@ -392,6 +304,22 @@ export function resolveBaseURL(input) {
|
|
|
392
304
|
* server proxy. Throws {@link AbloAuthenticationError} when a secret key is
|
|
393
305
|
* detected in a browser without opt-in.
|
|
394
306
|
*/
|
|
307
|
+
/**
|
|
308
|
+
* Rejects the REMOVED `databaseUrl` dial-in option loudly. The option is gone
|
|
309
|
+
* from the types, but an untyped or stale caller could still pass it — and
|
|
310
|
+
* silently ignoring it would reroute their writes to Ablo-hosted storage,
|
|
311
|
+
* the opposite of what that option used to promise. A construction-time throw
|
|
312
|
+
* turns a wrong-storage surprise into a clear migration instruction.
|
|
313
|
+
*/
|
|
314
|
+
export function rejectRemovedDatabaseUrlOption(options) {
|
|
315
|
+
const legacyValue = Reflect.get(options, 'databaseUrl');
|
|
316
|
+
if (legacyValue == null)
|
|
317
|
+
return;
|
|
318
|
+
throw new AbloValidationError('The `databaseUrl` option was removed. Your database connects through ' +
|
|
319
|
+
'`ablo connect` now: Ablo writes it directly with a scoped DML role and ' +
|
|
320
|
+
'confirms through its replication stream. Run `npx ablo connect` to ' +
|
|
321
|
+
'register it, then construct the client with your API key only.', { code: 'invalid_body' });
|
|
322
|
+
}
|
|
395
323
|
export function assertBrowserSafety(input) {
|
|
396
324
|
const inBrowser = typeof window !== 'undefined';
|
|
397
325
|
if (!input.dangerouslyAllowBrowser &&
|
|
@@ -405,14 +333,6 @@ export function assertBrowserSafety(input) {
|
|
|
405
333
|
'`dangerouslyAllowBrowser` option to `true`, e.g.,\n\n' +
|
|
406
334
|
' Ablo({ schema, apiKey, dangerouslyAllowBrowser: true });\n', { code: 'browser_apikey_blocked' });
|
|
407
335
|
}
|
|
408
|
-
// `databaseUrl` carries database credentials and is never browser-safe, so
|
|
409
|
-
// `dangerouslyAllowBrowser` does not override this check. Register your
|
|
410
|
-
// database from a server-side runtime.
|
|
411
|
-
if (inBrowser && typeof input.databaseUrl === 'string' && input.databaseUrl.length > 0) {
|
|
412
|
-
throw new AbloAuthenticationError('Ablo `databaseUrl` cannot be used in a browser-like environment — it ' +
|
|
413
|
-
'carries your database credentials. Initialize the client with ' +
|
|
414
|
-
'`databaseUrl` from a server-side runtime only.', { code: 'browser_database_url_blocked' });
|
|
415
|
-
}
|
|
416
336
|
}
|
|
417
337
|
/**
|
|
418
338
|
* Resolves an {@link ApiKeySetter} callable to its current string value, or
|
|
@@ -20,7 +20,7 @@ import type { OnDemandLoader } from '../sync/OnDemandLoader.js';
|
|
|
20
20
|
import type { JoinedParticipant } from '../sync/participants.js';
|
|
21
21
|
import type { LoadWhere } from '../query/types.js';
|
|
22
22
|
import { ModelScope } from '../types/index.js';
|
|
23
|
-
import type { Duration, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, ClaimWaitOptions, Snapshot, TargetRange } from '../types/streams.js';
|
|
23
|
+
import type { Duration, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, ClaimWaitOptions, Snapshot, TargetRange } from '../types/streams.js';
|
|
24
24
|
export interface ModelClientMeta {
|
|
25
25
|
readonly key: string;
|
|
26
26
|
readonly typename: string;
|
|
@@ -88,8 +88,8 @@ export interface ModelCollaboration<T> {
|
|
|
88
88
|
range?: TargetRange;
|
|
89
89
|
meta?: Record<string, unknown>;
|
|
90
90
|
};
|
|
91
|
-
/**
|
|
92
|
-
|
|
91
|
+
/** Peer-visible description of the work (`'rewriting the risk section'`). */
|
|
92
|
+
description?: string;
|
|
93
93
|
ttl?: Duration;
|
|
94
94
|
/**
|
|
95
95
|
* Block on the server's fair FIFO queue when the target is held, rather
|
|
@@ -179,10 +179,9 @@ export interface ModelCollaboration<T> {
|
|
|
179
179
|
createWatch?(modelKey: string, ids: string | readonly string[], options?: WatchOptions): Promise<JoinedParticipant>;
|
|
180
180
|
}
|
|
181
181
|
export interface ClaimTargetOptions<T = Record<string, unknown>> {
|
|
182
|
-
/**
|
|
183
|
-
*
|
|
184
|
-
|
|
185
|
-
/** Peer-visible explanation of the work being performed. */
|
|
182
|
+
/** Peer-visible description of the work being performed — the sentence a
|
|
183
|
+
* contending participant reads to decide whether to wait, work elsewhere, or
|
|
184
|
+
* move on. Defaults to `'editing'`. The same field on every claim surface. */
|
|
186
185
|
description?: string;
|
|
187
186
|
/** Field-level target, for fine-grained claimed-state badges. */
|
|
188
187
|
field?: string;
|
|
@@ -257,7 +256,6 @@ export interface ClaimReorderParams<T = Record<string, unknown>> extends ClaimLo
|
|
|
257
256
|
* ```ts
|
|
258
257
|
* const claim = await ablo.weatherReports.claim({
|
|
259
258
|
* id: 'report_stockholm',
|
|
260
|
-
* reason: 'forecasting',
|
|
261
259
|
* description: 'Fetching current weather before writing the forecast.',
|
|
262
260
|
* });
|
|
263
261
|
* try {
|
|
@@ -275,7 +273,7 @@ export interface ClaimReorderParams<T = Record<string, unknown>> extends ClaimLo
|
|
|
275
273
|
* `ablo.<model>.update({ id, data, claim })` verb — the handle carries the
|
|
276
274
|
* lease id and snapshot watermark for attribution and stale-write protection.
|
|
277
275
|
*/
|
|
278
|
-
export type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim };
|
|
276
|
+
export type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease };
|
|
279
277
|
export type ClaimOptions<T = Record<string, unknown>> = ClaimTargetOptions<T>;
|
|
280
278
|
/**
|
|
281
279
|
* The coordination surface for a model, exposed as a callable namespace.
|
|
@@ -289,7 +287,6 @@ export type ClaimOptions<T = Record<string, unknown>> = ClaimTargetOptions<T>;
|
|
|
289
287
|
* data: { title },
|
|
290
288
|
* claim: {
|
|
291
289
|
* field: 'title',
|
|
292
|
-
* reason: 'renaming',
|
|
293
290
|
* description: 'Renaming the task to match the project brief.',
|
|
294
291
|
* },
|
|
295
292
|
* });
|
|
@@ -345,6 +342,15 @@ export interface ClaimApi<T> extends ClaimReadApi<T> {
|
|
|
345
342
|
* directly and `await using` works without a guard.
|
|
346
343
|
*/
|
|
347
344
|
(params: ClaimParams<T>): Promise<HeldClaim<T>>;
|
|
345
|
+
/**
|
|
346
|
+
* Takes a claim by id alone, for a row that lives only in the customer's own
|
|
347
|
+
* database — Ablo has never seen it, so there is nothing to re-read. Returns a
|
|
348
|
+
* {@link HeldLease}: the same lease controls as {@link HeldClaim}
|
|
349
|
+
* (`release`, `revoke`, `heartbeat`, `await using`) but no `.data`. Locking a
|
|
350
|
+
* key you know by id is exactly this — serialize writers without first
|
|
351
|
+
* syncing the row into Ablo.
|
|
352
|
+
*/
|
|
353
|
+
(id: string, opts?: ClaimOptions<T>): Promise<HeldLease>;
|
|
348
354
|
}
|
|
349
355
|
export interface ModelRetrieveParams extends ServerRetrieveOptions {
|
|
350
356
|
readonly id: string;
|
|
@@ -440,7 +446,6 @@ export interface ModelOperations<T, CreateInput> {
|
|
|
440
446
|
* ```ts
|
|
441
447
|
* const claim = await ablo.weatherReports.claim({
|
|
442
448
|
* id: 'report_stockholm',
|
|
443
|
-
* reason: 'forecasting',
|
|
444
449
|
* description: 'Fetching fresh weather before updating the report.',
|
|
445
450
|
* });
|
|
446
451
|
* const weather = await getWeather(claim.data.location);
|