@abloatai/transaction 0.37.1 → 0.39.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.
Files changed (181) hide show
  1. package/dist/ai-sdk/index.d.ts +3 -1
  2. package/dist/ai-sdk/index.d.ts.map +1 -1
  3. package/dist/ai-sdk/index.js +2 -1
  4. package/dist/ai-sdk/index.js.map +1 -1
  5. package/dist/ai-sdk/modelTools.d.ts +67 -0
  6. package/dist/ai-sdk/modelTools.d.ts.map +1 -0
  7. package/dist/ai-sdk/modelTools.js +89 -0
  8. package/dist/ai-sdk/modelTools.js.map +1 -0
  9. package/dist/ai-sdk/toolOptions.d.ts +8 -0
  10. package/dist/ai-sdk/toolOptions.d.ts.map +1 -0
  11. package/dist/ai-sdk/toolOptions.js +2 -0
  12. package/dist/ai-sdk/toolOptions.js.map +1 -0
  13. package/dist/ai-sdk/{coordinatedTool.d.ts → updateTool.d.ts} +21 -28
  14. package/dist/ai-sdk/updateTool.d.ts.map +1 -0
  15. package/dist/ai-sdk/{coordinatedTool.js → updateTool.js} +39 -50
  16. package/dist/ai-sdk/updateTool.js.map +1 -0
  17. package/dist/auth/capability.d.ts +6 -0
  18. package/dist/auth/capability.d.ts.map +1 -1
  19. package/dist/auth/capability.js +4 -0
  20. package/dist/auth/capability.js.map +1 -1
  21. package/dist/auth/capabilityLifecycle.d.ts +3 -0
  22. package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
  23. package/dist/auth/identity.d.ts +6 -4
  24. package/dist/auth/identity.d.ts.map +1 -1
  25. package/dist/auth/identity.js +7 -11
  26. package/dist/auth/identity.js.map +1 -1
  27. package/dist/auth/index.d.ts +16 -2
  28. package/dist/auth/index.d.ts.map +1 -1
  29. package/dist/auth/index.js +10 -1
  30. package/dist/auth/index.js.map +1 -1
  31. package/dist/auth/schemas.d.ts +5 -5
  32. package/dist/auth/schemas.d.ts.map +1 -1
  33. package/dist/auth/schemas.js +5 -2
  34. package/dist/auth/schemas.js.map +1 -1
  35. package/dist/auth/sessionMint.d.ts.map +1 -1
  36. package/dist/auth/sessionMint.js +3 -0
  37. package/dist/auth/sessionMint.js.map +1 -1
  38. package/dist/branches.d.ts +251 -0
  39. package/dist/branches.d.ts.map +1 -0
  40. package/dist/branches.js +108 -0
  41. package/dist/branches.js.map +1 -0
  42. package/dist/coordination/awaitClaimGrant.d.ts +2 -0
  43. package/dist/coordination/awaitClaimGrant.d.ts.map +1 -1
  44. package/dist/coordination/awaitClaimGrant.js +15 -2
  45. package/dist/coordination/awaitClaimGrant.js.map +1 -1
  46. package/dist/coordination/schema.d.ts +2 -0
  47. package/dist/coordination/schema.d.ts.map +1 -1
  48. package/dist/coordination/schema.js +2 -0
  49. package/dist/coordination/schema.js.map +1 -1
  50. package/dist/docs/catalog.d.ts +5 -5
  51. package/dist/docs/catalog.d.ts.map +1 -1
  52. package/dist/docs/catalog.js +12 -12
  53. package/dist/docs/catalog.js.map +1 -1
  54. package/dist/errorCodes.d.ts +1 -0
  55. package/dist/errorCodes.d.ts.map +1 -1
  56. package/dist/errorCodes.js +1 -0
  57. package/dist/errorCodes.js.map +1 -1
  58. package/dist/footprint.d.ts +3 -5
  59. package/dist/footprint.d.ts.map +1 -1
  60. package/dist/footprint.js +0 -0
  61. package/dist/footprint.js.map +1 -1
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/keys/index.d.ts +15 -7
  67. package/dist/keys/index.d.ts.map +1 -1
  68. package/dist/keys/index.js +45 -14
  69. package/dist/keys/index.js.map +1 -1
  70. package/dist/schema/index.d.ts +1 -1
  71. package/dist/schema/index.d.ts.map +1 -1
  72. package/dist/schema/index.js +1 -1
  73. package/dist/schema/index.js.map +1 -1
  74. package/dist/schema/openapi.d.ts +1 -1
  75. package/dist/schema/openapi.d.ts.map +1 -1
  76. package/dist/schema/openapi.js +308 -29
  77. package/dist/schema/openapi.js.map +1 -1
  78. package/dist/schema/schema.d.ts +12 -0
  79. package/dist/schema/schema.d.ts.map +1 -1
  80. package/dist/schema/schema.js.map +1 -1
  81. package/dist/schema/select.d.ts +13 -0
  82. package/dist/schema/select.d.ts.map +1 -1
  83. package/dist/schema/select.js +26 -0
  84. package/dist/schema/select.js.map +1 -1
  85. package/dist/server/commit.d.ts +2 -15
  86. package/dist/server/commit.d.ts.map +1 -1
  87. package/dist/source/adapter.d.ts +7 -2
  88. package/dist/source/adapter.d.ts.map +1 -1
  89. package/dist/source/adapter.js +4 -2
  90. package/dist/source/adapter.js.map +1 -1
  91. package/dist/source/adapterFactory.d.ts +10 -0
  92. package/dist/source/adapterFactory.d.ts.map +1 -0
  93. package/dist/source/adapterFactory.js +33 -0
  94. package/dist/source/adapterFactory.js.map +1 -0
  95. package/dist/source/adapterProfile.d.ts +55 -0
  96. package/dist/source/adapterProfile.d.ts.map +1 -0
  97. package/dist/source/adapterProfile.js +54 -0
  98. package/dist/source/adapterProfile.js.map +1 -0
  99. package/dist/source/adapters/drizzle.d.ts.map +1 -1
  100. package/dist/source/adapters/drizzle.js +5 -2
  101. package/dist/source/adapters/drizzle.js.map +1 -1
  102. package/dist/source/adapters/kysely.d.ts.map +1 -1
  103. package/dist/source/adapters/kysely.js +7 -4
  104. package/dist/source/adapters/kysely.js.map +1 -1
  105. package/dist/source/adapters/memory.d.ts.map +1 -1
  106. package/dist/source/adapters/memory.js +5 -2
  107. package/dist/source/adapters/memory.js.map +1 -1
  108. package/dist/source/adapters/prisma.d.ts.map +1 -1
  109. package/dist/source/adapters/prisma.js +5 -2
  110. package/dist/source/adapters/prisma.js.map +1 -1
  111. package/dist/source/conformance.d.ts.map +1 -1
  112. package/dist/source/conformance.js +18 -0
  113. package/dist/source/conformance.js.map +1 -1
  114. package/dist/source/index.d.ts +2 -0
  115. package/dist/source/index.d.ts.map +1 -1
  116. package/dist/source/index.js +2 -0
  117. package/dist/source/index.js.map +1 -1
  118. package/dist/source/types.d.ts +3 -14
  119. package/dist/source/types.d.ts.map +1 -1
  120. package/dist/source/types.js.map +1 -1
  121. package/dist/syncLog/contract.d.ts +1 -1
  122. package/dist/syncLog/contract.d.ts.map +1 -1
  123. package/dist/syncLog/contract.js +1 -2
  124. package/dist/syncLog/contract.js.map +1 -1
  125. package/dist/transport/httpClient.d.ts.map +1 -1
  126. package/dist/transport/httpClient.js +10 -1
  127. package/dist/transport/httpClient.js.map +1 -1
  128. package/dist/wire/accountResponses.d.ts +5 -9
  129. package/dist/wire/accountResponses.d.ts.map +1 -1
  130. package/dist/wire/accountResponses.js +5 -6
  131. package/dist/wire/accountResponses.js.map +1 -1
  132. package/dist/wire/auth.d.ts +7 -1
  133. package/dist/wire/auth.d.ts.map +1 -1
  134. package/dist/wire/auth.js +26 -3
  135. package/dist/wire/auth.js.map +1 -1
  136. package/dist/wire/delta.d.ts +11 -0
  137. package/dist/wire/delta.d.ts.map +1 -1
  138. package/dist/wire/delta.js +11 -0
  139. package/dist/wire/delta.js.map +1 -1
  140. package/dist/wire/inboundFrames.d.ts +2 -0
  141. package/dist/wire/inboundFrames.d.ts.map +1 -1
  142. package/package.json +8 -2
  143. package/src/ai-sdk/index.ts +19 -6
  144. package/src/ai-sdk/modelTools.ts +165 -0
  145. package/src/ai-sdk/toolOptions.ts +18 -0
  146. package/src/ai-sdk/{coordinatedTool.ts → updateTool.ts} +59 -69
  147. package/src/auth/capability.ts +4 -0
  148. package/src/auth/identity.ts +16 -16
  149. package/src/auth/index.ts +31 -3
  150. package/src/auth/schemas.ts +5 -2
  151. package/src/auth/sessionMint.ts +3 -0
  152. package/src/branches.ts +134 -0
  153. package/src/coordination/awaitClaimGrant.ts +18 -2
  154. package/src/coordination/schema.ts +2 -0
  155. package/src/docs/catalog.ts +24 -12
  156. package/src/errorCodes.ts +4 -0
  157. package/src/footprint.ts +0 -0
  158. package/src/index.ts +1 -0
  159. package/src/keys/index.ts +56 -17
  160. package/src/schema/index.ts +1 -1
  161. package/src/schema/openapi.ts +366 -38
  162. package/src/schema/schema.ts +13 -0
  163. package/src/schema/select.ts +30 -0
  164. package/src/server/commit.ts +2 -15
  165. package/src/source/adapter.ts +7 -2
  166. package/src/source/adapterFactory.ts +60 -0
  167. package/src/source/adapterProfile.ts +75 -0
  168. package/src/source/adapters/drizzle.ts +5 -2
  169. package/src/source/adapters/kysely.ts +10 -4
  170. package/src/source/adapters/memory.ts +5 -2
  171. package/src/source/adapters/prisma.ts +5 -2
  172. package/src/source/conformance.ts +18 -0
  173. package/src/source/index.ts +14 -0
  174. package/src/source/types.ts +3 -14
  175. package/src/syncLog/contract.ts +1 -2
  176. package/src/transport/httpClient.ts +8 -1
  177. package/src/wire/accountResponses.ts +5 -6
  178. package/src/wire/auth.ts +30 -3
  179. package/src/wire/delta.ts +11 -0
  180. package/dist/ai-sdk/coordinatedTool.d.ts.map +0 -1
  181. package/dist/ai-sdk/coordinatedTool.js.map +0 -1
