@jskit-ai/crud-core 0.1.40 → 0.1.41

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/crud-core",
4
- version: "0.1.40",
4
+ version: "0.1.41",
5
5
  kind: "runtime",
6
6
  description: "Shared CRUD helpers used by CRUD modules.",
7
7
  dependsOn: [
@@ -26,7 +26,7 @@ export default Object.freeze({
26
26
  mutations: {
27
27
  dependencies: {
28
28
  runtime: {
29
- "@jskit-ai/crud-core": "0.1.40"
29
+ "@jskit-ai/crud-core": "0.1.41"
30
30
  },
31
31
  dev: {}
32
32
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/crud-core",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -26,11 +26,12 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@tanstack/vue-query": "^5.90.5",
29
- "@jskit-ai/kernel": "0.1.32",
30
- "@jskit-ai/realtime": "0.1.31",
31
- "@jskit-ai/shell-web": "0.1.31",
32
- "@jskit-ai/users-core": "0.1.42",
33
- "@jskit-ai/users-web": "0.1.47",
29
+ "@jskit-ai/database-runtime": "0.1.33",
30
+ "@jskit-ai/kernel": "0.1.33",
31
+ "@jskit-ai/realtime": "0.1.32",
32
+ "@jskit-ai/shell-web": "0.1.32",
33
+ "@jskit-ai/users-core": "0.1.43",
34
+ "@jskit-ai/users-web": "0.1.48",
34
35
  "typebox": "^1.0.81"
35
36
  }
36
37
  }
@@ -113,7 +113,7 @@ function useCrudClientContext(source = {}) {
113
113
  return resolveCrudRecordPathTemplates(listPathTemplate, recordIdParam);
114
114
  }
115
115
 
116
- function resolveRecordParams(recordIdLike = 0, { recordIdParam = defaultRecordIdParam } = {}) {
116
+ function resolveRecordParams(recordIdLike = "", { recordIdParam = defaultRecordIdParam } = {}) {
117
117
  return resolveCrudRecordPathParams(recordIdLike, recordIdParam);
118
118
  }
119
119
 
@@ -122,7 +122,7 @@ function useCrudClientContext(source = {}) {
122
122
  return crudListQueryKey(normalizedSurfaceId, workspaceSlugToken.value, crudConfig.namespace);
123
123
  }
124
124
 
125
- function viewQueryKey(surfaceId = "", recordId = 0) {
125
+ function viewQueryKey(surfaceId = "", recordId = "") {
126
126
  const normalizedSurfaceId = String(surfaceId || paths.currentSurfaceId.value || "").trim();
127
127
  return crudViewQueryKey(normalizedSurfaceId, workspaceSlugToken.value, recordId, crudConfig.namespace);
128
128
  }
@@ -1,4 +1,4 @@
1
- import { normalizeText, normalizeQueryToken } from "@jskit-ai/kernel/shared/support/normalize";
1
+ import { normalizeQueryToken, normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
2
  import { normalizeRouteVisibilityToken } from "@jskit-ai/kernel/shared/support/visibility";
3
3
  import { formatDateTime } from "@jskit-ai/kernel/shared/support";
4
4
  import {
@@ -58,13 +58,13 @@ function crudListQueryKey(surfaceId = "", workspaceSlug = "", namespace = "") {
58
58
  ]);
59
59
  }
60
60
 
61
- function crudViewQueryKey(surfaceId = "", workspaceSlug = "", recordId = 0, namespace = "") {
61
+ function crudViewQueryKey(surfaceId = "", workspaceSlug = "", recordId = "", namespace = "") {
62
62
  return Object.freeze([
63
63
  ...crudScopeQueryKey(namespace),
64
64
  "view",
65
65
  normalizeQueryToken(surfaceId),
66
66
  normalizeQueryToken(workspaceSlug),
67
- Number(recordId) || 0
67
+ normalizeRecordId(recordId, { fallback: "0" })
68
68
  ]);
69
69
  }
70
70
 
@@ -87,8 +87,9 @@ function toRouteRecordId(value) {
87
87
  return toRouteRecordId(value[0]);
88
88
  }
89
89
 
90
- const parsed = Number(value);
91
- return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
90
+ return normalizeRecordId(value, {
91
+ fallback: ""
92
+ });
92
93
  }
93
94
 
94
95
  function normalizeCrudRouteParamName(value, { context = "normalizeCrudRouteParamName" } = {}) {
@@ -120,7 +121,7 @@ function resolveCrudRecordPathTemplates(relativePath = "", recordIdParam = "reco
120
121
  });
121
122
  }
122
123
 
123
- function resolveCrudRecordPathParams(recordIdLike = 0, recordIdParam = "recordId") {
124
+ function resolveCrudRecordPathParams(recordIdLike = "", recordIdParam = "recordId") {
124
125
  const normalizedRecordIdParam = normalizeCrudRouteParamName(recordIdParam, {
125
126
  context: "resolveCrudRecordPathParams"
126
127
  });
@@ -130,7 +131,7 @@ function resolveCrudRecordPathParams(recordIdLike = 0, recordIdParam = "recordId
130
131
  }
131
132
 
132
133
  return Object.freeze({
133
- [normalizedRecordIdParam]: String(normalizedRecordId)
134
+ [normalizedRecordIdParam]: normalizedRecordId
134
135
  });
135
136
  }
136
137
 
@@ -8,6 +8,7 @@ import {
8
8
  crudRepositoryUpdateById,
9
9
  crudRepositoryDeleteById
10
10
  } from "./repositoryMethods.js";
11
+ import { createWithTransaction } from "@jskit-ai/database-runtime/shared";
11
12
 
12
13
  function createCrudRepositoryFromResource(resource = {}, { context = "crudRepository", list = {} } = {}) {
13
14
  const runtime = createCrudRepositoryRuntime(resource, {
@@ -19,6 +20,7 @@ function createCrudRepositoryFromResource(resource = {}, { context = "crudReposi
19
20
  if (typeof knex !== "function") {
20
21
  throw new TypeError("crudRepository requires knex.");
21
22
  }
23
+ const withTransaction = createWithTransaction(knex);
22
24
 
23
25
  async function listRecords(query = {}, callOptions = {}, hooks = null) {
24
26
  return crudRepositoryList(runtime, knex, query, options, callOptions, hooks);
@@ -49,6 +51,7 @@ function createCrudRepositoryFromResource(resource = {}, { context = "crudReposi
49
51
  }
50
52
 
51
53
  return Object.freeze({
54
+ withTransaction,
52
55
  list: listRecords,
53
56
  findById,
54
57
  listByIds,
@@ -529,7 +529,7 @@ function resolveLookupVisibilityContext(
529
529
  nextVisibilityContext.scopeOwnerId = parentVisibilityContext.scopeOwnerId;
530
530
  }
531
531
  if (providerOwnershipFilter === "user" || providerOwnershipFilter === "workspace_user") {
532
- nextVisibilityContext.userOwnerId = parentVisibilityContext.userOwnerId;
532
+ nextVisibilityContext.userId = parentVisibilityContext.userId;
533
533
  }
534
534
 
535
535
  return nextVisibilityContext;
@@ -1,7 +1,7 @@
1
- import { toInsertDateTime } from "@jskit-ai/database-runtime/shared";
1
+ import { resolveInsertedRecordId, toInsertDateTime } from "@jskit-ai/database-runtime/shared";
2
2
  import { applyVisibility, applyVisibilityOwners } from "@jskit-ai/database-runtime/shared/visibility";
3
3
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
4
- import { normalizeText, normalizeUniqueTextList } from "@jskit-ai/kernel/shared/support/normalize";
4
+ import { normalizeRecordId, normalizeText, normalizeUniqueTextList } from "@jskit-ai/kernel/shared/support/normalize";
5
5
  import { Check, Errors } from "typebox/value";
6
6
  import {
7
7
  DEFAULT_LIST_LIMIT,
@@ -29,6 +29,14 @@ const ORDERED_LIST_CURSOR_VALUE_TYPE_KEY = "__jskitCursorValueType";
29
29
  const ORDERED_LIST_CURSOR_VALUE_KEY = "value";
30
30
  const ORDERED_LIST_CURSOR_VALUE_TYPE_DATE = "date";
31
31
 
32
+ function requireCrudRecordId(value, { context = "crudRepository" } = {}) {
33
+ const recordId = normalizeRecordId(value, { fallback: null });
34
+ if (!recordId) {
35
+ throw new TypeError(`${context} requires recordId.`);
36
+ }
37
+ return recordId;
38
+ }
39
+
32
40
  function resolveRepositoryDefaults(resource = {}, repositoryMapping = {}) {
33
41
  const resourceName = normalizeText(resource.resource);
34
42
  const tableName = normalizeText(resource.tableName) || resourceName;
@@ -757,7 +765,12 @@ async function crudRepositoryList(runtime, knex, query = {}, repositoryOptions =
757
765
  const pageRows = hasMore ? rows.slice(0, normalizedLimit) : rows;
758
766
  const items = [];
759
767
  for (const row of pageRows) {
760
- const mappedRecord = mapRecordRow(row, runtime.mapping.outputKeys, runtime.mapping.columnOverrides);
768
+ const mappedRecord = mapRecordRow(
769
+ row,
770
+ runtime.mapping.outputKeys,
771
+ runtime.mapping.columnOverrides,
772
+ { recordIdKeys: runtime.mapping.outputRecordIdKeys }
773
+ );
761
774
  if (!mappedRecord) {
762
775
  continue;
763
776
  }
@@ -855,6 +868,7 @@ async function crudRepositoryList(runtime, knex, query = {}, repositoryOptions =
855
868
 
856
869
  async function crudRepositoryFindById(runtime, knex, recordId, repositoryOptions = {}, callOptions = {}, hooks = null) {
857
870
  const { client, tableName, idColumn, visible } = resolveCrudRepositoryCall(runtime, knex, repositoryOptions, callOptions);
871
+ const normalizedRecordId = requireCrudRecordId(recordId, { context: "crudRepositoryFindById" });
858
872
  const methodHooks = normalizeCrudRepositoryHooks(
859
873
  hooks,
860
874
  ["modifyQuery", "afterQuery", "transformReturnedRecord", "finalizeOutput"],
@@ -895,12 +909,17 @@ async function crudRepositoryFindById(runtime, knex, recordId, repositoryOptions
895
909
  dbQuery = dbQuery
896
910
  .where(visible)
897
911
  .where({
898
- [idColumn]: Number(recordId)
912
+ [idColumn]: normalizedRecordId
899
913
  });
900
914
 
901
915
  const row = await dbQuery.first();
902
916
 
903
- const mappedRecord = mapRecordRow(row, runtime.mapping.outputKeys, runtime.mapping.columnOverrides);
917
+ const mappedRecord = mapRecordRow(
918
+ row,
919
+ runtime.mapping.outputKeys,
920
+ runtime.mapping.columnOverrides,
921
+ { recordIdKeys: runtime.mapping.outputRecordIdKeys }
922
+ );
904
923
  let records = [];
905
924
  if (mappedRecord) {
906
925
  const normalizedRecord = await normalizeRepositoryOutputRecord(runtime, mappedRecord, {
@@ -1032,7 +1051,12 @@ async function crudRepositoryListByIds(runtime, knex, ids = [], repositoryOption
1032
1051
 
1033
1052
  const records = [];
1034
1053
  for (const row of rows) {
1035
- const mappedRecord = mapRecordRow(row, runtime.mapping.outputKeys, runtime.mapping.columnOverrides);
1054
+ const mappedRecord = mapRecordRow(
1055
+ row,
1056
+ runtime.mapping.outputKeys,
1057
+ runtime.mapping.columnOverrides,
1058
+ { recordIdKeys: runtime.mapping.outputRecordIdKeys }
1059
+ );
1036
1060
  if (!mappedRecord) {
1037
1061
  continue;
1038
1062
  }
@@ -1206,7 +1230,11 @@ async function crudRepositoryCreate(runtime, knex, payload = {}, repositoryOptio
1206
1230
  );
1207
1231
  createQuery = createHookResult.queryBuilder;
1208
1232
 
1209
- const [recordId] = await createQuery.insert(withOwners);
1233
+ const insertResult = await createQuery.insert(withOwners);
1234
+ const recordId = resolveInsertedRecordId(insertResult, { fallback: null });
1235
+ if (!recordId) {
1236
+ throw new Error("crudRepositoryCreate could not resolve inserted id.");
1237
+ }
1210
1238
 
1211
1239
  const createdRecord = await crudRepositoryFindById(runtime, knex, recordId, repositoryOptions, {
1212
1240
  ...callOptions,
@@ -1237,6 +1265,7 @@ async function crudRepositoryCreate(runtime, knex, payload = {}, repositoryOptio
1237
1265
 
1238
1266
  async function crudRepositoryUpdateById(runtime, knex, recordId, patch = {}, repositoryOptions = {}, callOptions = {}, hooks = null) {
1239
1267
  const { client, tableName, idColumn, visible } = resolveCrudRepositoryCall(runtime, knex, repositoryOptions, callOptions);
1268
+ const normalizedRecordId = requireCrudRecordId(recordId, { context: "crudRepositoryUpdateById" });
1240
1269
  const methodHooks = normalizeCrudRepositoryHooks(hooks, ["modifyPatch", "modifyQuery", "afterWrite"], {
1241
1270
  context: "crudRepositoryUpdateById"
1242
1271
  });
@@ -1299,7 +1328,7 @@ async function crudRepositoryUpdateById(runtime, knex, recordId, patch = {}, rep
1299
1328
  updateQuery = updateQuery
1300
1329
  .where(visible)
1301
1330
  .where({
1302
- [idColumn]: Number(recordId)
1331
+ [idColumn]: normalizedRecordId
1303
1332
  });
1304
1333
 
1305
1334
  await updateQuery.update(dbPatch);
@@ -1333,6 +1362,7 @@ async function crudRepositoryUpdateById(runtime, knex, recordId, patch = {}, rep
1333
1362
 
1334
1363
  async function crudRepositoryDeleteById(runtime, knex, recordId, repositoryOptions = {}, callOptions = {}, hooks = null) {
1335
1364
  const { client, tableName, idColumn, visible } = resolveCrudRepositoryCall(runtime, knex, repositoryOptions, callOptions);
1365
+ const normalizedRecordId = requireCrudRecordId(recordId, { context: "crudRepositoryDeleteById" });
1336
1366
  const methodHooks = normalizeCrudRepositoryHooks(hooks, ["modifyQuery", "finalizeOutput", "afterWrite"], {
1337
1367
  context: "crudRepositoryDeleteById"
1338
1368
  });
@@ -1394,7 +1424,7 @@ async function crudRepositoryDeleteById(runtime, knex, recordId, repositoryOptio
1394
1424
  deleteQuery = deleteQuery
1395
1425
  .where(visible)
1396
1426
  .where({
1397
- [idColumn]: Number(recordId)
1427
+ [idColumn]: normalizedRecordId
1398
1428
  });
1399
1429
 
1400
1430
  await deleteQuery.delete();
@@ -1,5 +1,7 @@
1
+ import { normalizeDbRecordId } from "@jskit-ai/database-runtime/shared";
1
2
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { RECORD_ID_PATTERN } from "@jskit-ai/kernel/shared/validators";
4
+ import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
5
  import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
4
6
  import { toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
5
7
  import {
@@ -13,24 +15,24 @@ const MAX_LIST_LIMIT = 100;
13
15
 
14
16
  function normalizeCrudListCursor(cursor = null, { allowEmpty = true } = {}) {
15
17
  if (cursor === undefined || cursor === null) {
16
- return allowEmpty === true ? 0 : null;
18
+ return allowEmpty === true ? "" : null;
17
19
  }
18
20
 
19
21
  const normalizedCursor = typeof cursor === "string"
20
22
  ? cursor.trim()
21
23
  : cursor;
22
24
  if (normalizedCursor === "" || normalizedCursor === 0 || normalizedCursor === "0") {
23
- return allowEmpty === true ? 0 : null;
25
+ return allowEmpty === true ? "" : null;
24
26
  }
25
27
 
26
- const numericCursor = Number(normalizedCursor);
27
- if (!Number.isInteger(numericCursor) || numericCursor < 1) {
28
+ const recordId = normalizeRecordId(normalizedCursor, { fallback: null });
29
+ if (!recordId) {
28
30
  throw new AppError(400, "Invalid cursor.", {
29
31
  code: "INVALID_CURSOR"
30
32
  });
31
33
  }
32
34
 
33
- return numericCursor;
35
+ return recordId;
34
36
  }
35
37
 
36
38
  function normalizeCrudListLimit(value, { fallback = DEFAULT_LIST_LIMIT, max = MAX_LIST_LIMIT } = {}) {
@@ -152,6 +154,29 @@ function schemaIncludesStringType(schema = {}) {
152
154
  return variants.some((entry) => schemaIncludesStringType(entry));
153
155
  }
154
156
 
157
+ function schemaIncludesRecordIdType(schema = {}) {
158
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
159
+ return false;
160
+ }
161
+
162
+ const type = Array.isArray(schema.type)
163
+ ? schema.type.map((entry) => normalizeText(entry).toLowerCase()).filter(Boolean)
164
+ : normalizeText(schema.type).toLowerCase();
165
+ const hasStringType = Array.isArray(type)
166
+ ? type.includes("string")
167
+ : type === "string";
168
+ if (hasStringType && normalizeText(schema.pattern) === RECORD_ID_PATTERN) {
169
+ return true;
170
+ }
171
+
172
+ const variants = Array.isArray(schema.anyOf)
173
+ ? schema.anyOf
174
+ : Array.isArray(schema.oneOf)
175
+ ? schema.oneOf
176
+ : [];
177
+ return variants.some((entry) => schemaIncludesRecordIdType(entry));
178
+ }
179
+
155
180
  function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRepository" } = {}) {
156
181
  if (!resource || typeof resource !== "object" || Array.isArray(resource)) {
157
182
  throw new TypeError(`${context} requires resource object.`);
@@ -214,20 +239,33 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
214
239
  parentFilterColumns[key] = columnName;
215
240
  }
216
241
 
242
+ const outputRecordIdKeys = [];
243
+ for (const [key, schema] of Object.entries(outputProperties)) {
244
+ if (schemaIncludesRecordIdType(schema)) {
245
+ outputRecordIdKeys.push(key);
246
+ }
247
+ }
248
+
217
249
  return Object.freeze({
218
250
  outputKeys,
219
251
  writeKeys,
220
252
  columnOverrides: Object.freeze(columnOverrides),
221
253
  listSearchColumns: Object.freeze(listSearchColumns),
222
- parentFilterColumns: Object.freeze(parentFilterColumns)
254
+ parentFilterColumns: Object.freeze(parentFilterColumns),
255
+ outputRecordIdKeys: Object.freeze(outputRecordIdKeys)
223
256
  });
224
257
  }
225
258
 
226
- function mapRecordRow(row, fieldKeys = [], overrides = {}) {
259
+ function mapRecordRow(row, fieldKeys = [], overrides = {}, { recordIdKeys = [] } = {}) {
227
260
  if (!row) {
228
261
  return null;
229
262
  }
230
263
 
264
+ const recordIdKeySet = new Set(
265
+ (Array.isArray(recordIdKeys) ? recordIdKeys : [])
266
+ .map((key) => String(key || "").trim())
267
+ .filter(Boolean)
268
+ );
231
269
  const mapped = {};
232
270
  for (const key of fieldKeys) {
233
271
  const normalizedKey = String(key || "").trim();
@@ -235,7 +273,15 @@ function mapRecordRow(row, fieldKeys = [], overrides = {}) {
235
273
  if (!normalizedKey || !columnName) {
236
274
  continue;
237
275
  }
238
- mapped[normalizedKey] = row[columnName];
276
+
277
+ const rawValue = row[columnName];
278
+ if (recordIdKeySet.has(normalizedKey)) {
279
+ const normalizedIdValue = normalizeDbRecordId(rawValue, { fallback: null });
280
+ mapped[normalizedKey] = normalizedIdValue || rawValue;
281
+ continue;
282
+ }
283
+
284
+ mapped[normalizedKey] = rawValue;
239
285
  }
240
286
  return mapped;
241
287
  }
@@ -244,7 +290,7 @@ function applyCrudListQueryFilters(
244
290
  query,
245
291
  {
246
292
  idColumn = "id",
247
- cursor = 0,
293
+ cursor = "",
248
294
  applyCursor = true,
249
295
  q = "",
250
296
  searchColumns = [],
@@ -307,7 +353,7 @@ function applyCrudListQueryFilters(
307
353
  const normalizedIdColumn = String(idColumn || "").trim() || "id";
308
354
  if (applyCursor !== false) {
309
355
  const normalizedCursor = normalizeCrudListCursor(cursor);
310
- if (normalizedCursor > 0) {
356
+ if (normalizedCursor) {
311
357
  nextQuery = nextQuery.where(normalizedIdColumn, ">", normalizedCursor);
312
358
  }
313
359
  }
@@ -61,7 +61,7 @@ test("crudListQueryKey and crudViewQueryKey normalize cache keys", () => {
61
61
  "view",
62
62
  "admin",
63
63
  "tonymobily3",
64
- 12
64
+ "12"
65
65
  ]);
66
66
  });
67
67
 
@@ -85,9 +85,9 @@ test("invalidateCrudQueries invalidates by CRUD namespace scope key", async () =
85
85
  });
86
86
 
87
87
  test("toRouteRecordId parses scalar and array params safely", () => {
88
- assert.equal(toRouteRecordId("42"), 42);
89
- assert.equal(toRouteRecordId(["7"]), 7);
90
- assert.equal(toRouteRecordId("not-a-number"), 0);
88
+ assert.equal(toRouteRecordId("42"), "42");
89
+ assert.equal(toRouteRecordId(["7"]), "7");
90
+ assert.equal(toRouteRecordId("not-a-number"), "");
91
91
  });
92
92
 
93
93
  test("normalizeCrudRouteParamName validates route parameter names", () => {
@@ -114,7 +114,7 @@ test("resolveCrudRecordPathTemplates supports custom route parameter names", ()
114
114
  });
115
115
 
116
116
  test("resolveCrudRecordPathParams maps record ids to selected route parameter names", () => {
117
- assert.deepEqual(resolveCrudRecordPathParams(42, "addressId"), { addressId: "42" });
117
+ assert.deepEqual(resolveCrudRecordPathParams("42", "addressId"), { addressId: "42" });
118
118
  assert.deepEqual(resolveCrudRecordPathParams("7", "recordId"), { recordId: "7" });
119
119
  assert.deepEqual(resolveCrudRecordPathParams("invalid", "addressId"), {});
120
120
  });
@@ -1,7 +1,20 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { RECORD_ID_PATTERN } from "@jskit-ai/kernel/shared/validators";
3
4
  import { createCrudRepositoryFromResource } from "../src/server/createCrudRepositoryFromResource.js";
4
5
 
6
+ const recordIdSchema = Object.freeze({
7
+ type: "string",
8
+ pattern: RECORD_ID_PATTERN
9
+ });
10
+
11
+ const nullableRecordIdSchema = Object.freeze({
12
+ anyOf: [
13
+ recordIdSchema,
14
+ { type: "null" }
15
+ ]
16
+ });
17
+
5
18
  function createListKnexDouble(
6
19
  rows = [],
7
20
  {
@@ -185,7 +198,7 @@ function createResourceFixture() {
185
198
  schema: {
186
199
  type: "object",
187
200
  properties: {
188
- id: { type: "integer" },
201
+ id: recordIdSchema,
189
202
  firstName: { type: "string" }
190
203
  }
191
204
  }
@@ -222,10 +235,10 @@ function createLookupResourceFixture() {
222
235
  schema: {
223
236
  type: "object",
224
237
  properties: {
225
- id: { type: "integer" },
238
+ id: recordIdSchema,
226
239
  firstName: { type: "string" },
227
- primaryVetId: { type: "integer" },
228
- secondaryVetId: { type: ["integer", "null"] },
240
+ primaryVetId: recordIdSchema,
241
+ secondaryVetId: nullableRecordIdSchema,
229
242
  lookups: {
230
243
  type: "object"
231
244
  }
@@ -239,8 +252,8 @@ function createLookupResourceFixture() {
239
252
  type: "object",
240
253
  properties: {
241
254
  firstName: { type: "string" },
242
- primaryVetId: { type: "integer" },
243
- secondaryVetId: { type: "integer" }
255
+ primaryVetId: recordIdSchema,
256
+ secondaryVetId: recordIdSchema
244
257
  }
245
258
  }
246
259
  }
@@ -289,10 +302,10 @@ function createLookupResourceWithCustomContainerKeyFixture() {
289
302
  schema: {
290
303
  type: "object",
291
304
  properties: {
292
- id: { type: "integer" },
305
+ id: recordIdSchema,
293
306
  firstName: { type: "string" },
294
- primaryVetId: { type: "integer" },
295
- secondaryVetId: { type: "integer" },
307
+ primaryVetId: recordIdSchema,
308
+ secondaryVetId: recordIdSchema,
296
309
  lookupData: {
297
310
  type: "object"
298
311
  }
@@ -306,8 +319,8 @@ function createLookupResourceWithCustomContainerKeyFixture() {
306
319
  type: "object",
307
320
  properties: {
308
321
  firstName: { type: "string" },
309
- primaryVetId: { type: "integer" },
310
- secondaryVetId: { type: "integer" }
322
+ primaryVetId: recordIdSchema,
323
+ secondaryVetId: recordIdSchema
311
324
  }
312
325
  }
313
326
  }
@@ -351,7 +364,7 @@ function createCollectionLookupResourceFixture() {
351
364
  schema: {
352
365
  type: "object",
353
366
  properties: {
354
- id: { type: "integer" },
367
+ id: recordIdSchema,
355
368
  firstName: { type: "string" },
356
369
  lookups: {
357
370
  type: "object"
@@ -400,9 +413,9 @@ function createPetsLookupBackToContactsResourceFixture() {
400
413
  schema: {
401
414
  type: "object",
402
415
  properties: {
403
- id: { type: "integer" },
416
+ id: recordIdSchema,
404
417
  name: { type: "string" },
405
- customerId: { type: "integer" },
418
+ customerId: recordIdSchema,
406
419
  lookups: {
407
420
  type: "object"
408
421
  }
@@ -450,14 +463,14 @@ function createNormalizedResourceFixture() {
450
463
  schema: {
451
464
  type: "object",
452
465
  properties: {
453
- id: { type: "integer" },
466
+ id: recordIdSchema,
454
467
  firstName: { type: "string" }
455
468
  },
456
469
  required: ["id", "firstName"]
457
470
  },
458
471
  normalize(payload = {}) {
459
472
  return {
460
- id: Number(payload.id),
473
+ id: String(payload.id || ""),
461
474
  firstName: String(payload.firstName || "").trim()
462
475
  };
463
476
  }
@@ -494,7 +507,7 @@ function createWritableHookResourceFixture() {
494
507
  schema: {
495
508
  type: "object",
496
509
  properties: {
497
- id: { type: "integer" },
510
+ id: recordIdSchema,
498
511
  firstName: { type: "string" },
499
512
  createdAt: { type: "string" },
500
513
  updatedAt: { type: "string" }
@@ -545,7 +558,7 @@ test("createCrudRepositoryFromResource requires table metadata from resource", (
545
558
  schema: {
546
559
  type: "object",
547
560
  properties: {
548
- id: { type: "integer" }
561
+ id: recordIdSchema
549
562
  }
550
563
  }
551
564
  }
@@ -576,21 +589,21 @@ test("createCrudRepositoryFromResource defaults table and id columns from resour
576
589
  const repository = createRepository(knex);
577
590
 
578
591
  const result = await repository.list({
579
- cursor: 2,
592
+ cursor: "2",
580
593
  q: "to"
581
594
  });
582
595
 
583
596
  assert.deepEqual(result, {
584
597
  items: [
585
598
  {
586
- id: 3,
599
+ id: "3",
587
600
  firstName: "Tony"
588
601
  }
589
602
  ],
590
603
  nextCursor: null
591
604
  });
592
605
  assert.equal(calls[0][1], "contacts_table");
593
- assert.ok(calls.some((call) => call[0] === "where" && call[1] === "contact_id" && call[2] === ">" && call[3] === 2));
606
+ assert.ok(calls.some((call) => call[0] === "where" && call[1] === "contact_id" && call[2] === ">" && call[3] === "2"));
594
607
  });
595
608
 
596
609
  test("createCrudRepositoryFromResource createRepository requires knex", () => {
@@ -601,6 +614,22 @@ test("createCrudRepositoryFromResource createRepository requires knex", () => {
601
614
  );
602
615
  });
603
616
 
617
+ test("createCrudRepositoryFromResource adds withTransaction to repository instances", async () => {
618
+ const createRepository = createCrudRepositoryFromResource(createResourceFixture());
619
+ const knex = Object.assign(() => {
620
+ throw new Error("query execution not expected");
621
+ }, {
622
+ async transaction(work) {
623
+ return work({ trxId: "trx-1" });
624
+ }
625
+ });
626
+
627
+ const repository = createRepository(knex);
628
+ assert.equal(typeof repository.withTransaction, "function");
629
+ const result = await repository.withTransaction(async (trx) => ({ id: trx.trxId }));
630
+ assert.deepEqual(result, { id: "trx-1" });
631
+ });
632
+
604
633
  test("createCrudRepositoryFromResource allows list tuning through list config", async () => {
605
634
  const createRepository = createCrudRepositoryFromResource(createResourceFixture(), {
606
635
  list: {
@@ -646,8 +675,8 @@ test("createCrudRepositoryFromResource supports declarative ordered list paginat
646
675
 
647
676
  assert.deepEqual(result, {
648
677
  items: [
649
- { id: 9, firstName: "Tina" },
650
- { id: 7, firstName: "Tony" }
678
+ { id: "9", firstName: "Tina" },
679
+ { id: "7", firstName: "Tony" }
651
680
  ],
652
681
  nextCursor: Buffer.from(
653
682
  JSON.stringify({ values: ["2026-04-04T09:00:00.000Z", 7] }),
@@ -855,11 +884,11 @@ test("createCrudRepositoryFromResource exposes listByIds for lookup providers",
855
884
  ]);
856
885
  const repository = createRepository(knex);
857
886
 
858
- const records = await repository.listByIds([3, 3, 4]);
887
+ const records = await repository.listByIds(["3", "3", "4"]);
859
888
 
860
889
  assert.equal(records.length, 1);
861
890
  assert.deepEqual(records[0], {
862
- id: 3,
891
+ id: "3",
863
892
  firstName: "Tony"
864
893
  });
865
894
  assert.ok(calls.some((call) => call[0] === "whereIn" && call[1] === "contact_id"));
@@ -879,7 +908,7 @@ test("createCrudRepositoryFromResource exposes listByForeignIds for arbitrary ou
879
908
 
880
909
  assert.equal(records.length, 1);
881
910
  assert.deepEqual(records[0], {
882
- id: 3,
911
+ id: "3",
883
912
  firstName: "Tony"
884
913
  });
885
914
  assert.ok(calls.some((call) => call[0] === "whereIn" && call[1] === "first_name"));
@@ -913,7 +942,7 @@ test("createCrudRepositoryFromResource listByIds fails fast when valueKey is not
913
942
 
914
943
  await assert.rejects(
915
944
  () =>
916
- repository.listByIds([3], {
945
+ repository.listByIds(["3"], {
917
946
  valueKey: "externalCustomerId"
918
947
  }),
919
948
  /valueKey "externalCustomerId" to exist in output schema/
@@ -930,10 +959,10 @@ test("createCrudRepositoryFromResource normalizes listByIds output using resourc
930
959
  ]);
931
960
  const repository = createRepository(knex);
932
961
 
933
- const result = await repository.listByIds([3]);
962
+ const result = await repository.listByIds(["3"]);
934
963
  assert.deepEqual(result, [
935
964
  {
936
- id: 3,
965
+ id: "3",
937
966
  firstName: "Tony"
938
967
  }
939
968
  ]);
@@ -943,14 +972,14 @@ test("createCrudRepositoryFromResource fails when mapped output violates resourc
943
972
  const createRepository = createCrudRepositoryFromResource(createResourceFixture());
944
973
  const { knex } = createListKnexDouble([
945
974
  {
946
- contact_id: "3",
975
+ contact_id: "invalid-id",
947
976
  first_name: "Tony"
948
977
  }
949
978
  ]);
950
979
  const repository = createRepository(knex);
951
980
 
952
981
  await assert.rejects(
953
- () => repository.listByIds([3]),
982
+ () => repository.listByIds(["3"]),
954
983
  /output validation failed/
955
984
  );
956
985
  });
@@ -983,8 +1012,8 @@ test("createCrudRepositoryFromResource hydrates lookup relations by default and
983
1012
  options
984
1013
  });
985
1014
  return [
986
- { id: 10, name: "Vet A" },
987
- { id: 12, name: "Vet B" }
1015
+ { id: "10", name: "Vet A" },
1016
+ { id: "12", name: "Vet B" }
988
1017
  ];
989
1018
  }
990
1019
  };
@@ -994,16 +1023,16 @@ test("createCrudRepositoryFromResource hydrates lookup relations by default and
994
1023
  const result = await repository.list({});
995
1024
 
996
1025
  assert.equal(lookupCalls.length, 1);
997
- assert.deepEqual(lookupCalls[0].ids, [10, 12]);
1026
+ assert.deepEqual(lookupCalls[0].ids, ["10", "12"]);
998
1027
  assert.equal(lookupCalls[0].options.include, "*");
999
1028
  assert.equal(lookupCalls[0].options.lookupDepth, 1);
1000
1029
  assert.equal(lookupCalls[0].options.lookupMaxDepth, 3);
1001
1030
  assert.deepEqual(result.items[0].lookups, {
1002
- primaryVetId: { id: 10, name: "Vet A" },
1003
- secondaryVetId: { id: 12, name: "Vet B" }
1031
+ primaryVetId: { id: "10", name: "Vet A" },
1032
+ secondaryVetId: { id: "12", name: "Vet B" }
1004
1033
  });
1005
1034
  assert.deepEqual(result.items[1].lookups, {
1006
- primaryVetId: { id: 10, name: "Vet A" },
1035
+ primaryVetId: { id: "10", name: "Vet A" },
1007
1036
  secondaryVetId: null
1008
1037
  });
1009
1038
  });
@@ -1023,8 +1052,8 @@ test("createCrudRepositoryFromResource writes hydrated lookups into custom outpu
1023
1052
  return {
1024
1053
  async listByIds() {
1025
1054
  return [
1026
- { id: 10, name: "Vet A" },
1027
- { id: 12, name: "Vet B" }
1055
+ { id: "10", name: "Vet A" },
1056
+ { id: "12", name: "Vet B" }
1028
1057
  ];
1029
1058
  }
1030
1059
  };
@@ -1034,8 +1063,8 @@ test("createCrudRepositoryFromResource writes hydrated lookups into custom outpu
1034
1063
  const result = await repository.list({});
1035
1064
  assert.equal(Object.hasOwn(result.items[0], "lookups"), false);
1036
1065
  assert.deepEqual(result.items[0].lookupData, {
1037
- primaryVetId: { id: 10, name: "Vet A" },
1038
- secondaryVetId: { id: 12, name: "Vet B" }
1066
+ primaryVetId: { id: "10", name: "Vet A" },
1067
+ secondaryVetId: { id: "12", name: "Vet B" }
1039
1068
  });
1040
1069
  });
1041
1070
 
@@ -1113,7 +1142,7 @@ test("createCrudRepositoryFromResource forwards nested include paths to child lo
1113
1142
  ids,
1114
1143
  options
1115
1144
  });
1116
- return [{ id: 10, name: "Vet A" }, { id: 12, name: "Vet B" }];
1145
+ return [{ id: "10", name: "Vet A" }, { id: "12", name: "Vet B" }];
1117
1146
  }
1118
1147
  };
1119
1148
  }
@@ -1147,7 +1176,7 @@ test("createCrudRepositoryFromResource forwards wildcard nested include paths to
1147
1176
  ids,
1148
1177
  options
1149
1178
  });
1150
- return [{ id: 10, name: "Vet A" }, { id: 12, name: "Vet B" }];
1179
+ return [{ id: "10", name: "Vet A" }, { id: "12", name: "Vet B" }];
1151
1180
  }
1152
1181
  };
1153
1182
  }
@@ -1182,7 +1211,7 @@ test("createCrudRepositoryFromResource remaps child lookup visibility for public
1182
1211
  ids,
1183
1212
  options
1184
1213
  });
1185
- return [{ id: 10, name: "Vet A" }, { id: 12, name: "Vet B" }];
1214
+ return [{ id: "10", name: "Vet A" }, { id: "12", name: "Vet B" }];
1186
1215
  }
1187
1216
  };
1188
1217
  }
@@ -1222,7 +1251,7 @@ test("createCrudRepositoryFromResource remaps child lookup visibility for worksp
1222
1251
  ids,
1223
1252
  options
1224
1253
  });
1225
- return [{ id: 10, name: "Vet A" }, { id: 12, name: "Vet B" }];
1254
+ return [{ id: "10", name: "Vet A" }, { id: "12", name: "Vet B" }];
1226
1255
  }
1227
1256
  };
1228
1257
  }
@@ -1232,7 +1261,7 @@ test("createCrudRepositoryFromResource remaps child lookup visibility for worksp
1232
1261
  visibilityContext: {
1233
1262
  visibility: "workspace_user",
1234
1263
  scopeOwnerId: "workspace-1",
1235
- userOwnerId: "user-1"
1264
+ userId: "user-1"
1236
1265
  }
1237
1266
  });
1238
1267
 
@@ -1299,9 +1328,9 @@ test("createCrudRepositoryFromResource hydrates collection relations through lis
1299
1328
  options
1300
1329
  });
1301
1330
  return [
1302
- { id: 11, name: "Milo", customerId: 3 },
1303
- { id: 12, name: "Luna", customerId: 3 },
1304
- { id: 20, name: "Ruby", customerId: 4 }
1331
+ { id: "11", name: "Milo", customerId: "3" },
1332
+ { id: "12", name: "Luna", customerId: "3" },
1333
+ { id: "20", name: "Ruby", customerId: "4" }
1305
1334
  ];
1306
1335
  }
1307
1336
  };
@@ -1313,7 +1342,7 @@ test("createCrudRepositoryFromResource hydrates collection relations through lis
1313
1342
  });
1314
1343
 
1315
1344
  assert.equal(lookupCalls.length, 1);
1316
- assert.deepEqual(lookupCalls[0].ids, [3, 4]);
1345
+ assert.deepEqual(lookupCalls[0].ids, ["3", "4"]);
1317
1346
  assert.equal(lookupCalls[0].options.include, "none");
1318
1347
  assert.equal(lookupCalls[0].options.valueKey, "customerId");
1319
1348
  assert.deepEqual(result.items[0].lookups?.pets?.map((item) => item.name), ["Milo", "Luna"]);
@@ -1402,7 +1431,7 @@ test("createCrudRepositoryFromResource forwards configured lookup maxDepth to ch
1402
1431
  ids,
1403
1432
  options
1404
1433
  });
1405
- return [{ id: 10, name: "Vet A" }, { id: 12, name: "Vet B" }];
1434
+ return [{ id: "10", name: "Vet A" }, { id: "12", name: "Vet B" }];
1406
1435
  }
1407
1436
  };
1408
1437
  }
@@ -1598,7 +1627,7 @@ test("createCrudRepositoryFromResource list hooks keep visibility and canonical
1598
1627
  const repository = createRepository(knex);
1599
1628
 
1600
1629
  await repository.list({
1601
- cursor: 2,
1630
+ cursor: "2",
1602
1631
  limit: 5
1603
1632
  }, {
1604
1633
  visibilityContext: {
@@ -1612,8 +1641,8 @@ test("createCrudRepositoryFromResource list hooks keep visibility and canonical
1612
1641
  }
1613
1642
  });
1614
1643
 
1615
- assert.ok(calls.some((call) => call[0] === "where" && call[1] === "contact_id" && call[2] === ">" && call[3] === 2));
1616
- assert.ok(calls.some((call) => call[0] === "where" && call[1] === "workspace_owner_id" && call[2] === "workspace-1"));
1644
+ assert.ok(calls.some((call) => call[0] === "where" && call[1] === "contact_id" && call[2] === ">" && call[3] === "2"));
1645
+ assert.ok(calls.some((call) => call[0] === "where" && call[1] === "workspace_id" && call[2] === "workspace-1"));
1617
1646
  assert.ok(calls.some((call) => call[0] === "clearOrder"));
1618
1647
  assert.ok(calls.some((call) => call[0] === "clear" && call[1] === "limit"));
1619
1648
  assert.ok(calls.some((call) => call[0] === "orderBy" && call[1] === "contact_id" && call[2] === "asc"));
@@ -1630,7 +1659,7 @@ test("createCrudRepositoryFromResource findById hooks keep visibility and id pre
1630
1659
  ]);
1631
1660
  const repository = createRepository(knex);
1632
1661
 
1633
- await repository.findById(7, {
1662
+ await repository.findById("7", {
1634
1663
  visibilityContext: {
1635
1664
  visibility: "workspace",
1636
1665
  scopeOwnerId: "workspace-1"
@@ -1643,8 +1672,8 @@ test("createCrudRepositoryFromResource findById hooks keep visibility and id pre
1643
1672
  });
1644
1673
 
1645
1674
  assert.ok(calls.some((call) => call[0] === "where" && call[1] === "contact_id" && call[2] === 999));
1646
- assert.ok(calls.some((call) => call[0] === "where" && call[1] === "workspace_owner_id" && call[2] === "workspace-1"));
1647
- assert.ok(calls.some((call) => call[0] === "where" && call[1]?.contact_id === 7));
1675
+ assert.ok(calls.some((call) => call[0] === "where" && call[1] === "workspace_id" && call[2] === "workspace-1"));
1676
+ assert.ok(calls.some((call) => call[0] === "where" && call[1]?.contact_id === "7"));
1648
1677
  });
1649
1678
 
1650
1679
  test("createCrudRepositoryFromResource findById hooks share state between query and transformReturnedRecord", async () => {
@@ -1657,7 +1686,7 @@ test("createCrudRepositoryFromResource findById hooks share state between query
1657
1686
  ]);
1658
1687
  const repository = createRepository(knex);
1659
1688
 
1660
- const record = await repository.findById(7, {}, {
1689
+ const record = await repository.findById("7", {}, {
1661
1690
  modifyQuery(_dbQuery, context = {}) {
1662
1691
  context.state.recordTag = "from-state";
1663
1692
  },
@@ -1712,7 +1741,7 @@ test("createCrudRepositoryFromResource listByIds hooks support afterQuery/transf
1712
1741
  }
1713
1742
  });
1714
1743
 
1715
- assert.deepEqual(items.map((item) => item.id), [4, 3]);
1744
+ assert.deepEqual(items.map((item) => item.id), ["4", "3"]);
1716
1745
  assert.deepEqual(items.map((item) => item.nameTag), ["SAM", "TONY"]);
1717
1746
  });
1718
1747
 
@@ -1742,7 +1771,7 @@ test("createCrudRepositoryFromResource create hooks keep write-key filtering and
1742
1771
  return {
1743
1772
  ...payload,
1744
1773
  unexpectedField: "blocked",
1745
- workspaceOwnerId: "blocked"
1774
+ workspaceId: "blocked"
1746
1775
  };
1747
1776
  },
1748
1777
  modifyQuery(_dbQuery, context = {}) {
@@ -1756,8 +1785,8 @@ test("createCrudRepositoryFromResource create hooks keep write-key filtering and
1756
1785
  assert.deepEqual(state.insertPayloads[0].first_name, "Tony");
1757
1786
  assert.equal(Object.hasOwn(state.insertPayloads[0], "unexpectedField"), false);
1758
1787
  assert.equal(Object.hasOwn(state.insertPayloads[0], "unexpectedFieldFromQuery"), false);
1759
- assert.equal(Object.hasOwn(state.insertPayloads[0], "workspaceOwnerId"), false);
1760
- assert.equal(state.insertPayloads[0].workspace_owner_id, "workspace-1");
1788
+ assert.equal(Object.hasOwn(state.insertPayloads[0], "workspaceId"), false);
1789
+ assert.equal(state.insertPayloads[0].workspace_id, "workspace-1");
1761
1790
  assert.ok(state.insertPayloads[0].created_at);
1762
1791
  assert.ok(state.insertPayloads[0].updated_at);
1763
1792
  });
@@ -1855,7 +1884,7 @@ test("createCrudRepositoryFromResource create hooks support afterWrite and canon
1855
1884
  assert.equal(afterWriteCalls.length, 1);
1856
1885
  assert.equal(afterWriteCalls[0].operation, "create");
1857
1886
  assert.equal(afterWriteCalls[0].createdName, "Tony");
1858
- assert.equal(afterWriteCalls[0].recordId, 11);
1887
+ assert.equal(afterWriteCalls[0].recordId, "11");
1859
1888
  });
1860
1889
 
1861
1890
  test("createCrudRepositoryFromResource update hooks keep write-key filtering and by-id visibility constraints", async () => {
@@ -1870,7 +1899,7 @@ test("createCrudRepositoryFromResource update hooks keep write-key filtering and
1870
1899
  ]);
1871
1900
  const repository = createRepository(knex);
1872
1901
 
1873
- await repository.updateById(11, {
1902
+ await repository.updateById("11", {
1874
1903
  firstName: "Tony"
1875
1904
  }, {
1876
1905
  visibilityContext: {
@@ -1899,8 +1928,8 @@ test("createCrudRepositoryFromResource update hooks keep write-key filtering and
1899
1928
  assert.equal(Object.hasOwn(state.updatePayloads[0], "unexpectedFieldFromQuery"), false);
1900
1929
  assert.ok(state.updatePayloads[0].updated_at);
1901
1930
  assert.ok(calls.some((call) => call[0] === "where" && call[1] === "vip" && call[2] === 1));
1902
- assert.ok(calls.some((call) => call[0] === "where" && call[1] === "workspace_owner_id" && call[2] === "workspace-1"));
1903
- assert.ok(calls.some((call) => call[0] === "where" && call[1]?.contact_id === 11));
1931
+ assert.ok(calls.some((call) => call[0] === "where" && call[1] === "workspace_id" && call[2] === "workspace-1"));
1932
+ assert.ok(calls.some((call) => call[0] === "where" && call[1]?.contact_id === "11"));
1904
1933
  });
1905
1934
 
1906
1935
  test("createCrudRepositoryFromResource update hooks reject read-phase hook keys", async () => {
@@ -1916,7 +1945,7 @@ test("createCrudRepositoryFromResource update hooks reject read-phase hook keys"
1916
1945
  const repository = createRepository(knex);
1917
1946
 
1918
1947
  await assert.rejects(
1919
- () => repository.updateById(11, {
1948
+ () => repository.updateById("11", {
1920
1949
  firstName: "Tony"
1921
1950
  }, {}, {
1922
1951
  transformReturnedRecord(record = {}) {
@@ -1940,7 +1969,7 @@ test("createCrudRepositoryFromResource update hooks support afterWrite and canon
1940
1969
  const repository = createRepository(knex);
1941
1970
  const afterWriteCalls = [];
1942
1971
 
1943
- const record = await repository.updateById(11, {
1972
+ const record = await repository.updateById("11", {
1944
1973
  firstName: "Tony"
1945
1974
  }, {}, {
1946
1975
  modifyPatch(patch = {}, context = {}) {
@@ -1961,7 +1990,7 @@ test("createCrudRepositoryFromResource update hooks support afterWrite and canon
1961
1990
  assert.equal(afterWriteCalls.length, 1);
1962
1991
  assert.equal(afterWriteCalls[0].operation, "update");
1963
1992
  assert.deepEqual(afterWriteCalls[0].patchKeys, ["firstName"]);
1964
- assert.equal(afterWriteCalls[0].recordId, 11);
1993
+ assert.equal(afterWriteCalls[0].recordId, "11");
1965
1994
  });
1966
1995
 
1967
1996
  test("createCrudRepositoryFromResource delete hooks run through callOptions.trx client", async () => {
@@ -1980,7 +2009,7 @@ test("createCrudRepositoryFromResource delete hooks run through callOptions.trx
1980
2009
  ]);
1981
2010
  const repository = createRepository(baseKnex.knex);
1982
2011
 
1983
- await repository.deleteById(3, {
2012
+ await repository.deleteById("3", {
1984
2013
  trx: trxKnex.knex,
1985
2014
  visibilityContext: {
1986
2015
  visibility: "workspace",
@@ -2010,7 +2039,7 @@ test("createCrudRepositoryFromResource delete hooks support afterWrite", async (
2010
2039
  const repository = createRepository(knex);
2011
2040
  const afterWriteCalls = [];
2012
2041
 
2013
- const result = await repository.deleteById(3, {}, {
2042
+ const result = await repository.deleteById("3", {}, {
2014
2043
  afterWrite(meta = {}, context = {}) {
2015
2044
  context.state.deletedId = meta?.output?.id || null;
2016
2045
  afterWriteCalls.push({
@@ -2023,7 +2052,7 @@ test("createCrudRepositoryFromResource delete hooks support afterWrite", async (
2023
2052
  assert.equal(result.deleted, true);
2024
2053
  assert.equal(afterWriteCalls.length, 1);
2025
2054
  assert.equal(afterWriteCalls[0].operation, "delete");
2026
- assert.equal(afterWriteCalls[0].deletedId, 3);
2055
+ assert.equal(afterWriteCalls[0].deletedId, "3");
2027
2056
  });
2028
2057
 
2029
2058
  test("createCrudRepositoryFromResource delete hooks support finalizeOutput for record and null flows", async () => {
@@ -2038,7 +2067,7 @@ test("createCrudRepositoryFromResource delete hooks support finalizeOutput for r
2038
2067
  const presentRepository = createRepository(presentKnex.knex);
2039
2068
  const missingRepository = createRepository(missingKnex.knex);
2040
2069
 
2041
- const deletedOutput = await presentRepository.deleteById(3, {}, {
2070
+ const deletedOutput = await presentRepository.deleteById("3", {}, {
2042
2071
  finalizeOutput(output) {
2043
2072
  return output
2044
2073
  ? { ...output, status: "deleted" }
@@ -2046,7 +2075,7 @@ test("createCrudRepositoryFromResource delete hooks support finalizeOutput for r
2046
2075
  }
2047
2076
  });
2048
2077
 
2049
- const missingOutput = await missingRepository.deleteById(3, {}, {
2078
+ const missingOutput = await missingRepository.deleteById("3", {}, {
2050
2079
  finalizeOutput(output) {
2051
2080
  return output === null ? { deleted: false } : output;
2052
2081
  }
@@ -89,8 +89,8 @@ test("normalizeCrudListLimit enforces fallback and max", () => {
89
89
  });
90
90
 
91
91
  test("normalizeCrudListCursor rejects malformed id cursors", () => {
92
- assert.equal(normalizeCrudListCursor("7"), 7);
93
- assert.equal(normalizeCrudListCursor(""), 0);
92
+ assert.equal(normalizeCrudListCursor("7"), "7");
93
+ assert.equal(normalizeCrudListCursor(""), "");
94
94
  assert.throws(
95
95
  () => normalizeCrudListCursor("abc"),
96
96
  /Invalid cursor/
@@ -147,7 +147,7 @@ test("applyCrudListQueryFilters applies search and cursor filters", () => {
147
147
  ["whereGroup"],
148
148
  ["innerWhere", "first_name", "like", "%ani%"],
149
149
  ["innerOrWhere", "last_name", "like", "%ani%"],
150
- ["where", "id", ">", 3]
150
+ ["where", "id", ">", "3"]
151
151
  ]);
152
152
  });
153
153