@abloatai/ablo 0.29.3 → 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.
Files changed (118) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +3 -2
  3. package/dist/BaseSyncedStore.js +3 -1
  4. package/dist/Database.js +29 -2
  5. package/dist/SyncClient.d.ts +5 -3
  6. package/dist/SyncClient.js +8 -4
  7. package/dist/ai-sdk/coordinatedTool.d.ts +0 -1
  8. package/dist/ai-sdk/coordinatedTool.js +0 -1
  9. package/dist/ai-sdk/coordinationContext.js +1 -5
  10. package/dist/cli.cjs +377 -84
  11. package/dist/client/Ablo.d.ts +1 -1
  12. package/dist/client/Ablo.js +24 -36
  13. package/dist/client/auth.d.ts +8 -18
  14. package/dist/client/auth.js +16 -96
  15. package/dist/client/createModelProxy.d.ts +16 -11
  16. package/dist/client/createModelProxy.js +162 -20
  17. package/dist/client/httpClient.d.ts +1 -1
  18. package/dist/client/httpTransport.js +263 -89
  19. package/dist/client/options.d.ts +0 -15
  20. package/dist/client/resourceTypes.d.ts +13 -26
  21. package/dist/client/schemaConfig.js +5 -0
  22. package/dist/client/writeOptionsSchema.d.ts +1 -0
  23. package/dist/client/writeOptionsSchema.js +11 -2
  24. package/dist/client/wsMutationExecutor.d.ts +3 -5
  25. package/dist/coordination/schema.d.ts +17 -13
  26. package/dist/coordination/schema.js +36 -10
  27. package/dist/errorCodes.d.ts +9 -4
  28. package/dist/errorCodes.js +10 -5
  29. package/dist/errors.js +4 -9
  30. package/dist/index.d.ts +4 -0
  31. package/dist/interfaces/index.d.ts +31 -19
  32. package/dist/schema/index.d.ts +1 -1
  33. package/dist/schema/index.js +4 -1
  34. package/dist/schema/schema.d.ts +13 -0
  35. package/dist/schema/select.js +7 -0
  36. package/dist/schema/syncDeltaRow.d.ts +31 -13
  37. package/dist/schema/syncDeltaRow.js +33 -15
  38. package/dist/schema/tenancy.d.ts +52 -16
  39. package/dist/schema/tenancy.js +86 -25
  40. package/dist/server/commit.d.ts +8 -24
  41. package/dist/server/index.d.ts +1 -0
  42. package/dist/server/index.js +1 -0
  43. package/dist/server/readConfig.d.ts +10 -0
  44. package/dist/source/adapter.d.ts +26 -15
  45. package/dist/source/adapter.js +5 -3
  46. package/dist/source/adapters/drizzle.js +35 -9
  47. package/dist/source/adapters/kysely.d.ts +33 -61
  48. package/dist/source/adapters/kysely.js +170 -177
  49. package/dist/source/adapters/kyselyMutationCore.d.ts +76 -0
  50. package/dist/source/adapters/kyselyMutationCore.js +125 -0
  51. package/dist/source/adapters/memory.d.ts +1 -1
  52. package/dist/source/adapters/memory.js +24 -10
  53. package/dist/source/adapters/prisma.js +35 -8
  54. package/dist/source/conformance.d.ts +26 -28
  55. package/dist/source/conformance.js +142 -69
  56. package/dist/source/contract.d.ts +46 -4
  57. package/dist/source/contract.js +66 -5
  58. package/dist/source/factory.d.ts +3 -3
  59. package/dist/source/factory.js +24 -3
  60. package/dist/source/idempotency.d.ts +46 -0
  61. package/dist/source/idempotency.js +129 -0
  62. package/dist/source/index.d.ts +6 -4
  63. package/dist/source/index.js +5 -3
  64. package/dist/source/migrations.d.ts +9 -5
  65. package/dist/source/migrations.js +39 -6
  66. package/dist/source/types.d.ts +88 -14
  67. package/dist/source/types.js +16 -0
  68. package/dist/surface.d.ts +1 -1
  69. package/dist/surface.js +0 -1
  70. package/dist/sync/BootstrapFetcher.js +7 -0
  71. package/dist/sync/SyncWebSocket.d.ts +5 -27
  72. package/dist/sync/awaitClaimGrant.d.ts +6 -0
  73. package/dist/sync/awaitClaimGrant.js +12 -2
  74. package/dist/sync/commitFrames.d.ts +2 -4
  75. package/dist/sync/commitFrames.js +1 -0
  76. package/dist/sync/createClaimStream.d.ts +1 -1
  77. package/dist/sync/createClaimStream.js +13 -15
  78. package/dist/sync/deltaPipeline.d.ts +1 -1
  79. package/dist/sync/deltaPipeline.js +1 -1
  80. package/dist/sync/participants.d.ts +2 -3
  81. package/dist/sync/participants.js +2 -2
  82. package/dist/sync/wsFrameHandlers.js +35 -15
  83. package/dist/testing/mocks/MockMutationExecutor.d.ts +21 -0
  84. package/dist/testing/mocks/MockMutationExecutor.js +53 -1
  85. package/dist/transactions/TransactionQueue.d.ts +61 -9
  86. package/dist/transactions/TransactionQueue.js +502 -114
  87. package/dist/transactions/commitEnvelope.d.ts +4 -0
  88. package/dist/transactions/commitEnvelope.js +25 -0
  89. package/dist/transactions/commitPayload.d.ts +23 -5
  90. package/dist/transactions/commitPayload.js +3 -0
  91. package/dist/transactions/deltaConfirmation.d.ts +2 -1
  92. package/dist/transactions/deltaConfirmation.js +22 -4
  93. package/dist/transactions/durableWriteStore.d.ts +8 -1
  94. package/dist/transactions/httpCommitEnvelope.d.ts +3 -1
  95. package/dist/transactions/httpCommitEnvelope.js +30 -0
  96. package/dist/transactions/replayValidation.d.ts +4 -0
  97. package/dist/transactions/replayValidation.js +6 -0
  98. package/dist/types/streams.d.ts +29 -17
  99. package/dist/wire/commit.d.ts +671 -0
  100. package/dist/wire/commit.js +300 -0
  101. package/dist/wire/delta.d.ts +4 -0
  102. package/dist/wire/delta.js +9 -0
  103. package/dist/wire/frames.d.ts +17 -45
  104. package/dist/wire/index.d.ts +2 -0
  105. package/dist/wire/index.js +4 -0
  106. package/docs/agent-messaging.md +4 -5
  107. package/docs/agents.md +1 -2
  108. package/docs/api-keys.md +1 -2
  109. package/docs/client-behavior.md +4 -6
  110. package/docs/coordination.md +64 -9
  111. package/docs/data-sources.md +4 -7
  112. package/docs/integration-guide.md +6 -8
  113. package/docs/migration.md +0 -3
  114. package/docs/quickstart.md +7 -8
  115. package/docs/schema-contract.md +7 -8
  116. package/package.json +4 -4
  117. package/dist/client/registerDataSource.d.ts +0 -19
  118. package/dist/client/registerDataSource.js +0 -66
