@proteinjs/db 1.50.2 → 1.51.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.51.0](https://github.com/proteinjs/db/compare/@proteinjs/db@1.50.2...@proteinjs/db@1.51.0) (2026-10-01)
7
+
8
+
9
+ ### Features
10
+
11
+ * **db:** the db service's reads declared on the factory — query, get, getRowCount and tableExists ride the service client's retry policy ([173ab85](https://github.com/proteinjs/db/commit/173ab8598f4f9bd413795baa80136d85ca684267))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [1.50.2](https://github.com/proteinjs/db/compare/@proteinjs/db@1.50.1...@proteinjs/db@1.50.2) (2026-09-30)
7
18
 
8
19
 
@@ -4,6 +4,13 @@ import { Record } from '../Record';
4
4
  import { QueryBuilder } from '@proteinjs/db-query';
5
5
  import { ArrayMembershipUpdate } from '../reference/ArrayMembershipOps';
6
6
  import { PreservedPath } from '../UpdatePreserving';
7
+ /**
8
+ * The db service rides the service client's retry policy by declaration alone, no logic of its
9
+ * own: the reads (`query`, `get`, `getRowCount`, `tableExists`) are declared `'read'` — bounded at
10
+ * the first contact and redelivered under the client's series when a delivery produced no
11
+ * response — and every write stays undeclared: one delivery, never redelivered (a redelivered
12
+ * `insert` would insert twice). The policy's numbers and exceptions are @proteinjs/service's.
13
+ */
7
14
  export declare const getDbService: () => DbService<Record>;
8
15
  export type Query<T> = ObjectQuery<T> | QueryBuilder<T>;
9
16
  export type ObjectQuery<T> = Partial<{
@@ -1 +1 @@
1
- {"version":3,"file":"DbService.d.ts","sourceRoot":"","sources":["../../../src/services/DbService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DbService.d.ts","sourceRoot":"","sources":["../../../src/services/DbService.ts"],"names":[],"mappings":"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"}
@@ -2,5 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getDbService = void 0;
4
4
  var service_1 = require("@proteinjs/service");
5
- exports.getDbService = (0, service_1.serviceFactory)('@proteinjs/db/DbService');
5
+ /**
6
+ * The db service rides the service client's retry policy by declaration alone, no logic of its
7
+ * own: the reads (`query`, `get`, `getRowCount`, `tableExists`) are declared `'read'` — bounded at
8
+ * the first contact and redelivered under the client's series when a delivery produced no
9
+ * response — and every write stays undeclared: one delivery, never redelivered (a redelivered
10
+ * `insert` would insert twice). The policy's numbers and exceptions are @proteinjs/service's.
11
+ */
12
+ exports.getDbService = (0, service_1.serviceFactory)('@proteinjs/db/DbService', undefined, {
13
+ query: 'read',
14
+ get: 'read',
15
+ getRowCount: 'read',
16
+ tableExists: 'read',
17
+ });
6
18
  //# sourceMappingURL=DbService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DbService.js","sourceRoot":"","sources":["../../../src/services/DbService.ts"],"names":[],"mappings":";;;AAAA,8CAA6D;AAOhD,QAAA,YAAY,GAAG,IAAA,wBAAc,EAAY,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"DbService.js","sourceRoot":"","sources":["../../../src/services/DbService.ts"],"names":[],"mappings":";;;AAAA,8CAA6D;AAO7D;;;;;;GAMG;AACU,QAAA,YAAY,GAAG,IAAA,wBAAc,EAAY,yBAAyB,EAAE,SAAS,EAAE;IAC1F,KAAK,EAAE,MAAM;IACb,GAAG,EAAE,MAAM;IACX,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;CACpB,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DbServiceRetryDeclaration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DbServiceRetryDeclaration.test.d.ts","sourceRoot":"","sources":["../../test/DbServiceRetryDeclaration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ 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;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
39
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
40
+ if (ar || !(i in from)) {
41
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
42
+ ar[i] = from[i];
43
+ }
44
+ }
45
+ return to.concat(ar || Array.prototype.slice.call(from));
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ var reflection_1 = require("@proteinjs/reflection");
49
+ var service_1 = require("@proteinjs/service");
50
+ var DbService_1 = require("../src/services/DbService");
51
+ var READS = ['query', 'get', 'getRowCount', 'tableExists'];
52
+ var WRITES = ['insert', 'update', 'updateArrayMembership', 'updatePreserving', 'delete'];
53
+ beforeAll(function () {
54
+ // Node's Request rejects the relative service paths a browser resolves against the page origin.
55
+ global.Request = /** @class */ (function () {
56
+ function class_1(url, init) {
57
+ this.url = url;
58
+ this.init = init;
59
+ }
60
+ return class_1;
61
+ }());
62
+ var returnType = { name: 'Promise<any>' };
63
+ var methods = __spreadArray(__spreadArray([], READS, true), WRITES, true).map(function (name) { return new reflection_1.Method(name, returnType, true, false, false, false, 'public', []); });
64
+ reflection_1.SourceRepository.get().flattenedSourceGraph.interfaces['@proteinjs/db/DbService'] = new reflection_1.Interface('@proteinjs/db', 'DbService', [], methods);
65
+ });
66
+ /** A transport that never answers: its promise settles only when the request is abandoned (its signal aborts). */
67
+ var neverAnswers = function () {
68
+ return jest.fn(function (request) {
69
+ return new Promise(function (unused, reject) {
70
+ var _a;
71
+ (_a = request.init.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', function () {
72
+ return reject(Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' }));
73
+ });
74
+ });
75
+ });
76
+ };
77
+ var sentRequests = function () { return global.fetch.mock.calls.map(function (_a) {
78
+ var request = _a[0];
79
+ return request;
80
+ }); };
81
+ /** The call's outcome, readable without awaiting it — a pending call must read as pending. */
82
+ var track = function (promise) {
83
+ var outcome = { state: 'pending' };
84
+ promise.then(function () {
85
+ outcome.state = 'resolved';
86
+ }, function () {
87
+ outcome.state = 'rejected';
88
+ });
89
+ return outcome;
90
+ };
91
+ var aTable = { name: 'a_table' };
92
+ describe('the db service under the client retry policy', function () {
93
+ beforeEach(function () { return jest.useFakeTimers(); });
94
+ afterEach(function () { return jest.useRealTimers(); });
95
+ it.each(READS)('%s is a declared read: bounded at the first contact, redelivered as a fresh request', function (name) { return __awaiter(void 0, void 0, void 0, function () {
96
+ var service, outcome;
97
+ return __generator(this, function (_a) {
98
+ switch (_a.label) {
99
+ case 0:
100
+ global.fetch = neverAnswers();
101
+ service = (0, DbService_1.getDbService)();
102
+ outcome = track(service[name](aTable, {}));
103
+ return [4 /*yield*/, jest.advanceTimersByTimeAsync(service_1.READ_CONTACT_TIMEOUT_MS)];
104
+ case 1:
105
+ _a.sent();
106
+ expect(sentRequests()).toHaveLength(1);
107
+ expect(sentRequests()[0].url).toBe("/service/@proteinjs/db/DbService/".concat(name));
108
+ expect(sentRequests()[0].init.signal.aborted).toBe(true);
109
+ return [4 /*yield*/, jest.advanceTimersByTimeAsync(service_1.REDELIVERY_BASE_MS)];
110
+ case 2:
111
+ _a.sent();
112
+ expect(sentRequests()).toHaveLength(2);
113
+ expect(sentRequests()[1].init.signal.aborted).toBe(false);
114
+ expect(outcome.state).toBe('pending');
115
+ return [2 /*return*/];
116
+ }
117
+ });
118
+ }); });
119
+ it.each(WRITES)('%s is undeclared: one delivery, no bound, never redelivered', function (name) { return __awaiter(void 0, void 0, void 0, function () {
120
+ var service, outcome;
121
+ return __generator(this, function (_a) {
122
+ switch (_a.label) {
123
+ case 0:
124
+ global.fetch = neverAnswers();
125
+ service = (0, DbService_1.getDbService)();
126
+ outcome = track(service[name](aTable, {}));
127
+ return [4 /*yield*/, jest.advanceTimersByTimeAsync(service_1.READ_CONTACT_TIMEOUT_MS * 4)];
128
+ case 1:
129
+ _a.sent();
130
+ expect(sentRequests()).toHaveLength(1);
131
+ expect(sentRequests()[0].url).toBe("/service/@proteinjs/db/DbService/".concat(name));
132
+ expect(sentRequests()[0].init.signal).toBeUndefined();
133
+ expect(outcome.state).toBe('pending');
134
+ return [2 /*return*/];
135
+ }
136
+ });
137
+ }); });
138
+ });
139
+ //# sourceMappingURL=DbServiceRetryDeclaration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DbServiceRetryDeclaration.test.js","sourceRoot":"","sources":["../../test/DbServiceRetryDeclaration.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAAkG;AAClG,8CAAiF;AACjF,uDAAyD;AAmBzD,IAAM,KAAK,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;AAC7D,IAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AAE3F,SAAS,CAAC;IACR,gGAAgG;IAChG,MAAM,CAAC,OAAO,GAAG;QACf,iBACS,GAAW,EACX,IAAS;YADT,QAAG,GAAH,GAAG,CAAQ;YACX,SAAI,GAAJ,IAAI,CAAK;QACf,CAAC;QACN,cAAC;IAAD,CAAC,AALgB,GAKT,CAAC;IACT,IAAM,UAAU,GAAG,EAAE,IAAI,EAAE,cAAc,EAAqC,CAAC;IAC/E,IAAM,OAAO,GAAG,gCAAI,KAAK,SAAK,MAAM,QAAE,GAAG,CACvC,UAAC,IAAI,IAAK,OAAA,IAAI,mBAAM,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,EAArE,CAAqE,CAChF,CAAC;IACD,6BAAgB,CAAC,GAAG,EAAqC,CAAC,oBAAoB,CAAC,UAAU,CACxF,yBAAyB,CAC1B,GAAG,IAAI,sBAAS,CAAC,eAAe,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;AAC/D,CAAC,CAAC,CAAC;AAEH,kHAAkH;AAClH,IAAM,YAAY,GAAG;IACnB,OAAA,IAAI,CAAC,EAAE,CACL,UAAC,OAAY;QACX,OAAA,IAAI,OAAO,CAAC,UAAC,MAAM,EAAE,MAAM;;YACzB,MAAA,OAAO,CAAC,IAAI,CAAC,MAAM,0CAAE,gBAAgB,CAAC,OAAO,EAAE;gBAC7C,OAAA,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,CAAC,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;YAAtF,CAAsF,CACvF,CAAC;QACJ,CAAC,CAAC;IAJF,CAIE,CACL;AAPD,CAOC,CAAC;AAEJ,IAAM,YAAY,GAAG,cAAa,OAAC,MAAM,CAAC,KAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAC,EAAS;QAAR,OAAO,QAAA;IAAM,OAAA,OAAO;AAAP,CAAO,CAAC,EAAlE,CAAkE,CAAC;AAErG,8FAA8F;AAC9F,IAAM,KAAK,GAAG,UAAC,OAAqB;IAClC,IAAM,OAAO,GAAmD,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACrF,OAAO,CAAC,IAAI,CACV;QACE,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC;IAC7B,CAAC,EACD;QACE,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC;IAC7B,CAAC,CACF,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,IAAM,MAAM,GAAG,EAAE,IAAI,EAAE,SAAS,EAAS,CAAC;AAE1C,QAAQ,CAAC,8CAA8C,EAAE;IACvD,UAAU,CAAC,cAAM,OAAA,IAAI,CAAC,aAAa,EAAE,EAApB,CAAoB,CAAC,CAAC;IACvC,SAAS,CAAC,cAAM,OAAA,IAAI,CAAC,aAAa,EAAE,EAApB,CAAoB,CAAC,CAAC;IAEtC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CACZ,qFAAqF,EACrF,UAAO,IAAI;;;;;oBACT,MAAM,CAAC,KAAK,GAAG,YAAY,EAAS,CAAC;oBAC/B,OAAO,GAAG,IAAA,wBAAY,GAAS,CAAC;oBAChC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;oBAEjD,qBAAM,IAAI,CAAC,wBAAwB,CAAC,iCAAuB,CAAC,EAAA;;oBAA5D,SAA4D,CAAC;oBAC7D,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBACvC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,2CAAoC,IAAI,CAAE,CAAC,CAAC;oBAC/E,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAEzD,qBAAM,IAAI,CAAC,wBAAwB,CAAC,4BAAkB,CAAC,EAAA;;oBAAvD,SAAuD,CAAC;oBACxD,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBACvC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAC1D,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;;;;SACvC,CACF,CAAC;IAEF,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,6DAA6D,EAAE,UAAO,IAAI;;;;;oBACxF,MAAM,CAAC,KAAK,GAAG,YAAY,EAAS,CAAC;oBAC/B,OAAO,GAAG,IAAA,wBAAY,GAAS,CAAC;oBAChC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;oBAEjD,qBAAM,IAAI,CAAC,wBAAwB,CAAC,iCAAuB,GAAG,CAAC,CAAC,EAAA;;oBAAhE,SAAgE,CAAC;oBACjE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;oBACvC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,2CAAoC,IAAI,CAAE,CAAC,CAAC;oBAC/E,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAC;oBACtD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;;;;SACvC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db",
3
- "version": "1.50.2",
3
+ "version": "1.51.0",
4
4
  "main": "./dist/generated/index.js",
5
5
  "types": "./dist/generated/index.d.ts",
6
6
  "exports": {
@@ -46,7 +46,7 @@
46
46
  "@proteinjs/reflection": "^1.2.0",
47
47
  "@proteinjs/serializer": "^1.1.10",
48
48
  "@proteinjs/server-api": "^3.0.11",
49
- "@proteinjs/service": "^1.6.0",
49
+ "@proteinjs/service": "^1.8.0",
50
50
  "@proteinjs/user-auth": "^1.3.0",
51
51
  "@proteinjs/util": "^1.6.1",
52
52
  "moment": "2.29.4",
@@ -66,5 +66,5 @@
66
66
  "ts-jest": "29.1.1",
67
67
  "typescript": "5.2.2"
68
68
  },
69
- "gitHead": "6a487f9e29bc1ab5e3a7f79e1cce178cd2512beb"
69
+ "gitHead": "1128b1e6374c7f52cd5de172620b619b8f92e119"
70
70
  }
@@ -5,7 +5,19 @@ import { QueryBuilder } from '@proteinjs/db-query';
5
5
  import { ArrayMembershipUpdate } from '../reference/ArrayMembershipOps';
6
6
  import { PreservedPath } from '../UpdatePreserving';
7
7
 
8
- export const getDbService = serviceFactory<DbService>('@proteinjs/db/DbService');
8
+ /**
9
+ * The db service rides the service client's retry policy by declaration alone, no logic of its
10
+ * own: the reads (`query`, `get`, `getRowCount`, `tableExists`) are declared `'read'` — bounded at
11
+ * the first contact and redelivered under the client's series when a delivery produced no
12
+ * response — and every write stays undeclared: one delivery, never redelivered (a redelivered
13
+ * `insert` would insert twice). The policy's numbers and exceptions are @proteinjs/service's.
14
+ */
15
+ export const getDbService = serviceFactory<DbService>('@proteinjs/db/DbService', undefined, {
16
+ query: 'read',
17
+ get: 'read',
18
+ getRowCount: 'read',
19
+ tableExists: 'read',
20
+ });
9
21
 
10
22
  export type Query<T> = ObjectQuery<T> | QueryBuilder<T>;
11
23
  export type ObjectQuery<T> = Partial<{ [P in keyof T]: any }>;
@@ -0,0 +1,105 @@
1
+ import { Interface, Method, SourceRepository, TypeAliasDeclaration } from '@proteinjs/reflection';
2
+ import { READ_CONTACT_TIMEOUT_MS, REDELIVERY_BASE_MS } from '@proteinjs/service';
3
+ import { getDbService } from '../src/services/DbService';
4
+
5
+ /**
6
+ * The db service rides the service client's retry policy by declaration alone: its reads (`query`,
7
+ * `get`, `getRowCount`, `tableExists`) are declared `'read'` on the factory — bounded at the first
8
+ * contact and redelivered as fresh requests under the client's series when a delivery produced no
9
+ * response — and its writes (`insert`, `update`, `updateArrayMembership`, `updatePreserving`,
10
+ * `delete`) stay undeclared: one delivery, no bound, never redelivered. Nothing here is the db
11
+ * package's own logic — the policy, its numbers and its exceptions are @proteinjs/service's; this
12
+ * suite pins the declaration. Read as OUTCOMES off the transport: the requests that left and their
13
+ * watchdog signals, under fake timers.
14
+ *
15
+ * The service interface is seeded into the source repository directly (this package's own test pattern —
16
+ * DbDefaultDriverDuplicateModuleState seeds the object cache the same way), so the real factory
17
+ * builds the real clients without the generated source graph.
18
+ */
19
+
20
+ type RepositoryInternals = { flattenedSourceGraph: { interfaces: { [qualifiedName: string]: Interface } } };
21
+
22
+ const READS = ['query', 'get', 'getRowCount', 'tableExists'];
23
+ const WRITES = ['insert', 'update', 'updateArrayMembership', 'updatePreserving', 'delete'];
24
+
25
+ beforeAll(() => {
26
+ // Node's Request rejects the relative service paths a browser resolves against the page origin.
27
+ global.Request = class {
28
+ constructor(
29
+ public url: string,
30
+ public init: any
31
+ ) {}
32
+ } as any;
33
+ const returnType = { name: 'Promise<any>' } as unknown as TypeAliasDeclaration;
34
+ const methods = [...READS, ...WRITES].map(
35
+ (name) => new Method(name, returnType, true, false, false, false, 'public', [])
36
+ );
37
+ (SourceRepository.get() as unknown as RepositoryInternals).flattenedSourceGraph.interfaces[
38
+ '@proteinjs/db/DbService'
39
+ ] = new Interface('@proteinjs/db', 'DbService', [], methods);
40
+ });
41
+
42
+ /** A transport that never answers: its promise settles only when the request is abandoned (its signal aborts). */
43
+ const neverAnswers = () =>
44
+ jest.fn(
45
+ (request: any) =>
46
+ new Promise((unused, reject) => {
47
+ request.init.signal?.addEventListener('abort', () =>
48
+ reject(Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' }))
49
+ );
50
+ })
51
+ );
52
+
53
+ const sentRequests = (): any[] => (global.fetch as jest.Mock).mock.calls.map(([request]) => request);
54
+
55
+ /** The call's outcome, readable without awaiting it — a pending call must read as pending. */
56
+ const track = (promise: Promise<any>) => {
57
+ const outcome: { state: 'pending' | 'resolved' | 'rejected' } = { state: 'pending' };
58
+ promise.then(
59
+ () => {
60
+ outcome.state = 'resolved';
61
+ },
62
+ () => {
63
+ outcome.state = 'rejected';
64
+ }
65
+ );
66
+ return outcome;
67
+ };
68
+
69
+ const aTable = { name: 'a_table' } as any;
70
+
71
+ describe('the db service under the client retry policy', () => {
72
+ beforeEach(() => jest.useFakeTimers());
73
+ afterEach(() => jest.useRealTimers());
74
+
75
+ it.each(READS)(
76
+ '%s is a declared read: bounded at the first contact, redelivered as a fresh request',
77
+ async (name) => {
78
+ global.fetch = neverAnswers() as any;
79
+ const service = getDbService() as any;
80
+ const outcome = track(service[name](aTable, {}));
81
+
82
+ await jest.advanceTimersByTimeAsync(READ_CONTACT_TIMEOUT_MS);
83
+ expect(sentRequests()).toHaveLength(1);
84
+ expect(sentRequests()[0].url).toBe(`/service/@proteinjs/db/DbService/${name}`);
85
+ expect(sentRequests()[0].init.signal.aborted).toBe(true);
86
+
87
+ await jest.advanceTimersByTimeAsync(REDELIVERY_BASE_MS);
88
+ expect(sentRequests()).toHaveLength(2);
89
+ expect(sentRequests()[1].init.signal.aborted).toBe(false);
90
+ expect(outcome.state).toBe('pending');
91
+ }
92
+ );
93
+
94
+ it.each(WRITES)('%s is undeclared: one delivery, no bound, never redelivered', async (name) => {
95
+ global.fetch = neverAnswers() as any;
96
+ const service = getDbService() as any;
97
+ const outcome = track(service[name](aTable, {}));
98
+
99
+ await jest.advanceTimersByTimeAsync(READ_CONTACT_TIMEOUT_MS * 4);
100
+ expect(sentRequests()).toHaveLength(1);
101
+ expect(sentRequests()[0].url).toBe(`/service/@proteinjs/db/DbService/${name}`);
102
+ expect(sentRequests()[0].init.signal).toBeUndefined();
103
+ expect(outcome.state).toBe('pending');
104
+ });
105
+ });