@abloatai/transaction 0.51.0 → 0.53.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 (216) 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/apiKey.d.ts +0 -6
  6. package/dist/auth/apiKey.d.ts.map +1 -1
  7. package/dist/auth/apiKey.js +13 -60
  8. package/dist/auth/apiKey.js.map +1 -1
  9. package/dist/auth/baseUrl.d.ts +38 -0
  10. package/dist/auth/baseUrl.d.ts.map +1 -0
  11. package/dist/auth/baseUrl.js +126 -0
  12. package/dist/auth/baseUrl.js.map +1 -0
  13. package/dist/auth/capability.d.ts +2 -2
  14. package/dist/auth/capability.js +1 -1
  15. package/dist/auth/index.d.ts +15 -2
  16. package/dist/auth/index.d.ts.map +1 -1
  17. package/dist/auth/index.js +35 -21
  18. package/dist/auth/index.js.map +1 -1
  19. package/dist/auth/schemas.d.ts +24 -1
  20. package/dist/auth/schemas.d.ts.map +1 -1
  21. package/dist/auth/schemas.js +8 -2
  22. package/dist/auth/schemas.js.map +1 -1
  23. package/dist/auth/sessionIssuanceProtocol.d.ts +41 -0
  24. package/dist/auth/sessionIssuanceProtocol.d.ts.map +1 -0
  25. package/dist/auth/sessionIssuanceProtocol.js +48 -0
  26. package/dist/auth/sessionIssuanceProtocol.js.map +1 -0
  27. package/dist/auth/sessionMint.js +2 -2
  28. package/dist/branches.d.ts +34 -33
  29. package/dist/branches.d.ts.map +1 -1
  30. package/dist/branches.js +2 -4
  31. package/dist/branches.js.map +1 -1
  32. package/dist/coordination/schema.d.ts +3 -2
  33. package/dist/coordination/schema.d.ts.map +1 -1
  34. package/dist/coordination/schema.js +7 -2
  35. package/dist/coordination/schema.js.map +1 -1
  36. package/dist/errorCodes.d.ts +7 -3
  37. package/dist/errorCodes.d.ts.map +1 -1
  38. package/dist/errorCodes.js +8 -4
  39. package/dist/errorCodes.js.map +1 -1
  40. package/dist/errors.d.ts +1 -1
  41. package/dist/errors.js +1 -1
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/resources/httpResources.d.ts +35 -2
  46. package/dist/resources/httpResources.d.ts.map +1 -1
  47. package/dist/resources/httpResources.js +10 -1
  48. package/dist/resources/httpResources.js.map +1 -1
  49. package/dist/resources/modelOperations.d.ts +18 -6
  50. package/dist/resources/modelOperations.d.ts.map +1 -1
  51. package/dist/resources/modelOperations.js.map +1 -1
  52. package/dist/resources/where.d.ts +67 -2
  53. package/dist/resources/where.d.ts.map +1 -1
  54. package/dist/resources/where.js +73 -0
  55. package/dist/resources/where.js.map +1 -1
  56. package/dist/schema/ddl.d.ts.map +1 -1
  57. package/dist/schema/ddl.js +9 -12
  58. package/dist/schema/ddl.js.map +1 -1
  59. package/dist/schema/field.d.ts +3 -3
  60. package/dist/schema/field.js +2 -2
  61. package/dist/schema/fieldRef.d.ts +1 -1
  62. package/dist/schema/generate.d.ts +2 -3
  63. package/dist/schema/generate.d.ts.map +1 -1
  64. package/dist/schema/generate.js +2 -8
  65. package/dist/schema/generate.js.map +1 -1
  66. package/dist/schema/index.d.ts +2 -2
  67. package/dist/schema/index.js +3 -3
  68. package/dist/schema/model.d.ts +7 -7
  69. package/dist/schema/model.js +2 -2
  70. package/dist/schema/openapi.d.ts.map +1 -1
  71. package/dist/schema/openapi.js +63 -21
  72. package/dist/schema/openapi.js.map +1 -1
  73. package/dist/schema/queries.d.ts +1 -1
  74. package/dist/schema/queries.js +1 -1
  75. package/dist/schema/relation.d.ts +8 -8
  76. package/dist/schema/relation.js +5 -5
  77. package/dist/schema/schema.d.ts +24 -37
  78. package/dist/schema/schema.d.ts.map +1 -1
  79. package/dist/schema/schema.js +15 -28
  80. package/dist/schema/schema.js.map +1 -1
  81. package/dist/schema/sugar.d.ts +7 -7
  82. package/dist/schema/sugar.js +5 -5
  83. package/dist/server/adapter.d.ts +2 -0
  84. package/dist/server/adapter.d.ts.map +1 -1
  85. package/dist/server/commit.d.ts +5 -1
  86. package/dist/server/commit.d.ts.map +1 -1
  87. package/dist/server/index.d.ts +3 -2
  88. package/dist/server/index.d.ts.map +1 -1
  89. package/dist/server/index.js.map +1 -1
  90. package/dist/server/readConfig.d.ts +109 -57
  91. package/dist/server/readConfig.d.ts.map +1 -1
  92. package/dist/server/readConfig.js +19 -5
  93. package/dist/server/readConfig.js.map +1 -1
  94. package/dist/source/adapters/drizzle.d.ts.map +1 -1
  95. package/dist/source/adapters/drizzle.js +5 -0
  96. package/dist/source/adapters/drizzle.js.map +1 -1
  97. package/dist/source/adapters/kysely.d.ts.map +1 -1
  98. package/dist/source/adapters/kysely.js +33 -8
  99. package/dist/source/adapters/kysely.js.map +1 -1
  100. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  101. package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
  102. package/dist/source/adapters/kyselyMutationCore.js +58 -20
  103. package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
  104. package/dist/source/adapters/memory.d.ts.map +1 -1
  105. package/dist/source/adapters/memory.js +5 -0
  106. package/dist/source/adapters/memory.js.map +1 -1
  107. package/dist/source/adapters/prisma.d.ts.map +1 -1
  108. package/dist/source/adapters/prisma.js +6 -1
  109. package/dist/source/adapters/prisma.js.map +1 -1
  110. package/dist/source/conformance.js +21 -21
  111. package/dist/source/contract.d.ts +17 -0
  112. package/dist/source/contract.d.ts.map +1 -1
  113. package/dist/source/contract.js +31 -0
  114. package/dist/source/contract.js.map +1 -1
  115. package/dist/source/index.d.ts +1 -1
  116. package/dist/source/index.d.ts.map +1 -1
  117. package/dist/source/index.js +1 -1
  118. package/dist/source/index.js.map +1 -1
  119. package/dist/source/types.d.ts +1 -0
  120. package/dist/source/types.d.ts.map +1 -1
  121. package/dist/source/types.js.map +1 -1
  122. package/dist/transactionLayer.d.ts +3 -3
  123. package/dist/transactionLayer.js +3 -3
  124. package/dist/transport/httpClient.d.ts +13 -4
  125. package/dist/transport/httpClient.d.ts.map +1 -1
  126. package/dist/transport/httpClient.js +4 -2
  127. package/dist/transport/httpClient.js.map +1 -1
  128. package/dist/transport/httpTransport.d.ts +1 -0
  129. package/dist/transport/httpTransport.d.ts.map +1 -1
  130. package/dist/transport/httpTransport.js +79 -11
  131. package/dist/transport/httpTransport.js.map +1 -1
  132. package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
  133. package/dist/transport/wsFrameHandlers.js +3 -0
  134. package/dist/transport/wsFrameHandlers.js.map +1 -1
  135. package/dist/types/streams.d.ts +2 -2
  136. package/dist/wire/accountResponses.d.ts +18 -13
  137. package/dist/wire/accountResponses.d.ts.map +1 -1
  138. package/dist/wire/accountResponses.js +5 -11
  139. package/dist/wire/accountResponses.js.map +1 -1
  140. package/dist/wire/claims.d.ts +1 -0
  141. package/dist/wire/claims.d.ts.map +1 -1
  142. package/dist/wire/claims.js +7 -1
  143. package/dist/wire/claims.js.map +1 -1
  144. package/dist/wire/commit.d.ts +144 -0
  145. package/dist/wire/commit.d.ts.map +1 -1
  146. package/dist/wire/commit.js +22 -1
  147. package/dist/wire/commit.js.map +1 -1
  148. package/dist/wire/frames.d.ts +4 -0
  149. package/dist/wire/frames.d.ts.map +1 -1
  150. package/dist/wire/inboundFrames.d.ts +22 -0
  151. package/dist/wire/inboundFrames.d.ts.map +1 -1
  152. package/dist/wire/index.d.ts +3 -3
  153. package/dist/wire/index.d.ts.map +1 -1
  154. package/dist/wire/index.js +2 -2
  155. package/dist/wire/index.js.map +1 -1
  156. package/dist/wire/listEnvelope.d.ts +20 -0
  157. package/dist/wire/listEnvelope.d.ts.map +1 -1
  158. package/dist/wire/listEnvelope.js +20 -0
  159. package/dist/wire/listEnvelope.js.map +1 -1
  160. package/dist/wire/protocol.d.ts +1 -1
  161. package/dist/wire/protocol.js +1 -1
  162. package/package.json +1 -1
  163. package/src/ablo.ts +1 -1
  164. package/src/ai-sdk/updateTool.ts +1 -1
  165. package/src/auth/apiKey.ts +13 -61
  166. package/src/auth/baseUrl.ts +144 -0
  167. package/src/auth/capability.ts +3 -3
  168. package/src/auth/index.ts +62 -21
  169. package/src/auth/schemas.ts +16 -2
  170. package/src/auth/sessionIssuanceProtocol.ts +57 -0
  171. package/src/auth/sessionMint.ts +2 -2
  172. package/src/branches.ts +2 -4
  173. package/src/coordination/schema.ts +7 -2
  174. package/src/errorCodes.ts +29 -5
  175. package/src/errors.ts +1 -1
  176. package/src/index.ts +2 -0
  177. package/src/resources/httpResources.ts +45 -2
  178. package/src/resources/modelOperations.ts +18 -6
  179. package/src/resources/where.ts +70 -4
  180. package/src/schema/ddl.ts +9 -13
  181. package/src/schema/field.ts +3 -3
  182. package/src/schema/fieldRef.ts +1 -1
  183. package/src/schema/generate.ts +2 -8
  184. package/src/schema/index.ts +3 -3
  185. package/src/schema/model.ts +7 -7
  186. package/src/schema/openapi.ts +64 -21
  187. package/src/schema/queries.ts +1 -1
  188. package/src/schema/relation.ts +8 -8
  189. package/src/schema/residency.ts +0 -1
  190. package/src/schema/schema.ts +33 -58
  191. package/src/schema/sugar.ts +7 -7
  192. package/src/server/adapter.ts +2 -0
  193. package/src/server/commit.ts +5 -1
  194. package/src/server/index.ts +8 -2
  195. package/src/server/readConfig.ts +111 -57
  196. package/src/source/adapters/drizzle.ts +5 -0
  197. package/src/source/adapters/kysely.ts +48 -10
  198. package/src/source/adapters/kyselyMutationCore.ts +69 -22
  199. package/src/source/adapters/memory.ts +5 -0
  200. package/src/source/adapters/prisma.ts +6 -1
  201. package/src/source/conformance.ts +21 -21
  202. package/src/source/contract.ts +34 -0
  203. package/src/source/index.ts +2 -0
  204. package/src/source/types.ts +1 -0
  205. package/src/transactionLayer.ts +3 -3
  206. package/src/transport/httpClient.ts +23 -6
  207. package/src/transport/httpTransport.ts +88 -11
  208. package/src/transport/wsFrameHandlers.ts +3 -0
  209. package/src/types/global.ts +1 -1
  210. package/src/types/streams.ts +2 -2
  211. package/src/wire/accountResponses.ts +5 -11
  212. package/src/wire/claims.ts +7 -1
  213. package/src/wire/commit.ts +31 -1
  214. package/src/wire/index.ts +10 -1
  215. package/src/wire/listEnvelope.ts +23 -0
  216. 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(),
