@abloatai/transaction 0.52.0 → 0.54.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 (120) hide show
  1. package/dist/auth/apiKey.d.ts +0 -6
  2. package/dist/auth/apiKey.d.ts.map +1 -1
  3. package/dist/auth/apiKey.js +13 -60
  4. package/dist/auth/apiKey.js.map +1 -1
  5. package/dist/auth/baseUrl.d.ts +38 -0
  6. package/dist/auth/baseUrl.d.ts.map +1 -0
  7. package/dist/auth/baseUrl.js +126 -0
  8. package/dist/auth/baseUrl.js.map +1 -0
  9. package/dist/auth/capability.js +1 -1
  10. package/dist/auth/index.d.ts +5 -5
  11. package/dist/auth/index.d.ts.map +1 -1
  12. package/dist/auth/index.js +1 -1
  13. package/dist/auth/index.js.map +1 -1
  14. package/dist/branches.d.ts +31 -30
  15. package/dist/branches.d.ts.map +1 -1
  16. package/dist/branches.js +2 -4
  17. package/dist/branches.js.map +1 -1
  18. package/dist/errorCodes.d.ts +4 -1
  19. package/dist/errorCodes.d.ts.map +1 -1
  20. package/dist/errorCodes.js +4 -1
  21. package/dist/errorCodes.js.map +1 -1
  22. package/dist/errorObservation.d.ts.map +1 -1
  23. package/dist/errorObservation.js +9 -1
  24. package/dist/errorObservation.js.map +1 -1
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/resources/httpResources.d.ts +34 -2
  29. package/dist/resources/httpResources.d.ts.map +1 -1
  30. package/dist/resources/httpResources.js +10 -1
  31. package/dist/resources/httpResources.js.map +1 -1
  32. package/dist/resources/modelOperations.d.ts +12 -0
  33. package/dist/resources/modelOperations.d.ts.map +1 -1
  34. package/dist/resources/modelOperations.js.map +1 -1
  35. package/dist/resources/where.d.ts +67 -2
  36. package/dist/resources/where.d.ts.map +1 -1
  37. package/dist/resources/where.js +73 -0
  38. package/dist/resources/where.js.map +1 -1
  39. package/dist/schema/index.d.ts +1 -1
  40. package/dist/schema/index.d.ts.map +1 -1
  41. package/dist/schema/index.js +1 -1
  42. package/dist/schema/index.js.map +1 -1
  43. package/dist/schema/openapi.d.ts.map +1 -1
  44. package/dist/schema/openapi.js +59 -14
  45. package/dist/schema/openapi.js.map +1 -1
  46. package/dist/schema/schema.d.ts +1 -1
  47. package/dist/schema/schema.d.ts.map +1 -1
  48. package/dist/schema/schema.js +1 -1
  49. package/dist/schema/schema.js.map +1 -1
  50. package/dist/server/adapter.d.ts +2 -0
  51. package/dist/server/adapter.d.ts.map +1 -1
  52. package/dist/server/index.d.ts +3 -2
  53. package/dist/server/index.d.ts.map +1 -1
  54. package/dist/server/index.js.map +1 -1
  55. package/dist/server/readConfig.d.ts +109 -57
  56. package/dist/server/readConfig.d.ts.map +1 -1
  57. package/dist/server/readConfig.js +19 -5
  58. package/dist/server/readConfig.js.map +1 -1
  59. package/dist/source/adapters/kysely.d.ts.map +1 -1
  60. package/dist/source/adapters/kysely.js +64 -6
  61. package/dist/source/adapters/kysely.js.map +1 -1
  62. package/dist/source/contract.d.ts.map +1 -1
  63. package/dist/source/contract.js +2 -0
  64. package/dist/source/contract.js.map +1 -1
  65. package/dist/source/types.d.ts +21 -3
  66. package/dist/source/types.d.ts.map +1 -1
  67. package/dist/source/types.js.map +1 -1
  68. package/dist/transport/httpClient.d.ts +7 -2
  69. package/dist/transport/httpClient.d.ts.map +1 -1
  70. package/dist/transport/httpClient.js +4 -2
  71. package/dist/transport/httpClient.js.map +1 -1
  72. package/dist/transport/httpTransport.d.ts.map +1 -1
  73. package/dist/transport/httpTransport.js +29 -7
  74. package/dist/transport/httpTransport.js.map +1 -1
  75. package/dist/wire/accountResponses.d.ts +18 -13
  76. package/dist/wire/accountResponses.d.ts.map +1 -1
  77. package/dist/wire/accountResponses.js +3 -9
  78. package/dist/wire/accountResponses.js.map +1 -1
  79. package/dist/wire/auth.d.ts +1 -1
  80. package/dist/wire/auth.js +3 -3
  81. package/dist/wire/claims.d.ts +1 -0
  82. package/dist/wire/claims.d.ts.map +1 -1
  83. package/dist/wire/claims.js +7 -1
  84. package/dist/wire/claims.js.map +1 -1
  85. package/dist/wire/index.d.ts +1 -1
  86. package/dist/wire/index.d.ts.map +1 -1
  87. package/dist/wire/index.js +1 -1
  88. package/dist/wire/index.js.map +1 -1
  89. package/dist/wire/listEnvelope.d.ts +20 -0
  90. package/dist/wire/listEnvelope.d.ts.map +1 -1
  91. package/dist/wire/listEnvelope.js +20 -0
  92. package/dist/wire/listEnvelope.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/auth/apiKey.ts +13 -61
  95. package/src/auth/baseUrl.ts +144 -0
  96. package/src/auth/capability.ts +1 -1
  97. package/src/auth/index.ts +5 -5
  98. package/src/branches.ts +2 -4
  99. package/src/errorCodes.ts +19 -1
  100. package/src/errorObservation.ts +9 -1
  101. package/src/index.ts +2 -0
  102. package/src/resources/httpResources.ts +44 -2
  103. package/src/resources/modelOperations.ts +12 -0
  104. package/src/resources/where.ts +70 -4
  105. package/src/schema/index.ts +3 -0
  106. package/src/schema/openapi.ts +60 -14
  107. package/src/schema/schema.ts +3 -0
  108. package/src/server/adapter.ts +2 -0
  109. package/src/server/index.ts +8 -2
  110. package/src/server/readConfig.ts +111 -57
  111. package/src/source/adapters/kysely.ts +73 -8
  112. package/src/source/contract.ts +2 -0
  113. package/src/source/types.ts +21 -3
  114. package/src/transport/httpClient.ts +17 -4
  115. package/src/transport/httpTransport.ts +29 -7
  116. package/src/wire/accountResponses.ts +3 -9
  117. package/src/wire/auth.ts +3 -3
  118. package/src/wire/claims.ts +7 -1
  119. package/src/wire/index.ts +7 -1
  120. package/src/wire/listEnvelope.ts +23 -0
