@orkestrel/database 0.0.14 → 0.0.15

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,4 +1,4 @@
1
- import { cloneJSONRecord, cloneJSONValue, compileGuard, compileSchema, createContract, isArray, isRecord, isString, objectShape, parseNumber, resolveField } from "@orkestrel/contract";
1
+ import { arrayOf, cloneJSONRecord, cloneJSONValue, compileGuard, compileSchema, createContract, holds, isArray, isBoolean, isError, isFiniteNumber, isInstance, isInteger, isNumber, isRecord, isString, objectShape, parseNumber, resolveField } from "@orkestrel/contract";
2
2
  import { Emitter } from "@orkestrel/emitter";
3
3
  //#region src/core/constants.ts
4
4
  /**
@@ -143,7 +143,7 @@ var DatabaseError = class extends Error {
143
143
  * ```
144
144
  */
145
145
  function isDatabaseError(value) {
146
- return value instanceof DatabaseError;
146
+ return isInstance(value, DatabaseError);
147
147
  }
148
148
  //#endregion
149
149
  //#region src/core/validators.ts
@@ -154,7 +154,7 @@ function isDatabaseError(value) {
154
154
  * @returns True if `value` is a string or a finite number; false otherwise
155
155
  */
156
156
  function isKey(value) {
157
- return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
157
+ return isString(value) || isFiniteNumber(value);
158
158
  }
159
159
  /**
160
160
  * Checks whether a value is a portable column schema.
@@ -167,13 +167,11 @@ function isKey(value) {
167
167
  * @returns True if `value` is a complete {@link ColumnSchema}; false otherwise
168
168
  */
169
169
  function isColumnSchema(value) {
170
- try {
170
+ return holds(() => {
171
171
  const column = cloneJSONRecord(value);
172
172
  const keys = Object.keys(column);
173
- return keys.length === 4 && keys.includes("name") && keys.includes("storage") && keys.includes("optional") && keys.includes("nullable") && typeof column.name === "string" && column.name.length > 0 && (column.storage === "text" || column.storage === "integer" || column.storage === "real" || column.storage === "boolean" || column.storage === "json" || column.storage === "blob") && typeof column.optional === "boolean" && typeof column.nullable === "boolean";
174
- } catch {
175
- return false;
176
- }
173
+ return keys.length === 4 && keys.includes("name") && keys.includes("storage") && keys.includes("optional") && keys.includes("nullable") && isString(column.name) && column.name.length > 0 && (column.storage === "text" || column.storage === "integer" || column.storage === "real" || column.storage === "boolean" || column.storage === "json" || column.storage === "blob") && isBoolean(column.optional) && isBoolean(column.nullable);
174
+ });
177
175
  }
178
176
  /**
179
177
  * Checks whether a value is a portable table schema.
@@ -186,17 +184,15 @@ function isColumnSchema(value) {
186
184
  * @returns True if `value` is a complete {@link TableSchema}; false otherwise
187
185
  */
188
186
  function isTableSchema(value) {
189
- try {
187
+ return holds(() => {
190
188
  const table = cloneJSONRecord(value);
191
189
  const keys = Object.keys(table);
192
- if (keys.length !== 4 || !keys.includes("name") || !keys.includes("primary") || !keys.includes("columns") || !keys.includes("indexes") || typeof table.name !== "string" || table.name.length === 0 || typeof table.primary !== "string" || table.primary.length === 0 || !Array.isArray(table.columns) || !Array.isArray(table.indexes) || !table.columns.every(isColumnSchema)) return false;
190
+ if (keys.length !== 4 || !keys.includes("name") || !keys.includes("primary") || !keys.includes("columns") || !keys.includes("indexes") || !isString(table.name) || table.name.length === 0 || !isString(table.primary) || table.primary.length === 0 || !arrayOf(isColumnSchema)(table.columns) || !isArray(table.indexes)) return false;
193
191
  const names = table.columns.map((column) => column.name);
194
- if (new Set(names).size !== names.length || !names.includes(table.primary) || !table.indexes.every((index) => Array.isArray(index) && index.length > 0 && index.every((column) => typeof column === "string" && names.includes(column)))) return false;
192
+ if (new Set(names).size !== names.length || !names.includes(table.primary) || !table.indexes.every((index) => isArray(index) && index.length > 0 && index.every((column) => isString(column) && names.includes(column)))) return false;
195
193
  const indexes = table.indexes.map((index) => JSON.stringify(index));
196
194
  return new Set(indexes).size === indexes.length;
197
- } catch {
198
- return false;
199
- }
195
+ });
200
196
  }
201
197
  /**
202
198
  * Checks whether a value is a complete portable driver schema.
@@ -209,14 +205,12 @@ function isTableSchema(value) {
209
205
  * @returns True if `value` is a table-schema collection with unique table names; false otherwise
210
206
  */
211
207
  function isDriverSchema(value) {
212
- try {
208
+ return holds(() => {
213
209
  const schema = cloneJSONValue(value);
214
- if (!Array.isArray(schema) || !schema.every(isTableSchema)) return false;
210
+ if (!arrayOf(isTableSchema)(schema)) return false;
215
211
  const names = schema.map((table) => table.name);
216
212
  return new Set(names).size === names.length;
217
- } catch {
218
- return false;
219
- }
213
+ });
220
214
  }
221
215
  /**
222
216
  * Checks whether a value is one ordered migration step.
@@ -229,22 +223,20 @@ function isDriverSchema(value) {
229
223
  * @returns True if `value` is a complete {@link MigrationStep}; false otherwise
230
224
  */
231
225
  function isMigrationStep(value) {
232
- try {
226
+ return holds(() => {
233
227
  const step = cloneJSONRecord(value);
234
- if (typeof step.operation !== "string") return false;
228
+ if (!isString(step.operation)) return false;
235
229
  const keys = Object.keys(step);
236
230
  switch (step.operation) {
237
231
  case "table.add": return keys.length === 2 && keys.includes("operation") && keys.includes("table") && isTableSchema(step.table);
238
- case "table.remove": return keys.length === 2 && keys.includes("operation") && keys.includes("table") && typeof step.table === "string" && step.table.length > 0;
239
- case "column.add": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && typeof step.table === "string" && step.table.length > 0 && isColumnSchema(step.column);
240
- case "column.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && typeof step.table === "string" && step.table.length > 0 && typeof step.column === "string" && step.column.length > 0;
232
+ case "table.remove": return keys.length === 2 && keys.includes("operation") && keys.includes("table") && isString(step.table) && step.table.length > 0;
233
+ case "column.add": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && isString(step.table) && step.table.length > 0 && isColumnSchema(step.column);
234
+ case "column.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("column") && isString(step.table) && step.table.length > 0 && isString(step.column) && step.column.length > 0;
241
235
  case "index.add":
242
- case "index.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("index") && typeof step.table === "string" && step.table.length > 0 && Array.isArray(step.index) && step.index.length > 0 && step.index.every((column) => typeof column === "string" && column.length > 0);
236
+ case "index.remove": return keys.length === 3 && keys.includes("operation") && keys.includes("table") && keys.includes("index") && isString(step.table) && step.table.length > 0 && isArray(step.index) && step.index.length > 0 && step.index.every((column) => isString(column) && column.length > 0);
243
237
  default: return false;
244
238
  }
245
- } catch {
246
- return false;
247
- }
239
+ });
248
240
  }
249
241
  /**
250
242
  * Checks whether a value is an ordered migration plan.
@@ -257,13 +249,11 @@ function isMigrationStep(value) {
257
249
  * @returns True if `value` is a complete {@link Migration}; false otherwise
258
250
  */
259
251
  function isMigration(value) {
260
- try {
252
+ return holds(() => {
261
253
  const migration = cloneJSONRecord(value);
262
254
  const keys = Object.keys(migration);
263
- return keys.length === 3 && keys.includes("from") && keys.includes("to") && keys.includes("steps") && typeof migration.from === "number" && Number.isFinite(migration.from) && typeof migration.to === "number" && Number.isFinite(migration.to) && Array.isArray(migration.steps) && migration.steps.every(isMigrationStep);
264
- } catch {
265
- return false;
266
- }
255
+ return keys.length === 3 && keys.includes("from") && keys.includes("to") && keys.includes("steps") && isFiniteNumber(migration.from) && isFiniteNumber(migration.to) && isArray(migration.steps) && migration.steps.every(isMigrationStep);
256
+ });
267
257
  }
268
258
  /**
269
259
  * Checks whether a value is persisted driver metadata.
@@ -278,13 +268,11 @@ function isMigration(value) {
278
268
  * @returns True if `value` is complete {@link DriverMetadata}; false otherwise
279
269
  */
280
270
  function isDriverMetadata(value) {
281
- try {
271
+ return holds(() => {
282
272
  const metadata = cloneJSONRecord(value);
283
273
  const keys = Object.keys(metadata);
284
- return keys.length === 2 && keys.includes("version") && keys.includes("schema") && typeof metadata.version === "number" && Number.isFinite(metadata.version) && isDriverSchema(metadata.schema);
285
- } catch {
286
- return false;
287
- }
274
+ return keys.length === 2 && keys.includes("version") && keys.includes("schema") && isFiniteNumber(metadata.version) && isDriverSchema(metadata.schema);
275
+ });
288
276
  }
289
277
  /**
290
278
  * Checks whether a value is one atomic migration request.
@@ -297,13 +285,11 @@ function isDriverMetadata(value) {
297
285
  * @returns True if `value` is a complete {@link MigrationInput}; false otherwise
298
286
  */
299
287
  function isMigrationInput(value) {
300
- try {
288
+ return holds(() => {
301
289
  const input = cloneJSONRecord(value);
302
290
  const keys = Object.keys(input);
303
291
  return (keys.length === 1 || keys.length === 2) && keys.includes("plan") && (keys.length === 1 || keys.includes("metadata")) && isMigration(input.plan) && (input.metadata === void 0 || isDriverMetadata(input.metadata));
304
- } catch {
305
- return false;
306
- }
292
+ });
307
293
  }
308
294
  //#endregion
309
295
  //#region src/core/cloners.ts
@@ -324,7 +310,7 @@ function cloneDriverMetadata(value) {
324
310
  if (isDriverMetadata(metadata)) return metadata;
325
311
  throw new DatabaseError("VALIDATION", "Driver metadata is invalid", { path: "metadata" });
326
312
  } catch (error) {
327
- if (error instanceof DatabaseError) throw error;
313
+ if (isDatabaseError(error)) throw error;
328
314
  throw new DatabaseError("VALIDATION", "Driver metadata is invalid", {
329
315
  path: "metadata",
330
316
  cause: error
@@ -348,7 +334,7 @@ function cloneDriverSchema(value) {
348
334
  if (isDriverSchema(schema)) return schema;
349
335
  throw new DatabaseError("VALIDATION", "Driver schema is invalid", { path: "schema" });
350
336
  } catch (error) {
351
- if (error instanceof DatabaseError) throw error;
337
+ if (isDatabaseError(error)) throw error;
352
338
  throw new DatabaseError("VALIDATION", "Driver schema is invalid", {
353
339
  path: "schema",
354
340
  cause: error
@@ -372,7 +358,7 @@ function cloneMigrationInput(value) {
372
358
  if (isMigrationInput(input)) return input;
373
359
  throw new DatabaseError("VALIDATION", "Migration input is invalid", { path: "migration" });
374
360
  } catch (error) {
375
- if (error instanceof DatabaseError) throw error;
361
+ if (isDatabaseError(error)) throw error;
376
362
  throw new DatabaseError("VALIDATION", "Migration input is invalid", {
377
363
  path: "migration",
378
364
  cause: error
@@ -395,14 +381,14 @@ function cloneMigrationInput(value) {
395
381
  */
396
382
  function validatePage(input) {
397
383
  const limit = input?.limit;
398
- if (limit !== void 0 && (!Number.isInteger(limit) || limit < 0)) throw new DatabaseError("VALIDATION", "Query limit must be a nonnegative integer", {
384
+ if (limit !== void 0 && (!isInteger(limit) || limit < 0)) throw new DatabaseError("VALIDATION", "Query limit must be a nonnegative integer", {
399
385
  field: "limit",
400
- value: Number.isFinite(limit) ? limit : String(limit)
386
+ value: isFiniteNumber(limit) ? limit : String(limit)
401
387
  });
402
388
  const offset = input?.offset;
403
- if (offset !== void 0 && (!Number.isInteger(offset) || offset < 0)) throw new DatabaseError("VALIDATION", "Query offset must be a nonnegative integer", {
389
+ if (offset !== void 0 && (!isInteger(offset) || offset < 0)) throw new DatabaseError("VALIDATION", "Query offset must be a nonnegative integer", {
404
390
  field: "offset",
405
- value: Number.isFinite(offset) ? offset : String(offset)
391
+ value: isFiniteNumber(offset) ? offset : String(offset)
406
392
  });
407
393
  }
408
394
  /**
@@ -420,14 +406,14 @@ function validatePage(input) {
420
406
  * @returns `-1`, `0`, or `1`
421
407
  */
422
408
  function compareValues(left, right) {
423
- const [leftRank = 5, rightRank = 5] = [left, right].map((value) => value === void 0 ? 0 : value === null ? 1 : typeof value === "boolean" ? 2 : typeof value === "number" ? 3 : typeof value === "string" ? 4 : 5);
409
+ const [leftRank = 5, rightRank = 5] = [left, right].map((value) => value === void 0 ? 0 : value === null ? 1 : isBoolean(value) ? 2 : isNumber(value) ? 3 : isString(value) ? 4 : 5);
424
410
  if (leftRank !== rightRank) return leftRank < rightRank ? -1 : 1;
425
- if (typeof left === "number" && typeof right === "number") {
411
+ if (isNumber(left) && isNumber(right)) {
426
412
  if (Number.isNaN(left) || Number.isNaN(right)) return Number.isNaN(left) ? Number.isNaN(right) ? 0 : 1 : -1;
427
413
  return left < right ? -1 : left > right ? 1 : 0;
428
414
  }
429
- if (typeof left === "string" && typeof right === "string") return left < right ? -1 : left > right ? 1 : 0;
430
- if (typeof left === "boolean" && typeof right === "boolean") return left === right ? 0 : left ? 1 : -1;
415
+ if (isString(left) && isString(right)) return left < right ? -1 : left > right ? 1 : 0;
416
+ if (isBoolean(left) && isBoolean(right)) return left === right ? 0 : left ? 1 : -1;
431
417
  return 0;
432
418
  }
433
419
  /**
@@ -466,13 +452,13 @@ function equalsValue(left, right) {
466
452
  const pair = pending.pop();
467
453
  if (pair === void 0) continue;
468
454
  const [currentLeft, currentRight] = pair;
469
- if (typeof currentLeft === "number" && typeof currentRight === "number") {
455
+ if (isNumber(currentLeft) && isNumber(currentRight)) {
470
456
  if (Number.isNaN(currentLeft) && Number.isNaN(currentRight) || currentLeft === currentRight) continue;
471
457
  return false;
472
458
  }
473
459
  if (currentLeft === currentRight) continue;
474
- const leftArray = Array.isArray(currentLeft);
475
- const rightArray = Array.isArray(currentRight);
460
+ const leftArray = isArray(currentLeft);
461
+ const rightArray = isArray(currentRight);
476
462
  const leftRecord = isRecord(currentLeft);
477
463
  const rightRecord = isRecord(currentRight);
478
464
  if (leftArray !== rightArray || leftRecord !== rightRecord) return false;
@@ -837,8 +823,8 @@ function shapeToColumnStorage(shape) {
837
823
  case "number": return shape.integer === true ? "integer" : "real";
838
824
  case "boolean": return "boolean";
839
825
  case "literal":
840
- if (shape.values.every((value) => typeof value === "boolean")) return "boolean";
841
- if (shape.values.every((value) => typeof value === "number")) return shape.values.every((value) => Number.isInteger(value)) ? "integer" : "real";
826
+ if (shape.values.every((value) => isBoolean(value))) return "boolean";
827
+ if (shape.values.every((value) => isNumber(value))) return shape.values.every((value) => isInteger(value)) ? "integer" : "real";
842
828
  return "text";
843
829
  case "optional":
844
830
  case "nullable": return shapeToColumnStorage(shape.inner);
@@ -987,7 +973,7 @@ function checkAbort(signal) {
987
973
  * ```
988
974
  */
989
975
  function planMigration(deployed, declared, from = 0, to = 1) {
990
- if (!Number.isFinite(from) || !Number.isFinite(to)) throw new DatabaseError("MIGRATION", "Migration versions must be finite", {
976
+ if (!isFiniteNumber(from) || !isFiniteNumber(to)) throw new DatabaseError("MIGRATION", "Migration versions must be finite", {
991
977
  from,
992
978
  to
993
979
  });
@@ -1291,7 +1277,7 @@ async function* scanDriver(factory) {
1291
1277
  } catch (error) {
1292
1278
  yield {
1293
1279
  check: "open-close",
1294
- message: error instanceof Error ? error.message : String(error),
1280
+ message: isError(error) ? error.message : String(error),
1295
1281
  context: { error }
1296
1282
  };
1297
1283
  }
@@ -1312,7 +1298,7 @@ async function* scanDriver(factory) {
1312
1298
  } catch (error) {
1313
1299
  yield {
1314
1300
  check: "read-missing",
1315
- message: error instanceof Error ? error.message : String(error),
1301
+ message: isError(error) ? error.message : String(error),
1316
1302
  context: { error }
1317
1303
  };
1318
1304
  }
@@ -1388,7 +1374,7 @@ async function* scanDriver(factory) {
1388
1374
  } catch (error) {
1389
1375
  yield {
1390
1376
  check: "write-read",
1391
- message: error instanceof Error ? error.message : String(error),
1377
+ message: isError(error) ? error.message : String(error),
1392
1378
  context: { error }
1393
1379
  };
1394
1380
  }
@@ -1429,7 +1415,7 @@ async function* scanDriver(factory) {
1429
1415
  } catch (error) {
1430
1416
  yield {
1431
1417
  check: "insert-atomic",
1432
- message: error instanceof Error ? error.message : String(error),
1418
+ message: isError(error) ? error.message : String(error),
1433
1419
  context: { error }
1434
1420
  };
1435
1421
  }
@@ -1469,7 +1455,7 @@ async function* scanDriver(factory) {
1469
1455
  } catch (error) {
1470
1456
  yield {
1471
1457
  check: "delete",
1472
- message: error instanceof Error ? error.message : String(error),
1458
+ message: isError(error) ? error.message : String(error),
1473
1459
  context: { error }
1474
1460
  };
1475
1461
  }
@@ -1532,7 +1518,7 @@ async function* scanDriver(factory) {
1532
1518
  } catch (error) {
1533
1519
  yield {
1534
1520
  check: "mutation-abort",
1535
- message: error instanceof Error ? error.message : String(error),
1521
+ message: isError(error) ? error.message : String(error),
1536
1522
  context: { error }
1537
1523
  };
1538
1524
  }
@@ -1591,7 +1577,7 @@ async function* scanDriver(factory) {
1591
1577
  } catch (error) {
1592
1578
  yield {
1593
1579
  check: "order",
1594
- message: error instanceof Error ? error.message : String(error),
1580
+ message: isError(error) ? error.message : String(error),
1595
1581
  context: { error }
1596
1582
  };
1597
1583
  }
@@ -1635,7 +1621,7 @@ async function* scanDriver(factory) {
1635
1621
  } catch (error) {
1636
1622
  yield {
1637
1623
  check: "clear",
1638
- message: error instanceof Error ? error.message : String(error),
1624
+ message: isError(error) ? error.message : String(error),
1639
1625
  context: { error }
1640
1626
  };
1641
1627
  }
@@ -1684,7 +1670,7 @@ async function* scanDriver(factory) {
1684
1670
  } catch (error) {
1685
1671
  yield {
1686
1672
  check: "snapshot",
1687
- message: error instanceof Error ? error.message : String(error),
1673
+ message: isError(error) ? error.message : String(error),
1688
1674
  context: { error }
1689
1675
  };
1690
1676
  }
@@ -1722,7 +1708,7 @@ async function* scanDriver(factory) {
1722
1708
  } catch (error) {
1723
1709
  yield {
1724
1710
  check: "snapshot-nested",
1725
- message: error instanceof Error ? error.message : String(error),
1711
+ message: isError(error) ? error.message : String(error),
1726
1712
  context: { error }
1727
1713
  };
1728
1714
  }
@@ -1748,7 +1734,7 @@ async function* scanDriver(factory) {
1748
1734
  } catch (error) {
1749
1735
  yield {
1750
1736
  check: "non-id-primary",
1751
- message: error instanceof Error ? error.message : String(error),
1737
+ message: isError(error) ? error.message : String(error),
1752
1738
  context: { error }
1753
1739
  };
1754
1740
  }
@@ -1779,7 +1765,7 @@ async function* scanDriver(factory) {
1779
1765
  } catch (error) {
1780
1766
  yield {
1781
1767
  check: "nested-roundtrip",
1782
- message: error instanceof Error ? error.message : String(error),
1768
+ message: isError(error) ? error.message : String(error),
1783
1769
  context: { error }
1784
1770
  };
1785
1771
  }
@@ -1844,7 +1830,7 @@ async function* scanDriver(factory) {
1844
1830
  } catch (error) {
1845
1831
  yield {
1846
1832
  check: "migrate",
1847
- message: error instanceof Error ? error.message : String(error),
1833
+ message: isError(error) ? error.message : String(error),
1848
1834
  context: { error }
1849
1835
  };
1850
1836
  }
@@ -1909,7 +1895,7 @@ async function* scanDriver(factory) {
1909
1895
  } catch (error) {
1910
1896
  yield {
1911
1897
  check: "stream",
1912
- message: error instanceof Error ? error.message : String(error),
1898
+ message: isError(error) ? error.message : String(error),
1913
1899
  context: { error }
1914
1900
  };
1915
1901
  }
@@ -1970,7 +1956,7 @@ async function* scanDriver(factory) {
1970
1956
  } catch (error) {
1971
1957
  yield {
1972
1958
  check: "transaction",
1973
- message: error instanceof Error ? error.message : String(error),
1959
+ message: isError(error) ? error.message : String(error),
1974
1960
  context: { error }
1975
1961
  };
1976
1962
  }
@@ -2009,7 +1995,7 @@ async function* scanDriver(factory) {
2009
1995
  } catch (error) {
2010
1996
  yield {
2011
1997
  check: "metadata-stamp",
2012
- message: error instanceof Error ? error.message : String(error),
1998
+ message: isError(error) ? error.message : String(error),
2013
1999
  context: { error }
2014
2000
  };
2015
2001
  }
@@ -2064,7 +2050,7 @@ async function* scanDriver(factory) {
2064
2050
  } catch (error) {
2065
2051
  yield {
2066
2052
  check: "snapshot-scoped",
2067
- message: error instanceof Error ? error.message : String(error),
2053
+ message: isError(error) ? error.message : String(error),
2068
2054
  context: { error }
2069
2055
  };
2070
2056
  }