@@ -80,9 +80,39 @@ export function selectModels<S extends SchemaRecord, K extends keyof S & string>
80
80
  // subset still points at the original full schema rather than an intermediate
81
81
  // one. `schemaHash` ignores this field, so re-projecting stays deterministic.
82
82
  sourceSchemaHash: schema.sourceSchemaHash ?? schemaHash(schema),
83
+ // What was dropped, unioned with what the source had already dropped, so a
84
+ // subset-of-a-subset still names every model of the original full schema
85
+ // that this client cannot reach. Clients turn these names into throwing
86
+ // accessors — see `omittedModelError`.
87
+ omittedModels: [
88
+ ...new Set([
89
+ ...(schema.omittedModels ?? []),
90
+ ...Object.keys(schema.models).filter((k) => !keep.has(k)),
91
+ ]),
92
+ ].sort(),
83
93
  };
84
94
  }
85
95
 
96
+ /**
97
+ * The error a client raises when a caller reaches for a model its schema
98
+ * projection left out.
99
+ *
100
+ * Defined beside the projection because the two are one contract: what
101
+ * `selectModels` records in `omittedModels`, a client answers with this error.
102
+ * Without it the access reads as `undefined` and the caller crashes one
103
+ * property later with a bare TypeError that never names the model or the fix —
104
+ * which is exactly what happens when an app compiles against the full source
105
+ * schema but runs a projection, the one misuse the type system cannot see.
106
+ */
107
+ export function omittedModelError(model: string): AbloValidationError {
108
+ return new AbloValidationError(
109
+ `The ${model} model is not in this client's schema. ` +
110
+ `The schema is a projection that leaves ${model} out, so this client cannot read or write it. ` +
111
+ `Add '${model}' to the projection's model list to use it here.`,
112
+ { code: 'model_not_in_schema', param: model },
113
+ );
114
+ }
115
+
86
116
  /**
87
117
  * `omitModels` is `selectModels` from the other side: keep every model EXCEPT
88
118
  * the named ones. Use it when an app is the general case and a separate app
@@ -16,7 +16,6 @@
16
16
  import type { ParticipantKind, ConfirmationState } from '../log/syncDeltaRow.js';
17
17
  import type { ParticipantRef } from '../wire/delta.js';
18
18
  import type { CommitExecutionResultInput } from '../wire/commit.js';
19
- import type { Environment } from '../environment.js';
20
19
  import type { ReadDependency, TrackDependency } from '../coordination/schema.js';
21
20
 
22
21
  export interface CommitContext {
@@ -27,21 +26,15 @@ export interface CommitContext {
27
26
  */
