@abloatai/transaction 0.47.0 → 0.49.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/CONVENTIONS.md +7 -7
- package/README.md +1 -1
- package/dist/ai-sdk/modelTools.d.ts +1 -1
- package/dist/ai-sdk/modelTools.js +1 -1
- package/dist/ai-sdk/updateTool.d.ts +2 -2
- package/dist/ai-sdk/updateTool.d.ts.map +1 -1
- package/dist/auth/bootstrapScope.d.ts +1 -1
- package/dist/auth/capability.d.ts +32 -10
- package/dist/auth/capability.d.ts.map +1 -1
- package/dist/auth/capability.js +9 -3
- package/dist/auth/capability.js.map +1 -1
- package/dist/auth/capabilityLifecycle.d.ts +2 -2
- package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
- package/dist/auth/capabilityLifecycle.js +3 -2
- package/dist/auth/capabilityLifecycle.js.map +1 -1
- package/dist/auth/identity.d.ts +3 -0
- package/dist/auth/identity.d.ts.map +1 -1
- package/dist/auth/identity.js +13 -1
- package/dist/auth/identity.js.map +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/schemas.d.ts +18 -1
- package/dist/auth/schemas.d.ts.map +1 -1
- package/dist/auth/schemas.js +3 -1
- package/dist/auth/schemas.js.map +1 -1
- package/dist/auth/sessionMint.d.ts.map +1 -1
- package/dist/auth/sessionMint.js +0 -1
- package/dist/auth/sessionMint.js.map +1 -1
- package/dist/branches.d.ts +61 -12
- package/dist/branches.d.ts.map +1 -1
- package/dist/branches.js +25 -3
- package/dist/branches.js.map +1 -1
- package/dist/commitRecordRuntime.d.ts +30 -0
- package/dist/commitRecordRuntime.d.ts.map +1 -0
- package/dist/commitRecordRuntime.js +188 -0
- package/dist/commitRecordRuntime.js.map +1 -0
- package/dist/coordination/index.d.ts +3 -3
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2 -2
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/locator.d.ts +5 -0
- package/dist/coordination/locator.d.ts.map +1 -1
- package/dist/coordination/locator.js +12 -0
- package/dist/coordination/locator.js.map +1 -1
- package/dist/coordination/schema.d.ts +205 -15
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +88 -47
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/targetConflict.js +1 -1
- package/dist/durableWrites.d.ts +3 -3
- package/dist/durableWrites.d.ts.map +1 -1
- package/dist/durableWrites.js +1 -1
- package/dist/errorCodes.d.ts +35 -2
- package/dist/errorCodes.d.ts.map +1 -1
- package/dist/errorCodes.js +67 -19
- package/dist/errorCodes.js.map +1 -1
- package/dist/errorObservation.d.ts +82 -0
- package/dist/errorObservation.d.ts.map +1 -0
- package/dist/errorObservation.js +101 -0
- package/dist/errorObservation.js.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -1
- package/dist/errors.js.map +1 -1
- package/dist/footprint.d.ts +10 -22
- package/dist/footprint.d.ts.map +1 -1
- package/dist/footprint.js +12 -24
- package/dist/footprint.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/read-set.d.ts +8 -0
- package/dist/internal/read-set.d.ts.map +1 -0
- package/dist/internal/read-set.js +7 -0
- package/dist/internal/read-set.js.map +1 -0
- package/dist/logger.d.ts +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +1 -1
- package/dist/readSetContext.d.ts +54 -0
- package/dist/readSetContext.d.ts.map +1 -0
- package/dist/readSetContext.js +98 -0
- package/dist/readSetContext.js.map +1 -0
- package/dist/resources/functionalUpdate.d.ts +13 -3
- package/dist/resources/functionalUpdate.d.ts.map +1 -1
- package/dist/resources/functionalUpdate.js.map +1 -1
- package/dist/resources/httpResources.d.ts +23 -7
- package/dist/resources/httpResources.d.ts.map +1 -1
- package/dist/resources/modelOperations.d.ts +2 -2
- package/dist/resources/modelOperations.js +1 -1
- package/dist/resources/mutationOptions.d.ts +1 -1
- package/dist/resources/mutationOptions.js +1 -1
- package/dist/resources/where.d.ts +1 -1
- package/dist/resources/where.js +1 -1
- package/dist/resources/writeOptionsSchema.d.ts +39 -4
- package/dist/resources/writeOptionsSchema.d.ts.map +1 -1
- package/dist/resources/writeOptionsSchema.js +12 -6
- package/dist/resources/writeOptionsSchema.js.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/openapi.d.ts.map +1 -1
- package/dist/schema/openapi.js +45 -11
- package/dist/schema/openapi.js.map +1 -1
- package/dist/server/commit.d.ts +7 -1
- package/dist/server/commit.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/source/adapters/kysely.d.ts.map +1 -1
- package/dist/source/adapters/kysely.js +11 -4
- package/dist/source/adapters/kysely.js.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +14 -2
- package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
- package/dist/source/contract.d.ts +2 -0
- package/dist/source/contract.d.ts.map +1 -1
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.d.ts.map +1 -1
- package/dist/source/types.d.ts +1 -1
- package/dist/testing/fixtures/httpResponses.d.ts +30 -1
- package/dist/testing/fixtures/httpResponses.d.ts.map +1 -1
- package/dist/testing/fixtures/httpResponses.js +48 -1
- package/dist/testing/fixtures/httpResponses.js.map +1 -1
- package/dist/transactionLayer.d.ts +1 -1
- package/dist/transactions/{settlement → confirmation}/commitEnvelope.d.ts +4 -2
- package/dist/transactions/confirmation/commitEnvelope.d.ts.map +1 -0
- package/dist/transactions/{settlement → confirmation}/commitEnvelope.js +8 -3
- package/dist/transactions/confirmation/commitEnvelope.js.map +1 -0
- package/dist/transactions/confirmation/httpCommitEnvelope.d.ts.map +1 -0
- package/dist/transactions/confirmation/httpCommitEnvelope.js.map +1 -0
- package/dist/transactions/confirmation/idempotencyKey.d.ts.map +1 -0
- package/dist/transactions/confirmation/idempotencyKey.js.map +1 -0
- package/dist/transactions/{settlement → confirmation}/pendingWrite.d.ts +3 -2
- package/dist/transactions/confirmation/pendingWrite.d.ts.map +1 -0
- package/dist/transactions/confirmation/pendingWrite.js.map +1 -0
- package/dist/transport/commitFrames.d.ts +1 -0
- package/dist/transport/commitFrames.d.ts.map +1 -1
- package/dist/transport/commitFrames.js +8 -7
- package/dist/transport/commitFrames.js.map +1 -1
- package/dist/transport/connectionManager.d.ts +1 -1
- package/dist/transport/connectionManager.js +1 -1
- package/dist/transport/httpClient.d.ts +23 -9
- package/dist/transport/httpClient.d.ts.map +1 -1
- package/dist/transport/httpClient.js +134 -15
- package/dist/transport/httpClient.js.map +1 -1
- package/dist/transport/httpOptions.d.ts +9 -1
- package/dist/transport/httpOptions.d.ts.map +1 -1
- package/dist/transport/httpTransport.d.ts +3 -0
- package/dist/transport/httpTransport.d.ts.map +1 -1
- package/dist/transport/httpTransport.js +109 -22
- package/dist/transport/httpTransport.js.map +1 -1
- package/dist/transport/wsFrameHandlers.d.ts +3 -1
- package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
- package/dist/transport/wsFrameHandlers.js +38 -17
- package/dist/transport/wsFrameHandlers.js.map +1 -1
- package/dist/transport/wsTransport.d.ts +3 -0
- package/dist/transport/wsTransport.d.ts.map +1 -1
- package/dist/transport/wsTransport.js +32 -1
- package/dist/transport/wsTransport.js.map +1 -1
- package/dist/utils/json.d.ts +1 -1
- package/dist/utils/json.js +1 -1
- package/dist/wire/accountResponses.d.ts +1 -1
- package/dist/wire/auth.d.ts +2 -2
- package/dist/wire/commit.d.ts +1652 -213
- package/dist/wire/commit.d.ts.map +1 -1
- package/dist/wire/commit.js +196 -132
- package/dist/wire/commit.js.map +1 -1
- package/dist/wire/delta.d.ts +1 -1
- package/dist/wire/errorEnvelope.d.ts +1 -0
- package/dist/wire/errorEnvelope.d.ts.map +1 -1
- package/dist/wire/errorEnvelope.js +1 -0
- package/dist/wire/errorEnvelope.js.map +1 -1
- package/dist/wire/feedCursor.d.ts +1 -1
- package/dist/wire/feedCursor.js +1 -1
- package/dist/wire/feedEvent.d.ts +3 -3
- package/dist/wire/frames.d.ts +8 -4
- package/dist/wire/frames.d.ts.map +1 -1
- package/dist/wire/frames.js +6 -3
- package/dist/wire/frames.js.map +1 -1
- package/dist/wire/inboundFrames.d.ts +44 -4
- package/dist/wire/inboundFrames.d.ts.map +1 -1
- package/dist/wire/index.d.ts +6 -4
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +6 -5
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/modelMutations.d.ts +35 -0
- package/dist/wire/modelMutations.d.ts.map +1 -1
- package/dist/wire/modelMutations.js +9 -2
- package/dist/wire/modelMutations.js.map +1 -1
- package/dist/wire/modelResponses.d.ts +12 -1
- package/dist/wire/modelResponses.d.ts.map +1 -1
- package/dist/wire/modelResponses.js +12 -1
- package/dist/wire/modelResponses.js.map +1 -1
- package/package.json +12 -2
- package/src/ai-sdk/modelTools.ts +1 -1
- package/src/ai-sdk/updateTool.ts +2 -2
- package/src/auth/bootstrapScope.ts +1 -1
- package/src/auth/capability.ts +13 -4
- package/src/auth/capabilityLifecycle.ts +3 -2
- package/src/auth/identity.ts +16 -1
- package/src/auth/index.ts +2 -0
- package/src/auth/schemas.ts +3 -0
- package/src/auth/sessionMint.ts +0 -1
- package/src/branches.ts +27 -3
- package/src/commitRecordRuntime.ts +245 -0
- package/src/coordination/index.ts +24 -1
- package/src/coordination/locator.ts +17 -0
- package/src/coordination/schema.ts +114 -49
- package/src/coordination/targetConflict.ts +1 -1
- package/src/durableWrites.ts +3 -3
- package/src/errorCodes.ts +205 -25
- package/src/errorObservation.ts +121 -0
- package/src/errors.ts +5 -1
- package/src/footprint.ts +16 -27
- package/src/index.ts +8 -1
- package/src/internal/read-set.ts +18 -0
- package/src/logger.ts +1 -1
- package/src/observability.ts +1 -1
- package/src/readSetContext.ts +164 -0
- package/src/resources/functionalUpdate.ts +16 -4
- package/src/resources/httpResources.ts +27 -8
- package/src/resources/modelOperations.ts +2 -2
- package/src/resources/mutationOptions.ts +1 -1
- package/src/resources/where.ts +1 -1
- package/src/resources/writeOptionsSchema.ts +30 -12
- package/src/schema/index.ts +1 -1
- package/src/schema/openapi.ts +52 -11
- package/src/server/commit.ts +7 -1
- package/src/server/index.ts +5 -1
- package/src/source/adapters/kysely.ts +12 -4
- package/src/source/adapters/kyselyMutationCore.ts +14 -2
- package/src/source/index.ts +1 -1
- package/src/source/types.ts +1 -1
- package/src/testing/fixtures/httpResponses.ts +60 -1
- package/src/transactionLayer.ts +1 -1
- package/src/transactions/{settlement → confirmation}/commitEnvelope.ts +13 -3
- package/src/transport/commitFrames.ts +7 -5
- package/src/transport/connectionManager.ts +1 -1
- package/src/transport/httpClient.ts +256 -29
- package/src/transport/httpOptions.ts +9 -1
- package/src/transport/httpTransport.ts +139 -27
- package/src/transport/wsFrameHandlers.ts +46 -8
- package/src/transport/wsTransport.ts +42 -1
- package/src/utils/json.ts +1 -1
- package/src/wire/commit.ts +248 -147
- package/src/wire/errorEnvelope.ts +1 -0
- package/src/wire/feedCursor.ts +1 -1
- package/src/wire/frames.ts +9 -4
- package/src/wire/index.ts +38 -5
- package/src/wire/modelMutations.ts +16 -2
- package/src/wire/modelResponses.ts +14 -1
- package/dist/transactions/settlement/commitEnvelope.d.ts.map +0 -1
- package/dist/transactions/settlement/commitEnvelope.js.map +0 -1
- package/dist/transactions/settlement/httpCommitEnvelope.d.ts.map +0 -1
- package/dist/transactions/settlement/httpCommitEnvelope.js.map +0 -1
- package/dist/transactions/settlement/idempotencyKey.d.ts.map +0 -1
- package/dist/transactions/settlement/idempotencyKey.js.map +0 -1
- package/dist/transactions/settlement/pendingWrite.d.ts.map +0 -1
- package/dist/transactions/settlement/pendingWrite.js.map +0 -1
- /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.d.ts +0 -0
- /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.js +0 -0
- /package/dist/transactions/{settlement → confirmation}/idempotencyKey.d.ts +0 -0
- /package/dist/transactions/{settlement → confirmation}/idempotencyKey.js +0 -0
- /package/dist/transactions/{settlement → confirmation}/pendingWrite.js +0 -0
- /package/src/transactions/{settlement → confirmation}/httpCommitEnvelope.ts +0 -0
- /package/src/transactions/{settlement → confirmation}/idempotencyKey.ts +0 -0
- /package/src/transactions/{settlement → confirmation}/pendingWrite.ts +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const errorObservationSeveritySchema = z.enum([
|
|
4
|
+
'debug', 'info', 'warning', 'error', 'fatal',
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
export const errorObservationChannelSchema = z.enum([
|
|
8
|
+
'http', 'websocket', 'connector', 'background', 'process', 'cli', 'browser',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const TRUNCATION_MARKER = '…[truncated]';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Keep an observation field inside its wire bound without dropping the event.
|
|
15
|
+
* This lives beside the schema so producers cannot accidentally enforce a
|
|
16
|
+
* different limit. Redaction still happens before parsing at each boundary.
|
|
17
|
+
*/
|
|
18
|
+
export function boundObservationString(value: string, maxLength: number): string {
|
|
19
|
+
if (value.length <= maxLength) return value;
|
|
20
|
+
return `${value.slice(0, maxLength - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const boundedString = (maxLength: number) =>
|
|
24
|
+
z.preprocess(
|
|
25
|
+
(value) => typeof value === 'string' ? boundObservationString(value, maxLength) : value,
|
|
26
|
+
z.string().max(maxLength),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const boundedCauseSchema = z.object({
|
|
30
|
+
type: boundedString(200),
|
|
31
|
+
message: boundedString(2_000),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Runtime-neutral private event vocabulary shared by every error boundary. */
|
|
35
|
+
export const errorObservationSchema = z.object({
|
|
36
|
+
eventId: boundedString(200).pipe(z.string().min(1)),
|
|
37
|
+
occurredAt: z.iso.datetime(),
|
|
38
|
+
service: boundedString(100).pipe(z.string().min(1)),
|
|
39
|
+
stage: boundedString(100).pipe(z.string().min(1)),
|
|
40
|
+
release: boundedString(200).optional(),
|
|
41
|
+
severity: errorObservationSeveritySchema,
|
|
42
|
+
channel: errorObservationChannelSchema,
|
|
43
|
+
scope: boundedString(200).pipe(z.string().min(1)),
|
|
44
|
+
operation: boundedString(300).pipe(z.string().min(1)),
|
|
45
|
+
errorCode: boundedString(200).pipe(z.string().min(1)),
|
|
46
|
+
errorType: boundedString(200).pipe(z.string().min(1)),
|
|
47
|
+
category: boundedString(100).pipe(z.string().min(1)),
|
|
48
|
+
retryable: z.boolean(),
|
|
49
|
+
/** Whether application code converted the failure into an expected outcome. */
|
|
50
|
+
handled: z.boolean(),
|
|
51
|
+
publicMessage: boundedString(2_000),
|
|
52
|
+
internalMessage: boundedString(4_000).optional(),
|
|
53
|
+
stack: boundedString(20_000).optional(),
|
|
54
|
+
httpStatus: z.number().int().min(100).max(599).optional(),
|
|
55
|
+
requestId: boundedString(200).optional(),
|
|
56
|
+
traceId: boundedString(200).optional(),
|
|
57
|
+
spanId: boundedString(200).optional(),
|
|
58
|
+
organizationId: boundedString(200).optional(),
|
|
59
|
+
projectId: boundedString(200).optional(),
|
|
60
|
+
branchId: boundedString(200).optional(),
|
|
61
|
+
keyKind: boundedString(100).optional(),
|
|
62
|
+
storageKind: boundedString(100).optional(),
|
|
63
|
+
storageTransport: boundedString(100).optional(),
|
|
64
|
+
dataSourceId: boundedString(200).optional(),
|
|
65
|
+
connectorState: boundedString(100).optional(),
|
|
66
|
+
model: boundedString(200).optional(),
|
|
67
|
+
cause: z.array(boundedCauseSchema).max(5).optional(),
|
|
68
|
+
diagnosticContext: z.record(z.string(), z.unknown()).optional(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export type ErrorObservation = Readonly<z.infer<typeof errorObservationSchema>>;
|
|
72
|
+
export type ErrorObservationChannel = z.infer<typeof errorObservationChannelSchema>;
|
|
73
|
+
export type ErrorObservationSeverity = z.infer<typeof errorObservationSeveritySchema>;
|
|
74
|
+
|
|
75
|
+
const SENSITIVE_KEYS = [
|
|
76
|
+
'password', 'secret', 'token', 'apikey', 'authorization', 'cookie',
|
|
77
|
+
'databaseurl', 'connectionstring', 'ek', 'sk', 'rk', 'body', 'payload', 'sql', 'query',
|
|
78
|
+
];
|
|
79
|
+
const MASK = '[redacted]';
|
|
80
|
+
const SECRET_PATTERNS = [
|
|
81
|
+
/postgres(?:ql)?:\/\/[^\s"']+/gi,
|
|
82
|
+
/\b[a-z]{2,4}_[A-Za-z0-9]{12,}\b/g,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
export function redactObservationString(value: string): string {
|
|
86
|
+
return SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, MASK), value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isSensitiveObservationKey(key: string): boolean {
|
|
90
|
+
const normalized = key.toLowerCase().replace(/[^a-z]/g, '');
|
|
91
|
+
return SENSITIVE_KEYS.some(
|
|
92
|
+
(sensitive) => normalized === sensitive || (sensitive.length > 3 && normalized.includes(sensitive)),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Runtime-neutral, circular-safe sanitizer used before any observation sink. */
|
|
97
|
+
export function sanitizeObservationValue(
|
|
98
|
+
value: unknown,
|
|
99
|
+
depth = 0,
|
|
100
|
+
seen = new WeakSet(),
|
|
101
|
+
): unknown {
|
|
102
|
+
if (depth > 6) return '[truncated]';
|
|
103
|
+
if (typeof value === 'string') return redactObservationString(value);
|
|
104
|
+
if (value && typeof value === 'object') {
|
|
105
|
+
if (seen.has(value)) return '[circular]';
|
|
106
|
+
seen.add(value);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.map((item) => sanitizeObservationValue(item, depth + 1, seen));
|
|
110
|
+
}
|
|
111
|
+
if (value && typeof value === 'object') {
|
|
112
|
+
const sanitized: Record<string, unknown> = {};
|
|
113
|
+
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
114
|
+
sanitized[key] = isSensitiveObservationKey(key)
|
|
115
|
+
? MASK
|
|
116
|
+
: sanitizeObservationValue(item, depth + 1, seen);
|
|
117
|
+
}
|
|
118
|
+
return sanitized;
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -64,6 +64,8 @@ export class AbloError extends Error {
|
|
|
64
64
|
* `x-request-id` header or a live commit's rejection frame. Include it in
|
|
65
65
|
* support requests. */
|
|
66
66
|
readonly requestId?: string;
|
|
67
|
+
/** Opaque id of the exact private observation associated with this error. */
|
|
68
|
+
readonly eventId?: string;
|
|
67
69
|
/** The specific input that caused the error, as a model or field path such as
|
|
68
70
|
* `'dataroomMember.grants.subject'`, so tooling can point at the exact
|
|
69
71
|
* offending value. */
|
|
@@ -96,6 +98,7 @@ export class AbloError extends Error {
|
|
|
96
98
|
if (options?.requestId !== undefined) this.requestId = options.requestId;
|
|
97
99
|
if (options?.param !== undefined) this.param = options.param;
|
|
98
100
|
if (options?.details !== undefined) this.details = options.details;
|
|
101
|
+
if (typeof options?.details?.event_id === 'string') this.eventId = options.details.event_id;
|
|
99
102
|
const docUrl = options?.docUrl ?? (options?.code ? docUrlForCode(options.code) : undefined);
|
|
100
103
|
if (docUrl !== undefined) this.docUrl = docUrl;
|
|
101
104
|
if (options?.cause !== undefined) {
|
|
@@ -142,7 +145,8 @@ export class AbloError extends Error {
|
|
|
142
145
|
const code = this.code ? ` [${this.code}]` : '';
|
|
143
146
|
const docs = this.docUrl ? ` (see ${this.docUrl})` : '';
|
|
144
147
|
const req = this.requestId ? ` [request_id: ${this.requestId}]` : '';
|
|
145
|
-
|
|
148
|
+
const event = this.eventId ? ` [event_id: ${this.eventId}]` : '';
|
|
149
|
+
return `${this.name}${code}: ${this.message}${docs}${req}${event}`;
|
|
146
150
|
}
|
|
147
151
|
}
|
|
148
152
|
|
package/src/footprint.ts
CHANGED
|
@@ -51,25 +51,14 @@ export interface FootprintArtifact {
|
|
|
51
51
|
|
|
52
52
|
// ── The names, for the code that creates and reads these objects ────────────
|
|
53
53
|
|
|
54
|
-
/**
|
|
55
|
-
*
|
|
56
|
-
* publication declares what streams, and several readers are meant to share one
|
|
57
|
-
* (`publication_names` is a per-stream option, not slot state).
|
|
58
|
-
*/
|
|
54
|
+
/** Prefix used to derive a branch-scoped publication name. The exact unsuffixed
|
|
55
|
+
* name is retained only so `ablo connect scan` can identify an old installation. */
|
|
59
56
|
export const ABLO_PUBLICATION = 'ablo_publication';
|
|
60
|
-
/**
|
|
61
|
-
* The replication slot that holds Ablo's position in the write-ahead log.
|
|
62
|
-
*
|
|
63
|
-
* Constant, and it should not be — a slot stores ONE position, so two
|
|
64
|
-
* connections sharing this name compete for the same marker. It is why a
|
|
65
|
-
* database can be connected to one plane at a time. ADR 0020 derives it per
|
|
66
|
-
* connection; {@link isValidReplicationSlotName} is the check a derived name
|
|
67
|
-
* has to pass.
|
|
68
|
-
*/
|
|
57
|
+
/** Prefix used to derive the branch-scoped replication slot. */
|
|
69
58
|
export const ABLO_REPLICATION_SLOT = 'ablo_slot';
|
|
70
|
-
/**
|
|
59
|
+
/** Prefix used to derive the branch-scoped least-privilege replication login. */
|
|
71
60
|
export const ABLO_REPLICATION_ROLE = 'ablo_replicator';
|
|
72
|
-
/**
|
|
61
|
+
/** Prefix used to derive the branch-scoped login used only for row writes. */
|
|
73
62
|
export const ABLO_WRITE_ROLE = 'ablo_writer';
|
|
74
63
|
/** The bookkeeping table that makes a retried write land once. */
|
|
75
64
|
export const ABLO_IDEMPOTENCY_TABLE = 'ablo_idempotency';
|
|
@@ -90,15 +79,15 @@ export function isValidReplicationSlotName(name: string): boolean {
|
|
|
90
79
|
|
|
91
80
|
// ── Per-connection names (ADR 0020) ─────────────────────────────────────────
|
|
92
81
|
|
|
93
|
-
/** The immutable branch coordinates
|
|
94
|
-
export interface
|
|
82
|
+
/** The immutable branch coordinates that identify one customer Data Source. */
|
|
83
|
+
export interface DataSourceIdentity {
|
|
95
84
|
readonly organizationId: string;
|
|
96
85
|
readonly branchId: string;
|
|
97
86
|
/** Omitted for the organization-default project. */
|
|
98
87
|
readonly projectId?: string;
|
|
99
88
|
}
|
|
100
89
|
|
|
101
|
-
/** The names one connection owns. Nothing here is shared with another
|
|
90
|
+
/** The names one connection owns. Nothing here is shared with another Data Source. */
|
|
102
91
|
export interface FootprintNames {
|
|
103
92
|
readonly slot: string;
|
|
104
93
|
readonly publication: string;
|
|
@@ -114,7 +103,7 @@ export interface FootprintNames {
|
|
|
114
103
|
const SUFFIX_LENGTH = 16;
|
|
115
104
|
|
|
116
105
|
/**
|
|
117
|
-
* FNV-1a over the
|
|
106
|
+
* FNV-1a over the Data Source identity, in hex.
|
|
118
107
|
*
|
|
119
108
|
* Deliberately not a cryptographic hash: this module is imported by the CLI,
|
|
120
109
|
* which runs in environments without `node:crypto` guaranteed, and the property
|
|
@@ -123,13 +112,14 @@ const SUFFIX_LENGTH = 16;
|
|
|
123
112
|
* over different seeds give the sixteen hex characters, which is ample for the
|
|
124
113
|
* handful of planes any one database is ever connected to.
|
|
125
114
|
*/
|
|
126
|
-
function
|
|
115
|
+
function dataSourceDigest(identity: DataSourceIdentity): string {
|
|
127
116
|
// The organization-default project may be omitted or repeated as the
|
|
128
117
|
// organization id; both spell the same branch coordinates.
|
|
129
|
-
const project =
|
|
118
|
+
const project =
|
|
119
|
+
identity.projectId === identity.organizationId ? '' : (identity.projectId ?? '');
|
|
130
120
|
// Delimit the coordinates. Concatenation alone makes structurally different
|
|
131
121
|
// planes such as ("ab", "c") and ("a", "bc") hash the same input.
|
|
132
|
-
const key = [
|
|
122
|
+
const key = [identity.organizationId, project, identity.branchId].join('\0');
|
|
133
123
|
|
|
134
124
|
const round = (seed: number): string => {
|
|
135
125
|
let hash = seed;
|
|
@@ -148,16 +138,15 @@ function planeDigest(plane: FootprintPlane): string {
|
|
|
148
138
|
* The objects this connection owns, named so no other connection can claim them.
|
|
149
139
|
*
|
|
150
140
|
* A slot stores ONE position, so two connections sharing a name compete for the
|
|
151
|
-
* same marker and Postgres reports nothing
|
|
152
|
-
* replace made a database connectable to one plane at a time. The publication
|
|
141
|
+
* same marker and Postgres reports nothing. The publication
|
|
153
142
|
* and roles are derived from the same digest so a database's footprint reads as
|
|
154
143
|
* one set per connection rather than a mix of shared and private objects.
|
|
155
144
|
*
|
|
156
145
|
* Stable: the same plane always derives the same names, so re-running setup is a
|
|
157
146
|
* no-op rather than a second installation.
|
|
158
147
|
*/
|
|
159
|
-
export function footprintNamesFor(
|
|
160
|
-
const suffix =
|
|
148
|
+
export function footprintNamesFor(identity: DataSourceIdentity): FootprintNames {
|
|
149
|
+
const suffix = dataSourceDigest(identity);
|
|
161
150
|
const names: FootprintNames = {
|
|
162
151
|
slot: `${ABLO_REPLICATION_SLOT}_${suffix}`,
|
|
163
152
|
publication: `${ABLO_PUBLICATION}_${suffix}`,
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Ablo
|
|
2
|
+
* The Ablo confirmation core (ADR 0013).
|
|
3
3
|
*
|
|
4
4
|
* The transaction layer that the reactive client is a consumer of, not the
|
|
5
5
|
* other way round. The root barrel is deliberate: it exports the seam —
|
|
@@ -31,8 +31,15 @@ export { Ablo } from './ablo.js';
|
|
|
31
31
|
export type {
|
|
32
32
|
AbloHttpClient,
|
|
33
33
|
AbloHttpClientOptions,
|
|
34
|
+
CapturedRow,
|
|
34
35
|
HttpModelClient,
|
|
36
|
+
HttpModelMutationParams,
|
|
35
37
|
} from './transport/httpClient.js';
|
|
38
|
+
export type {
|
|
39
|
+
FunctionalUpdateOptions,
|
|
40
|
+
/** @deprecated Use `FunctionalUpdateOptions`. */
|
|
41
|
+
ContentionOptions,
|
|
42
|
+
} from './resources/functionalUpdate.js';
|
|
36
43
|
|
|
37
44
|
// The types the seam's signatures reference.
|
|
38
45
|
export type {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-private ReadSet integration seam used by the reactive materialiser.
|
|
3
|
+
* Application code declares dependencies through `reads: [returnedRow]`.
|
|
4
|
+
*/
|
|
5
|
+
export {
|
|
6
|
+
abortReadSetCommit,
|
|
7
|
+
capturePointRead,
|
|
8
|
+
createReadSetContext,
|
|
9
|
+
commitRecordIdentity,
|
|
10
|
+
consumeReadSet,
|
|
11
|
+
prepareReadSet,
|
|
12
|
+
publishCommitRecord,
|
|
13
|
+
} from '../readSetContext.js';
|
|
14
|
+
export { recordWebSocketCommitReceipt } from '../commitRecordRuntime.js';
|
|
15
|
+
export type {
|
|
16
|
+
PreparedReadSet,
|
|
17
|
+
ReadSetContext,
|
|
18
|
+
} from '../readSetContext.js';
|
package/src/logger.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A contract with no framework and no local state: credential exchange, commit,
|
|
5
5
|
* and claim all need to log with no UI and no offline store present, so the port
|
|
6
|
-
* belongs to the
|
|
6
|
+
* belongs to the confirmation core (ADR 0016). The consumer supplies the
|
|
7
7
|
* implementation; the SDK ships a no-op default.
|
|
8
8
|
*/
|
|
9
9
|
export interface Logger {
|
package/src/observability.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The observability the
|
|
2
|
+
* The observability the confirmation core reports on its own behalf.
|
|
3
3
|
*
|
|
4
4
|
* Coordination outcomes — a claim changing state, a stale-write collision that
|
|
5
5
|
* notified instead of aborting — happen with no UI and no local store anywhere,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-local opaque evidence for rows returned by authoritative reads.
|
|
3
|
+
*
|
|
4
|
+
* The name is retained internally to avoid churn across the two transports,
|
|
5
|
+
* but this is not an execution context: it has no async carrier and no ambient
|
|
6
|
+
* scope. Evidence is keyed only by the exact object returned to the caller.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
readDependencySchema,
|
|
10
|
+
type CommitReadSetEntry,
|
|
11
|
+
type OnStaleMode,
|
|
12
|
+
type ReadDependency,
|
|
13
|
+
} from './coordination/schema.js';
|
|
14
|
+
import { AbloValidationError } from './errors.js';
|
|
15
|
+
import type { CommitRecord } from './wire/commit.js';
|
|
16
|
+
|
|
17
|
+
type ClientIdentity = object;
|
|
18
|
+
|
|
19
|
+
/** @internal One exact returned-row assertion. */
|
|
20
|
+
export interface CapturedReadEvidence {
|
|
21
|
+
readonly client: ClientIdentity;
|
|
22
|
+
readonly entry: CommitReadSetEntry;
|
|
23
|
+
readonly row: object;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @internal Per-client registry. Weak keys do not retain application rows. */
|
|
27
|
+
export interface ReadRegistry {
|
|
28
|
+
readonly byRow: WeakMap<object, CapturedReadEvidence>;
|
|
29
|
+
readonly commitRecords: Map<string, CommitRecord>;
|
|
30
|
+
onCommitRecord?: (record: CommitRecord) => void | Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @internal Client-local evidence registry handle. */
|
|
34
|
+
export interface ReadSetContext {
|
|
35
|
+
getStore(): ReadRegistry;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PreparedReadSet {
|
|
39
|
+
readonly readAt?: number;
|
|
40
|
+
readonly onStale?: OnStaleMode;
|
|
41
|
+
readonly reads?: readonly ReadDependency[] | null;
|
|
42
|
+
readonly consumed: readonly CapturedReadEvidence[];
|
|
43
|
+
readonly automaticCommit: false;
|
|
44
|
+
readonly idempotencyKey?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @internal Creates one isolated evidence registry for one Ablo client. */
|
|
48
|
+
export function createReadSetContext(options?: {
|
|
49
|
+
readonly onCommitRecord?: (record: CommitRecord) => void | Promise<void>;
|
|
50
|
+
}): ReadSetContext {
|
|
51
|
+
const registry: ReadRegistry = {
|
|
52
|
+
byRow: new WeakMap(),
|
|
53
|
+
commitRecords: new Map(),
|
|
54
|
+
...(options?.onCommitRecord ? { onCommitRecord: options.onCommitRecord } : {}),
|
|
55
|
+
};
|
|
56
|
+
return { getStore: () => registry };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @internal Retains and reports one immutable logical commit snapshot. */
|
|
60
|
+
export function publishCommitRecord(
|
|
61
|
+
context: ReadSetContext | undefined,
|
|
62
|
+
record: CommitRecord,
|
|
63
|
+
): void {
|
|
64
|
+
const registry = context?.getStore();
|
|
65
|
+
if (!registry) return;
|
|
66
|
+
registry.commitRecords.set(record.id, record);
|
|
67
|
+
try {
|
|
68
|
+
const observed = registry.onCommitRecord?.(record);
|
|
69
|
+
if (observed) void Promise.resolve(observed).catch(() => undefined);
|
|
70
|
+
} catch {
|
|
71
|
+
// Observability must never change a commit's outcome.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @internal Associates evidence with the exact row object returned to a caller. */
|
|
76
|
+
export function capturePointRead(
|
|
77
|
+
context: ReadSetContext | undefined,
|
|
78
|
+
client: ClientIdentity,
|
|
79
|
+
model: string,
|
|
80
|
+
id: string,
|
|
81
|
+
row: unknown,
|
|
82
|
+
readAt: number,
|
|
83
|
+
): void {
|
|
84
|
+
if (!context || typeof row !== 'object' || row === null) return;
|
|
85
|
+
const rowObject = row as object;
|
|
86
|
+
context.getStore().byRow.set(rowObject, {
|
|
87
|
+
client,
|
|
88
|
+
row: rowObject,
|
|
89
|
+
entry: {
|
|
90
|
+
target: { scope: 'row', model, id },
|
|
91
|
+
watermark: readAt,
|
|
92
|
+
lifetime: 'commit',
|
|
93
|
+
onStale: 'reject',
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @internal Resolves only dependencies explicitly supplied through `reads`. */
|
|
99
|
+
export function prepareReadSet(
|
|
100
|
+
context: ReadSetContext | undefined,
|
|
101
|
+
client: ClientIdentity,
|
|
102
|
+
explicitReadAt: number | null | undefined,
|
|
103
|
+
explicitOnStale: OnStaleMode | null | undefined,
|
|
104
|
+
explicitIdempotencyKey: string | null | undefined,
|
|
105
|
+
reads: readonly unknown[] | null | undefined,
|
|
106
|
+
): PreparedReadSet {
|
|
107
|
+
const registry = context?.getStore();
|
|
108
|
+
const consumed: CapturedReadEvidence[] = [];
|
|
109
|
+
const resolvedReads: ReadDependency[] = [];
|
|
110
|
+
|
|
111
|
+
for (const entry of reads ?? []) {
|
|
112
|
+
const captured =
|
|
113
|
+
registry && typeof entry === 'object' && entry !== null
|
|
114
|
+
? registry.byRow.get(entry)
|
|
115
|
+
: undefined;
|
|
116
|
+
if (captured?.client === client) {
|
|
117
|
+
const target = captured.entry.target;
|
|
118
|
+
if (target.scope !== 'row') {
|
|
119
|
+
throw new AbloValidationError('Captured read evidence must name a row.', {
|
|
120
|
+
code: 'write_options_invalid', param: 'reads',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
resolvedReads.push({
|
|
124
|
+
model: target.model,
|
|
125
|
+
id: target.id,
|
|
126
|
+
readAt: captured.entry.watermark,
|
|
127
|
+
});
|
|
128
|
+
consumed.push(captured);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const canonical = readDependencySchema.safeParse(entry);
|
|
132
|
+
if (canonical.success) {
|
|
133
|
+
resolvedReads.push(canonical.data);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
throw new AbloValidationError(
|
|
137
|
+
'A row passed through `reads` was not the exact object returned by this Ablo client. Re-read it and pass that object directly.',
|
|
138
|
+
{ code: 'write_options_invalid', param: 'reads' },
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...(explicitReadAt != null ? { readAt: explicitReadAt } : {}),
|
|
144
|
+
...(explicitOnStale != null ? { onStale: explicitOnStale } : {}),
|
|
145
|
+
...(reads !== undefined ? { reads: reads === null ? null : resolvedReads } : {}),
|
|
146
|
+
consumed,
|
|
147
|
+
automaticCommit: false,
|
|
148
|
+
...(explicitIdempotencyKey != null ? { idempotencyKey: explicitIdempotencyKey } : {}),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @internal Commit identity is the explicit request idempotency identity. */
|
|
153
|
+
export function commitRecordIdentity(
|
|
154
|
+
_context: ReadSetContext | undefined,
|
|
155
|
+
attemptId: string,
|
|
156
|
+
): { readonly id: string } {
|
|
157
|
+
return { id: attemptId };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @internal Explicit row evidence is reusable; successful writes do not consume it. */
|
|
161
|
+
export function consumeReadSet(..._args: readonly unknown[]): void {}
|
|
162
|
+
|
|
163
|
+
/** @internal There is no ambient automatic-commit reservation to release. */
|
|
164
|
+
export function abortReadSetCommit(..._args: readonly unknown[]): void {}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
AbloClaimedError,
|
|
29
29
|
AbloContentionError,
|
|
30
30
|
} from '../errors.js';
|
|
31
|
+
import type { ReadDependency } from '../coordination/schema.js';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* The functional form of an update: given the freshly-read row, return the
|
|
@@ -38,8 +39,8 @@ export type ModelUpdater<T> = (
|
|
|
38
39
|
current: T,
|
|
39
40
|
) => Partial<T> | null | undefined | Promise<Partial<T> | null | undefined>;
|
|
40
41
|
|
|
41
|
-
/**
|
|
42
|
-
export interface
|
|
42
|
+
/** Options for the functional `update(id, reducer, options)` form. */
|
|
43
|
+
export interface FunctionalUpdateOptions<Dependency = ReadDependency> {
|
|
43
44
|
/**
|
|
44
45
|
* Max reconcile rounds under contention before throwing
|
|
45
46
|
* {@link AbloContentionError}. Each round re-reads the latest row and re-runs
|
|
@@ -48,8 +49,19 @@ export interface ContentionOptions {
|
|
|
48
49
|
readonly retries?: number;
|
|
49
50
|
/** Abort the reconcile loop (e.g. the request was cancelled). */
|
|
50
51
|
readonly signal?: AbortSignal;
|
|
52
|
+
/**
|
|
53
|
+
* Cross-target state this reducer decision depends on. Captured point rows
|
|
54
|
+
* are resolved by the public client; canonical dependencies remain the
|
|
55
|
+
* low-level escape hatch. Dependencies survive reconcilable CAS failures and
|
|
56
|
+
* are consumed only with the successful attempt.
|
|
57
|
+
*/
|
|
58
|
+
readonly reads?: readonly Dependency[] | null;
|
|
51
59
|
}
|
|
52
60
|
|
|
61
|
+
/** @deprecated Use {@link FunctionalUpdateOptions}. */
|
|
62
|
+
export type ContentionOptions<Dependency = ReadDependency> =
|
|
63
|
+
FunctionalUpdateOptions<Dependency>;
|
|
64
|
+
|
|
53
65
|
/** Reconcile rounds before a hot row is declared permanently contended. */
|
|
54
66
|
export const DEFAULT_CONTENTION_RETRIES = 16;
|
|
55
67
|
|
|
@@ -104,9 +116,9 @@ export interface ReconcileTransport<T, R> {
|
|
|
104
116
|
* both transports so the guarantee is provably identical. Returns the write's
|
|
105
117
|
* result, or `undefined` when the updater opted out of writing.
|
|
106
118
|
*/
|
|
107
|
-
export async function reconcileFunctionalUpdate<T, R>(
|
|
119
|
+
export async function reconcileFunctionalUpdate<T, R, Read = ReadDependency>(
|
|
108
120
|
updater: ModelUpdater<T>,
|
|
109
|
-
options:
|
|
121
|
+
options: FunctionalUpdateOptions<Read> | undefined,
|
|
110
122
|
transport: ReconcileTransport<T, R>,
|
|
111
123
|
): Promise<R | undefined> {
|
|
112
124
|
const retries = options?.retries ?? DEFAULT_CONTENTION_RETRIES;
|
|
@@ -15,14 +15,19 @@ import type { ClaimHeartbeatReply, ClaimState } from '../wire/claims.js';
|
|
|
15
15
|
import type {
|
|
16
16
|
ClientCommitReceipt,
|
|
17
17
|
CommitWait,
|
|
18
|
+
CommitRecord,
|
|
19
|
+
CommitRecordList,
|
|
20
|
+
CommitRecordListOptions,
|
|
21
|
+
CommitRecordWhere,
|
|
18
22
|
} from '../wire/commit.js';
|
|
19
23
|
import type { LogListResponse, LogQuery } from '../wire/feedEvent.js';
|
|
20
|
-
|
|
24
|
+
import type { ModelListEvidence } from '../wire/modelResponses.js';
|
|
25
|
+
// Re-exported, not redeclared. `wire/commit.ts` owns the commit-status vocabulary
|
|
21
26
|
// and derives the waitable subset from it; this module serves that name to SDK
|
|
22
27
|
// consumers. Restating the subset here as its own union produced a type that
|
|
23
28
|
// matched the canonical one only by both happening to list the same two
|
|
24
29
|
// strings — and would have silently disagreed with the runtime `wait`
|
|
25
|
-
// validator the moment a third
|
|
30
|
+
// validator the moment a third commit status existed.
|
|
26
31
|
export type { CommitWait };
|
|
27
32
|
// `ModelTarget` (the `model` and `id` locator) and `ModelClaim` (the resolved
|
|
28
33
|
// claim view) are defined in `../coordination/schema`, derived from a single
|
|
@@ -39,7 +44,7 @@ import type { SyncGroupInput } from '../schema/roles.js';
|
|
|
39
44
|
import type {
|
|
40
45
|
CapabilityCan,
|
|
41
46
|
CapabilityOperation,
|
|
42
|
-
|
|
47
|
+
EffectiveAuthority,
|
|
43
48
|
} from '../auth/capability.js';
|
|
44
49
|
import type {
|
|
45
50
|
Claim,
|
|
@@ -48,7 +53,7 @@ import type {
|
|
|
48
53
|
Duration,
|
|
49
54
|
HeldClaim,
|
|
50
55
|
} from '../types/streams.js';
|
|
51
|
-
import type { ModelUpdater,
|
|
56
|
+
import type { ModelUpdater, FunctionalUpdateOptions } from './functionalUpdate.js';
|
|
52
57
|
import type {
|
|
53
58
|
ClaimOptions,
|
|
54
59
|
ClaimAttemptEvent,
|
|
@@ -93,6 +98,12 @@ export interface HttpTransportRead<T = Record<string, unknown>> {
|
|
|
93
98
|
readonly claims: readonly ModelClaim[];
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
/** @internal Exact collection envelope retained until the typed facade captures evidence. */
|
|
102
|
+
export interface HttpTransportList<T = Record<string, unknown>> {
|
|
103
|
+
readonly data: readonly T[];
|
|
104
|
+
readonly evidence?: readonly ModelListEvidence[];
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
export type IfClaimedPolicy = 'return' | 'fail';
|
|
97
108
|
|
|
98
109
|
export interface ClaimedOptions {
|
|
@@ -160,6 +171,8 @@ export interface CommitOperationInput {
|
|
|
160
171
|
readonly id?: string | null;
|
|
161
172
|
readonly data?: Record<string, unknown> | null;
|
|
162
173
|
readonly transactionId?: string | null;
|
|
174
|
+
/** Claim identity derived from a held claim; not an application id. */
|
|
175
|
+
readonly claimId?: string | null;
|
|
163
176
|
readonly readAt?: number | null;
|
|
164
177
|
readonly onStale?: OnStaleMode | null;
|
|
165
178
|
/** Fencing token (Option B) from the batch's claim handle; server-validated. */
|
|
@@ -207,6 +220,8 @@ export type CommitReceipt = ClientCommitReceipt;
|
|
|
207
220
|
|
|
208
221
|
export interface CommitResource {
|
|
209
222
|
create(options: CommitCreateOptions): Promise<CommitReceipt>;
|
|
223
|
+
get(options: { readonly id: string }): Promise<CommitRecord | null>;
|
|
224
|
+
list(options?: CommitRecordListOptions): Promise<CommitRecordList>;
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
export interface HttpLogListOptions
|
|
@@ -276,6 +291,10 @@ export interface ModelMutationOptions extends ClaimedOptions {
|
|
|
276
291
|
readonly idempotencyKey?: string | null;
|
|
277
292
|
readonly readAt?: number | null;
|
|
278
293
|
readonly onStale?: OnStaleMode | null;
|
|
294
|
+
/** Commit-lifetime read dependencies checked before this mutation lands. */
|
|
295
|
+
readonly reads?: readonly ReadDependency[] | null;
|
|
296
|
+
/** Persisted read dependencies registered by this mutation. */
|
|
297
|
+
readonly track?: readonly TrackDependency[] | null;
|
|
279
298
|
readonly claim?: Claim | ClaimOptions | null;
|
|
280
299
|
/** Fencing token (Option B) from the claim; server-validated at commit. */
|
|
281
300
|
readonly fenceToken?: number | null;
|
|
@@ -332,7 +351,7 @@ export interface HttpTransportModel<
|
|
|
332
351
|
* and `limit`. The typed public client always exposes `ablo.<model>.list`;
|
|
333
352
|
* this protocol shape is private transport machinery.
|
|
334
353
|
*/
|
|
335
|
-
list(options?: ServerReadOptions<T>): Promise<T
|
|
354
|
+
list(options?: ServerReadOptions<T>): Promise<HttpTransportList<T>>;
|
|
336
355
|
/**
|
|
337
356
|
* Creates a row and returns the confirmed server row, including framework
|
|
338
357
|
* defaults such as `createdAt` and `createdBy`. Matches the stateful client's
|
|
@@ -354,7 +373,7 @@ export interface HttpTransportModel<
|
|
|
354
373
|
update(
|
|
355
374
|
id: string,
|
|
356
375
|
updater: ModelUpdater<T>,
|
|
357
|
-
options?:
|
|
376
|
+
options?: FunctionalUpdateOptions,
|
|
358
377
|
): Promise<CommitReceipt | undefined>;
|
|
359
378
|
delete(params: ModelMutationOptions & { readonly id: string }): Promise<CommitReceipt>;
|
|
360
379
|
/**
|
|
@@ -475,7 +494,7 @@ export interface AbloSession {
|
|
|
475
494
|
organizationId: string;
|
|
476
495
|
/** The grant this token carries, on both axes — the same shape the key row
|
|
477
496
|
* stores and the gates enforce. */
|
|
478
|
-
scope:
|
|
497
|
+
scope: EffectiveAuthority;
|
|
479
498
|
userMeta: Record<string, unknown>;
|
|
480
499
|
}
|
|
481
500
|
|
|
@@ -492,7 +511,7 @@ export interface SessionRotation {
|
|
|
492
511
|
token: string;
|
|
493
512
|
expiresAt: string | null;
|
|
494
513
|
organizationId: string;
|
|
495
|
-
scope:
|
|
514
|
+
scope: EffectiveAuthority;
|
|
496
515
|
rotatedFrom: {
|
|
497
516
|
id: string;
|
|
498
517
|
expiresAt: string;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* caller passes to a read, a write, or a claim.
|
|
4
4
|
*
|
|
5
5
|
* These types describe the *change or the query being requested*, never a local
|
|
6
|
-
* copy of the rows it touches, so they sit in the
|
|
6
|
+
* copy of the rows it touches, so they sit in the commit core and are shared
|
|
7
7
|
* by every transport and every caller (ADR 0013 §4, ADR 0016). The factory that
|
|
8
8
|
* binds them to reactive model instances — `createModelProxy` — stays with the
|
|
9
9
|
* reactive consumer, along with `ModelOperations` and `ModelCollaboration`,
|
|
@@ -562,7 +562,7 @@ export interface ModelRetrieveParams extends ServerRetrieveOptions {
|
|
|
562
562
|
* Options shared by schema model writes.
|
|
563
563
|
*
|
|
564
564
|
* Reactive clients apply the row change optimistically before returning from
|
|
565
|
-
* the call. The returned promise has one stable
|
|
565
|
+
* the call. The returned promise has one stable confirmation contract across
|
|
566
566
|
* reactive and stateless clients: it resolves only after authoritative
|
|
567
567
|
* confirmation. Callers that need an earlier queued receipt use the lower-level
|
|
568
568
|
* `commits.create` resource instead.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Every field here defines, orders, settles, or authorises *the change itself*
|
|
5
5
|
* — request identity, commit disposition, fencing, and the premise it rests on.
|
|
6
|
-
* None of it touches a local copy of rows, so it belongs with the
|
|
6
|
+
* None of it touches a local copy of rows, so it belongs with the commit
|
|
7
7
|
* core rather than the reactive consumer (ADR 0013 §4, ADR 0016).
|
|
8
8
|
*/
|
|
9
9
|
|
package/src/resources/where.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* key — for example, fetching every block whose `sectionId` falls in a set of ids.
|
|
11
11
|
*
|
|
12
12
|
* These types describe the *request*, not any local copy of the rows it returns,
|
|
13
|
-
* so they live with the
|
|
13
|
+
* so they live with the commit core rather than the reactive consumer.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { z } from 'zod';
|