@abloatai/transaction 0.50.0 → 0.52.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 (170) hide show
  1. package/dist/ablo.d.ts +1 -1
  2. package/dist/ablo.js +1 -1
  3. package/dist/ai-sdk/updateTool.d.ts +1 -1
  4. package/dist/ai-sdk/updateTool.js +1 -1
  5. package/dist/auth/capability.d.ts +2 -2
  6. package/dist/auth/capability.js +1 -1
  7. package/dist/auth/index.d.ts +9 -3
  8. package/dist/auth/index.d.ts.map +1 -1
  9. package/dist/auth/index.js +25 -20
  10. package/dist/auth/index.js.map +1 -1
  11. package/dist/auth/schemas.d.ts +24 -1
  12. package/dist/auth/schemas.d.ts.map +1 -1
  13. package/dist/auth/schemas.js +8 -2
  14. package/dist/auth/schemas.js.map +1 -1
  15. package/dist/auth/sessionIssuanceProtocol.d.ts +41 -0
  16. package/dist/auth/sessionIssuanceProtocol.d.ts.map +1 -0
  17. package/dist/auth/sessionIssuanceProtocol.js +48 -0
  18. package/dist/auth/sessionIssuanceProtocol.js.map +1 -0
  19. package/dist/auth/sessionMint.d.ts.map +1 -1
  20. package/dist/auth/sessionMint.js +3 -2
  21. package/dist/auth/sessionMint.js.map +1 -1
  22. package/dist/branches.d.ts +4 -4
  23. package/dist/coordination/schema.d.ts +3 -2
  24. package/dist/coordination/schema.d.ts.map +1 -1
  25. package/dist/coordination/schema.js +7 -2
  26. package/dist/coordination/schema.js.map +1 -1
  27. package/dist/errorCodes.d.ts +3 -2
  28. package/dist/errorCodes.d.ts.map +1 -1
  29. package/dist/errorCodes.js +4 -3
  30. package/dist/errorCodes.js.map +1 -1
  31. package/dist/errors.d.ts +1 -1
  32. package/dist/errors.js +1 -1
  33. package/dist/resources/httpResources.d.ts +11 -2
  34. package/dist/resources/httpResources.d.ts.map +1 -1
  35. package/dist/resources/modelOperations.d.ts +6 -6
  36. package/dist/schema/ddl.d.ts.map +1 -1
  37. package/dist/schema/ddl.js +9 -12
  38. package/dist/schema/ddl.js.map +1 -1
  39. package/dist/schema/field.d.ts +3 -3
  40. package/dist/schema/field.js +2 -2
  41. package/dist/schema/fieldRef.d.ts +1 -1
  42. package/dist/schema/generate.d.ts +2 -3
  43. package/dist/schema/generate.d.ts.map +1 -1
  44. package/dist/schema/generate.js +2 -8
  45. package/dist/schema/generate.js.map +1 -1
  46. package/dist/schema/index.d.ts +2 -2
  47. package/dist/schema/index.js +3 -3
  48. package/dist/schema/model.d.ts +7 -7
  49. package/dist/schema/model.js +2 -2
  50. package/dist/schema/openapi.d.ts.map +1 -1
  51. package/dist/schema/openapi.js +4 -7
  52. package/dist/schema/openapi.js.map +1 -1
  53. package/dist/schema/queries.d.ts +1 -1
  54. package/dist/schema/queries.js +1 -1
  55. package/dist/schema/relation.d.ts +8 -8
  56. package/dist/schema/relation.js +5 -5
  57. package/dist/schema/schema.d.ts +24 -37
  58. package/dist/schema/schema.d.ts.map +1 -1
  59. package/dist/schema/schema.js +15 -28
  60. package/dist/schema/schema.js.map +1 -1
  61. package/dist/schema/sugar.d.ts +7 -7
  62. package/dist/schema/sugar.js +5 -5
  63. package/dist/server/commit.d.ts +5 -1
  64. package/dist/server/commit.d.ts.map +1 -1
  65. package/dist/source/adapters/drizzle.d.ts.map +1 -1
  66. package/dist/source/adapters/drizzle.js +5 -0
  67. package/dist/source/adapters/drizzle.js.map +1 -1
  68. package/dist/source/adapters/kysely.d.ts.map +1 -1
  69. package/dist/source/adapters/kysely.js +33 -8
  70. package/dist/source/adapters/kysely.js.map +1 -1
  71. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  72. package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
  73. package/dist/source/adapters/kyselyMutationCore.js +58 -20
  74. package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
  75. package/dist/source/adapters/memory.d.ts.map +1 -1
  76. package/dist/source/adapters/memory.js +5 -0
  77. package/dist/source/adapters/memory.js.map +1 -1
  78. package/dist/source/adapters/prisma.d.ts.map +1 -1
  79. package/dist/source/adapters/prisma.js +6 -1
  80. package/dist/source/adapters/prisma.js.map +1 -1
  81. package/dist/source/conformance.js +21 -21
  82. package/dist/source/contract.d.ts +17 -0
  83. package/dist/source/contract.d.ts.map +1 -1
  84. package/dist/source/contract.js +31 -0
  85. package/dist/source/contract.js.map +1 -1
  86. package/dist/source/index.d.ts +1 -1
  87. package/dist/source/index.d.ts.map +1 -1
  88. package/dist/source/index.js +1 -1
  89. package/dist/source/index.js.map +1 -1
  90. package/dist/source/types.d.ts +1 -0
  91. package/dist/source/types.d.ts.map +1 -1
  92. package/dist/source/types.js.map +1 -1
  93. package/dist/transactionLayer.d.ts +3 -3
  94. package/dist/transactionLayer.js +3 -3
  95. package/dist/transport/httpClient.d.ts +6 -2
  96. package/dist/transport/httpClient.d.ts.map +1 -1
  97. package/dist/transport/httpClient.js.map +1 -1
  98. package/dist/transport/httpTransport.d.ts +1 -0
  99. package/dist/transport/httpTransport.d.ts.map +1 -1
  100. package/dist/transport/httpTransport.js +50 -4
  101. package/dist/transport/httpTransport.js.map +1 -1
  102. package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
  103. package/dist/transport/wsFrameHandlers.js +3 -0
  104. package/dist/transport/wsFrameHandlers.js.map +1 -1
  105. package/dist/types/streams.d.ts +2 -2
  106. package/dist/wire/accountResponses.js +2 -2
  107. package/dist/wire/accountResponses.js.map +1 -1
  108. package/dist/wire/auth.d.ts.map +1 -1
  109. package/dist/wire/auth.js +4 -3
  110. package/dist/wire/auth.js.map +1 -1
  111. package/dist/wire/commit.d.ts +144 -0
  112. package/dist/wire/commit.d.ts.map +1 -1
  113. package/dist/wire/commit.js +22 -1
  114. package/dist/wire/commit.js.map +1 -1
  115. package/dist/wire/frames.d.ts +4 -0
  116. package/dist/wire/frames.d.ts.map +1 -1
  117. package/dist/wire/inboundFrames.d.ts +22 -0
  118. package/dist/wire/inboundFrames.d.ts.map +1 -1
  119. package/dist/wire/index.d.ts +2 -2
  120. package/dist/wire/index.d.ts.map +1 -1
  121. package/dist/wire/index.js +1 -1
  122. package/dist/wire/index.js.map +1 -1
  123. package/dist/wire/protocol.d.ts +1 -1
  124. package/dist/wire/protocol.js +1 -1
  125. package/package.json +1 -1
  126. package/src/ablo.ts +1 -1
  127. package/src/ai-sdk/updateTool.ts +1 -1
  128. package/src/auth/capability.ts +3 -3
  129. package/src/auth/index.ts +54 -22
  130. package/src/auth/schemas.ts +16 -2
  131. package/src/auth/sessionIssuanceProtocol.ts +57 -0
  132. package/src/auth/sessionMint.ts +3 -2
  133. package/src/coordination/schema.ts +7 -2
  134. package/src/errorCodes.ts +10 -4
  135. package/src/errors.ts +1 -1
  136. package/src/resources/httpResources.ts +11 -2
  137. package/src/resources/modelOperations.ts +6 -6
  138. package/src/schema/ddl.ts +9 -13
  139. package/src/schema/field.ts +3 -3
  140. package/src/schema/fieldRef.ts +1 -1
  141. package/src/schema/generate.ts +2 -8
  142. package/src/schema/index.ts +3 -3
  143. package/src/schema/model.ts +7 -7
  144. package/src/schema/openapi.ts +4 -7
  145. package/src/schema/queries.ts +1 -1
  146. package/src/schema/relation.ts +8 -8
  147. package/src/schema/residency.ts +0 -1
  148. package/src/schema/schema.ts +33 -58
  149. package/src/schema/sugar.ts +7 -7
  150. package/src/server/commit.ts +5 -1
  151. package/src/source/adapters/drizzle.ts +5 -0
  152. package/src/source/adapters/kysely.ts +48 -10
  153. package/src/source/adapters/kyselyMutationCore.ts +69 -22
  154. package/src/source/adapters/memory.ts +5 -0
  155. package/src/source/adapters/prisma.ts +6 -1
  156. package/src/source/conformance.ts +21 -21
  157. package/src/source/contract.ts +34 -0
  158. package/src/source/index.ts +2 -0
  159. package/src/source/types.ts +1 -0
  160. package/src/transactionLayer.ts +3 -3
  161. package/src/transport/httpClient.ts +6 -2
  162. package/src/transport/httpTransport.ts +59 -4
  163. package/src/transport/wsFrameHandlers.ts +3 -0
  164. package/src/types/global.ts +1 -1
  165. package/src/types/streams.ts +2 -2
  166. package/src/wire/accountResponses.ts +2 -2
  167. package/src/wire/auth.ts +4 -3
  168. package/src/wire/commit.ts +31 -1
  169. package/src/wire/index.ts +3 -0
  170. package/src/wire/protocol.ts +1 -1