28
27
  participantKind: ParticipantKind;
29
28
  organizationId: string;
29
+ /** Immutable branch selected by the authenticated credential. */
30
+ branchId: string;
30
31
  /**
31
32
  * Project scope used to route source-mode storage. When omitted, the commit
32
33
  * targets the organization's default project.
33
34
  */
34
35
  projectId?: string;
35
- /** Exact sandbox plane derived from the authenticated key, when present. */
36
- sandboxId?: string;
37
36
  /** Optional external account scope forwarded to storage resolvers. */
38
37
  accountScope?: string;
39
- /**
40
- * The environment this commit runs in. Source-mode adapters forward it to the
41
- * customer's handlers so that sandbox and production traffic can reach distinct
42
- * customer-owned stores.
43
- */
44
- environment?: Environment;
45
38
  /**
46
39
  * The sync groups this participant subscribes to, taken from the connection
47
40
  * upgrade or the capability token. Each is appended to every delta's `sync_groups`
@@ -50,12 +43,6 @@ export interface CommitContext {
50
43
  * commit fans out to just the organization and user groups.
51
44
  */
52
45
  syncGroups?: readonly string[];
53
- /**
54
- * When true, the commit does not add `org:<organizationId>` to a delta's sync
55
- * groups. Set this for sandbox writes, so that live organization subscribers do
56
- * not receive test-environment changes.
57
- */
58
- omitOrgSyncGroup?: boolean;
59
46
  /**
60
47
  * The participant on whose authority the actor acted. For a direct human commit
61
48
  * this equals the actor; for an agent commit it is the human at the root of the
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * The canonical interface every data-source backend implements, together with the bridge
3
3
  * that wires an implementation into the `dataSource()` HTTP handler. This package
4
- * defines the contract and ships adapters for three object-relational mappers —
4
+ * defines the contract and ships PostgreSQL bindings for three object-relational mappers —
5
5
  * {@link prismaDataSource}, {@link drizzleDataSource}, and {@link kyselyDataSource} —
6
6
  * each verified by shared mutation checks plus endpoint-only outbox checks. The
7
7
  * direct Kysely wrapper runs only the shared mutation contract. You can also
8
- * write your own.
8
+ * write your own through the `defineDatabaseAdapter` factory. PostgreSQL, the ORM
9
+ * binding, and WAL/outbox observation are recorded as separate profile axes;
10
+ * an ORM name never implies database portability.
9
11
  *
10
12
  * An endpoint adapter reads and writes your database, and it owns the transactional
11
13
  * outbox and idempotency bookkeeping as well, so you never write those by hand.
@@ -29,6 +31,7 @@ import type {
29
31
  EventsPage,
30
32
  Migration,
31
33
  } from './contract.js';
34
+ import type { DatabaseAdapterProfile } from './adapterProfile.js';
32
35
 
33
36
  /**
34
37
  * A single row keyed by the schema's field names (for example `operatorId`), not
@@ -57,6 +60,8 @@ export interface AdapterCommitResult {
57
60
  * transaction id is never used as that namespace.
58
61
  */
