@proteinjs/db 1.54.0 → 1.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db",
3
- "version": "1.54.0",
3
+ "version": "1.54.1",
4
4
  "main": "./dist/generated/index.js",
5
5
  "types": "./dist/generated/index.d.ts",
6
6
  "exports": {
@@ -66,5 +66,5 @@
66
66
  "ts-jest": "29.1.1",
67
67
  "typescript": "5.2.2"
68
68
  },
69
- "gitHead": "856ac58e8bb61e5d46261ffb57434994fb808390"
69
+ "gitHead": "9c87e1ee2e65c4b2ffcf5308cbab21b88d53365c"
70
70
  }
@@ -17,7 +17,9 @@ export type TransactionRunOptions = {
17
17
  * Ids of rows the operations depend on that may not exist yet: the row an update or a
18
18
  * membership update targets while its insert is still in flight, the permission source a
19
19
  * child row attaches to while the root's own insert (and the owner grant that commits with
20
- * it) is still landing. Every id must be a row the operations themselves reference. The
20
+ * it) is still landing, any row an insert names through one of its table's reference columns
21
+ * while that row is still being born. Every id must be a row the operations themselves
22
+ * reference (an insert references every row its reference columns name). The
21
23
  * runner waits, bounded, for each to exist AND be visible to the caller before running the
22
24
  * operations; a row that never appears fails the request with a plain clause — never a
23
25
  * silent zero-row write, never a hang.
@@ -1,6 +1,11 @@
1
1
  import { Logger } from '@proteinjs/logger';
2
+ import { isInstanceOf } from '@proteinjs/util';
2
3
  import { Db, getDb } from '../Db';
3
- import { Table, tableByName } from '../Table';
4
+ import { Column, Table, getColumnPropertyName, tableByName } from '../Table';
5
+ // After Table: Columns ↔ Table ↔ Record load in a cycle that resolves only when Record is entered
6
+ // before Columns (Record constructs its columns at module load); a module that enters Columns
7
+ // first — as this one would when it is the first module loaded — leaves those constructors undefined.
8
+ import { DynamicReferenceColumn, ReferenceColumn } from '../Columns';
4
9
  import { getDefaultTransactionContextFactory } from './TransactionContextFactory';
5
10
  import {
6
11
  TransactionRunnerService,
@@ -135,10 +140,11 @@ export class TransactionRunner implements TransactionRunnerService {
135
140
  }
136
141
 
137
142
  /**
138
- * The rows the operations reference, by id → the table(s) they are read from: the permission
139
- * source a row attaches to (an insert whose `permissionSource` is another row), the row an
140
- * update / preserving update / membership update / delete targets. The rows the operations
141
- * insert themselves are not dependencies.
143
+ * The rows the operations reference, by id → the table(s) they are read from. An insert
144
+ * references every row its table's reference columns name (`rowsTheInsertReferences`) — the
145
+ * permission source a row attaches to is one of these, a row named through any other reference
146
+ * column another. An update / preserving update / membership update / delete references the row
147
+ * it targets. The rows the operations insert themselves are not dependencies.
142
148
  */
143
149
  private static rowsTheOperationsReference(ops: Operation<any>[]): Map<string, Table<any>[]> {
144
150
  const inserted: string[] = [];
@@ -161,16 +167,12 @@ export class TransactionRunner implements TransactionRunnerService {
161
167
  const args = op.args as unknown[];
162
168
  const table = args[0] as Table<any> | undefined;
163
169
  if (op.name === 'insert') {
164
- const record = args[1] as
165
- | { id?: string; permissionSource?: { _table?: string; _id?: string }; permissionSourceTable?: string }
166
- | undefined;
170
+ const record = args[1] as { id?: string } | undefined;
167
171
  if (record?.id) {
168
172
  inserted.push(record.id);
169
173
  }
170
- const source = record?.permissionSource;
171
- if (source?._id && source._id !== record?.id) {
172
- const sourceTableName = record?.permissionSourceTable ?? source._table;
173
- reference(sourceTableName ? TransactionRunner.tableNamed(sourceTableName, table) : table, source._id);
174
+ for (const row of TransactionRunner.rowsTheInsertReferences(table, record)) {
175
+ reference(TransactionRunner.tableNamed(row.tableName, table), row.id);
174
176
  }
175
177
  } else if (op.name === 'update' || op.name === 'updatePreserving') {
176
178
  const record = args[1] as { id?: string } | undefined;
@@ -188,7 +190,45 @@ export class TransactionRunner implements TransactionRunnerService {
188
190
  return referenced;
189
191
  }
190
192
 
191
- /** The table a permission source lives in: the insert's own table when it names the same one. */
193
+ /**
194
+ * The rows an inserted record names through its table's reference columns: for each column
195
+ * whose value carries an id, that id and the table it lives in — a `ReferenceColumn`'s declared
196
+ * table; for a `DynamicReferenceColumn`, the table its paired table-name column carries on the
197
+ * record (a client's queued insert ran the column's default), else the reference's own.
198
+ */
199
+ private static rowsTheInsertReferences(
200
+ table: Table<any> | undefined,
201
+ record: { [field: string]: unknown } | undefined
202
+ ): { id: string; tableName: string }[] {
203
+ if (!table || !record) {
204
+ return [];
205
+ }
206
+
207
+ const rows: { id: string; tableName: string }[] = [];
208
+ for (const [field, column] of Object.entries(table.columns as { [field: string]: Column<any, any> })) {
209
+ const value = record[field] as { _id?: unknown; _table?: unknown } | null | undefined;
210
+ if (!value || typeof value._id !== 'string') {
211
+ continue;
212
+ }
213
+ let tableName: unknown;
214
+ if (isInstanceOf(column, ReferenceColumn)) {
215
+ tableName = (column as ReferenceColumn<any>).referenceTable;
216
+ } else if (isInstanceOf(column, DynamicReferenceColumn)) {
217
+ const tableNameField = getColumnPropertyName(
218
+ table,
219
+ (column as DynamicReferenceColumn<any>).dynamicRefTableColName
220
+ );
221
+ tableName = (tableNameField && record[tableNameField]) || value._table;
222
+ }
223
+ if (typeof tableName === 'string' && tableName) {
224
+ rows.push({ id: value._id, tableName });
225
+ }
226
+ }
227
+
228
+ return rows;
229
+ }
230
+
231
+ /** The table a referenced row lives in: the insert's own table when it names the same one. */
192
232
  private static tableNamed(tableName: string, inserting: Table<any> | undefined): Table<any> {
193
233
  return inserting && inserting.name === tableName ? inserting : tableByName(tableName);
194
234
  }