@@ -32,10 +32,12 @@ import type {
32
32
  HttpTransportModel,
33
33
  ModelReadOptions,
34
34
  ModelMutationOptions,
35
+ ModelList,
35
36
  CreateSessionParams,
36
37
  AbloSession,
37
38
  SessionResource,
38
39
  } from '../resources/httpResources.js';
40
+ import { modelList } from '../resources/httpResources.js';
39
41
  import type {
40
42
  ModelCreateParams,
41
43
  ModelDeleteParams,
@@ -128,7 +130,12 @@ export interface HttpModelClient<T, C = T> {
128
130
  * Reads the rows matching a filter. Same resolution as `get`, in bulk,
129
131
  * and deduplicated so concurrent identical calls share one request.
130
132
  */
131
- list(options?: ServerReadOptions<T>): Promise<CapturedRow<T>[]>;
133
+ /**
134
+ * The rows are an array as before; `hasMore` and `nextCursor` on the result
135
+ * say whether the collection continues past this page. Pass `nextCursor`
136
+ * back as `cursor`, with the same `where` and `orderBy`, to walk it.
137
+ */
138
+ list(options?: ServerReadOptions<T>): Promise<ModelList<CapturedRow<T>>>;
132
139
  /**
133
140
  * Creates a row and returns it, including any framework-applied defaults.
134
141
  * Passing an id that already exists is idempotent: the existing row is
@@ -427,10 +434,16 @@ function createHttpModelClient<T, C = T>(
427
434
  return read.data as CapturedRow<T> | undefined;
428
435
  };
429
436
 
430
- const list = async (options?: ServerReadOptions<T>): Promise<CapturedRow<T>[]> => {
437
+ const list = async (
438
+ options?: ServerReadOptions<T>,
439
+ ): Promise<ModelList<CapturedRow<T>>> => {
431
440
  const snapshot = await protocol.list(options);
441
+ const page = modelList<CapturedRow<T>>(
442
+ snapshot.data as readonly CapturedRow<T>[],
443
+ snapshot,
444
+ );
432
445
  const registry = readSetContext?.getStore();
433
- if (!registry) return [...snapshot.data] as CapturedRow<T>[];
446
+ if (!registry) return page;
434
447
  if (!snapshot.evidence) {
435
448
  throw new AbloConnectionError(
436
449
  `${modelName}.list did not return row evidence. Upgrade the Ablo server or use get({ id }).`,
@@ -451,7 +464,7 @@ function createHttpModelClient<T, C = T>(
451
464
  }
452
465
  capturePointRead(readSetContext, clientIdentity, modelName, id, row, stamp);
453
466
  }
454
- return [...snapshot.data] as CapturedRow<T>[];
467
+ return page;
455
468
  };
456
469
 
457
470
  // Claim acquisition performs its authoritative read only after the grant.
@@ -84,6 +84,7 @@ import type {
84
84
  ModelMutationOptions,
85
85
  ModelReadOptions,
86
86
  HttpTransportRead,
87
+ HttpTransportList,
87
88
  HttpLogsResource,
88
89
  ModelTarget,
89
90
  CreateSessionParams,
@@ -151,6 +152,7 @@ import { declaredMeta, wireMeta } from '../coordination/claimMeta.js';
151
152
  import type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim } from '../types/streams.js';
152
153
  import type { CoordinationObservability } from '../observability.js';
153
154
  import { assertWriteOptions } from '../resources/writeOptionsSchema.js';
155
+ import { normalizeWhere } from '../resources/where.js';
154
156
  import {
155
157
  createDurableHttpCommitEnvelope,
156
158
  canonicalHttpCommitBody,
@@ -1493,9 +1495,12 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1493
1495
  async function listModel<T>(
1494
1496
  modelName: string,
1495
1497
  options?: ServerReadOptions<T>,
1496
- ): Promise<{ readonly data: readonly T[]; readonly evidence?: readonly { id: string; stamp: number }[] }> {
1498
+ ): Promise<HttpTransportList<T>> {
1497
1499
  const params = new URLSearchParams();
1498
1500
  if (options?.limit !== undefined) params.set('limit', String(options.limit));
1501
+ if (options?.cursor !== undefined) {
1502
+ params.set('cursor', options.cursor);
1503
+ }
1499
1504
  if (options?.orderBy) {
1500
1505
  const [col, dir] = Object.entries(options.orderBy)[0] ?? [];
1501
1506
  if (col) {
@@ -1503,12 +1508,27 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1503
1508
  if (dir === 'desc') params.set('order', 'desc');
1504
1509
  }
1505
1510
  }
1506
- // The collection route turns any non-reserved query param into an equality
1507
- // filter (`?status=todo`). The wire is AND-only equality matches what a
1508
- // stateless reactor needs; richer predicates stay on the stateful path.
1509
- if (options?.where && typeof options.where === 'object') {
1510
- for (const [k, v] of Object.entries(options.where as Record<string, unknown>)) {
1511
- if (v !== undefined && v !== null && typeof v !== 'object') params.set(k, String(v));
1511
+ // The whole filter travels as the canonical clause list, so every operator
1512
+ // the grammar declares survives the trip. The previous encoding walked the
1513
+ // object's own entries and skipped any value that was an object, which
1514
+ // silently discarded `IN` filters and every tuple-form clause: the request
1515
+ // went out unfiltered and the caller read the result as a filtered one.
1516
+ const clauses = normalizeWhere(options?.where);
1517
+ if (clauses.length > 0) {
1518
+ params.set('where', JSON.stringify(clauses));
1519
+ // Plain equality also goes out in the older `?column=value` shorthand.
1520
+ // A server that predates the `where` parameter does not reserve the name
1521
+ // and does not declare it as a column, so it skips it — and a dropped
1522
+ // filter is not an error there, it is a wider answer than the caller
1523
+ // asked for. Sending both means a version skew loses only the operator
1524
+ // filters, which that server could not have honoured anyway. A current
1525
+ // server ignores a shorthand key the clause list already carries.
1526
+ for (const clause of clauses) {
1527
+ const [column] = clause;
1528
+ const value = clause.length === 2 ? clause[1] : clause[2];
1529
+ if (clause.length === 3 && clause[1] !== '=') continue;
1530
+ if (value === null || typeof value === 'object') continue;
1531
+ params.set(column, String(value));
1512
1532
  }
1513
1533
  }
1514
1534
  const qs = params.toString();
@@ -1523,6 +1543,8 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1523
1543
  // the typed facade above, which holds the model's schema.
1524
1544
  return {
1525
1545
  data: res.data as T[],
1546
+ hasMore: res.has_more,
1547
+ nextCursor: res.next_cursor,
1526
1548
  ...(res.evidence ? { evidence: res.evidence } : {}),
1527
1549
  };
1528
1550
  }
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { z } from 'zod';
17
+ import { listEnvelopeSchema } from './listEnvelope.js';
17
18
  // Composed, never restated: these are the artifact's own shapes, and a
18
19
  // hand-written mirror here would be a second copy of the exact record this
19
20
  // response exists to stop withholding.
@@ -45,16 +46,9 @@ export type ProjectResponse = z.infer<typeof projectResponseSchema>;
45
46
  /**
46
47
  * `GET /v1/projects`.
47
48
  *
48
- * Note what this is NOT: the canonical {@link ListEnvelope}. An org's projects
49
- * are returned whole, so the route emits `data` with no `has_more`/`next_cursor`
50
- * beside it. That divergence is real and this schema states it rather than
51
- * describing a pagination the endpoint does not implement — adding the two
52
- * fields is an API change, not a documentation fix.
49
+ * Uses the canonical paginated list envelope shared by every collection.
53
50
  */
54
- export const projectListResponseSchema = z.object({
55
- object: z.literal('list'),
56
- data: z.array(projectResponseSchema).readonly(),
57
- });
51
+ export const projectListResponseSchema = listEnvelopeSchema(projectResponseSchema);
58
52
  export type ProjectListResponse = z.infer<typeof projectListResponseSchema>;
59
53
 
60
54
  /** One minted key, returned exactly once — the plaintext is never readable again. */
package/src/wire/auth.ts CHANGED
@@ -27,7 +27,7 @@ export type EphemeralKeyUser = z.infer<typeof ephemeralKeyUserSchema>;
27
27
  * Only a secret key (`sk_`) may call this: a session cannot mint itself. By
28
28
  * default the key mints into the caller's own organization; naming a different
29
29
  * `organizationId` is the multi-tenant case and requires the
30
- * `ephemeral:mint-any-org` scope, which is the same privilege that allows
30
+ * `organization:act-as` scope, which is the same privilege that allows
31
31
  * binding the session's schema to another organization's project.
32
32
  */
33
33
  export const ephemeralKeyRequestSchema = z.object({
@@ -35,7 +35,7 @@ export const ephemeralKeyRequestSchema = z.object({
35
35
  user: ephemeralKeyUserSchema,
36
36
  /**
37
37
  * Mint into this organization instead of the caller's own. Requires the
38
- * `ephemeral:mint-any-org` scope — without it a secret key can never mint a
38
+ * `organization:act-as` scope — without it a secret key can never mint a
39
39
  * session into another tenant.
40
40
  */
41
41
  organizationId: z.string().min(1).optional(),
@@ -43,7 +43,7 @@ export const ephemeralKeyRequestSchema = z.object({
43
43
  * Override the shared project used to resolve this session's schema while
44
44
  * its data stays scoped to the target organization. A cross-organization
45
45
  * mint defaults both coordinates to the caller key's own project. Both
46
- * fields are set together, and both require `ephemeral:mint-any-org`.
46
+ * fields are set together, and both require `organization:act-as`.
47
47
  */
48
48
  schemaProjectId: z.string().min(1).optional(),
49
49
  schemaOwnerOrgId: z.string().min(1).optional(),
@@ -121,7 +121,13 @@ export const listQuerySchema = z.object({
121
121
  limit: z.string().optional(),
122
122
  order_by: z.string().optional(),
123
123
  order: z.enum(['asc', 'desc']).optional(),
124
- /** Keyset cursor: the id of the last row from the previous page. */
124
+ /**
125
+ * Keyset cursor: the opaque `next_cursor` the previous page returned. It
126
+ * encodes the sort position it was issued for, so it is not a row id and is
127
+ * refused against a different `order_by`/`order`.
128
+ */
129
+ cursor: z.string().optional(),
130
+ /** @deprecated The pre-0.53.0 spelling of `cursor`. Send `cursor`. */
125
131
  starting_after: z.string().optional(),
126
132
  });
127
133
  export type ListQuery = z.infer<typeof listQuerySchema>;
package/src/wire/index.ts CHANGED
@@ -22,7 +22,13 @@ export {
22
22
  INTERNAL_ERROR_PUBLIC_MESSAGE,
23
23
  } from './errorEnvelope.js';
24
24
  export type { ErrorEnvelope } from './errorEnvelope.js';
25
- export { listEnvelopeSchema, listEnvelope } from './listEnvelope.js';
25
+ export {
26
+ listEnvelopeSchema,
27
+ listEnvelope,
28
+ CURSOR_PARAM,
29
+ CURSOR_PARAM_ALIAS,
30
+ CURSOR_PARAM_NAMES,
31
+ } from './listEnvelope.js';
26
32
  export type { ListEnvelope } from './listEnvelope.js';
27
33
 
28
34
  // The `GET /v1/logs` feed — the two arms, their envelope, and the cursor that
@@ -10,6 +10,29 @@
10
10
  */
11
11
  import { z } from 'zod';
12
12
 
13
+ /**
14
+ * The query parameter a caller resumes a collection with, and the single alias
15
+ * retained for it.
16
+ *
17
+ * Declared beside the envelope that issues `next_cursor`, because the parameter
18
+ * and the field it consumes are one contract and both planes read them: the
19
+ * server to know which names are taken, the spec generator to document them,
20
+ * the client to send one. When the name lived in each of those separately, this
21
+ * spec described `starting_after` for a model list and `cursor` for a commit
22
+ * record list — the same concept under two names, in one document.
23
+ *
24
+ * `starting_after` is the spelling used through 0.52.0. It borrowed a name whose
25
+ * established meaning elsewhere is a row id, while this value has always been an
26
+ * opaque token bound to the sort it was issued for.
27
+ */
28
+ export const CURSOR_PARAM = 'cursor';
29
+
30
+ /** @deprecated The pre-0.53.0 spelling of {@link CURSOR_PARAM}. */
31
+ export const CURSOR_PARAM_ALIAS = 'starting_after';
32
+
33
+ /** Both names a cursor may arrive under, for reserving them in a query string. */
34
+ export const CURSOR_PARAM_NAMES: readonly string[] = [CURSOR_PARAM, CURSOR_PARAM_ALIAS];
35
+
13
36
  /**
14
37
  * Builds the authoritative list-envelope schema for a row schema. Keeping the
15
38
  * item validator generic lets every endpoint share the same envelope contract