@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.
- package/dist/ablo.d.ts +1 -1
- package/dist/ablo.js +1 -1
- package/dist/ai-sdk/updateTool.d.ts +1 -1
- package/dist/ai-sdk/updateTool.js +1 -1
- package/dist/auth/apiKey.d.ts +0 -6
- package/dist/auth/apiKey.d.ts.map +1 -1
- package/dist/auth/apiKey.js +13 -60
- package/dist/auth/apiKey.js.map +1 -1
- package/dist/auth/baseUrl.d.ts +38 -0
- package/dist/auth/baseUrl.d.ts.map +1 -0
- package/dist/auth/baseUrl.js +126 -0
- package/dist/auth/baseUrl.js.map +1 -0
- package/dist/auth/capability.d.ts +2 -2
- package/dist/auth/capability.js +1 -1
- package/dist/auth/index.d.ts +15 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +35 -21
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/schemas.d.ts +24 -1
- package/dist/auth/schemas.d.ts.map +1 -1
- package/dist/auth/schemas.js +8 -2
- package/dist/auth/schemas.js.map +1 -1
- package/dist/auth/sessionIssuanceProtocol.d.ts +41 -0
- package/dist/auth/sessionIssuanceProtocol.d.ts.map +1 -0
- package/dist/auth/sessionIssuanceProtocol.js +48 -0
- package/dist/auth/sessionIssuanceProtocol.js.map +1 -0
- package/dist/auth/sessionMint.js +2 -2
- package/dist/branches.d.ts +34 -33
- package/dist/branches.d.ts.map +1 -1
- package/dist/branches.js +2 -4
- package/dist/branches.js.map +1 -1
- package/dist/coordination/schema.d.ts +3 -2
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +7 -2
- package/dist/coordination/schema.js.map +1 -1
- package/dist/errorCodes.d.ts +7 -3
- package/dist/errorCodes.d.ts.map +1 -1
- package/dist/errorCodes.js +8 -4
- package/dist/errorCodes.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/httpResources.d.ts +35 -2
- package/dist/resources/httpResources.d.ts.map +1 -1
- package/dist/resources/httpResources.js +10 -1
- package/dist/resources/httpResources.js.map +1 -1
- package/dist/resources/modelOperations.d.ts +18 -6
- package/dist/resources/modelOperations.d.ts.map +1 -1
- package/dist/resources/modelOperations.js.map +1 -1
- package/dist/resources/where.d.ts +67 -2
- package/dist/resources/where.d.ts.map +1 -1
- package/dist/resources/where.js +73 -0
- package/dist/resources/where.js.map +1 -1
- package/dist/schema/ddl.d.ts.map +1 -1
- package/dist/schema/ddl.js +9 -12
- package/dist/schema/ddl.js.map +1 -1
- package/dist/schema/field.d.ts +3 -3
- package/dist/schema/field.js +2 -2
- package/dist/schema/fieldRef.d.ts +1 -1
- package/dist/schema/generate.d.ts +2 -3
- package/dist/schema/generate.d.ts.map +1 -1
- package/dist/schema/generate.js +2 -8
- package/dist/schema/generate.js.map +1 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +3 -3
- package/dist/schema/model.d.ts +7 -7
- package/dist/schema/model.js +2 -2
- package/dist/schema/openapi.d.ts.map +1 -1
- package/dist/schema/openapi.js +63 -21
- package/dist/schema/openapi.js.map +1 -1
- package/dist/schema/queries.d.ts +1 -1
- package/dist/schema/queries.js +1 -1
- package/dist/schema/relation.d.ts +8 -8
- package/dist/schema/relation.js +5 -5
- package/dist/schema/schema.d.ts +24 -37
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +15 -28
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/sugar.d.ts +7 -7
- package/dist/schema/sugar.js +5 -5
- package/dist/server/adapter.d.ts +2 -0
- package/dist/server/adapter.d.ts.map +1 -1
- package/dist/server/commit.d.ts +5 -1
- package/dist/server/commit.d.ts.map +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/readConfig.d.ts +109 -57
- package/dist/server/readConfig.d.ts.map +1 -1
- package/dist/server/readConfig.js +19 -5
- package/dist/server/readConfig.js.map +1 -1
- package/dist/source/adapters/drizzle.d.ts.map +1 -1
- package/dist/source/adapters/drizzle.js +5 -0
- package/dist/source/adapters/drizzle.js.map +1 -1
- package/dist/source/adapters/kysely.d.ts.map +1 -1
- package/dist/source/adapters/kysely.js +33 -8
- package/dist/source/adapters/kysely.js.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +58 -20
- package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
- package/dist/source/adapters/memory.d.ts.map +1 -1
- package/dist/source/adapters/memory.js +5 -0
- package/dist/source/adapters/memory.js.map +1 -1
- package/dist/source/adapters/prisma.d.ts.map +1 -1
- package/dist/source/adapters/prisma.js +6 -1
- package/dist/source/adapters/prisma.js.map +1 -1
- package/dist/source/conformance.js +21 -21
- package/dist/source/contract.d.ts +17 -0
- package/dist/source/contract.d.ts.map +1 -1
- package/dist/source/contract.js +31 -0
- package/dist/source/contract.js.map +1 -1
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.d.ts.map +1 -1
- package/dist/source/index.js +1 -1
- package/dist/source/index.js.map +1 -1
- package/dist/source/types.d.ts +1 -0
- package/dist/source/types.d.ts.map +1 -1
- package/dist/source/types.js.map +1 -1
- package/dist/transactionLayer.d.ts +3 -3
- package/dist/transactionLayer.js +3 -3
- package/dist/transport/httpClient.d.ts +13 -4
- package/dist/transport/httpClient.d.ts.map +1 -1
- package/dist/transport/httpClient.js +4 -2
- package/dist/transport/httpClient.js.map +1 -1
- package/dist/transport/httpTransport.d.ts +1 -0
- package/dist/transport/httpTransport.d.ts.map +1 -1
- package/dist/transport/httpTransport.js +79 -11
- package/dist/transport/httpTransport.js.map +1 -1
- package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
- package/dist/transport/wsFrameHandlers.js +3 -0
- package/dist/transport/wsFrameHandlers.js.map +1 -1
- package/dist/types/streams.d.ts +2 -2
- package/dist/wire/accountResponses.d.ts +18 -13
- package/dist/wire/accountResponses.d.ts.map +1 -1
- package/dist/wire/accountResponses.js +5 -11
- package/dist/wire/accountResponses.js.map +1 -1
- package/dist/wire/claims.d.ts +1 -0
- package/dist/wire/claims.d.ts.map +1 -1
- package/dist/wire/claims.js +7 -1
- package/dist/wire/claims.js.map +1 -1
- package/dist/wire/commit.d.ts +144 -0
- package/dist/wire/commit.d.ts.map +1 -1
- package/dist/wire/commit.js +22 -1
- package/dist/wire/commit.js.map +1 -1
- package/dist/wire/frames.d.ts +4 -0
- package/dist/wire/frames.d.ts.map +1 -1
- package/dist/wire/inboundFrames.d.ts +22 -0
- package/dist/wire/inboundFrames.d.ts.map +1 -1
- package/dist/wire/index.d.ts +3 -3
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +2 -2
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/listEnvelope.d.ts +20 -0
- package/dist/wire/listEnvelope.d.ts.map +1 -1
- package/dist/wire/listEnvelope.js +20 -0
- package/dist/wire/listEnvelope.js.map +1 -1
- package/dist/wire/protocol.d.ts +1 -1
- package/dist/wire/protocol.js +1 -1
- package/package.json +1 -1
- package/src/ablo.ts +1 -1
- package/src/ai-sdk/updateTool.ts +1 -1
- package/src/auth/apiKey.ts +13 -61
- package/src/auth/baseUrl.ts +144 -0
- package/src/auth/capability.ts +3 -3
- package/src/auth/index.ts +62 -21
- package/src/auth/schemas.ts +16 -2
- package/src/auth/sessionIssuanceProtocol.ts +57 -0
- package/src/auth/sessionMint.ts +2 -2
- package/src/branches.ts +2 -4
- package/src/coordination/schema.ts +7 -2
- package/src/errorCodes.ts +29 -5
- package/src/errors.ts +1 -1
- package/src/index.ts +2 -0
- package/src/resources/httpResources.ts +45 -2
- package/src/resources/modelOperations.ts +18 -6
- package/src/resources/where.ts +70 -4
- package/src/schema/ddl.ts +9 -13
- package/src/schema/field.ts +3 -3
- package/src/schema/fieldRef.ts +1 -1
- package/src/schema/generate.ts +2 -8
- package/src/schema/index.ts +3 -3
- package/src/schema/model.ts +7 -7
- package/src/schema/openapi.ts +64 -21
- package/src/schema/queries.ts +1 -1
- package/src/schema/relation.ts +8 -8
- package/src/schema/residency.ts +0 -1
- package/src/schema/schema.ts +33 -58
- package/src/schema/sugar.ts +7 -7
- package/src/server/adapter.ts +2 -0
- package/src/server/commit.ts +5 -1
- package/src/server/index.ts +8 -2
- package/src/server/readConfig.ts +111 -57
- package/src/source/adapters/drizzle.ts +5 -0
- package/src/source/adapters/kysely.ts +48 -10
- package/src/source/adapters/kyselyMutationCore.ts +69 -22
- package/src/source/adapters/memory.ts +5 -0
- package/src/source/adapters/prisma.ts +6 -1
- package/src/source/conformance.ts +21 -21
- package/src/source/contract.ts +34 -0
- package/src/source/index.ts +2 -0
- package/src/source/types.ts +1 -0
- package/src/transactionLayer.ts +3 -3
- package/src/transport/httpClient.ts +23 -6
- package/src/transport/httpTransport.ts +88 -11
- package/src/transport/wsFrameHandlers.ts +3 -0
- package/src/types/global.ts +1 -1
- package/src/types/streams.ts +2 -2
- package/src/wire/accountResponses.ts +5 -11
- package/src/wire/claims.ts +7 -1
- package/src/wire/commit.ts +31 -1
- package/src/wire/index.ts +10 -1
- package/src/wire/listEnvelope.ts +23 -0
- 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'
|
|
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
|
|
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
|
-
* `
|
|
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'
|
|
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('
|
|
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
|
|
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)};`);
|
package/src/schema/field.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Usage:
|
|
10
10
|
* import { field } from '@abloatai/transaction/schema';
|
|
11
11
|
*
|
|
12
|
-
* const
|
|
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
|
|
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
|
|
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>;
|
package/src/schema/fieldRef.ts
CHANGED
|
@@ -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.
|
|
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,
|
package/src/schema/generate.ts
CHANGED
|
@@ -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
|
|
14
|
-
* enums emitted as string-literal unions. Relations are not expanded here
|
|
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
|
}
|
package/src/schema/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* import { defineSchema, model, relation } from '@abloatai/transaction/schema';
|
|
9
9
|
*
|
|
10
10
|
* export const schema = defineSchema({
|
|
11
|
-
*
|
|
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
|
|
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.
|
|
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 {
|
package/src/schema/model.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* import { z } from 'zod';
|
|
9
9
|
* import { model, relation } from '@abloatai/transaction/schema';
|
|
10
10
|
*
|
|
11
|
-
* const
|
|
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
|
-
* `
|
|
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', '
|
|
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 (`
|
|
114
|
-
* `'
|
|
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
|
-
* //
|
|
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
|
|
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(),
|
package/src/schema/openapi.ts
CHANGED
|
@@ -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
|
|
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. `
|
|
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
|
-
|
|
318
|
-
in
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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 `
|
|
496
|
-
'
|
|
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: [
|
|
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: [
|
|
1042
|
+
parameters: [idempotencyKeyParam()],
|
|
1000
1043
|
requestBody: commitBody(),
|
|
1001
1044
|
responses: { '200': commitReceipt() },
|
|
1002
1045
|
},
|
package/src/schema/queries.ts
CHANGED
|
@@ -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({
|
|
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
|
*
|
package/src/schema/relation.ts
CHANGED
|
@@ -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
|
-
* `
|
|
7
|
+
* `item.project` or `project.items` directly.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
10
10
|
* import { relation } from '@abloatai/transaction/schema';
|
|
11
11
|
*
|
|
12
|
-
* const
|
|
12
|
+
* const itemRelations = {
|
|
13
13
|
* project: relation.belongsTo('projects', 'projectId'),
|
|
14
14
|
* assignee: relation.belongsTo('users', 'assigneeId'),
|
|
15
|
-
* comments: relation.hasMany('comments', '
|
|
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 `
|
|
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 `
|
|
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
|
|
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.,
|
|
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
|
-
*
|
|
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
|
package/src/schema/residency.ts
CHANGED