@proteinjs/db 1.54.3 → 1.55.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 (63) hide show
  1. package/.eslintrc.js +15 -1
  2. package/CHANGELOG.md +11 -0
  3. package/dist/generated/test/index.d.ts.map +1 -1
  4. package/dist/generated/test/index.js +7 -1
  5. package/dist/generated/test/index.js.map +1 -1
  6. package/dist/src/Record.d.ts +15 -0
  7. package/dist/src/Record.d.ts.map +1 -1
  8. package/dist/src/Record.js +118 -67
  9. package/dist/src/Record.js.map +1 -1
  10. package/dist/src/auth/TableServiceAuth.js.map +1 -1
  11. package/dist/src/schema/TableManager.d.ts +8 -0
  12. package/dist/src/schema/TableManager.d.ts.map +1 -1
  13. package/dist/src/schema/TableManager.js +22 -0
  14. package/dist/src/schema/TableManager.js.map +1 -1
  15. package/dist/src/source/SourceRecordSyncRunner.js +1 -1
  16. package/dist/src/source/SourceRecordSyncRunner.js.map +1 -1
  17. package/dist/test/SettleThenFail.test.d.ts +2 -0
  18. package/dist/test/SettleThenFail.test.d.ts.map +1 -0
  19. package/dist/test/SettleThenFail.test.js +237 -0
  20. package/dist/test/SettleThenFail.test.js.map +1 -0
  21. package/dist/test/StoredValueSeating.test.d.ts +2 -0
  22. package/dist/test/StoredValueSeating.test.d.ts.map +1 -0
  23. package/dist/test/StoredValueSeating.test.js +243 -0
  24. package/dist/test/StoredValueSeating.test.js.map +1 -0
  25. package/dist/test/SyncRowHydration.test.js.map +1 -1
  26. package/dist/test/UndeclaredColumnCensus.test.d.ts +2 -0
  27. package/dist/test/UndeclaredColumnCensus.test.d.ts.map +1 -0
  28. package/dist/test/UndeclaredColumnCensus.test.js +139 -0
  29. package/dist/test/UndeclaredColumnCensus.test.js.map +1 -0
  30. package/dist/test/index.d.ts +2 -0
  31. package/dist/test/index.d.ts.map +1 -1
  32. package/dist/test/index.js +2 -0
  33. package/dist/test/index.js.map +1 -1
  34. package/dist/test/reusable/CascadeDeleteTests.d.ts.map +1 -1
  35. package/dist/test/reusable/CascadeDeleteTests.js.map +1 -1
  36. package/dist/test/reusable/StoredValueContractTests.d.ts +3 -0
  37. package/dist/test/reusable/StoredValueContractTests.d.ts.map +1 -0
  38. package/dist/test/reusable/StoredValueContractTests.js +666 -0
  39. package/dist/test/reusable/StoredValueContractTests.js.map +1 -0
  40. package/dist/test/reusable/TransactionTests.js +8 -8
  41. package/dist/test/util/DbTestEnvironment.d.ts.map +1 -1
  42. package/dist/test/util/DbTestEnvironment.js +5 -1
  43. package/dist/test/util/DbTestEnvironment.js.map +1 -1
  44. package/dist/test/util/tables/storedValueContractTestTables.d.ts +66 -0
  45. package/dist/test/util/tables/storedValueContractTestTables.d.ts.map +1 -0
  46. package/dist/test/util/tables/storedValueContractTestTables.js +85 -0
  47. package/dist/test/util/tables/storedValueContractTestTables.js.map +1 -0
  48. package/generated/test/index.ts +7 -1
  49. package/package.json +3 -3
  50. package/src/Record.ts +125 -66
  51. package/src/auth/TableServiceAuth.ts +1 -1
  52. package/src/schema/TableManager.ts +25 -0
  53. package/src/source/SourceRecordSyncRunner.ts +1 -1
  54. package/test/SettleThenFail.test.ts +167 -0
  55. package/test/StoredValueSeating.test.ts +169 -0
  56. package/test/SyncRowHydration.test.ts +1 -1
  57. package/test/UndeclaredColumnCensus.test.ts +87 -0
  58. package/test/index.ts +2 -0
  59. package/test/reusable/CascadeDeleteTests.ts +1 -15
  60. package/test/reusable/StoredValueContractTests.ts +419 -0
  61. package/test/reusable/TransactionTests.ts +1 -1
  62. package/test/util/DbTestEnvironment.ts +3 -0
  63. package/test/util/tables/storedValueContractTestTables.ts +62 -0