59
62
  export interface MutationAdapter {
63
+ /** Database, binding, and observation are separate, explicit axes. */
64
+ readonly profile: DatabaseAdapterProfile;
60
65
  readonly capabilities: AdapterCapabilities;
61
66
  /** Infrastructure migrations required by this wrapper's advertised capabilities. */
62
67
  migrations(): readonly Migration[];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Normalizes and validates a database adapter declaration.
3
+ *
4
+ * Database integrations should return through this function rather than
5
+ * assembling an object directly. It is the single place that relates database,
6
+ * binding, observation, and advertised guarantees.
7
+ */
8
+
9
+ import { AbloValidationError } from '../errors.js';
10
+ import type { MutationAdapter } from './adapter.js';
11
+ import { databaseAdapterProfileSchema } from './adapterProfile.js';
12
+
13
+ export function defineDatabaseAdapter<T extends MutationAdapter>(
14
+ adapter: T,
15
+ ): T {
16
+ const profile = databaseAdapterProfileSchema.parse(adapter.profile);
17
+ const { capabilities } = adapter;
18
+
19
+ if (!capabilities.transactions) {
20
+ throw new AbloValidationError(
21
+ `Database adapter "${profile.id}" cannot provide Ablo commits without atomic transactions`,
22
+ { code: 'source_adapter_misconfigured' },
23
+ );
24
+ }
25
+
26
+ if (
27
+ capabilities.postgresWalEcho === true &&
28
+ profile.database !== 'postgresql'
29
+ ) {
30
+ throw new AbloValidationError(
31
+ `Database adapter "${profile.id}" advertises a PostgreSQL WAL marker on ${profile.database}`,
32
+ { code: 'source_adapter_misconfigured' },
33
+ );
34
+ }
35
+
36
+ if (profile.observation.kind === 'postgres-wal') {
37
+ if (
38
+ profile.database !== 'postgresql' ||
39
+ capabilities.postgresWalEcho !== true ||
40
+ capabilities.outboxEvents
41
+ ) {
42
+ throw new AbloValidationError(
43
+ `Database adapter "${profile.id}" has an invalid PostgreSQL WAL capability combination`,
44
+ { code: 'source_adapter_misconfigured' },
45
+ );
46
+ }
47
+ }
48
+
49
+ if (
50
+ profile.observation.kind === 'transactional-outbox' &&
51
+ !capabilities.outboxEvents
52
+ ) {
53
+ throw new AbloValidationError(
54
+ `Database adapter "${profile.id}" declares an outbox observation profile without outbox events`,
55
+ { code: 'source_adapter_misconfigured' },
56
+ );
57
+ }
58
+
59
+ return { ...adapter, profile } as T;
60
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The three independent axes of a customer-database adapter.
3
+ *
4
+ * An ORM binding is not a database, and a database is not an observation
5
+ * strategy. Keeping them separate prevents a Prisma or Kysely adapter from
6
+ * looking portable while depending on PostgreSQL SQL or WAL underneath.
7
+ */
8
+
9
+ import { z } from 'zod';
10
+
11
+ export const databaseKindSchema = z.enum(['postgresql', 'memory']);
12
+ export type DatabaseKind = z.infer<typeof databaseKindSchema>;
13
+
14
+ export const adapterBindingSchema = z.enum([
15
+ 'prisma',
16
+ 'drizzle',
17
+ 'kysely',
18
+ 'memory',
19
+ 'custom',
20
+ ]);
21
+ export type AdapterBinding = z.infer<typeof adapterBindingSchema>;
22
+
23
+ export const observationProfileSchema = z.discriminatedUnion('kind', [
24
+ z.strictObject({
25
+ kind: z.literal('transactional-outbox'),
26
+ externalWrites: z.boolean(),
27
+ }),
28
+ z.strictObject({
29
+ kind: z.literal('postgres-wal'),
30
+ externalWrites: z.literal(true),
31
+ }),
32
+ ]);
33
+ export type ObservationProfile = z.infer<typeof observationProfileSchema>;
34
+
35
+ export const databaseAdapterProfileSchema = z.strictObject({
36
+ id: z.string().min(1),
37
+ database: databaseKindSchema,
38
+ binding: adapterBindingSchema,
39
+ observation: observationProfileSchema,
40
+ });
41
+ export type DatabaseAdapterProfile = z.infer<
42
+ typeof databaseAdapterProfileSchema
43
+ >;
44
+
45
+ export type PostgresBinding = Extract<
46
+ AdapterBinding,
47
+ 'prisma' | 'drizzle' | 'kysely' | 'custom'
48
+ >;
49
+
50
+ export function postgresAdapterProfile(
51
+ binding: PostgresBinding,
52
+ observation: 'transactional-outbox' | 'postgres-wal',
53
+ ): DatabaseAdapterProfile {
54
+ return {
55
+ id: `postgresql-${binding}-${observation}`,
56
+ database: 'postgresql',
57
+ binding,
58
+ observation:
59
+ observation === 'postgres-wal'
60
+ ? { kind: 'postgres-wal', externalWrites: true }
61
+ : { kind: 'transactional-outbox', externalWrites: false },
62
+ };
63
+ }
64
+
65
+ export function memoryAdapterProfile(): DatabaseAdapterProfile {
66
+ return {
67
+ id: 'memory-native-transactional-outbox',
68
+ database: 'memory',
69
+ binding: 'memory',
70
+ observation: {
71
+ kind: 'transactional-outbox',
72
+ externalWrites: false,
73
+ },
74
+ };
75
+ }
@@ -42,6 +42,8 @@ import type {
42
42
  DataSourceAdapter,
43
43
  Row,
44
44
  } from '../adapter.js';
45
+ import { defineDatabaseAdapter } from '../adapterFactory.js';
46
+ import { postgresAdapterProfile } from '../adapterProfile.js';
45
47
  import type { ChangeSet, EventsPage, Migration, Operation } from '../contract.js';
46
48
  import { outboxEventSchema } from '../contract.js';
47
49
  import { adapterTableMigrations } from '../migrations.js';
@@ -188,7 +190,8 @@ export function drizzleDataSource<S extends SchemaRecord>(
188
190
  return updated[0] ? toFields(mc, updated[0]) : { id, ...input };
189
191
  };
190
192
 
191
- return {
193
+ return defineDatabaseAdapter({
194
+ profile: postgresAdapterProfile('drizzle', 'transactional-outbox'),
192
195
  capabilities: {
193
196
  transactions: true,
194
197
  propose: false,
@@ -287,5 +290,5 @@ export function drizzleDataSource<S extends SchemaRecord>(
287
290
  );
288
291
  return { events, nextCursor: events.at(-1)?.cursor ?? null };
289
292
  },
290
- };
293
+ });
291
294
  }
@@ -25,6 +25,8 @@ import type {
25
25
  MutationAdapter,
26
26
  Row,
27
27
  } from '../adapter.js';
28
+ import { defineDatabaseAdapter } from '../adapterFactory.js';
29
+ import { postgresAdapterProfile } from '../adapterProfile.js';
28
30
  import type { ChangeSet, EventsPage, Migration } from '../contract.js';
29
31
  import {
30
32
  changeSetSchema,
@@ -199,7 +201,11 @@ export function createKyselyMutationAdapter(
199
201
  options: KyselyMutationAdapterOptions = {},
200
202
  ): MutationAdapter {
201
203
  const markerModelFor = options.markerModelFor ?? ((model: string) => model);
202
- return {
204
+ return defineDatabaseAdapter({
205
+ profile: postgresAdapterProfile(
206
+ 'kysely',
207
+ mode === 'direct' ? 'postgres-wal' : 'transactional-outbox',
208
+ ),
203
209
  capabilities: {
204
210
  transactions: true,
205
211
  propose: false,
@@ -281,7 +287,7 @@ export function createKyselyMutationAdapter(
281
287
  return { rows };
282
288
  });
283
289
  },
284
- };
290
+ });
285
291
  }
286
292
 
287
293
  /** Endpoint wrapper: mutation + ledger + outbox, with an optional marker. */
@@ -295,7 +301,7 @@ export function kyselyDataSource<S extends SchemaRecord>(
295
301
  'endpoint',
296
302
  );
297
303
 
298
- return {
304
+ return defineDatabaseAdapter({
299
305
  ...mutation,
300
306
  capabilities: { ...mutation.capabilities, outboxEvents: true },
301
307
 
@@ -324,7 +330,7 @@ export function kyselyDataSource<S extends SchemaRecord>(
324
330
  );
325
331
  return { events, nextCursor: events.at(-1)?.cursor ?? null };
326
332
  },
327
- };
333
+ });
328
334
  }
