@getstrata/core 2.2.0 → 2.2.2

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 (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/core/contracts/serviceTokens.d.ts +3 -2
  3. package/dist/core/database/baseRepository.d.ts +5 -0
  4. package/dist/core/database/model.d.ts +3 -1
  5. package/dist/core/database/query.d.ts +6 -1
  6. package/dist/core/database/repositoryQuery.d.ts +7 -1
  7. package/dist/core/database/schema/blueprint.d.ts +2 -0
  8. package/dist/core/database/schema/columnDefinition.d.ts +1 -1
  9. package/dist/core/database/types.d.ts +10 -1
  10. package/dist/core/http/requestMetaContext.d.ts +2 -0
  11. package/dist/core/queue/createAppQueue.d.ts +2 -1
  12. package/dist/core/queue/failedJobService.d.ts +3 -1
  13. package/dist/core/queue/index.d.ts +10 -2
  14. package/dist/core/queue/jobRunner.d.ts +7 -1
  15. package/dist/core/queue/publicQueue.d.ts +2 -1
  16. package/dist/core/queue/queueConfig.d.ts +3 -0
  17. package/dist/core/queue/redisQueue.d.ts +5 -7
  18. package/dist/core/queue/redisQueueKeys.d.ts +8 -0
  19. package/dist/core/queue/redisStreams.d.ts +73 -0
  20. package/dist/core/queue/types.d.ts +1 -0
  21. package/dist/core/tenant/publicTenantHost.d.ts +11 -0
  22. package/dist/core/tenant/tenantMiddleware.d.ts +4 -0
  23. package/dist/entries/auth/basicAuthGuard.js +1 -0
  24. package/dist/entries/auth/jwtGuard.js +1 -0
  25. package/dist/entries/auth/sessionGuard.js +1 -0
  26. package/dist/entries/contracts/serviceTokens.js +2 -0
  27. package/dist/entries/database/query.js +70 -1
  28. package/dist/entries/database/repositoryQuery.js +99 -1
  29. package/dist/entries/database/schema.js +9 -0
  30. package/dist/entries/database/sqliteConnection.js +1 -1
  31. package/dist/entries/queue/createAppQueue.js +681 -66
  32. package/dist/entries/queue/failedJobRepository.js +1 -1
  33. package/dist/entries/queue/failedJobService.js +5 -1
  34. package/dist/entries/queue/jobRunner.js +67 -2
  35. package/dist/entries/queue/publicQueue.js +679 -65
  36. package/dist/entries/queue/queueMetrics.js +694 -72
  37. package/dist/entries/queue/redisQueue.js +765 -148
  38. package/dist/framework/public-api.d.ts +2 -2
  39. package/dist/index.js +855 -69
  40. package/package.json +1 -1
@@ -355,6 +355,31 @@ function getDefinedColumnEntries(table, values, options = {}) {
355
355
  return [[column, value]];
356
356
  });
357
357
  }
358
+ function buildSelectLock(table, options) {
359
+ const lock = options.lock;
360
+ if (!lock)
361
+ return "";
362
+ const driver = currentSqlDialect().driver;
363
+ if (driver === "sqlite")
364
+ throw new Error("Row locks are not supported on SQLite; use an explicit write transaction.");
365
+ if (lock.mode !== "update" && lock.mode !== "share")
366
+ throw new TypeError("Invalid row lock mode.");
367
+ if (lock.wait !== undefined && !["wait", "nowait", "skipLocked"].includes(lock.wait))
368
+ throw new TypeError("Invalid row lock wait policy.");
369
+ if (options.groupBy || options.having)
370
+ throw new Error("Row locks cannot be combined with grouped/aggregate queries.");
371
+ let target = "";
372
+ if (lock.of !== undefined) {
373
+ if (driver !== "pgsql")
374
+ throw new Error("Row lock targets are supported only on PostgreSQL.");
375
+ const tables = [table.name, ...(options.joins ?? []).map((join) => join.table)];
376
+ if (!lock.of.length || lock.of.some((name) => !tables.includes(name)))
377
+ throw new TypeError("Row lock targets must name tables in this query.");
378
+ target = ` OF ${lock.of.map(quoteIdentifier).join(", ")}`;
379
+ }
380
+ const wait = lock.wait === "nowait" ? " NOWAIT" : lock.wait === "skipLocked" ? " SKIP LOCKED" : "";
381
+ return ` FOR ${lock.mode === "update" ? "UPDATE" : "SHARE"}${target}${wait}`;
382
+ }
358
383
  function buildSelectQuery(table, options = {}, whereNodes = []) {
359
384
  const params = [];
360
385
  const columns = buildSelectList(table, options.select, params);
@@ -365,8 +390,9 @@ function buildSelectQuery(table, options = {}, whereNodes = []) {
365
390
  const orderBy = buildOrderByClause(table.name, options.orderBy ?? table.defaultOrderBy);
366
391
  const limit = buildLimitClause(options.limit);
367
392
  const offset = buildOffsetClause(options.offset);
393
+ const lock = buildSelectLock(table, options);
368
394
  return {
369
- text: `SELECT ${columns} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${havingClause}${orderBy}${limit}${offset}`,
395
+ text: `SELECT ${columns} FROM ${quoteIdentifier(table.name)}${joins}${clause}${groupBy}${havingClause}${orderBy}${limit}${offset}${lock}`,
370
396
  params
371
397
  };
372
398
  }
@@ -535,6 +561,48 @@ function buildDeleteByIdQuery(table, id) {
535
561
  params
536
562
  };
537
563
  }