@@ -0,0 +1,666 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.storedValueContractTests = void 0;
54
+ var db_1 = require("@proteinjs/db");
55
+ var moment_1 = __importDefault(require("moment"));
56
+ var db_query_1 = require("@proteinjs/db-query");
57
+ var StatementConfigFactory_1 = require("../../src/StatementConfigFactory");
58
+ var DbTestEnvironment_1 = require("../util/DbTestEnvironment");
59
+ var storedValueContractTestTables_1 = require("../util/tables/storedValueContractTestTables");
60
+ var columnTypesTestTables_1 = require("../util/tables/columnTypesTestTables");
61
+ var dynamicReferenceColumnTestTables_1 = require("../util/tables/dynamicReferenceColumnTestTables");
62
+ /**
63
+ * THE STORED-VALUE CONTRACT: what the row reader makes of each cell a database actually holds —
64
+ * written through the driver, the way a hand-run statement, an older writer or a cutover leaves
65
+ * it — not only what the serializer writes. One asserted law per shape, against a real database
66
+ * through each driver.
67
+ *
68
+ * Part 1 — a legacy column that shares a property's name. After a cutover (`tags` the text
69
+ * column → `tag_list` the array column, property `tags` both times) a row carries two cells: the
70
+ * undeclared legacy `tags` and the declared `tag_list`. The law: a stored value is seated by its
71
+ * COLUMN name alone, so the legacy cell — whatever it holds, whichever physical position it has —
72
+ * never reaches the array column's parser and never displaces the array. Both physical orders
73
+ * are built, because a row's keys arrive in the table's column order and the reader walks them
74
+ * in that order: the deployed shape (legacy first — the old column predates the new one) and the
75
+ * other (legacy added after).
76
+ *
77
+ * Part 2 — every shipped column × { null, undefined, well-formed, a malformed value written raw }.
78
+ * null reads as the column's null; undefined is refused at the write, naming the field; a
79
+ * well-formed value round-trips; a malformed cell is either refused by the storage type (the
80
+ * typed columns) or, where the storage holds text the column must parse, omitted from the record
81
+ * at one WARN naming table and column — never the value — while the row and the result set serve.
82
+ *
83
+ * Part 3 — a result set mixing the async road (a column whose `deserialize` answers with a
84
+ * promise) with a rejecting cell and a synchronous malformed cell resolves with every row served,
85
+ * and leaves no unhandled rejection behind.
86
+ */
87
+ /** The stored value no warning may ever carry — distinctive, so a leak is unmistakable. */
88
+ var LEGACY_WORD = 'legacyword7';
89
+ /**
90
+ * The table as a pre-cutover build declared it: a text column named `tags` under the property
91
+ * `tags`. Derived from the live table (same prototype, same name), never registered: loading it
92
+ * creates — or adds — the physical `tags` column the cutover left behind.
93
+ */
94
+ var legacyShapeOf = function (table) {
95
+ var shape = Object.create(Object.getPrototypeOf(table));
96
+ Object.assign(shape, table);
97
+ shape.columns = (0, db_1.withRecordColumns)({
98
+ name: new db_1.StringColumn('name'),
99
+ tags: new db_1.StringColumn('tags'),
100
+ });
101
+ return shape;
102
+ };
103
+ /** The unhandled rejections Node reported while `run` ran, counted after the loop drained twice. */
104
+ var unhandledRejectionsDuring = function (run) { return __awaiter(void 0, void 0, void 0, function () {
105
+ var unhandled, listener, value;
106
+ return __generator(this, function (_a) {
107
+ switch (_a.label) {
108
+ case 0:
109
+ unhandled = 0;
110
+ listener = function () {
111
+ unhandled++;
112
+ };
113
+ process.on('unhandledRejection', listener);
114
+ _a.label = 1;
115
+ case 1:
116
+ _a.trys.push([1, , 5, 6]);
117
+ return [4 /*yield*/, run()];
118
+ case 2:
119
+ value = _a.sent();
120
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
121
+ case 3:
122
+ _a.sent();
123
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
124
+ case 4:
125
+ _a.sent();
126
+ return [2 /*return*/, { unhandled: unhandled, value: value }];
127
+ case 5:
128
+ process.off('unhandledRejection', listener);
129
+ return [7 /*endfinally*/];
130
+ case 6: return [2 /*return*/];
131
+ }
132
+ });
133
+ }); };
134
+ var storedValueContractTests = function (driver, transactionContextFactory, dropTable) {
135
+ return function () {
136
+ var db = new db_1.Db(driver, undefined, transactionContextFactory);
137
+ var testEnv = new DbTestEnvironment_1.DbTestEnvironment(driver, dropTable);
138
+ var memberTable = storedValueContractTestTables_1.storedValueContractTestTables.Member, memberAfterTable = storedValueContractTestTables_1.storedValueContractTestTables.MemberAfter, mixedTable = storedValueContractTestTables_1.storedValueContractTestTables.Mixed;
139
+ var typesTable = columnTypesTestTables_1.columnTypesTestTables.Test;
140
+ var statementConfigFactory = new StatementConfigFactory_1.StatementConfigFactory(driver.getDbName());
141
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
142
+ return __generator(this, function (_a) {
143
+ switch (_a.label) {
144
+ case 0: return [4 /*yield*/, testEnv.beforeAll()];
145
+ case 1:
146
+ _a.sent();
147
+ // The deployed shape: the legacy `tags` column predates `tag_list`.
148
+ return [4 /*yield*/, dropTable(memberTable)];
149
+ case 2:
150
+ // The deployed shape: the legacy `tags` column predates `tag_list`.
151
+ _a.sent();
152
+ return [4 /*yield*/, driver.getTableManager().loadTable(legacyShapeOf(memberTable))];
153
+ case 3:
154
+ _a.sent();
155
+ return [4 /*yield*/, driver.getTableManager().loadTable(memberTable)];
156
+ case 4:
157
+ _a.sent();
158
+ // The other order: `tag_list` first, the legacy `tags` column added after it.
159
+ return [4 /*yield*/, dropTable(memberAfterTable)];
160
+ case 5:
161
+ // The other order: `tag_list` first, the legacy `tags` column added after it.
162
+ _a.sent();
163
+ return [4 /*yield*/, driver.getTableManager().loadTable(memberAfterTable)];
164
+ case 6:
165
+ _a.sent();
166
+ return [4 /*yield*/, driver.getTableManager().loadTable(legacyShapeOf(memberAfterTable))];
167
+ case 7:
168
+ _a.sent();
169
+ return [2 /*return*/];
170
+ }
171
+ });
172
+ }); }, 120000);
173
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
174
+ return __generator(this, function (_a) {
175
+ switch (_a.label) {
176
+ case 0: return [4 /*yield*/, dropTable(memberTable)];
177
+ case 1:
178
+ _a.sent();
179
+ return [4 /*yield*/, dropTable(memberAfterTable)];
180
+ case 2:
181
+ _a.sent();
182
+ return [4 /*yield*/, testEnv.afterAll()];
183
+ case 3:
184
+ _a.sent();
185
+ return [2 /*return*/];
186
+ }
187
+ });
188
+ }); }, 120000);
189
+ /** The table as a statement names it for this driver: qualified with the database name where the driver needs it. */
190
+ var qualified = function (table, config) {
191
+ return "".concat(config.dbName ? "`".concat(config.dbName, "`.") : '', "`").concat(table.name, "`");
192
+ };
193
+ /** The row as the database holds it — every physical cell, keyed by column name, in column order. */
194
+ var storedRow = function (table, id) { return __awaiter(void 0, void 0, void 0, function () {
195
+ var rows;
196
+ return __generator(this, function (_a) {
197
+ switch (_a.label) {
198
+ case 0: return [4 /*yield*/, driver.runQuery(function (config) { return ({
199
+ sql: "SELECT * FROM ".concat(qualified(table, statementConfigFactory.getStatementConfig(config)), " WHERE id = '").concat(id, "'"),
200
+ }); })];
201
+ case 1:
202
+ rows = _a.sent();
203
+ return [2 /*return*/, rows[0]];
204
+ }
205
+ });
206
+ }); };
207
+ /** A cell written past the serializer: the statement itself, handed the value as stored text (or NULL). */
208
+ var writeRawCell = function (table, id, columnName, value) { return __awaiter(void 0, void 0, void 0, function () {
209
+ var literal;
210
+ return __generator(this, function (_a) {
211
+ switch (_a.label) {
212
+ case 0:
213
+ literal = value === null ? 'NULL' : "'".concat(value, "'");
214
+ return [4 /*yield*/, driver.runDml(function (config) { return ({
215
+ sql: "UPDATE ".concat(qualified(table, statementConfigFactory.getStatementConfig(config)), " SET ").concat(columnName, " = ").concat(literal, " WHERE id = '").concat(id, "'"),
216
+ }); })];
217
+ case 1:
218
+ _a.sent();
219
+ return [2 /*return*/];
220
+ }
221
+ });
222
+ }); };
223
+ /**
224
+ * A cell written past the serializer into a column whose storage holds text the column must
225
+ * parse: the update statement the db layer itself emits, handed the cells as stored text.
226
+ */
227
+ var writeRawParsedCell = function (table, id, cells) { return __awaiter(void 0, void 0, void 0, function () {
228
+ var row;
229
+ return __generator(this, function (_a) {
230
+ switch (_a.label) {
231
+ case 0:
232
+ row = new db_1.QueryBuilderFactory().getQueryBuilder(table).condition({ field: 'id', operator: '=', value: id });
233
+ return [4 /*yield*/, driver.runDml(function (config) {
234
+ return new db_query_1.StatementFactory().update(table.name, cells, row, statementConfigFactory.getStatementConfig(config));
235
+ })];
236
+ case 1:
237
+ _a.sent();
238
+ return [2 /*return*/];
239
+ }
240
+ });
241
+ }); };
242
+ describe('part 1: a legacy column sharing a property name', function () {
243
+ var legacyCases = [
244
+ ['a bare word', LEGACY_WORD],
245
+ ['an empty string', ''],
246
+ ['NULL', null],
247
+ ];
248
+ describe.each([
249
+ ['the deployed shape: the legacy column before the declared one', memberTable, 'tags', 'tag_list'],
250
+ ['the other order: the legacy column after the declared one', memberAfterTable, 'tag_list', 'tags'],
251
+ ])('%s', function (_, table, firstKey, secondKey) {
252
+ test.each(legacyCases)('the declared array survives beside a legacy cell holding %s: a get and a query both carry it', function (_, legacy) { return __awaiter(void 0, void 0, void 0, function () {
253
+ var member, stored, keys, fetched, queried;
254
+ return __generator(this, function (_a) {
255
+ switch (_a.label) {
256
+ case 0: return [4 /*yield*/, db.insert(table, { name: "member ".concat(legacy), tags: ['a', 'b'] })];
257
+ case 1:
258
+ member = _a.sent();
259
+ return [4 /*yield*/, writeRawCell(table, member.id, 'tags', legacy)];
260
+ case 2:
261
+ _a.sent();
262
+ return [4 /*yield*/, storedRow(table, member.id)];
263
+ case 3:
264
+ stored = _a.sent();
265
+ keys = Object.keys(stored);
266
+ // The premise: the row holds both cells, in the physical order this block built.
267
+ expect(keys).toEqual(expect.arrayContaining(['tags', 'tag_list']));
268
+ expect(keys.indexOf(firstKey)).toBeLessThan(keys.indexOf(secondKey));
269
+ expect(stored['tags']).toBe(legacy);
270
+ return [4 /*yield*/, db.get(table, { id: member.id })];
271
+ case 4:
272
+ fetched = _a.sent();
273
+ expect(fetched.tags).toEqual(['a', 'b']);
274
+ expect(fetched).toMatchObject({ id: member.id, name: "member ".concat(legacy) });
275
+ return [4 /*yield*/, db.query(table, { id: member.id })];
276
+ case 5:
277
+ queried = _a.sent();
278
+ expect(queried).toHaveLength(1);
279
+ expect(queried[0].tags).toEqual(['a', 'b']);
280
+ return [4 /*yield*/, db.delete(table, { id: member.id })];
281
+ case 6:
282
+ _a.sent();
283
+ return [2 /*return*/];
284
+ }
285
+ });
286
+ }); }, 60000);
287
+ });
288
+ });
289
+ describe('part 2: every shipped column × { null, undefined, well-formed, malformed raw }', function () {
290
+ var properties = [
291
+ 'integerColumn',
292
+ 'stringColumn',
293
+ 'floatColumn',
294
+ 'decimalColumn',
295
+ 'booleanColumn',
296
+ 'dateColumn',
297
+ 'dateTimeColumn',
298
+ 'binaryColumn',
299
+ 'uuidColumn',
300
+ 'passwordColumn',
301
+ 'objectColumn',
302
+ 'arrayColumn',
303
+ 'referenceArrayColumn',
304
+ 'referenceColumn',
305
+ ];
306
+ var allNull = function () {
307
+ return properties.reduce(function (cells, property) {
308
+ var _a;
309
+ return (__assign(__assign({}, cells), (_a = {}, _a[property] = null, _a)));
310
+ }, {});
311
+ };
312
+ describe('null', function () {
313
+ var fetched;
314
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
315
+ var inserted;
316
+ return __generator(this, function (_a) {
317
+ switch (_a.label) {
318
+ case 0: return [4 /*yield*/, db.insert(typesTable, allNull())];
319
+ case 1:
320
+ inserted = _a.sent();
321
+ return [4 /*yield*/, db.get(typesTable, { id: inserted.id })];
322
+ case 2:
323
+ fetched = _a.sent();
324
+ return [2 /*return*/];
325
+ }
326
+ });
327
+ }); }, 60000);
328
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
329
+ return __generator(this, function (_a) {
330
+ switch (_a.label) {
331
+ case 0:
332
+ if (!fetched) return [3 /*break*/, 2];
333
+ return [4 /*yield*/, db.delete(typesTable, { id: fetched.id })];
334
+ case 1:
335
+ _a.sent();
336
+ _a.label = 2;
337
+ case 2: return [2 /*return*/];
338
+ }
339
+ });
340
+ }); }, 60000);
341
+ test.each(properties.filter(function (property) { return property !== 'referenceArrayColumn'; }))('%s: a null cell reads as null', function (property) {
342
+ expect(fetched[property]).toBeNull();
343
+ });
344
+ test('referenceArrayColumn: a null cell reads as an empty reference array', function () {
345
+ expect(fetched.referenceArrayColumn).toHaveProperty('_ids', []);
346
+ });
347
+ });
348
+ describe('undefined', function () {
349
+ test("uuidColumn: undefined takes the column's default — a generated uuid", function () { return __awaiter(void 0, void 0, void 0, function () {
350
+ var inserted, fetched;
351
+ return __generator(this, function (_a) {
352
+ switch (_a.label) {
353
+ case 0: return [4 /*yield*/, db.insert(typesTable, __assign(__assign({}, allNull()), { uuidColumn: undefined }))];
354
+ case 1:
355
+ inserted = _a.sent();
356
+ _a.label = 2;
357
+ case 2:
358
+ _a.trys.push([2, , 4, 6]);
359
+ return [4 /*yield*/, db.get(typesTable, { id: inserted.id })];
360
+ case 3:
361
+ fetched = _a.sent();
362
+ expect(fetched.uuidColumn).toMatch(/^[0-9a-f-]{36}$/);
363
+ return [3 /*break*/, 6];
364
+ case 4: return [4 /*yield*/, db.delete(typesTable, { id: inserted.id })];
365
+ case 5:
366
+ _a.sent();
367
+ return [7 /*endfinally*/];
368
+ case 6: return [2 /*return*/];
369
+ }
370
+ });
371
+ }); });
372
+ test.each(properties.filter(function (property) { return property !== 'uuidColumn'; }))('%s: undefined is refused at the write, naming the field', function (property) { return __awaiter(void 0, void 0, void 0, function () {
373
+ var _a;
374
+ return __generator(this, function (_b) {
375
+ switch (_b.label) {
376
+ case 0: return [4 /*yield*/, expect(db.insert(typesTable, __assign(__assign({}, allNull()), (_a = {}, _a[property] = undefined, _a)))).rejects.toThrow("Undefined was found for field: ".concat(typesTable.name, ".").concat(property))];
377
+ case 1:
378
+ _b.sent();
379
+ return [2 /*return*/];
380
+ }
381
+ });
382
+ }); });
383
+ });
384
+ describe('well-formed', function () {
385
+ var wellFormed = {
386
+ integerColumn: 7,
387
+ stringColumn: 'text',
388
+ floatColumn: 1.5,
389
+ decimalColumn: null,
390
+ booleanColumn: true,
391
+ dateColumn: new Date(2024, 0, 2, 12),
392
+ dateTimeColumn: moment_1.default.utc('2024-01-02T03:04:05Z'),
393
+ binaryColumn: null,
394
+ uuidColumn: 'a4b2f5f2-2f8b-4c1e-9b2e-6d3f8a1c0e11',
395
+ passwordColumn: 'stored-as-given',
396
+ objectColumn: { theme: 'dark', depth: 2 },
397
+ arrayColumn: ['a', 'b'],
398
+ referenceArrayColumn: new db_1.ReferenceArray('reference_table', ['r1', 'r2']),
399
+ referenceColumn: new db_1.Reference('reference_table', 'r3'),
400
+ };
401
+ var fetched;
402
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
403
+ var inserted;
404
+ return __generator(this, function (_a) {
405
+ switch (_a.label) {
406
+ case 0: return [4 /*yield*/, db.insert(typesTable, wellFormed)];
407
+ case 1:
408
+ inserted = _a.sent();
409
+ return [4 /*yield*/, db.get(typesTable, { id: inserted.id })];
410
+ case 2:
411
+ fetched = _a.sent();
412
+ return [2 /*return*/];
413
+ }
414
+ });
415
+ }); }, 60000);
416
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
417
+ return __generator(this, function (_a) {
418
+ switch (_a.label) {
419
+ case 0:
420
+ if (!fetched) return [3 /*break*/, 2];
421
+ return [4 /*yield*/, db.delete(typesTable, { id: fetched.id })];
422
+ case 1:
423
+ _a.sent();
424
+ _a.label = 2;
425
+ case 2: return [2 /*return*/];
426
+ }
427
+ });
428
+ }); }, 60000);
429
+ test('integerColumn round-trips', function () { return expect(fetched.integerColumn).toBe(7); });
430
+ test('stringColumn round-trips', function () { return expect(fetched.stringColumn).toBe('text'); });
431
+ test('floatColumn round-trips', function () { return expect(Number(fetched.floatColumn)).toBe(1.5); });
432
+ // A JS number does not bind to a NUMERIC parameter through the driver (the backend refuses the
433
+ // statement: "Could not parse … 2.25 as NUMERIC"); the law is unpinned until the binding is.
434
+ test.todo('decimalColumn round-trips (a JS number does not bind to NUMERIC through the driver)');
435
+ test('booleanColumn round-trips', function () { return expect(fetched.booleanColumn).toBe(true); });
436
+ test('dateTimeColumn round-trips to the stored instant', function () {
437
+ return expect((0, moment_1.default)(fetched.dateTimeColumn).valueOf()).toBe(Date.UTC(2024, 0, 2, 3, 4, 5));
438
+ });
439
+ test('dateColumn round-trips to the stored calendar day', function () {
440
+ var day = new Date(fetched.dateColumn);
441
+ expect([day.getFullYear(), day.getMonth(), day.getDate()]).toEqual([2024, 0, 2]);
442
+ });
443
+ test('uuidColumn round-trips', function () { return expect(fetched.uuidColumn).toBe('a4b2f5f2-2f8b-4c1e-9b2e-6d3f8a1c0e11'); });
444
+ test('passwordColumn stores what it is given', function () { return expect(fetched.passwordColumn).toBe('stored-as-given'); });
445
+ test('objectColumn round-trips', function () { return expect(fetched.objectColumn).toEqual({ theme: 'dark', depth: 2 }); });
446
+ test('arrayColumn round-trips', function () { return expect(fetched.arrayColumn).toEqual(['a', 'b']); });
447
+ test('referenceArrayColumn round-trips its ids', function () {
448
+ return expect(fetched.referenceArrayColumn).toHaveProperty('_ids', ['r1', 'r2']);
449
+ });
450
+ test('referenceColumn round-trips its id', function () { return expect(fetched.referenceColumn).toHaveProperty('_id', 'r3'); });
451
+ });
452
+ describe('a malformed value written raw through the driver', function () {
453
+ var legacy;
454
+ var intact;
455
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
456
+ return __generator(this, function (_a) {
457
+ switch (_a.label) {
458
+ case 0: return [4 /*yield*/, db.insert(typesTable, __assign(__assign({}, allNull()), { stringColumn: 'legacy' }))];
459
+ case 1:
460
+ legacy = _a.sent();
461
+ return [4 /*yield*/, db.insert(typesTable, __assign(__assign({}, allNull()), { stringColumn: 'intact', arrayColumn: ['c'] }))];
462
+ case 2:
463
+ intact = _a.sent();
464
+ return [2 /*return*/];
465
+ }
466
+ });
467
+ }); }, 60000);
468
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
469
+ return __generator(this, function (_a) {
470
+ switch (_a.label) {
471
+ case 0: return [4 /*yield*/, db.delete(typesTable, { id: legacy.id })];
472
+ case 1:
473
+ _a.sent();
474
+ return [4 /*yield*/, db.delete(typesTable, { id: intact.id })];
475
+ case 2:
476
+ _a.sent();
477
+ return [2 /*return*/];
478
+ }
479
+ });
480
+ }); }, 60000);
481
+ // The storage type is the guard: a word is refused before it can be stored.
482
+ test.each([
483
+ ['integerColumn', 'integer_column'],
484
+ ['floatColumn', 'float_column'],
485
+ ['decimalColumn', 'decimal_column'],
486
+ ['booleanColumn', 'boolean_column'],
487
+ ['dateColumn', 'date_column'],
488
+ ['dateTimeColumn', 'date_time_column'],
489
+ ])('%s: the storage type refuses a word', function (_, columnName) { return __awaiter(void 0, void 0, void 0, function () {
490
+ return __generator(this, function (_a) {
491
+ switch (_a.label) {
492
+ case 0: return [4 /*yield*/, expect(writeRawCell(typesTable, legacy.id, columnName, LEGACY_WORD)).rejects.toThrow()];
493
+ case 1:
494
+ _a.sent();
495
+ return [2 /*return*/];
496
+ }
497
+ });
498
+ }); });
499
+ // Text the column does not parse: whatever is stored is what is read.
500
+ test.each([
501
+ ['stringColumn', 'string_column'],
502
+ ['uuidColumn', 'uuid_column'],
503
+ ['passwordColumn', 'password_column'],
504
+ ])('%s: a stored word reads back as stored', function (property, columnName) { return __awaiter(void 0, void 0, void 0, function () {
505
+ var fetched;
506
+ return __generator(this, function (_a) {
507
+ switch (_a.label) {
508
+ case 0: return [4 /*yield*/, writeRawCell(typesTable, legacy.id, columnName, LEGACY_WORD)];
509
+ case 1:
510
+ _a.sent();
511
+ return [4 /*yield*/, db.get(typesTable, { id: legacy.id })];
512
+ case 2:
513
+ fetched = _a.sent();
514
+ expect(fetched[property]).toBe(LEGACY_WORD);
515
+ return [2 /*return*/];
516
+ }
517
+ });
518
+ }); });
519
+ test('referenceColumn: a stored word is the id the reference carries', function () { return __awaiter(void 0, void 0, void 0, function () {
520
+ var fetched;
521
+ return __generator(this, function (_a) {
522
+ switch (_a.label) {
523
+ case 0: return [4 /*yield*/, writeRawCell(typesTable, legacy.id, 'reference_column', LEGACY_WORD)];
524
+ case 1:
525
+ _a.sent();
526
+ return [4 /*yield*/, db.get(typesTable, { id: legacy.id })];
527
+ case 2:
528
+ fetched = _a.sent();
529
+ expect(fetched.referenceColumn).toHaveProperty('_id', LEGACY_WORD);
530
+ return [2 /*return*/];
531
+ }
532
+ });
533
+ }); });
534
+ // Text the column parses: a cell it cannot parse is omitted — the row serves, the set resolves.
535
+ test('objectColumn, arrayColumn, referenceArrayColumn: an unparseable cell is omitted; the row and the set serve', function () { return __awaiter(void 0, void 0, void 0, function () {
536
+ var fetched, all;
537
+ return __generator(this, function (_a) {
538
+ switch (_a.label) {
539
+ case 0: return [4 /*yield*/, writeRawParsedCell(typesTable, legacy.id, {
540
+ object_column: LEGACY_WORD,
541
+ array_column: LEGACY_WORD,
542
+ reference_array_column: LEGACY_WORD,
543
+ })];
544
+ case 1:
545
+ _a.sent();
546
+ return [4 /*yield*/, db.get(typesTable, { id: legacy.id })];
547
+ case 2:
548
+ fetched = _a.sent();
549
+ expect(fetched).toMatchObject({ id: legacy.id });
550
+ expect(fetched).not.toHaveProperty('objectColumn');
551
+ expect(fetched).not.toHaveProperty('arrayColumn');
552
+ expect(fetched).not.toHaveProperty('referenceArrayColumn');
553
+ return [4 /*yield*/, db.query(typesTable, {})];
554
+ case 3:
555
+ all = _a.sent();
556
+ expect(all.map(function (record) { return record.id; })).toEqual(expect.arrayContaining([legacy.id, intact.id]));
557
+ expect(all.find(function (record) { return record.id === intact.id; })).toMatchObject({
558
+ stringColumn: 'intact',
559
+ arrayColumn: ['c'],
560
+ });
561
+ return [2 /*return*/];
562
+ }
563
+ });
564
+ }); });
565
+ test('a dynamic reference whose companion table-name cell is empty is omitted; the row serves', function () { return __awaiter(void 0, void 0, void 0, function () {
566
+ var assignments, engineers, engineer, assignment, fetched;
567
+ return __generator(this, function (_a) {
568
+ switch (_a.label) {
569
+ case 0:
570
+ assignments = dynamicReferenceColumnTestTables_1.dynamicReferenceTestTables.ProjectAssignment, engineers = dynamicReferenceColumnTestTables_1.dynamicReferenceTestTables.Engineer;
571
+ return [4 /*yield*/, db.insert(engineers, { name: 'eng', yearsOfExperience: 3 })];
572
+ case 1:
573
+ engineer = _a.sent();
574
+ return [4 /*yield*/, db.insert(assignments, {
575
+ projectName: 'p',
576
+ employeeTableName: engineers.name,
577
+ employeeRef: new db_1.Reference(engineers.name, engineer.id),
578
+ startDate: '2024-01-02',
579
+ })];
580
+ case 2:
581
+ assignment = _a.sent();
582
+ _a.label = 3;
583
+ case 3:
584
+ _a.trys.push([3, , 6, 9]);
585
+ return [4 /*yield*/, writeRawCell(assignments, assignment.id, 'employee_table_name', null)];
586
+ case 4:
587
+ _a.sent();
588
+ return [4 /*yield*/, db.get(assignments, { id: assignment.id })];
589
+ case 5:
590
+ fetched = _a.sent();
591
+ expect(fetched).toMatchObject({ id: assignment.id, projectName: 'p', startDate: '2024-01-02' });
592
+ expect(fetched).not.toHaveProperty('employeeRef');
593
+ return [3 /*break*/, 9];
594
+ case 6: return [4 /*yield*/, driver.runDml(function (config) { return ({
595
+ sql: "DELETE FROM ".concat(qualified(assignments, statementConfigFactory.getStatementConfig(config)), " WHERE id = '").concat(assignment.id, "'"),
596
+ }); })];
597
+ case 7:
598
+ _a.sent();
599
+ return [4 /*yield*/, db.delete(engineers, { id: engineer.id })];
600
+ case 8:
601
+ _a.sent();
602
+ return [7 /*endfinally*/];
603
+ case 9: return [2 /*return*/];
604
+ }
605
+ });
606
+ }); });
607
+ });
608
+ });
609
+ describe('part 3: a result set mixing the async road, a rejecting cell and a malformed cell', function () {
610
+ test('resolves with every row served, and leaves no unhandled rejection', function () { return __awaiter(void 0, void 0, void 0, function () {
611
+ var plain, unreadable, malformed, _a, unhandled, records, byId, _i, records_1, record, _b, _c, record;
612
+ return __generator(this, function (_d) {
613
+ switch (_d.label) {
614
+ case 0: return [4 /*yield*/, db.insert(mixedTable, { title: 'plain', later: 1, tags: ['a'] })];
615
+ case 1:
616
+ plain = _d.sent();
617
+ return [4 /*yield*/, db.insert(mixedTable, { title: 'unreadable', later: -1, tags: ['b'] })];
618
+ case 2:
619
+ unreadable = _d.sent();
620
+ return [4 /*yield*/, db.insert(mixedTable, { title: 'malformed', later: 2, tags: ['c'] })];
621
+ case 3:
622
+ malformed = _d.sent();
623
+ return [4 /*yield*/, writeRawParsedCell(mixedTable, malformed.id, { tags: LEGACY_WORD })];
624
+ case 4:
625
+ _d.sent();
626
+ _d.label = 5;
627
+ case 5:
628
+ _d.trys.push([5, , 7, 12]);
629
+ return [4 /*yield*/, unhandledRejectionsDuring(function () { return db.query(mixedTable, {}); })];
630
+ case 6:
631
+ _a = _d.sent(), unhandled = _a.unhandled, records = _a.value;
632
+ expect(unhandled).toBe(0);
633
+ byId = {};
634
+ for (_i = 0, records_1 = records; _i < records_1.length; _i++) {
635
+ record = records_1[_i];
636
+ byId[record.id] = record;
637
+ }
638
+ expect(byId[plain.id]).toMatchObject({ title: 'plain', later: 1, tags: ['a'] });
639
+ expect(byId[unreadable.id]).toMatchObject({ title: 'unreadable', tags: ['b'] });
640
+ expect(byId[unreadable.id]).not.toHaveProperty('later');
641
+ expect(byId[malformed.id]).toMatchObject({ title: 'malformed', later: 2 });
642
+ expect(byId[malformed.id]).not.toHaveProperty('tags');
643
+ return [3 /*break*/, 12];
644
+ case 7:
645
+ _b = 0, _c = [plain, unreadable, malformed];
646
+ _d.label = 8;
647
+ case 8:
648
+ if (!(_b < _c.length)) return [3 /*break*/, 11];
649
+ record = _c[_b];
650
+ return [4 /*yield*/, db.delete(mixedTable, { id: record.id })];
651
+ case 9:
652
+ _d.sent();
653
+ _d.label = 10;
654
+ case 10:
655
+ _b++;
656
+ return [3 /*break*/, 8];
657
+ case 11: return [7 /*endfinally*/];
658
+ case 12: return [2 /*return*/];
659
+ }
660
+ });
661
+ }); }, 60000);
662
+ });
663
+ };
664
+ };
665
+ exports.storedValueContractTests = storedValueContractTests;
666
+ //# sourceMappingURL=StoredValueContractTests.js.map