329
335
 
330
336
  /** Direct wrapper: mutation + ledger + required logical marker, never outbox. */
@@ -17,6 +17,8 @@ import type {
17
17
  DataSourceAdapter,
18
18
  Row,
19
19
  } from '../adapter.js';
20
+ import { defineDatabaseAdapter } from '../adapterFactory.js';
21
+ import { memoryAdapterProfile } from '../adapterProfile.js';
20
22
  import type { ChangeSet, EventsPage, Migration, Operation, OutboxEvent } from '../contract.js';
21
23
  import {
22
24
  assertSourceIdempotencyIntent,
@@ -81,7 +83,8 @@ export function memoryDataSource(): DataSourceAdapter {
81
83
  }
82
84
  };
83
85
 
84
- return {
86
+ return defineDatabaseAdapter({
87
+ profile: memoryAdapterProfile(),
85
88
  capabilities: {
86
89
  transactions: true,
87
90
  propose: false,
@@ -150,5 +153,5 @@ export function memoryDataSource(): DataSourceAdapter {
150
153
  nextCursor: page.at(-1)?.cursor ?? null,
151
154
  };
152
155
  },
153
- };
156
+ });
154
157
  }
@@ -21,6 +21,8 @@ import type {
21
21
  DataSourceAdapter,
22
22
  Row,
23
23
  } from '../adapter.js';