package/src/schema/ddl.ts CHANGED
@@ -117,7 +117,7 @@ export function sqlType(fieldType: ModelJSON['fields'][string]['type']): string
117
117
  }
118
118
  }
119
119
 
120
- const BASE_COLUMNS = new Set(['id', 'organization_id', 'created_by', 'created_at', 'updated_at']);
120
+ const BASE_COLUMNS = new Set(['id']);
121
121
 
122
122
  // ── Foreign keys (relation-driven, sync-safe) ────────────────────────────────
123
123
 
@@ -154,11 +154,11 @@ interface ForeignKeyDdl {
154
154
  * in by setting `{ fk: true }`.
155
155
  *
156
156
  * The `fk` marker is deliberately separate from `parent`: `parent` controls
157
- * sync-group fan-out and visibility, while `fk` requests physical referential
157
+ * sync-group fan-out and visibility, while `fk` requests database referential
158
158
  * integrity in the database. A relation sets `fk` only when its target lives in
159
159
  * the same database, is written in the same commit, and is a strong, contained
160
160
  * entity. Soft references — provenance or template pointers such as
161
- * `sourceSlideId` or `templateId` — stay plain columns; a hard foreign key there
161
+ * `sourceDocumentId` or `templateId` — stay plain columns; a hard foreign key there
162
162
  * would reject a write that points across scopes or at an absent row and break
163
163
  * sync.
164
164
  *
@@ -204,7 +204,7 @@ function foreignKeyStatements(
204
204
  // The model's provisioned column set — guard so a relation whose FK field
205
205
  // isn't actually declared (no column) never produces a broken ALTER.
206
206
  const orgCol = tenancyColumn(resolveTenancy(model));
207
- const columns = new Set<string>(['id', 'created_by', 'created_at', 'updated_at']);
207
+ const columns = new Set<string>(['id']);
208
208
  if (orgCol) columns.add(orgCol);
209
209
  for (const [fieldName, meta] of Object.entries(model.fields)) {
210
210
  columns.add(meta.column ?? camelToSnake(fieldName));
@@ -275,8 +275,7 @@ export function generateProvisionPlan(
275
275
  // declared boundary is what makes the set of tables a customer's own
276
276
  // database receives derivable instead of hand-coded.
277
277
  if ((model.plane ?? 'tenant') === 'control') continue;
278
-
279
- // Default the physical table to the model key when `tableName` is omitted —
278
+ // Default the database table to the model key when `tableName` is omitted —
280
279
  // same fallback the migration path uses (`tableOfModel: m.tableName ?? key`).
281
280
  // Without this, a schema that doesn't set `tableName` (e.g. the `ablo init`
282
281
  // starter) provisions zero tables.
@@ -294,13 +293,10 @@ export function generateProvisionPlan(
294
293
  // no tenancy column — they're scoped via a parent FK or not at all.
295
294
  const orgCol = tenancyColumn(resolveTenancy(model));
296
295
  const baseColumns = [
297
- ` ${q('id')} TEXT PRIMARY KEY,`,
298
- ...(orgCol ? [` ${q(orgCol)} TEXT NOT NULL,`] : []),
299
- ` ${q('created_by')} TEXT,`,
300
- ` ${q('created_at')} TIMESTAMPTZ NOT NULL DEFAULT NOW(),`,
301
- ` ${q('updated_at')} TIMESTAMPTZ NOT NULL DEFAULT NOW()`,
296
+ ` ${q('id')} TEXT PRIMARY KEY`,
297
+ ...(orgCol ? [` ${q(orgCol)} TEXT NOT NULL`] : []),
302
298
  ];
303
- statements.push(`CREATE TABLE IF NOT EXISTS ${qt} (\n${baseColumns.join('\n')}\n);`);
299
+ statements.push(`CREATE TABLE IF NOT EXISTS ${qt} (\n${baseColumns.join(',\n')}\n);`);
304
300
 
305
301
  for (const [fieldName, meta] of Object.entries(model.fields)) {
306
302
  const col = meta.column ?? camelToSnake(fieldName);
@@ -458,7 +454,7 @@ export function generateMigrationPlan(
458
454
  case 'rename_model': {
459
455
  const fromTable = tableOfModel(prev, step.from);
460
456
  const toTable = tableOfModel(next, step.to);
461
- // A logical model rename only needs SQL when the physical table name
457
+ // A logical model rename only needs SQL when the database table name
462
458
  // actually changes; if tableName is unchanged the rename is metadata.
463
459
  if (fromTable && toTable && fromTable !== toTable) {
464
460
  statements.push(`ALTER TABLE ${qtFor(fromTable)} RENAME TO ${q(toTable)};`);
@@ -9,7 +9,7 @@
9
9
  * Usage:
10
10
  * import { field } from '@abloatai/transaction/schema';
11
11
  *
12
- * const tasks = model({
12
+ * const items = model({
13
13
  * title: field.string(),
14
14
  * projectId: field.string().indexed(), // fluent chain
15
15
  * priority: field.number().optional(),
@@ -19,7 +19,7 @@
19
19
  * Or use Zod directly (no metadata, but still works):
20
20
  * import { z } from 'zod';
21
21
  *
22
- * const tasks = model({
22
+ * const items = model({
23
23
  * title: z.string(),
24
24
  * });
25
25
  */
@@ -199,7 +199,7 @@ export function resolveFieldMeta(schema: z.ZodType): FieldMeta {
199
199
 
200
200
  /** A Zod schema returned by a {@link field} builder — the underlying Zod type plus
201
201
  * two chainable methods: `indexed()` marks the field for a database index, and
202
- * `from(column)` overrides the physical column name it maps to. */
202
+ * `from(column)` overrides the database column name it maps to. */
203
203
  export type FieldBuilder<T extends z.ZodType> = T & {
204
204
  indexed(): FieldBuilder<T>;
205
205
  from(column: string): FieldBuilder<T>;
@@ -24,7 +24,7 @@
24
24
  * `string`.
25
25
  *
26
26
  * `model` rides along so a reference carries where it came from. Claiming
27
- * `users.email` through `ablo.tasks` is a mistake nothing can currently see.
27
+ * `users.email` through `ablo.items` is a mistake nothing can currently see.
28
28
  */
29
29
  export interface FieldRef<
30
30
  Model extends string = string,
@@ -10,9 +10,8 @@
10
10
  * that same schema, so the types you code against, the rows the database stores, and
11
11
  * the entities the sync layer moves all describe the same thing.
12
12
  *
13
- * The generated interface covers the base columns plus each declared field, with
14
- * enums emitted as string-literal unions. Relations are not expanded here — the
15
- * runtime SDK resolves those through its typed accessors.
13
+ * The generated interface covers storage-mode fields plus each declared field,
14
+ * with enums emitted as string-literal unions. Relations are not expanded here.
16
15
  */
17
16
 
18
17
  import type { FieldMeta } from './field.js';
@@ -70,12 +69,7 @@ export function generateTypes(schema: SchemaJSON): string {
70
69
  for (const [key, model] of Object.entries(schema.models)) {
71
70
  lines.push(`export interface ${nameByKey.get(key)!} {`);
72
71
  lines.push(' id: string;');
73
- lines.push(' createdAt: Date;');
74
- lines.push(' updatedAt: Date;');
75
- lines.push(' organizationId?: string;');
76
- lines.push(' createdBy?: string;');
77
72
  for (const [fieldName, meta] of Object.entries(model.fields)) {
78
- // A model that redeclares a base column doesn't double-emit it.
79
73
  if ((BASE_FIELDS as readonly string[]).includes(fieldName)) continue;
80
74
  lines.push(` ${fieldName}${meta.isOptional ? '?' : ''}: ${tsType(meta)};`);
81
75
  }
@@ -8,7 +8,7 @@
8
8
  * import { defineSchema, model, relation } from '@abloatai/transaction/schema';
9
9
  *
10
10
  * export const schema = defineSchema({
11
- * tasks: model({
11
+ * items: model({
12
12
  * title: z.string(),
13
13
  * status: z.enum(['todo', 'doing', 'done']).default('todo'),
14
14
  * projectId: z.string().optional(),
@@ -17,7 +17,7 @@
17
17
  * }),
18
18
  * });
19
19
  *
20
- * type Task = Model<typeof schema, 'tasks'>;
20
+ * type Item = Model<typeof schema, 'items'>;
21
21
  * ```
22
22
  */
23
23
 
@@ -27,7 +27,7 @@ export { z } from 'zod';
27
27
  // Field helpers (optional convenience wrappers around Zod)
28
28
  export { field, indexed, getFieldMeta, type FieldBuilder, type FieldMeta } from './field.js';
29
29
 
30
- // A field as a value rather than a quoted name — `schema.fields.tasks.status`.
30
+ // A field as a value rather than a quoted name — `schema.fields.items.status`.
31
31
  // Surfaces that name a field (claims today) take one of these so a name that
32
32
  // does not exist stops compiling.
33
33
  export {
@@ -8,7 +8,7 @@
8
8
  * import { z } from 'zod';
9
9
  * import { model, relation } from '@abloatai/transaction/schema';
10
10
  *
11
- * const tasks = model({
11
+ * const items = model({
12
12
  * title: z.string(),
13
13
  * status: z.enum(['todo', 'doing', 'done']).default('todo'),
14
14
  * projectId: z.string().optional(),
@@ -90,13 +90,13 @@ export interface ModelOptions {
90
90
  * Edges from this model to others, keyed by the accessor name they create. The
91
91
  * engine reads them to index foreign keys, to order inserts so a parent row lands
92
92
  * before the rows referencing it, and to generate the accessors that let you read
93
- * `task.project` or `project.tasks` directly. Built with the {@link relation}
93
+ * `item.project` or `project.items` directly. Built with the {@link relation}
94
94
  * factories.
95
95
  *
96
96
  * ```ts
97
97
  * relations: {
98
98
  * project: relation.belongsTo('projects', 'projectId'),
99
- * comments: relation.hasMany('comments', 'taskId'),
99
+ * comments: relation.hasMany('comments', 'itemId'),
100
100
  * }
101
101
  * ```
102
102
  */
@@ -110,8 +110,8 @@ export interface ModelOptions {
110
110
  /**
111
111
  * The wire type name for this model — the value that identifies its rows on the
112
112
  * wire (the `__typename`). The loader stamps it onto incoming rows and uses it to
113
- * find the matching model class. It defaults to the schema key (`tasks` →
114
- * `'tasks'`); set it explicitly when the wire shape uses different casing, such as
113
+ * find the matching model class. It defaults to the schema key (`items` →
114
+ * `'items'`); set it explicitly when the wire shape uses different casing, such as
115
115
  * schema key `block` mapping to typename `'Block'`.
116
116
  *
117
117
  * This is the one value that identifies the model on the wire; the client-side
@@ -178,7 +178,7 @@ export interface ModelOptions {
178
178
  * inbox. Accepts one role or many.
179
179
  *
180
180
  * ```ts
181
- * // dataroomMember: { userId, dataroomId }
181
+ * // archiveMember: { userId, archiveId }
182
182
  * groups: { grants: { subject: 'user', scope: 'workspace' } }
183
183
  * // a message → its addressee's inbox, keyed on `toId`
184
184
  * groups: { roles: [entityRole({ kind: 'inbox', source: 'toId' })] }
@@ -395,7 +395,7 @@ export interface ModelDef<
395
395
  * const tags = model({ label: z.string() });
396
396
  *
397
397
  * // Loaded at bootstrap (the default), with an edge to its project
398
- * const tasks = model({
398
+ * const items = model({
399
399
  * title: z.string(),
400
400
  * status: z.enum(['todo', 'doing', 'done']).default('todo'),
401
401
  * projectId: z.string().optional(),
@@ -208,8 +208,9 @@ const namedResp = (description: string, name: string): Json =>
208
208
  *
209
209
  * - `$schema` belongs on a standalone JSON Schema document, not every embedded
210
210
  * OpenAPI Schema Object;
211
- * - `propertyNames: { type: 'string' }` is redundant for JSON objects and is
212
- * unsupported by Stainless;
211
+ * - `propertyNames` constraining keys to strings is redundant for JSON objects,
212
+ * whatever else it constrains, and is unsupported by Stainless. The key rule
213
+ * still binds at runtime, where the Zod schema parses;
213
214
  * - `additionalProperties: {}` means "any JSON value", but Stainless requires
214
215
  * the equivalent, explicit `true`.
215
216
  *
@@ -228,7 +229,6 @@ function portableSchema(value: unknown): unknown {
228
229
  key === 'propertyNames' &&
229
230
  typeof child === 'object' &&
230
231
  child !== null &&
231
- Object.keys(child).length === 1 &&
232
232
  (child as Record<string, unknown>).type === 'string'
233
233
  ) {
234
234
  continue;
@@ -267,7 +267,7 @@ const modelParam = (): Json => ({
267
267
  in: 'path',
268
268
  required: true,
269
269
  schema: { type: 'string' },
270
- description: 'A model name from your pushed schema, e.g. `task`.',
270
+ description: 'A model name from your pushed schema, e.g. `item`.',
271
271
  });
272
272
  const genericRow = (): Json => ({ type: 'object', additionalProperties: true });
273
273
 
@@ -591,9 +591,6 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
591
591
  description:
592
592
  'Call this first: every other route needs the key it returns. Requires a ' +
593
593
  'secret (`sk_`) key — a session cannot mint itself.',
594
- parameters: [
595
- { name: 'Idempotency-Key', in: 'header', schema: { type: 'string' }, description: 'Replay-safe key; the server returns the cached credential on retry.' },
596
- ],
597
594
  requestBody: jsonBody(derive(ephemeralKeyRequestSchema, 'input')),
598
595
  responses: { '201': jsonResp('The minted credential', derive(EphemeralKeyResponseSchema, 'output')) },
599
596
  },
@@ -44,7 +44,7 @@
44
44
  * the factory and defers the cross-reference check to schema assembly.
45
45
  *
46
46
  * - A query carries no `name` until `defineQueries()` assigns one from its
47
- * record key — the same pattern `defineSchema({ tasks: model(...) })`
47
+ * record key — the same pattern `defineSchema({ items: model(...) })`
48
48
  * uses, where the model's name is the record key rather than a field on
49
49
  * the factory.
50
50
  *
@@ -4,15 +4,15 @@
4
4
  * {@link relation} factories; the engine reads them to index foreign keys for
5
5
  * fast child lookups, to order inserts so a parent row lands before the rows
6
6
  * that reference it, and to generate the accessor properties that let you read
7
- * `task.project` or `project.tasks` directly.
7
+ * `item.project` or `project.items` directly.
8
8
  *
9
9
  * Usage:
10
10
  * import { relation } from '@abloatai/transaction/schema';
11
11
  *
12
- * const taskRelations = {
12
+ * const itemRelations = {
13
13
  * project: relation.belongsTo('projects', 'projectId'),
14
14
  * assignee: relation.belongsTo('users', 'assigneeId'),
15
- * comments: relation.hasMany('comments', 'taskId'),
15
+ * comments: relation.hasMany('comments', 'itemId'),
16
16
  * };
17
17
  */
18
18
 
@@ -28,9 +28,9 @@
28
28
  * way often, such as a block's `sectionId`.
29
29
  *
30
30
  * `enrich: true` auto-populates the parent reference on an incoming change
31
- * before the child data lands. A change to `Task { teamId: 't1' }` picks up the
31
+ * before the child data lands. A change to `Item { teamId: 't1' }` picks up the
32
32
  * already-loaded `teams:t1` record and attaches it as `data.team`, so you can
33
- * read `task.team` without a second lookup. Enrichment is best-effort: if the
33
+ * read `item.team` without a second lookup. Enrichment is best-effort: if the
34
34
  * parent has not loaded yet it quietly does nothing, and the child data still
35
35
  * applies.
36
36
  *
@@ -71,7 +71,7 @@ export interface BelongsToOptions {
71
71
  * provenance or as a template, such as `sourceSectionId` or `templateId`; doing
72
72
  * so would leak the record into an unrelated scope. The engine also cannot
73
73
  * infer the parent from whether a field is optional — many real parent keys
74
- * are optional, like a root folder or an inbox task — so you must declare the
74
+ * are optional, like a root folder or an inbox item — so you must declare the
75
75
  * parent edge explicitly.
76
76
  *
77
77
  * It reads naturally at the call site:
@@ -218,7 +218,7 @@ class RelationBuilder<
218
218
  export const relation = {
219
219
  /**
220
220
  * This model belongs to another model via a foreign key.
221
- * e.g., Task belongs to Project via projectId
221
+ * e.g., Item belongs to Project via projectId
222
222
  *
223
223
  * ```ts
224
224
  * // Simple reference (no options)
@@ -252,7 +252,7 @@ export const relation = {
252
252
 
253
253
  /**
254
254
  * This model has many of another model — for example, a project has many
255
- * tasks via `Task.projectId`.
255
+ * items via `Item.projectId`.
256
256
  *
257
257
  * At runtime the engine adds a getter to the parent model that returns every
258
258
  * child whose foreign key matches, and registers the foreign-key index on the
@@ -27,4 +27,3 @@ export type ModelResidency = z.infer<typeof residencySchema>;
27
27
 
28
28
  /** Default residency for a model that doesn't declare one — the tenant data plane. */
29
29
  export const DEFAULT_RESIDENCY: ModelResidency = 'tenant';
30
-
@@ -8,20 +8,20 @@
8
8
  * import { defineSchema, model, relation } from '@abloatai/transaction/schema';
9
9
  *
10
10
  * const schema = defineSchema({
11
- * tasks: model({
11
+ * items: model({
12
12
  * title: z.string(),
13
13
  * status: z.enum(['todo', 'doing', 'done']).default('todo'),
14
- * projectId: z.string().optional(),
14
+ * workspaceId: z.string().optional(),
15
15
  * }, {
16
- * project: relation.belongsTo('projects', 'projectId'),
16
+ * workspace: relation.belongsTo('workspaces', 'workspaceId'),
17
17
  * }),
18
18
  * });
19
19
  *
20
- * type Task = InferModel<typeof schema, 'tasks'>;
20
+ * type Item = InferModel<typeof schema, 'items'>;
21
21
  */
22
22
 
23
23
  import { z } from 'zod';
24
- import type { ModelDef, RelationRecord } from './model.js';
24
+ import type { ComputedRecord, ModelDef, RelationRecord } from './model.js';
25
25
  import type { RelationDef } from './relation.js';
26
26
  import { AbloValidationError } from '../errors.js';
27
27
  import type { IdentityRole } from './roles.js';
@@ -210,7 +210,7 @@ export interface DefineSchemaOptions {
210
210
  export type SchemaRecord = Record<string, ModelDef>;
211
211
 
212
212
  /**
213
- * Base fields every synced model gets automatically.
213
+ * The logical identity every model gets automatically.
214
214
  *
215
215
  * Exported (internal) so `parseSchema` can rebuild a model's validator the
216
216
  * same way `defineSchema` does — `baseFieldsSchema.merge(modelSchema)` — when
@@ -218,26 +218,14 @@ export type SchemaRecord = Record<string, ModelDef>;
218
218
  */
219
219
  export const baseFieldsSchema = z.object({
220
220
  id: z.string(),
221
- createdAt: z.date(),
222
- updatedAt: z.date(),
223
- organizationId: z.string().optional(),
224
- createdBy: z.string().optional(),
225
221
  });
226
222
 
227
223
  /**
228
- * The base-column names every model carries automatically the keys of
229
- * {@link baseFieldsSchema}, kept here as the single source of truth. Code
230
- * generation reads it to avoid emitting a base column twice, and
231
- * {@link defineSchema} uses it to reject a model that redeclares one, since
232
- * merging the user's field over the base field would produce a `string & Date`
233
- * type and break the build.
224
+ * The universal model field names. Audit and application metadata is declared
225
+ * by the application or carried by the commit record, never injected here.
234
226
  */
235
227
  export const BASE_FIELDS = [
236
228
  'id',
237
- 'createdAt',
238
- 'updatedAt',
239
- 'organizationId',
240
- 'createdBy',
241
229
  ] as const;
242
230
 
243
231
  /** The base fields type — pure data columns. */
@@ -262,15 +250,15 @@ export interface Schema<S extends SchemaRecord = SchemaRecord> {
262
250
 
263
251
  /**
264
252
  * Every declared field as a {@link FieldRef} — the field as a value rather
265
- * than a quoted name: `schema.fields.tasks.status`.
253
+ * than a quoted name: `schema.fields.items.status`.
266
254
  *
267
255
  * A surface that names a field takes one of these instead of a string, so a
268
256
  * name that does not exist stops compiling and a rename is a compile error at
269
257
  * every use. Claims are the first caller; anything else that has to say
270
258
  * "which field" should take a reference for the same reason.
271
259
  *
272
- * Base fields (`id`, `createdAt`, …) are not here: the schema does not
273
- * declare them, and a caller naming one means the row, not a part of it.
260
+ * Automatically supplied `id` is not here. Every application-declared field
261
+ * does appear here.
274
262
  */
275
263
  readonly fields: {
276
264
  readonly [K in keyof S & string]: S[K] extends ModelDef<infer Shape>
@@ -281,7 +269,7 @@ export interface Schema<S extends SchemaRecord = SchemaRecord> {
281
269
  : never;
282
270
  };
283
271
 
284
- /** Zod schemas with base fields merged in */
272
+ /** Zod schemas with logical identity merged into each declared model shape. */
285
273
  readonly validators: {
286
274
  readonly [K in keyof S]: S[K] extends ModelDef<infer Shape>
287
275
  ? z.ZodObject<Shape & typeof baseFieldsSchema.shape>
@@ -339,7 +327,7 @@ export interface Schema<S extends SchemaRecord = SchemaRecord> {
339
327
  * Includes base fields (id, createdAt, updatedAt, etc.)
340
328
  *
341
329
  * ```ts
342
- * type Task = InferModel<typeof schema, 'tasks'>;
330
+ * type Item = InferModel<typeof schema, 'items'>;
343
331
  * ```
344
332
  */
345
333
  /** The schema bound via `declare module … interface Register { Schema: … }`
@@ -351,15 +339,15 @@ type RegisteredSchema = import('../types/global.js').Register extends {
351
339
  : never;
352
340
 
353
341
  /**
354
- * The primary model-type helper. Once your project's `ablo/register.ts`
342
+ * The primary model-type helper. Once your workspace's `ablo/register.ts`
355
343
  * registers the schema, a single argument is all it takes:
356
344
  *
357
345
  * ```ts
358
- * type Task = Model<'tasks'>;
346
+ * type Item = Model<'items'>;
359
347
  * ```
360
348
  *
361
349
  * Without that registration, or for a second schema, pass the schema
362
- * explicitly: `Model<typeof schema, 'tasks'>`.
350
+ * explicitly: `Model<typeof schema, 'items'>`.
363
351
  */
364
352
  export type Model<A, B = never> = [B] extends [never]
365
353
  ? A extends keyof RegisteredSchema['models']
@@ -371,21 +359,14 @@ export type Model<A, B = never> = [B] extends [never]
371
359
 
372
360
  /**
373
361
  * The row type {@link Model} resolves to. Internal: `Model<typeof schema,
374
- * 'tasks'>` is the published spelling, because it reads as the domain rather
362
+ * 'items'>` is the published spelling, because it reads as the domain rather
375
363
  * than the machinery. This one is no longer exported from any subpath — it
376
364
  * stays because `Model` is defined in terms of it, not as a second name for
377
365
  * the same idea.
378
366
  */
379
367
  export type InferModel<S extends Schema, ModelName extends keyof S['models']> =
380
368
  S['models'][ModelName] extends ModelDef<infer Shape, infer R, infer C>
381
- ? // `Omit<…, keyof BaseModelFields>` so a model that (wrongly) redeclares a
382
- // reserved field degrades to "framework field wins" (e.g. `createdAt: Date`)
383
- // instead of intersecting to `never` (`string & Date`, which then surfaces
384
- // as a baffling "missing field" error three layers away). defineSchema also
385
- // throws on such a redeclaration at runtime (code `schema_reserved_field`);
386
- // this is the type-level belt to that runtime suspenders. No-op for correct
387
- // schemas — they never carry a base-field key, so nothing is omitted.
388
- Omit<z.infer<z.ZodObject<Shape>>, keyof BaseModelFields>
369
+ ? Omit<z.infer<z.ZodObject<Shape>>, 'id'>
389
370
  & BaseModelFields
390
371
  & BaseModelMethods
391
372
  & InferComputed<C>
@@ -452,14 +433,13 @@ export type InferRelations<S extends Schema, R extends RelationRecord> =
452
433
  */
453
434
  export type InferRow<S extends Schema, ModelName extends keyof S['models']> =
454
435
  S['models'][ModelName] extends ModelDef<infer Shape, RelationRecord, infer C>
455
- ? // Same reserved-field guard as InferModel — see the comment there.
456
- Omit<z.infer<z.ZodObject<Shape>>, keyof BaseModelFields>
436
+ ? Omit<z.infer<z.ZodObject<Shape>>, 'id'>
457
437
  & BaseModelFields
458
438
  & InferComputed<C>
459
439
  : never;
460
440
 
461
441
  /**
462
- * The reactive-row companion to {@link Model}. Once your project's
442
+ * The reactive-row companion to {@link Model}. Once your workspace's
463
443
  * `ablo/register.ts` registers the schema, a single argument is all it takes:
464
444
  *
465
445
  * ```ts
@@ -500,26 +480,22 @@ export type InferComputed<C> =
500
480
  : { readonly [K in keyof C]: C[K] extends (...args: any[]) => infer R ? R : never };
501
481
 
502
482
  /**
503
- * Infer the create input type. Only schema-defined fields are accepted
504
- * base fields (id, createdAt, updatedAt) are auto-generated by the SDK
505
- * and cannot be passed by the consumer.
483
+ * Infer the create input type. Application fields retain their declared input
484
+ * requirements and types.
506
485
  *
507
486
  * The only exception is `id`: consumers can optionally provide one for
508
487
  * client-generated IDs (useful for optimistic UI that needs to reference
509
488
  * the entity before the server confirms).
510
489
  *
511
490
  * ```ts
512
- * type CreateTask = InferCreate<typeof schema, 'tasks'>;
491
+ * type CreateItem = InferCreate<typeof schema, 'items'>;
513
492
  * // { title: string; status?: 'todo' | 'doing' | 'done'; id?: string }
514
- * // createdAt, updatedAt are NOT accepted — they're auto-generated
515
493
  * ```
516
494
  */
517
495
  export type InferCreate<S extends Schema, ModelName extends keyof S['models']> =
518
496
  S['models'][ModelName] extends ModelDef<infer Shape>
519
- ? // Same reserved-field guard as InferModel: drop any (wrongly) redeclared
520
- // base field so the input degrades to "framework field wins" rather than
521
- // collapsing to `never`. No-op for correct schemas.
522
- Omit<z.input<z.ZodObject<Shape>>, keyof BaseModelFields> & Partial<BaseModelFields>
497
+ ? Omit<z.input<z.ZodObject<Shape>>, 'id'>
498
+ & Partial<Pick<BaseModelFields, 'id'>>
523
499
  : never;
524
500
 
525
501
  /**
@@ -570,8 +546,8 @@ export interface DeleteId<S extends Schema, ModelName extends keyof S['models']>
570
546
  *
571
547
  * ```ts
572
548
  * const schema = defineSchema({
573
- * tasks: model({ title: z.string(), status: z.string().default('todo') }),
574
- * projects: model({ name: z.string() }),
549
+ * items: model({ title: z.string(), status: z.string().default('todo') }),
550
+ * workspaces: model({ name: z.string() }),
575
551
  * });
576
552
  * ```
577
553
  */
@@ -682,16 +658,15 @@ export function defineSchema<const S extends SchemaRecord>(
682
658
  // failure immediate and unambiguous.
683
659
  for (const fieldName of Object.keys(def.shape)) {
684
660
  assertRoundTrippableCamelCase(name, fieldName);
685
- // Reserved base columns are merged in below via `baseFieldsSchema.merge`,
686
- // and Zod `.merge` silently OVERWRITES the base field with the user's —
687
- // e.g. a model declaring `createdAt: z.string()` ends up with a field
688
- // typed `string & Date`, which breaks the build. Reject the collision at
689
- // definition time so the author sees an unambiguous error instead.
690
- if ((BASE_FIELDS as readonly string[]).includes(fieldName)) {
661
+ // Logical identity is the only universal model field. Application
662
+ // timestamps, tenancy columns and attribution columns are ordinary
663
+ // fields when an application declares them.
664
+ const reservedFields = ['id'];
665
+ if ((reservedFields as readonly string[]).includes(fieldName)) {
691
666
  throw new AbloValidationError(
692
667
  `[defineSchema] ${name}.${fieldName}: field \`${fieldName}\` collides with a ` +
693
668
  `reserved field that the SDK provides automatically ` +
694
- `(${BASE_FIELDS.join(', ')}). Remove it from your model — redeclaring it ` +
669
+ `(${reservedFields.join(', ')}). Remove it from your model — redeclaring it ` +
695
670
  `produces a \`string & Date\` type and breaks the build.`,
696
671
  { code: 'schema_reserved_field', param: `${name}.${fieldName}` },
697
672
  );
@@ -15,10 +15,10 @@
15
15
  *
16
16
  * @example
17
17
  * ```ts
18
- * tasks: mutable.lazy({ title: z.string() }, {
19
- * typename: 'Task', tableName: 'tasks',
18
+ * items: mutable.lazy({ title: z.string() }, {
19
+ * typename: 'Item', tableName: 'items',
20
20
  * relations: { ... },
21
- * computed: tasksComputed,
21
+ * computed: itemsComputed,
22
22
  * }),
23
23
  * ```
24
24
  */
@@ -47,13 +47,13 @@ export interface SugarOptions<
47
47
  /** Computed getters installed on the model class prototype. */
48
48
  computed?: C;
49
49
  /**
50
- * Wire `__typename` (PascalCase, e.g. `'Task'`). Defaults to the schema
50
+ * Wire `__typename` (PascalCase, e.g. `'Item'`). Defaults to the schema
51
51
  * key via `defineSchema` — override when the wire shape differs from
52
52
  * the camelCase schema key.
53
53
  */
54
54
  typename?: string;
55
55
  /**
56
- * The physical table name. Override it when the table name differs from the
56
+ * The database table name. Override it when the table name differs from the
57
57
  * snake_case of the typename — for example, a `Member` type stored in a table
58
58
  * named `'member'` rather than `'members'`.
59
59
  */
@@ -112,9 +112,9 @@ function build<
112
112
  * `{ mutable: true, load: X }` on `model()`.
113
113
  *
114
114
  * Pick the load suffix by data-access pattern:
115
- * - `.instant` — small, always-needed (Theme, Layout, StatusGroup)
115
+ * - `.instant` — small, always-needed control rows
116
116
  * - `.lazy` — large collections fetched on first query
117
- * (Block, Message, Task)
117
+ * (Block, Message, Item)
118
118
  */
119
119
  export const mutable = {
120
120
  instant: <
@@ -16,6 +16,7 @@
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 { CommitOperationResult } from '../wire/commit.js';
19
20
  import type { ReadDependency, TrackDependency } from '../coordination/schema.js';
20
21
  import type { EffectiveAuthority } from '../auth/capability.js';
21
22
 
@@ -99,4 +100,7 @@ export interface CommitContext {
99
100
  * lives with the HTTP/WS confirmation contract so queued correlation cannot drift
100
101
  * between cache, transport, and client.
101
102
  */
102
- export type CommitExecutionResult = CommitExecutionResultInput;
103
+ export type CommitExecutionResult = CommitExecutionResultInput & {
104
+ /** Response-time projection from the exact writing transaction. Never persisted. */
105
+ readonly operationResults?: readonly CommitOperationResult[];
106
+ };
@@ -151,6 +151,11 @@ export function drizzleDataSource<S extends SchemaRecord>(
151
151
  };
152
152
 
153
153
  const applyOperation = async (tx: DrizzleLike, op: Operation): Promise<Row> => {
154
+ if (op.where) {
155
+ throw new AbloValidationError('The Drizzle adapter does not support conditional operations', {
156
+ code: 'source_adapter_misconfigured',
157
+ });
158
+ }
154
159
  const mc = modelColumns(op.model);
155
160
  const table = sql.identifier(mc.table);
156
161
  const id = rowId(op);