564
+ function buildConditionalWriteQuery(table, operation, changes, options, returning) {
565
+ for (const key of [
566
+ "joins",
567
+ "groupBy",
568
+ "having",
569
+ "select",
570
+ "orderBy",
571
+ "limit",
572
+ "offset",
573
+ "lock"
574
+ ]) {
575
+ if (options[key] !== undefined)
576
+ throw new Error(`Conditional writes do not support ${key}.`);
577
+ }
578
+ const params = [];
579
+ const predicate = buildAdvancedWhereClause(table.name, options.where ?? {}, options.whereNodes ?? []);
580
+ if (!predicate.clause)
581
+ throw new Error("Conditional writes require an explicit predicate.");
582
+ if (returning !== undefined && currentSqlDialect().driver === "mysql")
583
+ throw new Error("Mutation RETURNING is not supported on MySQL.");
584
+ const columns = returning ?? [table.primaryKey];
585
+ if (!columns.length || columns.some((column) => !table.columns.includes(column)))
586
+ throw new Error("RETURNING columns must be nonempty declared table columns.");
587
+ const deletedAt = resolveSoftDeleteColumn(table);
588
+ const values = operation === "delete" && deletedAt ? { [deletedAt]: new Date } : changes;
589
+ let prefix;
590
+ if (operation === "update" || deletedAt) {
591
+ const entries = Object.entries(values).filter(([, value]) => value !== undefined);
592
+ if (!entries.length)
593
+ throw new Error("Conditional updates require at least one value.");
594
+ if (entries.some(([column]) => column === table.primaryKey || !table.columns.includes(column)))
595
+ throw new Error("Conditional updates require declared non-primary-key columns.");
596
+ const assignments = entries.map(([column, value]) => `${quoteIdentifier(column)} = ${pushParam(params, value)}`);
597
+ prefix = `UPDATE ${quoteIdentifier(table.name)} SET ${assignments.join(", ")}`;
598
+ } else
599
+ prefix = `DELETE FROM ${quoteIdentifier(table.name)}`;
600
+ const { clause } = buildQueryWhereClause(table, options, options.whereNodes ?? [], params);
601
+ return {
602
+ text: prefix + clause + returningSuffix(columns.map(quoteIdentifier).join(", ")),
603
+ params
604
+ };
605
+ }
538
606
 
539
607
  // ../../src/core/database/relationships.ts
540
608
  function hasMany(definition) {
@@ -856,6 +924,20 @@ class RepositoryQuery {
856
924
  this.whereNodes.push({ kind: "or", where: input });
857
925
  return this;
858
926
  }
927
+ lockForUpdate(options = {}) {
928
+ this.queryOptions = {
929
+ ...this.queryOptions,
930
+ lock: { ...options, of: options.of ? [...options.of] : undefined, mode: "update" }
931
+ };
932
+ return this;
933
+ }
934
+ sharedLock(options = {}) {
935
+ this.queryOptions = {
936
+ ...this.queryOptions,
937
+ lock: { ...options, of: options.of ? [...options.of] : undefined, mode: "share" }
938
+ };
939
+ return this;
940
+ }
859
941
  orderBy(orderBy) {
860
942
  this.queryOptions = { ...this.queryOptions, orderBy };
861
943
  return this;
@@ -995,6 +1077,22 @@ class RepositoryQuery {
995
1077
  this.queryOptions = { ...this.queryOptions, onlyTrashed: true };
996
1078
  return this;
997
1079
  }
1080
+ async update(changes) {
1081
+ this.assertNoMutationEagerLoads();
1082
+ return this.repository.updateWhere(changes, this.buildOptions());
1083
+ }
1084
+ async updateReturning(changes, ...columns) {
1085
+ this.assertNoMutationEagerLoads();
1086
+ return this.repository.updateWhereReturning(changes, columns, this.buildOptions());
1087
+ }
1088
+ async delete() {
1089
+ this.assertNoMutationEagerLoads();
1090
+ return this.repository.deleteWhere(this.buildOptions());
1091
+ }
1092
+ assertNoMutationEagerLoads() {
1093
+ if (this.eagerLoads.length)
1094
+ throw new Error("Conditional writes do not support eager loading.");
1095
+ }
998
1096
  async get() {
999
1097
  const rows = await this.repository.findAll(this.buildOptions());
1000
1098
  return await this.attach(rows);
@@ -340,6 +340,13 @@ class Blueprint {
340
340
  this.columns.push(column);
341
341
  return column;
342
342
  }
343
+ bigId(name = "id") {
344
+ const column = new ColumnDefinition(name, "bigId");
345
+ column.primary();
346
+ column.autoIncrement = true;
347
+ this.columns.push(column);
348
+ return column;
349
+ }
343
350
  string(name, length) {
344
351
  if (length !== undefined && (!Number.isSafeInteger(length) || length <= 0)) {
345
352
  throw new Error("String length must be a positive safe integer.");
@@ -486,6 +493,8 @@ init_dialect();
486
493
  // ../../src/core/database/schema/grammars/grammar.ts
487
494
  function compileColumnType(driver, column) {
488
495
  switch (column.kind) {
496
+ case "bigId":
497
+ return driver === "pgsql" ? "BIGSERIAL" : compileIdType(driver);
489
498
  case "id":
490
499
  return compileIdType(driver);
491
500
  case "string":
@@ -69,7 +69,7 @@ function createSqliteConnection(filename) {
69
69
  const transactions = new WeakSet;
70
70
  function execute(query, params) {
71
71
  const statement = db.query(query);
72
- const args = [...params];
72
+ const args = params.map((value) => value instanceof Date ? value.toISOString() : value);
73
73
  if (isRowReturning(query))
74
74
  return statement.all(...args);
75
75
  statement.run(...args);