@@ -9,5 +9,6 @@
9
9
  */
10
10
  export type { Row, ReadResult, SyncCursor, DataAdapterCapabilities, ProposalResult, ReadRequest, ChangeSet, SyncResult, DataAdapter, ProposableDataAdapter, AdapterResolver, } from './adapter.js';
11
11
  export type { CommitContext, CommitResult } from './commit.js';
12
+ export { commitExecutionResultSchema } from '../wire/commit.js';
12
13
  export { storageModeSchema, type StorageMode } from './storageMode.js';
13
14
  export type { ColumnOverride, BootstrapModel } from './readConfig.js';
@@ -1 +1,2 @@
1
+ export { commitExecutionResultSchema } from '../wire/commit.js';
1
2
  export { storageModeSchema } from './storageMode.js';
@@ -35,6 +35,16 @@ export interface BootstrapModel {
35
35
  orderBy?: string;
36
36
  /** Whether the table has organization_id. Default: true. */
37
37
  orgScoped?: boolean;
38
+ /**
39
+ * The model derives its tenant from the connected data source (`policy:
40
+ * { by: 'source' }`) rather than a row column. It carries no tenant predicate on a
41
+ * log plane, so it is excluded from the bootstrap set entirely until the
42
+ * write-through connect path can resolve the org from the source registration —
43
+ * serving it now would return every row to every tenant. Kept on the type as a
44
+ * defense-in-depth marker so a source-scoped model that reaches `getBootstrapData`
45
+ * fails closed rather than bootstrapping unscoped.
46
+ */
47
+ sourceScoped?: boolean;
38
48
  /** Physical tenancy column (default `organization_id`, configurable per model). */
39
49
  orgColumn?: string;
40
50
  /**
@@ -3,10 +3,12 @@
3
3
  * that wires an implementation into the `dataSource()` HTTP handler. This package
4
4
  * defines the contract and ships adapters for three object-relational mappers —
5
5
  * {@link prismaDataSource}, {@link drizzleDataSource}, and {@link kyselyDataSource} —
6
- * each verified by the shared conformance suite. You can also write your own.
6
+ * each verified by shared mutation checks plus endpoint-only outbox checks. The
7
+ * direct Kysely wrapper runs only the shared mutation contract. You can also
8
+ * write your own.
7
9
  *
8
- * An adapter reads and writes your database, and it owns the transactional outbox
9
- * and idempotency bookkeeping as well, so you never write those by hand:
10
+ * An endpoint adapter reads and writes your database, and it owns the transactional
11
+ * outbox and idempotency bookkeeping as well, so you never write those by hand:
10
12
  *
11
13
  * export const POST = dataSource({
12
14
  * schema, apiKey: process.env.ABLO_API_KEY!,
@@ -39,31 +41,40 @@ export type AdapterReadRequest = {
39
41
  readonly query?: SourceListQuery;
40
42
  readonly scope?: SourceRequestContext;
41
43
  };
42
- /** What {@link DataSourceAdapter.commit} returns: the rows as they stand after the write. */
44
+ /** What {@link MutationAdapter.commit} returns: the rows as they stand after the write. */
43
45
  export interface AdapterCommitResult {
44
46
  /** The affected rows after the write. The change log is derived from these. */
45
47
  readonly rows: readonly Row[];
46
48
  }
47
49
  /**
48
- * The interface an adapter implements to serve one data source. `read` and
49
- * `commit` read from and write to your database, `events` reads the outbox that
50
- * `commit` appends to, and `migrations` supplies the SQL that creates the adapter's
51
- * own two tables. `capabilities` advertises which optional features the adapter
52
- * supports.
50
+ * Guarantees shared by direct and endpoint mutation wrappers. Both apply row DML
51
+ * and write the permanent idempotency ledger in one transaction. The scoped,
52
+ * server-authored `correlationId` is the ledger key; a caller-authored operation
53
+ * transaction id is never used as that namespace.
53
54
  */
54
- export interface DataSourceAdapter {
55
+ export interface MutationAdapter {
55
56
  readonly capabilities: AdapterCapabilities;
56
- /** The table-creation SQL the adapter needs for its own tables, `ablo_idempotency` and `ablo_outbox`. */
57
+ /** Infrastructure migrations required by this wrapper's advertised capabilities. */
57
58
  migrations(): readonly Migration[];
58
59
  /** The rows matching a load or list request. */
59
60
  read(req: AdapterReadRequest): Promise<readonly Row[]>;
60
61
  /**
61
- * Applies a change set in one transaction, keyed for idempotency by `clientTxId`.
62
- * Replaying the same `clientTxId` returns the original rows without applying the
63
- * change again. The matching `ablo_outbox` rows are written in the same
64
- * transaction as the data rows, so the outbox can never drift from the data.
62
+ * Applies a change set in one transaction, keyed by its scoped `correlationId`.
63
+ * Replaying the same correlation and canonical request hash returns the original
64
+ * rows without applying DML again; a different hash fails closed.
65
65
  */
66
66
  commit(change: ChangeSet): Promise<AdapterCommitResult>;
67
+ }
68
+ /**
69
+ * Endpoint-only extension of {@link MutationAdapter}. Its commit writes one
70
+ * correlated outbox event per operation in the same transaction as the DML and
71
+ * ledger. Direct wrappers deliberately do not implement this interface: WAL is
72
+ * their authoritative source feed and they must never write `ablo_outbox`.
73
+ */
74
+ export interface DataSourceAdapter extends MutationAdapter {
75
+ readonly capabilities: AdapterCapabilities & {
76
+ readonly outboxEvents: true;
77
+ };
67
78
  /** Reads outbox events after `cursor` (`null` starts from the beginning), up to `limit` events. */
68
79
  events(cursor: string | null, limit: number): Promise<EventsPage>;
69
80
  }
@@ -3,10 +3,12 @@
3
3
  * that wires an implementation into the `dataSource()` HTTP handler. This package
4
4
  * defines the contract and ships adapters for three object-relational mappers —
5
5
  * {@link prismaDataSource}, {@link drizzleDataSource}, and {@link kyselyDataSource} —
6
- * each verified by the shared conformance suite. You can also write your own.
6
+ * each verified by shared mutation checks plus endpoint-only outbox checks. The
7
+ * direct Kysely wrapper runs only the shared mutation contract. You can also
8
+ * write your own.
7
9
  *
8
- * An adapter reads and writes your database, and it owns the transactional outbox
9
- * and idempotency bookkeeping as well, so you never write those by hand:
10
+ * An endpoint adapter reads and writes your database, and it owns the transactional
11
+ * outbox and idempotency bookkeeping as well, so you never write those by hand:
10
12
  *
11
13
  * export const POST = dataSource({
12
14
  * schema, apiKey: process.env.ABLO_API_KEY!,
@@ -37,9 +37,11 @@ import { AbloValidationError } from '../../errors.js';
37
37
  import { sql } from 'drizzle-orm';
38
38
  import { outboxEventSchema } from '../contract.js';
39
39
  import { adapterTableMigrations } from '../migrations.js';
40
+ import { assertSourceIdempotencyIntent, assertSourceIdempotencyRetention, sourceChangeIntentHash, } from '../idempotency.js';
40
41
  import { toSchemaJSON } from '../../schema/serialize.js';
41
42
  import { camelToSnake, snakeToCamel } from '../../schema/ddl.js';
42
43
  import { tenancyColumn } from '../../schema/tenancy.js';
44
+ import { ABLO_POSTGRES_COMMIT_ECHO_PREFIX } from '../types.js';
43
45
  function rowsOf(result) {
44
46
  return Array.isArray(result) ? result : result.rows;
45
47
  }
@@ -131,7 +133,13 @@ export function drizzleDataSource(db, schema) {
131
133
  return updated[0] ? toFields(mc, updated[0]) : { id, ...input };
132
134
  };
133
135
  return {
134
- capabilities: { transactions: true, propose: false, schemaIntrospection: true },
136
+ capabilities: {
137
+ transactions: true,
138
+ propose: false,
139
+ schemaIntrospection: true,
140
+ postgresWalEcho: true,
141
+ outboxEvents: true,
142
+ },
135
143
  migrations() {
136
144
  return adapterTableMigrations();
137
145
  },
@@ -147,33 +155,49 @@ export function drizzleDataSource(db, schema) {
147
155
  return rows.map((r) => toFields(mc, r));
148
156
  },
149
157
  async commit(change) {
158
+ const requestHash = sourceChangeIntentHash(change);
150
159
  return db.transaction(async (tx) => {
151
- const cached = rowsOf(await tx.execute(sql `SELECT response FROM ablo_idempotency WHERE client_tx_id = ${change.clientTxId} LIMIT 1`));
160
+ const cached = rowsOf(await tx.execute(sql `SELECT response, request_hash AS "requestHash", expires_at AS "expiresAt"
161
+ FROM ablo_idempotency
162
+ WHERE client_tx_id = ${change.correlationId} LIMIT 1`));
152
163
  const cachedRow = cached[0];
153
- if (cachedRow)
164
+ if (cachedRow) {
165
+ assertSourceIdempotencyIntent(cachedRow.requestHash, requestHash);
166
+ assertSourceIdempotencyRetention(cachedRow.expiresAt);
154
167
  return { rows: cachedRow.response };
168
+ }
155
169
  const rows = [];
156
170
  for (const [index, op] of change.operations.entries()) {
157
171
  const row = await applyOperation(tx, op);
158
172
  rows.push(row);
159
173
  const entityId = String(row.id ?? rowId(op));
160
174
  await tx.execute(sql `
161
- INSERT INTO ablo_outbox (id, model, entity_id, type, data, client_tx_id, occurred_at)
175
+ INSERT INTO ablo_outbox (
176
+ id, model, entity_id, type, data,
177
+ correlation_id, transaction_id, occurred_at
178
+ )
162
179
  VALUES (
163
- ${`${change.clientTxId}:${index}`}, ${op.model}, ${entityId}, ${op.type},
164
- ${op.type === 'DELETE' ? null : JSON.stringify(row)}::jsonb, ${change.clientTxId}, ${Date.now()}
180
+ ${`${change.correlationId}:${index}`}, ${op.model}, ${entityId}, ${op.type},
181
+ ${op.type === 'DELETE' ? null : JSON.stringify(row)}::jsonb,
182
+ ${change.correlationId}, ${op.transactionId ?? null}, ${Date.now()}
165
183
  )`);
166
184
  }
167
185
  await tx.execute(sql `
168
- INSERT INTO ablo_idempotency (client_tx_id, response)
169
- VALUES (${change.clientTxId}, ${JSON.stringify(rows)}::jsonb)`);
186
+ INSERT INTO ablo_idempotency (client_tx_id, response, request_hash)
187
+ VALUES (
188
+ ${change.correlationId}, ${JSON.stringify(rows)}::jsonb, ${requestHash}
189
+ )`);
190
+ if (change.echo?.kind === 'postgres-wal') {
191
+ await tx.execute(sql `SELECT pg_logical_emit_message(true, ${ABLO_POSTGRES_COMMIT_ECHO_PREFIX}, ${change.echo.payload})`);
192
+ }
170
193
  return { rows };
171
194
  });
172
195
  },
173
196
  async events(cursor, limit) {
174
197
  const after = cursor ?? '0';
175
198
  const rows = rowsOf(await db.execute(sql `
176
- SELECT cursor, id, model, entity_id, type, data, organization_id, client_tx_id, occurred_at
199
+ SELECT cursor, id, model, entity_id, type, data, organization_id,
200
+ client_tx_id, correlation_id, transaction_id, occurred_at
177
201
  FROM ablo_outbox WHERE cursor > ${after} ORDER BY cursor ASC LIMIT ${limit}`));
178
202
  const events = rows.map((r) => outboxEventSchema.parse({
179
203
  id: r.id,
@@ -183,6 +207,8 @@ export function drizzleDataSource(db, schema) {
183
207
  data: r.data ?? null,
184
208
  organizationId: r.organization_id ?? null,
185
209
  clientTxId: r.client_tx_id ?? null,
210
+ correlationId: r.correlation_id ?? null,
211
+ transactionId: r.transaction_id ?? null,
186
212
  occurredAt: r.occurred_at != null ? Number(r.occurred_at) : null,
187
213
  cursor: String(r.cursor),
188
214
  }));
@@ -1,70 +1,42 @@
1
1
  /**
2
- * The Kysely adapter for the data-source interface. It implements the same
3
- * {@link DataSourceAdapter} contract as {@link prismaDataSource} and
4
- * {@link drizzleDataSource} and passes the same conformance suite, built against
5
- * Kysely's query builder:
6
- * - `db.transaction().execute(async (trx) => …)` runs an interactive transaction.
7
- * - `insertInto` / `updateTable` / `deleteFrom` / `selectFrom` with
8
- * `returningAll()` form the fluent query. Table and column names are plain
9
- * strings, so the adapter needs no raw-SQL tag and imports nothing from
10
- * `kysely`; it depends only on the structural {@link KyselyLike} shape, the
11
- * same approach the Prisma adapter takes with {@link PrismaLike}.
2
+ * Kysely wrappers for Ablo's two customer-database write transports.
12
3
  *
13
- * Table and column names come from your schema. Kysely passes the column names you
14
- * give it straight through to SQL, so, like the Drizzle adapter, this one derives
15
- * every name from the same rule the table provisioner uses:
16
- * table = `model.tableName ?? key`
17
- * column = `fieldMeta.column ?? camelToSnake(field)` (plus the tenancy column)
18
- * This keeps the tables `ablo migrate` creates (for example `operator_id`) and the
19
- * columns this adapter uses in agreement. The adapter is the translation boundary:
20
- * the rows it accepts and returns are keyed by field name, while the physical
21
- * columns are snake_case.
4
+ * Both wrappers compose the same exported {@link createKyselyMutationCore}:
22
5
  *
23
- * A note on JSON columns: the outbox `data` and idempotency `response` values are
24
- * passed as JSON strings. Postgres infers the parameter type from the target
25
- * `jsonb` column, so the conversion happens on the server and works across drivers,
26
- * without the `::jsonb` cast that only raw SQL allows.
6
+ * - {@link kyselyDataSource} is the endpoint adapter. Its transaction contains
7
+ * row DML, the permanent idempotency ledger, and correlated outbox events. A
8
+ * Postgres logical marker remains optional for compatibility.
9
+ * - {@link kyselyDirectMutation} is the engine-side direct wrapper. Its
10
+ * transaction contains row DML, the same ledger, and a required logical
11
+ * marker. It never creates, writes, or serves `ablo_outbox`.
12
+ *
13
+ * The ledger reservation happens before DML via `INSERT ... ON CONFLICT DO
14
+ * NOTHING`. Postgres waits on a concurrent uncommitted owner of the same key;
15
+ * after that owner commits, the loser reads and hash-checks its durable response.
16
+ * If the owner rolls back, the waiter acquires the reservation and performs the
17
+ * mutation. This gives same-key concurrency one database arbiter and one effect.
27
18
  */
28
- import type { DataSourceAdapter, Row } from '../adapter.js';
29
19
  import type { Schema, SchemaRecord } from '../../schema/schema.js';
20
+ import type { DataSourceAdapter, MutationAdapter } from '../adapter.js';
21
+ import { type KyselyLike, type KyselyMutationCore } from './kyselyMutationCore.js';
22
+ export { createKyselyMutationCore, kyselyOperationRowId, type KyselyCompiledQuery, type KyselyDeleteBuilder, type KyselyInsertBuilder, type KyselyInsertValuesBuilder, type KyselyLike, type KyselyMutationCore, type KyselyReturningExecutable, type KyselySelectBuilder, type KyselyTransactionBuilder, type KyselyUpdateBuilder, type KyselyUpdateSetBuilder, } from './kyselyMutationCore.js';
23
+ type KyselyMutationMode = 'endpoint' | 'direct';
30
24
  /**
31
- * The subset of a Kysely instance, or transaction handle, that the adapter calls.
32
- * It is structural by design: declaring the members with method shorthand lets a
33
- * real `Kysely<DB>` whose parameters are narrowed to `keyof DB` — stay assignable
34
- * under TypeScript's method-parameter bivariance, the same way {@link PrismaLike}
35
- * accepts a real `PrismaClient`.
25
+ * Build the shared transaction policy around one Kysely mutation core. Exported
26
+ * for engine integrations that need to inject a prebuilt core while retaining
27
+ * the exact same ledger and mapping semantics as the endpoint adapter.
36
28
  */
37
- export interface KyselyLike {
38
- selectFrom(table: string): KyselySelectBuilder;
39
- insertInto(table: string): KyselyInsertBuilder;
40
- updateTable(table: string): KyselyUpdateBuilder;
41
- deleteFrom(table: string): KyselyDeleteBuilder;
42
- transaction(): KyselyTransactionBuilder;
43
- }
44
- export interface KyselyTransactionBuilder {
45
- execute<T>(fn: (trx: KyselyLike) => Promise<T>): Promise<T>;
46
- }
47
- export interface KyselySelectBuilder {
48
- selectAll(): KyselySelectBuilder;
49
- where(column: string, operator: string, value: unknown): KyselySelectBuilder;
50
- orderBy(column: string, direction: 'asc' | 'desc'): KyselySelectBuilder;
51
- limit(limit: number): KyselySelectBuilder;
52
- execute(): Promise<readonly Row[]>;
53
- }
54
- export interface KyselyInsertBuilder {
55
- values(row: Row): KyselyInsertBuilder;
56
- returningAll(): KyselyInsertBuilder;
57
- execute(): Promise<readonly Row[]>;
58
- }
59
- export interface KyselyUpdateBuilder {
60
- set(patch: Row): KyselyUpdateBuilder;
61
- where(column: string, operator: string, value: unknown): KyselyUpdateBuilder;
62
- returningAll(): KyselyUpdateBuilder;
63
- execute(): Promise<readonly Row[]>;
64
- }
65
- export interface KyselyDeleteBuilder {
66
- where(column: string, operator: string, value: unknown): KyselyDeleteBuilder;
67
- returningAll(): KyselyDeleteBuilder;
68
- execute(): Promise<readonly Row[]>;
29
+ export interface KyselyMutationAdapterOptions {
30
+ /**
31
+ * Translate a mutation operation's authoring model key into the canonical
32
+ * marker vocabulary (the schema typename). Direct wrappers must supply the
33
+ * schema-backed translation; identity is only correct when a schema's
34
+ * typenames equal its keys.
35
+ */
36
+ readonly markerModelFor?: (operationModel: string) => string;
69
37
  }
38
+ export declare function createKyselyMutationAdapter(db: KyselyLike, core: KyselyMutationCore, mode: KyselyMutationMode, options?: KyselyMutationAdapterOptions): MutationAdapter;
39
+ /** Endpoint wrapper: mutation + ledger + outbox, with an optional marker. */
70
40
  export declare function kyselyDataSource<S extends SchemaRecord>(db: KyselyLike, schema: Schema<S>): DataSourceAdapter;
41
+ /** Direct wrapper: mutation + ledger + required logical marker, never outbox. */
42
+ export declare function kyselyDirectMutation<S extends SchemaRecord>(db: KyselyLike, schema: Schema<S>): MutationAdapter;