@@ -36,6 +36,7 @@ import {
36
36
  commitRecordListSchema,
37
37
  commitRecordWhereSchema,
38
38
  } from '../wire/commit.js';
39
+ import { CURSOR_PARAM, CURSOR_PARAM_ALIAS } from '../wire/listEnvelope.js';
39
40
  import {
40
41
  claimRequestSchema,
41
42
  claimHeartbeatRequestSchema,
@@ -185,6 +186,43 @@ function fieldSchema(f: FieldMeta): Json {
185
186
 
186
187
  const pascal = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
187
188
  const idParam = (): Json => ({ name: 'id', in: 'path', required: true, schema: { type: 'string' } });
189
+ const idempotencyKeyParam = (): Json => ({
190
+ name: 'Idempotency-Key',
191
+ in: 'header',
192
+ schema: { type: 'string', maxLength: 255 },
193
+ description: 'Replay identity. Reuse the same key only for an identical request.',
194
+ });
195
+ const collectionPageParams = (): Json[] => [
196
+ {
197
+ name: 'limit',
198
+ in: 'query',
199
+ schema: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
200
+ },
201
+ cursorParam(),
202
+ retiredCursorParam(),
203
+ ];
204
+
205
+ /** The cursor parameter, named from the wire contract that issues its value. */
206
+ const cursorParam = (): Json => ({
207
+ name: CURSOR_PARAM,
208
+ in: 'query',
209
+ schema: { type: 'string' },
210
+ description: 'The opaque next_cursor returned by the preceding page.',
211
+ });
212
+
213
+ /**
214
+ * The retired spelling, documented as deprecated so a caller still sending it
215
+ * can see it is going. Declared once: `queryParams` reaches for it too, and a
216
+ * second copy is how the model list came to document the same parameter without
217
+ * the deprecation the collection routes carried.
218
+ */
219
+ const retiredCursorParam = (): Json => ({
220
+ name: CURSOR_PARAM_ALIAS,
221
+ in: 'query',
222
+ deprecated: true,
223
+ schema: { type: 'string' },
224
+ description: `Deprecated spelling of ${CURSOR_PARAM}, honoured until it is removed. Send ${CURSOR_PARAM}.`,
225
+ });
188
226
  const jsonBody = (schema: Json): Json => ({
189
227
  required: true,
190
228
  content: { 'application/json': { schema } },
@@ -208,8 +246,9 @@ const namedResp = (description: string, name: string): Json =>
208
246
  *
209
247
  * - `$schema` belongs on a standalone JSON Schema document, not every embedded
210
248
  * OpenAPI Schema Object;
211
- * - `propertyNames: { type: 'string' }` is redundant for JSON objects and is
212
- * unsupported by Stainless;
249
+ * - `propertyNames` constraining keys to strings is redundant for JSON objects,
250
+ * whatever else it constrains, and is unsupported by Stainless. The key rule
251
+ * still binds at runtime, where the Zod schema parses;
213
252
  * - `additionalProperties: {}` means "any JSON value", but Stainless requires
214
253
  * the equivalent, explicit `true`.
215
254
  *
@@ -228,7 +267,6 @@ function portableSchema(value: unknown): unknown {
228
267
  key === 'propertyNames' &&
229
268
  typeof child === 'object' &&
230
269
  child !== null &&
231
- Object.keys(child).length === 1 &&
232
270
  (child as Record<string, unknown>).type === 'string'
233
271
  ) {
234
272
  continue;
@@ -267,7 +305,7 @@ const modelParam = (): Json => ({
267
305
  in: 'path',
268
306
  required: true,
269
307
  schema: { type: 'string' },
270
- description: 'A model name from your pushed schema, e.g. `task`.',
308
+ description: 'A model name from your pushed schema, e.g. `item`.',
271
309
  });
272
310
  const genericRow = (): Json => ({ type: 'object', additionalProperties: true });
273
311
 
@@ -313,16 +351,22 @@ function withGenericRows(derived: Json): Json {
313
351
  */
314
352
  function queryParams(schema: z.ZodType): Json[] {
315
353
  const props = (derive(schema, 'input').properties ?? {}) as Record<string, Json>;
316
- return Object.entries(props).map(([name, s]) => ({
317
- name,
318
- in: 'query',
319
- // Query strings arrive at the server as text, but OpenAPI describes the
320
- // caller-facing value before serialization. Generated clients should take
321
- // an integer here and encode it, not expose a stringly typed page size.
322
- schema: name === 'limit'
323
- ? { type: 'integer', minimum: 1 }
324
- : s,
325
- }));
354
+ return Object.entries(props).map(([name, s]) => {
355
+ // The retired cursor spelling is documented the same way wherever it
356
+ // appears. Derived from the schema it still lives in, so the flag cannot be
357
+ // present on one route's copy of the parameter and absent on another's.
358
+ if (name === CURSOR_PARAM_ALIAS) return retiredCursorParam();
359
+ return {
360
+ name,
361
+ in: 'query',
362
+ // Query strings arrive at the server as text, but OpenAPI describes the
363
+ // caller-facing value before serialization. Generated clients should take
364
+ // an integer here and encode it, not expose a stringly typed page size.
365
+ schema: name === 'limit'
366
+ ? { type: 'integer', minimum: 1 }
367
+ : s,
368
+ };
369
+ });
326
370
  }
327
371
 
328
372
  const commitRecordListQuerySchema = commitRecordWhereSchema.safeExtend({
@@ -492,8 +536,8 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
492
536
  parameters: [modelParam(), ...queryParams(listQuerySchema)],
493
537
  responses: {
494
538
  '200': namedResp(
495
- 'A page of rows. `next_cursor` feeds `starting_after` on the next ' +
496
- 'call; `stamp` is the watermark the page was read at.',
539
+ 'A page of rows. `next_cursor` feeds `cursor` on the next call; ' +
540
+ '`stamp` is the watermark the page was read at.',
497
541
  'ModelPage',
498
542
  ),
499
543
  },
@@ -591,9 +635,6 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
591
635
  description:
592
636
  'Call this first: every other route needs the key it returns. Requires a ' +
593
637
  '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
638
  requestBody: jsonBody(derive(ephemeralKeyRequestSchema, 'input')),
598
639
  responses: { '201': jsonResp('The minted credential', derive(EphemeralKeyResponseSchema, 'output')) },
599
640
  },
@@ -602,6 +643,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
602
643
  get: {
603
644
  tags: ['branches'],
604
645
  summary: 'List transaction branches for the credential project',
646
+ parameters: collectionPageParams(),
605
647
  responses: {
606
648
  '200': jsonResp(
607
649
  'The root and every active child branch.',
@@ -614,6 +656,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
614
656
  summary: 'Create an isolated child branch',
615
657
  description:
616
658
  'The returned id is immutable; retain it for automation. The slug is a project-scoped human handle.',
659
+ parameters: [idempotencyKeyParam()],
617
660
  requestBody: jsonBody(derive(createBranchRequestSchema, 'input')),
618
661
  responses: {
619
662
  '201': jsonResp('The ready branch.', derive(branchResponseSchema, 'output')),
@@ -869,7 +912,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
869
912
  post: {
870
913
  tags: ['commits'],
871
914
  summary: 'Commit a batch of operations atomically, and/or register durable premises',
872
- parameters: [{ name: 'Idempotency-Key', in: 'header', schema: { type: 'string' }, description: 'Replay-safe key; the server returns the cached receipt on retry.' }],
915
+ parameters: [idempotencyKeyParam()],
873
916
  requestBody: commitBody(),
874
917
  responses: { '200': commitReceipt() },
875
918
  },
@@ -996,7 +1039,7 @@ export function schemaToOpenApi<S extends SchemaRecord>(
996
1039
  post: {
997
1040
  tags: ['commits'],
998
1041
  summary: 'Commit a batch of operations atomically, and/or register durable premises',
999
- parameters: [{ name: 'Idempotency-Key', in: 'header', schema: { type: 'string' }, description: 'Replay-safe key; the server returns the cached receipt on retry.' }],
1042
+ parameters: [idempotencyKeyParam()],
1000
1043
  requestBody: commitBody(),
1001
1044
  responses: { '200': commitReceipt() },
1002
1045
  },
@@ -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
-