24
+ import { defineDatabaseAdapter } from '../adapterFactory.js';
25
+ import { postgresAdapterProfile } from '../adapterProfile.js';
24
26
  import type { ChangeSet, EventsPage, Migration, Operation, OutboxEvent } from '../contract.js';
25
27
  import { outboxEventSchema } from '../contract.js';
26
28
  import { adapterTableMigrations } from '../migrations.js';
@@ -163,7 +165,8 @@ export function prismaDataSource<S extends SchemaRecord>(
163
165
  }
164
166
  };
165
167
 
166
- return {
168
+ return defineDatabaseAdapter({
169
+ profile: postgresAdapterProfile('prisma', 'transactional-outbox'),
167
170
  capabilities: {
168
171
  transactions: true,
169
172
  propose: false,
@@ -278,5 +281,5 @@ export function prismaDataSource<S extends SchemaRecord>(
278
281
  nextCursor: events.at(-1)?.cursor ?? null,
279
282
  };
280
283
  },
281
- };
284
+ });
282
285
  }
@@ -59,6 +59,16 @@ export function mutationConformanceChecks(
59
59
  return options.prepareChange?.(value) ?? value;
60
60
  };
61
61
  return [
62
+ {
63
+ name: 'adapter declares database, binding, and observation independently',
64
+ run: async () => {
65
+ const adapter = await make();
66
+ assert.ok(adapter.profile.id.length > 0);
67
+ assert.ok(adapter.profile.database.length > 0);
68
+ assert.ok(adapter.profile.binding.length > 0);
69
+ assert.ok(adapter.profile.observation.kind.length > 0);
70
+ },
71
+ },
62
72
  {
63
73
  name: 'commit applies a CREATE and returns the canonical row',
64
74
  run: async () => {
@@ -174,6 +184,14 @@ export function mutationConformanceChecks(
174
184
  /** Guarantees that apply only to endpoint wrappers with an outbox/events feed. */
175
185
  export function endpointConformanceChecks(make: MakeAdapter): ConformanceCheck[] {
176
186
  return [
187
+ {
188
+ name: 'endpoint adapter declares transactional-outbox observation',
189
+ run: async () => {
190
+ const adapter = await make();
191
+ assert.equal(adapter.profile.observation.kind, 'transactional-outbox');
192
+ assert.equal(adapter.capabilities.outboxEvents, true);
193
+ },
194
+ },
177
195
  {
178
196
  name: 'endpoint replay does not append its correlated outbox event twice',
179
197
  run: async () => {
@@ -109,6 +109,20 @@ export {
109
109
  type AdapterCommitResult,
110
110
  type Row as AdapterRow,
111
111
  } from './adapter.js';
112
+ export {
113
+ adapterBindingSchema,
114
+ databaseAdapterProfileSchema,
115
+ databaseKindSchema,
116
+ memoryAdapterProfile,
117
+ observationProfileSchema,
118
+ postgresAdapterProfile,
119
+ type AdapterBinding,
120
+ type DatabaseAdapterProfile,
121
+ type DatabaseKind,
122
+ type ObservationProfile,
123
+ type PostgresBinding,
124
+ } from './adapterProfile.js';
125
+ export { defineDatabaseAdapter } from './adapterFactory.js';
112
126
  export {
113
127
  operationSchema,
114
128
  operationTypeSchema,
@@ -11,7 +11,6 @@
11
11
  */
12
12
 
13
13
  import { AbloValidationError } from '../errors.js';
14
- import type { Environment } from '../environment.js';
15
14
  import type { CommitOperationType, OnStaleMode } from '../coordination/schema.js';
16
15
  import type { ParticipantKind } from '../types/participant.js';
17
16
 
@@ -88,21 +87,11 @@ export interface SourceRequestContext {
88
87
  readonly participantId?: string;
89
88
  readonly participantKind?: ParticipantKind;
90
89
  readonly organizationId?: string;
91
- /** Trusted project plane selected by the authenticating credential. */
90
+ /** Immutable branch selected by the authenticating credential. */
91
+ readonly branchId?: string;
92
+ /** Trusted project selected by the authenticating credential. */
92
93
  readonly projectId?: string;
93
- /** Trusted sandbox plane selected by the authenticating credential. */
94
- readonly sandboxId?: string;
95
94
  readonly requiredSyncGroups?: readonly string[];
96
- /**
97
- * Whether this request runs in production or sandbox mode. Branch your
98
- * handlers on it — for example, read and write a separate sandbox database
99
- * when `mode === 'sandbox'` — so sandbox traffic exercises the same code
100
- * against isolated data. Keeping the two apart is your handler's
101
- * responsibility, since your database holds the canonical rows.
102
- *
103
- * Defaults to `'production'` when omitted.
104
- */
105
- readonly mode?: Environment;
106
95
  }
107
96
 
108
97
  /**
@@ -1,7 +1,6 @@
1
1
  /** Canonical sync-log identity and position contracts shared by client/server. */
2
2
 
3
3
  import { z } from 'zod';
4
- import { environmentSchema } from '../environment.js';
5
4
 
6
5
  export const deltaPositionSchema = z.number().int().nonnegative();
7
6
  export type DeltaPosition = z.infer<typeof deltaPositionSchema>;
@@ -21,7 +20,7 @@ export type ReplicationLSN = z.infer<typeof replicationLsnSchema>;
21
20
  export const commitDispatchMarkerSchema = z.strictObject({
22
21
  kind: z.literal('sync_deltas'),
23
22
  organizationId: z.string().min(1),
24
- environment: environmentSchema,
23
+ branchId: z.string().min(1),
25
24
  firstSyncId: deltaPositionSchema,
26
25
  lastSyncId: deltaPositionSchema,
27
26
  }).superRefine((value, ctx) => {
@@ -45,6 +45,7 @@ import type {
45
45
  ModelUpdateParams,
46
46
  } from '../resources/modelOperations.js';
47
47
  import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
48
+ import { omittedModelError } from '../schema/select.js';
48
49
  import type { ModelUpdater, ContentionOptions } from '../resources/functionalUpdate.js';
49
50
  import { AbloConnectionError, AbloValidationError } from '../errors.js';
50
51
 
@@ -319,6 +320,7 @@ export function createAbloHttpClient<S extends SchemaRecord>(
319
320
  modelTypenames: modelWireNames(schema.models),
320
321
  });
321
322
  const schemaModels = new Set(Object.keys(schema.models));
323
+ const omittedModels = new Set(schema.omittedModels ?? []);
322
324
  const models = new Map<
323
325
  string,
324
326
  HttpModelClient<Record<string, unknown>, Record<string, unknown>>
@@ -343,7 +345,12 @@ export function createAbloHttpClient<S extends SchemaRecord>(
343
345
  }
344
346
  // Only schema models become model accessors. A typo or retired top-level
345
347
  // member resolves to undefined instead of manufacturing a plausible client.
346
- return schemaModels.has(prop) ? model(prop) : undefined;
348
+ if (schemaModels.has(prop)) return model(prop);
349
+ // A model the schema projection left out is neither a typo nor a member:
350
+ // the caller compiled against the full source schema, so answer with the
351
+ // error that names the model and the fix rather than `undefined`.
352
+ if (omittedModels.has(prop)) throw omittedModelError(prop);
353
+ return undefined;
347
354
  },
348
355
  });
349
356
 
@@ -63,18 +63,17 @@ export type ProvisionedKey = z.infer<typeof provisionedKeySchema>;
63
63
 
64
64
  /**
65
65
  * What the CLI device flow gets back once a browser approval is exchanged for
66
- * credentials: the sandbox key `ablo push` authors with, and the restricted
67
- * production key that only observes.
66
+ * a project-scoped control-plane management credential. Runtime branch
67
+ * credentials are minted separately by `ablo dev` / `ablo branch credential`.
68
68
  *
69
- * `project` is the project both keys are scoped to, resolved SERVER-SIDE from
69
+ * `project` is the project the management credential is scoped to, resolved SERVER-SIDE from
70
70
  * the requested slug — null means the organization's default. The server's
71
71
  * answer is authoritative because it is what resolved the slug to an id, so the
72
- * CLI stores keys under the project named here rather than the one it asked
72
+ * CLI stores the credential under the project named here rather than the one it asked
73
73
  * for.
74
74
  */
75
75
  export const provisionKeyResponseSchema = z.object({
76
- test: provisionedKeySchema,
77
- live: provisionedKeySchema.optional(),
76
+ management: provisionedKeySchema,
78
77
  organizationId: z.string().optional(),
79
78
  organizationSlug: z.string().optional(),
80
79
  project: z.object({ id: z.string(), slug: z.string() }).nullable().optional(),
package/src/wire/auth.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { z } from 'zod';
14
- import { grantedOperationSchema } from '../auth/capability.js';
14
+ import { capabilityOperationSchema, grantedOperationSchema } from '../auth/capability.js';
15
15
  import { syncGroupInputSchema } from '../coordination/schema.js';
16
16
 
17
17
  /** The participant this session acts as. */
@@ -48,8 +48,25 @@ export const ephemeralKeyRequestSchema = z.object({
48
48
  schemaOwnerOrgId: z.string().min(1).optional(),
49
49
  /** Narrow the session to these sync groups. */
50
50
  syncGroups: z.array(syncGroupInputSchema).readonly().optional(),
51
- /** Required least-privilege allowlist. */
52
- operations: z.array(grantedOperationSchema).min(1),
51
+ /**
52
+ * Least-privilege allowlist, named model by model. For a caller that knows
53
+ * the schema the session will resolve. Exactly one of this and
54
+ * {@link ephemeralKeyRequestSchema.shape.activeSchemaOperations} is required.
55
+ */
56
+ operations: z.array(grantedOperationSchema).min(1).optional(),
57
+ /**
58
+ * The same allowlist expressed as verbs alone, for a caller that CANNOT know
59
+ * the models: an identity service minting sessions for organizations whose
60
+ * schemas it does not own has no way to name them, and a grant is checked
61
+ * against the session's schema at mint time, so naming another schema's
62
+ * models fails every time.
63
+ *
64
+ * The server expands these across the models in the session's active schema
65
+ * and stores the concrete result, so what lands on the credential is the same
66
+ * enumerated allowlist as the field above. A verb is granted only where the
67
+ * model admits it — an immutable model takes `read` and nothing else.
68
+ */
69
+ activeSchemaOperations: z.array(capabilityOperationSchema).min(1).optional(),
53
70
  /** Lifetime in seconds. Capped by the server's maximum. */
54
71
  ttlSeconds: z.number().int().positive().optional(),
55
72
  /** A human-readable tag recorded with the key, for debugging. */
@@ -63,6 +80,16 @@ export const ephemeralKeyRequestSchema = z.object({
63
80
  'schemaProjectId and schemaOwnerOrgId must be provided together',
64
81
  path: ['schemaProjectId'],
65
82
  },
83
+ ).refine(
84
+ (request) =>
85
+ (request.operations === undefined) !== (request.activeSchemaOperations === undefined),
86
+ {
87
+ // Exactly one, never both and never neither. Neither would leave the
88
+ // credential with an empty allowlist, which reads as UNRESTRICTED at the
89
+ // gate rather than as nothing; both would leave two answers to one question.
90
+ message: 'provide either operations or activeSchemaOperations, not both',
91
+ path: ['operations'],
92
+ },
66
93
  );
67
94
  export type EphemeralKeyRequest = z.infer<typeof ephemeralKeyRequestSchema>;
68
95
 
package/src/wire/delta.ts CHANGED
@@ -66,6 +66,17 @@ export type SyncDeltaAction = z.infer<typeof syncDeltaActionSchema>;
66
66
  * serialized string (used by the group-change frames on `G` and `S` deltas), or
67
67
  * `null` on deletes. This is wider than the stored row's payload — which is only a
68
68
  * row or null — because the group-change frames encode their payload as a string.
69
+ *
70
+ * The record branch costs more than its contract suggests: it walks
71
+ * `Reflect.ownKeys` and rebuilds the payload into a fresh object on every parse,
72
+ * so client-side validation scales with a row's field count on a path that runs
73
+ * once per delta (2.7 microseconds at 23 fields, against 0.15 for a predicate).
74
+ * Replacing it was tried and reverted, because every faster spelling gives up
75
+ * something this contract owns: `z.custom` cannot be represented in JSON Schema
76
+ * and breaks the derived spec, and `z.unknown().refine` derives to an empty
77
+ * schema and infers `unknown`, which would widen {@link WireDeltaData}. A
78
+ * predicate must also match this schema's real acceptance, which is
79
+ * plain-object: `z.record` rejects `Date`, `Map`, and class instances.
69
80
  */
70
81
  export const wireDeltaDataSchema = z
71
82
  .union([z.record(z.string(), z.unknown()), z.string()])
@@ -1 +0,0 @@
1
- {"version":3,"file":"coordinatedTool.d.ts","sourceRoot":"","sources":["../../src/ai-sdk/coordinatedTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAGH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACxF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE/D,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC;;;;;OAKG;IACH,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,0CAA0C;IAC1C,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB,CAAC,MAAM,EAAE,CAAC;IAC/C,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,iEAAiE;IACjE,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,kHAAkH;IAClH,KAAK,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,kGAAkG;IAClG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD;AAID;;;;GAIG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,GAAG,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzD,qCAAqC;IACrC,QAAQ,CAAC,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,CACJ,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EACxB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC1B,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CACtD;AAED,wBAAgB,eAAe,CAC7B,MAAM,EACN,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3B,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,sBAAsB,CAAC,MAAM,EAAE,CAAC,CAAC,wDA4EvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"coordinatedTool.js","sourceRoot":"","sources":["../../src/ai-sdk/coordinatedTool.ts"],"names":[],"mappings":"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"}