@palbase/backend 25.1.0 → 27.0.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/dist/bin/palbase-backend.cjs +2401 -1039
- package/dist/bin/palbase-backend.cjs.map +1 -1
- package/dist/bin/palbase-backend.js +87 -51
- package/dist/bin/palbase-backend.js.map +1 -1
- package/dist/chunk-CGNN2PUH.js +213 -0
- package/dist/chunk-CGNN2PUH.js.map +1 -0
- package/dist/chunk-DRZFQRJI.js +370 -0
- package/dist/chunk-DRZFQRJI.js.map +1 -0
- package/dist/chunk-GYK6QYS4.js +708 -0
- package/dist/chunk-GYK6QYS4.js.map +1 -0
- package/dist/chunk-I3C4PFIW.js +930 -0
- package/dist/chunk-I3C4PFIW.js.map +1 -0
- package/dist/{chunk-VDF2T4AS.js → chunk-OO7R25AI.js} +1213 -591
- package/dist/chunk-OO7R25AI.js.map +1 -0
- package/dist/chunk-TS4U7NBD.js +885 -0
- package/dist/chunk-TS4U7NBD.js.map +1 -0
- package/dist/{chunk-YOY5DFQS.js → chunk-TWX6JTGJ.js} +76 -34
- package/dist/{chunk-YOY5DFQS.js.map → chunk-TWX6JTGJ.js.map} +1 -1
- package/dist/{chunk-35PNTIRN.js → chunk-VVMJEVQP.js} +63 -162
- package/dist/chunk-VVMJEVQP.js.map +1 -0
- package/dist/{chunk-7D4SUZUM.js → chunk-VXPNPVAG.js} +3 -1
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +2 -2
- package/dist/db/env.d.ts +2 -2
- package/dist/db/index.cjs +754 -344
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +7 -4
- package/dist/engine/index.cjs +2339 -1006
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +6 -6
- package/dist/engine/index.d.ts +6 -6
- package/dist/engine/index.js +7 -6
- package/dist/index-BrvvxSpn.d.ts +4844 -0
- package/dist/index-Bve7BBTL.d.cts +4844 -0
- package/dist/{index-CUomTA3e.d.ts → index-NuzRCuxe.d.ts} +171 -296
- package/dist/{index-ClpDeSos.d.cts → index-VtToZmUm.d.cts} +171 -296
- package/dist/index.cjs +2694 -1169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -19
- package/dist/index.d.ts +165 -19
- package/dist/index.js +738 -477
- package/dist/index.js.map +1 -1
- package/dist/module-Dl1KFVtc.d.cts +54 -0
- package/dist/module-Dl1KFVtc.d.ts +54 -0
- package/dist/openapi/index.cjs +1330 -484
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +4 -2
- package/dist/openapi/index.d.ts +4 -2
- package/dist/openapi/index.js +1264 -474
- package/dist/openapi/index.js.map +1 -1
- package/dist/{registry-dZZ5JKYg.d.ts → registry-B0eyOF9x.d.ts} +1 -1
- package/dist/{registry-CC0WBQq6.d.cts → registry-Bk9_rbNd.d.cts} +1 -1
- package/dist/stack.cjs.map +1 -1
- package/dist/test/index.cjs +705 -141
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.d.cts +30 -4
- package/dist/test/index.d.ts +30 -4
- package/dist/test/index.js +472 -124
- package/dist/test/index.js.map +1 -1
- package/docs/README.md +33 -18
- package/docs/auth.md +1 -1
- package/docs/background.md +2 -2
- package/docs/database.md +221 -50
- package/docs/endpoints.md +3 -4
- package/docs/events.md +3 -3
- package/docs/getting-started.md +1 -1
- package/docs/llms-full.txt +401 -117
- package/docs/migrations.md +2 -2
- package/docs/schema.md +19 -10
- package/docs/services.md +116 -26
- package/package.json +8 -4
- package/stager/generics.js +205 -0
- package/stager/stage.js +39 -3
- package/template/AGENTS.md +110 -72
- package/template/db/public.ts +1 -1
- package/template/{controllers → modules/health}/health.controller.ts +1 -1
- package/template/modules/health/health.module.ts +24 -0
- package/template/modules/notes/note.service.test.ts +49 -0
- package/template/modules/notes/note.service.ts +108 -0
- package/template/{controllers → modules/notes}/notes.controller.ts +17 -11
- package/template/modules/notes/notes.module.ts +37 -0
- package/template/package.json +5 -3
- package/template/scripts/test.sh +33 -0
- package/template/tsconfig.json +29 -30
- package/dist/chunk-35PNTIRN.js.map +0 -1
- package/dist/chunk-CJSKYY76.js +0 -627
- package/dist/chunk-CJSKYY76.js.map +0 -1
- package/dist/chunk-CRQKCRGF.js +0 -276
- package/dist/chunk-CRQKCRGF.js.map +0 -1
- package/dist/chunk-G4R6BTLV.js +0 -662
- package/dist/chunk-G4R6BTLV.js.map +0 -1
- package/dist/chunk-VDF2T4AS.js.map +0 -1
- package/dist/chunk-XABBC7JP.js +0 -55
- package/dist/chunk-XABBC7JP.js.map +0 -1
- package/dist/endpoint-CTEHhb7A.d.ts +0 -2386
- package/dist/endpoint-DYHMo6cC.d.cts +0 -2386
- package/dist/index-CW21M9Z3.d.ts +0 -1222
- package/dist/index-CmBK76nx.d.cts +0 -1222
- package/template/services/note.service.test.ts +0 -45
- package/template/services/note.service.ts +0 -76
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-VXPNPVAG.js.map} +0 -0
- /package/template/{models/notes → modules/notes/dto}/create.ts +0 -0
package/dist/test/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -23,7 +24,8 @@ __export(test_exports, {
|
|
|
23
24
|
TestApiError: () => TestApiError,
|
|
24
25
|
api: () => api,
|
|
25
26
|
createTestApi: () => createTestApi,
|
|
26
|
-
fakeDatabase: () => fakeDatabase
|
|
27
|
+
fakeDatabase: () => fakeDatabase,
|
|
28
|
+
isolated: () => isolated
|
|
27
29
|
});
|
|
28
30
|
module.exports = __toCommonJS(test_exports);
|
|
29
31
|
|
|
@@ -40,8 +42,13 @@ function asPowChallenge(details) {
|
|
|
40
42
|
const { id, prefix, difficulty } = c;
|
|
41
43
|
if (typeof id !== "string" || typeof prefix !== "string") return null;
|
|
42
44
|
if (typeof difficulty !== "number" || !Number.isInteger(difficulty) || difficulty < 0) return null;
|
|
43
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
id,
|
|
47
|
+
prefix,
|
|
48
|
+
difficulty
|
|
49
|
+
};
|
|
44
50
|
}
|
|
51
|
+
__name(asPowChallenge, "asPowChallenge");
|
|
45
52
|
var encoder = new TextEncoder();
|
|
46
53
|
var hasher = null;
|
|
47
54
|
async function digester() {
|
|
@@ -56,16 +63,17 @@ async function digester() {
|
|
|
56
63
|
);
|
|
57
64
|
if (typeof mod.createHash === "function") {
|
|
58
65
|
const createHash = mod.createHash;
|
|
59
|
-
hasher = (input) => new Uint8Array(createHash("sha256").update(input).digest());
|
|
66
|
+
hasher = /* @__PURE__ */ __name((input) => new Uint8Array(createHash("sha256").update(input).digest()), "hasher");
|
|
60
67
|
return hasher;
|
|
61
68
|
}
|
|
62
69
|
} catch {
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
|
-
hasher = async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(input)));
|
|
72
|
+
hasher = /* @__PURE__ */ __name(async (input) => new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(input))), "hasher");
|
|
66
73
|
return hasher;
|
|
67
74
|
}
|
|
68
|
-
|
|
75
|
+
__name(digester, "digester");
|
|
76
|
+
var now = /* @__PURE__ */ __name(() => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now(), "now");
|
|
69
77
|
function leadingZeroBits(hash) {
|
|
70
78
|
let bits = 0;
|
|
71
79
|
for (const byte of hash) {
|
|
@@ -77,18 +85,18 @@ function leadingZeroBits(hash) {
|
|
|
77
85
|
}
|
|
78
86
|
return bits;
|
|
79
87
|
}
|
|
88
|
+
__name(leadingZeroBits, "leadingZeroBits");
|
|
80
89
|
var MAX_POW_DIFFICULTY = 24;
|
|
81
90
|
function powBudget(difficulty) {
|
|
82
91
|
return 8 * 2 ** difficulty;
|
|
83
92
|
}
|
|
93
|
+
__name(powBudget, "powBudget");
|
|
84
94
|
var POW_TIME_BUDGET_MS = 12e4;
|
|
85
95
|
var CALIBRATION_WARMUP = 1024;
|
|
86
96
|
var CALIBRATION_END = 9216;
|
|
87
97
|
async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.difficulty), signal) {
|
|
88
98
|
if (challenge.difficulty > MAX_POW_DIFFICULTY) {
|
|
89
|
-
throw new Error(
|
|
90
|
-
`proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`
|
|
91
|
-
);
|
|
99
|
+
throw new Error(`proof-of-work: refusing difficulty ${challenge.difficulty}; this client attempts at most ${MAX_POW_DIFFICULTY}, which is the highest a Palbase stack issues`);
|
|
92
100
|
}
|
|
93
101
|
const digest = await digester();
|
|
94
102
|
let warmedAt = 0;
|
|
@@ -100,9 +108,7 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
100
108
|
throw new DOMException("proof-of-work solve aborted", "AbortError");
|
|
101
109
|
}
|
|
102
110
|
if (now() > deadline) {
|
|
103
|
-
throw new Error(
|
|
104
|
-
`proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1e3}s and ${nonce.toLocaleString()} attempts \u2014 the tail this run drew is longer than the allowance`
|
|
105
|
-
);
|
|
111
|
+
throw new Error(`proof-of-work: gave up on difficulty ${challenge.difficulty} after ${POW_TIME_BUDGET_MS / 1e3}s and ${nonce.toLocaleString()} attempts \u2014 the tail this run drew is longer than the allowance`);
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
if (nonce === CALIBRATION_WARMUP) {
|
|
@@ -114,9 +120,7 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
114
120
|
const rate = (CALIBRATION_END - CALIBRATION_WARMUP) / (elapsed / 1e3);
|
|
115
121
|
const expectedMs = 2 ** challenge.difficulty / rate * 1e3;
|
|
116
122
|
if (expectedMs > POW_TIME_BUDGET_MS) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
`proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1e3)}s here (${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1e3}s; refusing before spending the time rather than after`
|
|
119
|
-
);
|
|
123
|
+
throw new Error(`proof-of-work: difficulty ${challenge.difficulty} needs about ${Math.round(expectedMs / 1e3)}s here (${Math.round(rate).toLocaleString()} digests/s) and this client allows ${POW_TIME_BUDGET_MS / 1e3}s; refusing before spending the time rather than after`);
|
|
120
124
|
}
|
|
121
125
|
deadline = now() + (POW_TIME_BUDGET_MS - (now() - warmedAt));
|
|
122
126
|
}
|
|
@@ -128,13 +132,15 @@ async function solvePowChallenge(challenge, maxIterations = powBudget(challenge.
|
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
|
-
throw new Error(
|
|
132
|
-
`proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`
|
|
133
|
-
);
|
|
135
|
+
throw new Error(`proof-of-work: no nonce found for difficulty ${challenge.difficulty} within ${maxIterations} attempts`);
|
|
134
136
|
}
|
|
137
|
+
__name(solvePowChallenge, "solvePowChallenge");
|
|
135
138
|
|
|
136
139
|
// src/test/api.ts
|
|
137
140
|
var TestApiError = class extends Error {
|
|
141
|
+
static {
|
|
142
|
+
__name(this, "TestApiError");
|
|
143
|
+
}
|
|
138
144
|
status;
|
|
139
145
|
error;
|
|
140
146
|
/** Payload of an error your code threw — `new BadRequest({ fields })` arrives here. */
|
|
@@ -154,12 +160,11 @@ var TestApiError = class extends Error {
|
|
|
154
160
|
};
|
|
155
161
|
function required(value, envName) {
|
|
156
162
|
if (!value) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`${envName} is not set \u2014 the test client has nowhere to send requests. This is set by the deploy that runs your tests; if you are running them by hand, set it yourself.`
|
|
159
|
-
);
|
|
163
|
+
throw new Error(`${envName} is not set \u2014 the test client has nowhere to send requests. This is set by the deploy that runs your tests; if you are running them by hand, set it yourself.`);
|
|
160
164
|
}
|
|
161
165
|
return value;
|
|
162
166
|
}
|
|
167
|
+
__name(required, "required");
|
|
163
168
|
function isLocalTarget(baseUrl) {
|
|
164
169
|
try {
|
|
165
170
|
const { hostname } = new URL(baseUrl);
|
|
@@ -168,6 +173,7 @@ function isLocalTarget(baseUrl) {
|
|
|
168
173
|
return false;
|
|
169
174
|
}
|
|
170
175
|
}
|
|
176
|
+
__name(isLocalTarget, "isLocalTarget");
|
|
171
177
|
function secondsUntilExpiry(token) {
|
|
172
178
|
const body = token.split(".")[1];
|
|
173
179
|
if (!body) return null;
|
|
@@ -178,6 +184,7 @@ function secondsUntilExpiry(token) {
|
|
|
178
184
|
return null;
|
|
179
185
|
}
|
|
180
186
|
}
|
|
187
|
+
__name(secondsUntilExpiry, "secondsUntilExpiry");
|
|
181
188
|
function createTestApi(config) {
|
|
182
189
|
const baseUrl = required(config.baseUrl, "PALBASE_TEST_BASE_URL").replace(/\/$/, "");
|
|
183
190
|
const apiKey = required(config.apiKey, "PALBASE_TEST_API_KEY");
|
|
@@ -192,7 +199,9 @@ function createTestApi(config) {
|
|
|
192
199
|
// Selects the release under test. Omit it and the gateway serves the LIVE
|
|
193
200
|
// one, which would make the whole suite grade the wrong code. Absent only
|
|
194
201
|
// against a local stack, which has a single version.
|
|
195
|
-
...candidateToken ? {
|
|
202
|
+
...candidateToken ? {
|
|
203
|
+
"x-palbase-candidate": candidateToken
|
|
204
|
+
} : {},
|
|
196
205
|
...opts.headers
|
|
197
206
|
};
|
|
198
207
|
if (bearer) headers.authorization = `Bearer ${bearer}`;
|
|
@@ -205,7 +214,12 @@ function createTestApi(config) {
|
|
|
205
214
|
});
|
|
206
215
|
const text = await res.text();
|
|
207
216
|
const parsed = text ? safeParse(text) : void 0;
|
|
208
|
-
requests.push({
|
|
217
|
+
requests.push({
|
|
218
|
+
method,
|
|
219
|
+
path,
|
|
220
|
+
status: res.status,
|
|
221
|
+
ms: Date.now() - startedAt
|
|
222
|
+
});
|
|
209
223
|
if (!res.ok) {
|
|
210
224
|
if (res.status === 401 && bearer) {
|
|
211
225
|
const left = secondsUntilExpiry(bearer);
|
|
@@ -220,41 +234,39 @@ function createTestApi(config) {
|
|
|
220
234
|
}
|
|
221
235
|
return parsed;
|
|
222
236
|
}
|
|
237
|
+
__name(call, "call");
|
|
223
238
|
return {
|
|
224
239
|
requests,
|
|
225
|
-
get: (path, opts) => call("GET", path, void 0, opts),
|
|
226
|
-
post: (path, body, opts) => call("POST", path, body, opts),
|
|
227
|
-
patch: (path, body, opts) => call("PATCH", path, body, opts),
|
|
228
|
-
put: (path, body, opts) => call("PUT", path, body, opts),
|
|
229
|
-
delete: (path, opts) => call("DELETE", path, void 0, opts),
|
|
230
|
-
query: (path, body, opts) => call("QUERY", path, body, opts),
|
|
240
|
+
get: /* @__PURE__ */ __name((path, opts) => call("GET", path, void 0, opts), "get"),
|
|
241
|
+
post: /* @__PURE__ */ __name((path, body, opts) => call("POST", path, body, opts), "post"),
|
|
242
|
+
patch: /* @__PURE__ */ __name((path, body, opts) => call("PATCH", path, body, opts), "patch"),
|
|
243
|
+
put: /* @__PURE__ */ __name((path, body, opts) => call("PUT", path, body, opts), "put"),
|
|
244
|
+
delete: /* @__PURE__ */ __name((path, opts) => call("DELETE", path, void 0, opts), "delete"),
|
|
245
|
+
query: /* @__PURE__ */ __name((path, body, opts) => call("QUERY", path, body, opts), "query"),
|
|
231
246
|
async signInAs(name) {
|
|
232
247
|
const identity = (config.identities ?? {})[name];
|
|
233
248
|
if (!identity) {
|
|
234
249
|
const declared = Object.keys(config.identities ?? {});
|
|
235
|
-
throw new Error(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
", and this run has none \u2014 either your config declares no users or the deploy could not mint them; the deploy log says which"
|
|
242
|
-
))
|
|
243
|
-
);
|
|
250
|
+
throw new Error(`no test identity named ${JSON.stringify(name)} \u2014 the deploy mints one per user declared in config/test-users.ts` + (declared.length ? `; this run has: ${declared.join(", ")}` : (
|
|
251
|
+
// indistinguishable, and blaming the customer's config for a
|
|
252
|
+
// platform failure sends them to look in the wrong file. The
|
|
253
|
+
// deploy log names which one it was.
|
|
254
|
+
", and this run has none \u2014 either your config declares no users or the deploy could not mint them; the deploy log says which"
|
|
255
|
+
)));
|
|
244
256
|
}
|
|
245
257
|
if (identity.accessToken) {
|
|
246
258
|
bearer = identity.accessToken;
|
|
247
|
-
return {
|
|
259
|
+
return {
|
|
260
|
+
id: identity.id ?? "",
|
|
261
|
+
email: identity.email
|
|
262
|
+
};
|
|
248
263
|
}
|
|
249
264
|
return this.signIn(identity);
|
|
250
265
|
},
|
|
251
266
|
async signIn(credentials) {
|
|
252
|
-
const attempt = async (extra) => call(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
credentials,
|
|
256
|
-
extra ? { headers: extra } : {}
|
|
257
|
-
);
|
|
267
|
+
const attempt = /* @__PURE__ */ __name(async (extra) => call("POST", "/auth/login", credentials, extra ? {
|
|
268
|
+
headers: extra
|
|
269
|
+
} : {}), "attempt");
|
|
258
270
|
let result;
|
|
259
271
|
try {
|
|
260
272
|
result = await attempt();
|
|
@@ -265,7 +277,9 @@ function createTestApi(config) {
|
|
|
265
277
|
result = await attempt(await solvePowChallenge(challenge));
|
|
266
278
|
}
|
|
267
279
|
bearer = result.access_token;
|
|
268
|
-
return result.user ?? {
|
|
280
|
+
return result.user ?? {
|
|
281
|
+
id: ""
|
|
282
|
+
};
|
|
269
283
|
},
|
|
270
284
|
async signOut() {
|
|
271
285
|
await call("POST", "/auth/logout", void 0);
|
|
@@ -276,6 +290,7 @@ function createTestApi(config) {
|
|
|
276
290
|
}
|
|
277
291
|
};
|
|
278
292
|
}
|
|
293
|
+
__name(createTestApi, "createTestApi");
|
|
279
294
|
function parseIdentities(raw) {
|
|
280
295
|
if (!raw) return {};
|
|
281
296
|
try {
|
|
@@ -284,6 +299,7 @@ function parseIdentities(raw) {
|
|
|
284
299
|
return {};
|
|
285
300
|
}
|
|
286
301
|
}
|
|
302
|
+
__name(parseIdentities, "parseIdentities");
|
|
287
303
|
function safeParse(text) {
|
|
288
304
|
try {
|
|
289
305
|
return JSON.parse(text);
|
|
@@ -291,6 +307,7 @@ function safeParse(text) {
|
|
|
291
307
|
return text;
|
|
292
308
|
}
|
|
293
309
|
}
|
|
310
|
+
__name(safeParse, "safeParse");
|
|
294
311
|
var configured = null;
|
|
295
312
|
var api = new Proxy({}, {
|
|
296
313
|
get(_target, prop) {
|
|
@@ -304,57 +321,438 @@ var api = new Proxy({}, {
|
|
|
304
321
|
}
|
|
305
322
|
});
|
|
306
323
|
|
|
324
|
+
// src/test/container.ts
|
|
325
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
326
|
+
function isolated() {
|
|
327
|
+
const over = /* @__PURE__ */ new Map();
|
|
328
|
+
const local = /* @__PURE__ */ new Map();
|
|
329
|
+
const make = /* @__PURE__ */ __name((c) => {
|
|
330
|
+
if (over.has(c)) return over.get(c);
|
|
331
|
+
const hit = local.get(c);
|
|
332
|
+
if (hit !== void 0) return hit;
|
|
333
|
+
const meta = Reflect.getMetadata("design:paramtypes", c) ?? [];
|
|
334
|
+
const inst = new c(...meta.map((d) => make(d)));
|
|
335
|
+
local.set(c, inst);
|
|
336
|
+
return inst;
|
|
337
|
+
}, "make");
|
|
338
|
+
const api2 = {
|
|
339
|
+
with(t, v) {
|
|
340
|
+
over.set(t, v);
|
|
341
|
+
return api2;
|
|
342
|
+
},
|
|
343
|
+
get(t) {
|
|
344
|
+
return make(t);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
return api2;
|
|
348
|
+
}
|
|
349
|
+
__name(isolated, "isolated");
|
|
350
|
+
|
|
307
351
|
// src/db/input-guards.ts
|
|
308
|
-
var
|
|
352
|
+
var REF_BRAND = /* @__PURE__ */ Symbol.for("palbase.db.ref");
|
|
353
|
+
function brandOf(v) {
|
|
354
|
+
if (typeof v !== "object" || v === null) return void 0;
|
|
355
|
+
return Object.hasOwn(v, REF_BRAND) ? v[REF_BRAND] : void 0;
|
|
356
|
+
}
|
|
357
|
+
__name(brandOf, "brandOf");
|
|
358
|
+
function isColRef(v) {
|
|
359
|
+
return brandOf(v) === "col" && typeof v.$col === "string";
|
|
360
|
+
}
|
|
361
|
+
__name(isColRef, "isColRef");
|
|
362
|
+
function isSqlFragment(v) {
|
|
363
|
+
if (brandOf(v) !== "sql") return false;
|
|
364
|
+
const f = v.$sql;
|
|
365
|
+
return f !== void 0 && Array.isArray(f.text) && Array.isArray(f.values);
|
|
366
|
+
}
|
|
367
|
+
__name(isSqlFragment, "isSqlFragment");
|
|
368
|
+
var TX_EXPR = /* @__PURE__ */ Symbol.for("palbase.tx.expr");
|
|
369
|
+
function isColumnExpr(v) {
|
|
370
|
+
if (typeof v !== "object" && typeof v !== "function") return false;
|
|
371
|
+
if (v === null) return false;
|
|
372
|
+
try {
|
|
373
|
+
return v[TX_EXPR] !== void 0;
|
|
374
|
+
} catch {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
__name(isColumnExpr, "isColumnExpr");
|
|
379
|
+
function assertNoExpressionHandles(caller, table, cols, data) {
|
|
380
|
+
for (const c of cols) {
|
|
381
|
+
const v = data[c];
|
|
382
|
+
if (isColumnExpr(v)) {
|
|
383
|
+
throw new Error(`${caller}(${table}): "${c}" bir ifade tutama\u011F\u0131 ald\u0131 (increment()/decrement()/now()). Bu yolda de\u011Fer beklenir. Saya\xE7 art\u0131\u015F\u0131 i\xE7in updateMany(where, { ${c}: increment(n) }) ya da $transaction i\xE7inde tx.tables.${table}.updateWhere(where, { ${c}: increment(n) }) kullan\u0131n.`);
|
|
384
|
+
}
|
|
385
|
+
if (isColRef(v)) {
|
|
386
|
+
throw new Error(`${caller}(${table}): "${c}" bir col() ald\u0131. Kolon referans\u0131 yaln\u0131z F\u0130LTREDE durabilir; bir kolonun de\u011Ferini ba\u015Fka bir kolona yazmak i\xE7in $query kullan\u0131n.`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
__name(assertNoExpressionHandles, "assertNoExpressionHandles");
|
|
391
|
+
var KNOWN_OPS = /* @__PURE__ */ new Set([
|
|
392
|
+
"gt",
|
|
393
|
+
"gte",
|
|
394
|
+
"lt",
|
|
395
|
+
"lte",
|
|
396
|
+
"neq",
|
|
397
|
+
"in",
|
|
398
|
+
// K1 metin operatörleri (FR-005) ve null testi (FR-006). Bu küme
|
|
399
|
+
// `fakeDatabase()` ile ORTAK kaynaktır: fake bir çağrıyı motorun reddettiği
|
|
400
|
+
// yerde kabul ederse, yazarın testi üretimde patlayan koda karşı yeşil verir.
|
|
401
|
+
"contains",
|
|
402
|
+
"icontains",
|
|
403
|
+
"startsWith",
|
|
404
|
+
"endsWith",
|
|
405
|
+
"isNull"
|
|
406
|
+
]);
|
|
407
|
+
var REFUSED_OPS = {
|
|
408
|
+
eq: 'e\u015Fitlik \xC7IPLAK yaz\u0131l\u0131r: { <kolon>: <de\u011Fer> } (ya da kolon kar\u015F\u0131la\u015Ft\u0131rmas\u0131 i\xE7in { <kolon>: col("\u2026") })'
|
|
409
|
+
};
|
|
309
410
|
function assertUsableFilter(caller, table, where) {
|
|
411
|
+
const COMPOSITES = /* @__PURE__ */ new Set([
|
|
412
|
+
"OR",
|
|
413
|
+
"AND",
|
|
414
|
+
"NOT"
|
|
415
|
+
]);
|
|
310
416
|
if (!where) return;
|
|
417
|
+
if (isSqlFragment(where)) return;
|
|
311
418
|
for (const [col, cond] of Object.entries(where)) {
|
|
419
|
+
if (COMPOSITES.has(col)) {
|
|
420
|
+
const branches = col === "NOT" ? [
|
|
421
|
+
cond
|
|
422
|
+
] : cond;
|
|
423
|
+
if (!Array.isArray(branches) && col !== "NOT") {
|
|
424
|
+
throw new Error(`${caller}(${table}): where.${col} bir dizi olmal\u0131`);
|
|
425
|
+
}
|
|
426
|
+
for (const b of branches) {
|
|
427
|
+
if (b === null || typeof b !== "object") {
|
|
428
|
+
throw new Error(`${caller}(${table}): where.${col} dallar\u0131 filtre nesnesi olmal\u0131`);
|
|
429
|
+
}
|
|
430
|
+
assertUsableFilter(caller, table, b);
|
|
431
|
+
}
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (col === "has") {
|
|
435
|
+
if (cond === null || typeof cond !== "object" || Array.isArray(cond)) {
|
|
436
|
+
throw new Error(`${caller}(${table}): where.has bir ili\u015Fki haritas\u0131 olmal\u0131 ({ <ili\u015Fki>: { \u2026 } })`);
|
|
437
|
+
}
|
|
438
|
+
for (const [rel, inner] of Object.entries(cond)) {
|
|
439
|
+
if (inner === null || typeof inner !== "object" || Array.isArray(inner)) {
|
|
440
|
+
throw new Error(`${caller}(${table}): where.has.${rel} bir filtre nesnesi olmal\u0131`);
|
|
441
|
+
}
|
|
442
|
+
assertUsableFilter(caller, table, inner);
|
|
443
|
+
}
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
312
446
|
if (cond === void 0) {
|
|
313
|
-
throw new Error(
|
|
314
|
-
`${caller}(${table}): where.${col} de\u011Feri undefined \u2014 bu bir filtre de\u011Feri de\u011Fil. Ba\u011Flan\u0131nca NULL olur ve '= NULL' hi\xE7bir sat\u0131ra uymaz, yani sorgu sessizce bo\u015F sonu\xE7 d\xF6nerdi. De\u011Fer yoksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n.`
|
|
315
|
-
);
|
|
447
|
+
throw new Error(`${caller}(${table}): where.${col} de\u011Feri undefined \u2014 bu bir filtre de\u011Feri de\u011Fil. Ba\u011Flan\u0131nca NULL olur ve '= NULL' hi\xE7bir sat\u0131ra uymaz, yani sorgu sessizce bo\u015F sonu\xE7 d\xF6nerdi. De\u011Fer yoksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n.`);
|
|
316
448
|
}
|
|
317
449
|
if (cond === null || typeof cond !== "object" || Array.isArray(cond)) continue;
|
|
450
|
+
if (isColRef(cond)) continue;
|
|
318
451
|
const entries = Object.entries(cond);
|
|
319
452
|
if (entries.length === 0) {
|
|
320
|
-
throw new Error(
|
|
321
|
-
`${caller}(${table}): where.${col} bo\u015F bir operat\xF6r nesnesi ({}) \u2014 hi\xE7bir ko\u015Ful \xFCretmez, yani bu alan filtreden sessizce D\xDC\u015EERD\u0130. Ko\u015Ful kurulmayacaksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n (D-21).`
|
|
322
|
-
);
|
|
453
|
+
throw new Error(`${caller}(${table}): where.${col} bo\u015F bir operat\xF6r nesnesi ({}) \u2014 hi\xE7bir ko\u015Ful \xFCretmez, yani bu alan filtreden sessizce D\xDC\u015EERD\u0130. Ko\u015Ful kurulmayacaksa anahtar\u0131 filtreye hi\xE7 koymay\u0131n (D-21).`);
|
|
323
454
|
}
|
|
324
455
|
for (const [op, v] of entries) {
|
|
325
456
|
if (op === "in") {
|
|
326
457
|
if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${col}.in bir dizi olmal\u0131`);
|
|
327
458
|
if (v.some((x) => x === void 0)) {
|
|
328
|
-
throw new Error(
|
|
329
|
-
`${caller}(${table}): where.${col}.in listesinde undefined var \u2014 sessizce NULL'a ba\u011Flan\u0131r ve o eleman hi\xE7bir sat\u0131rla e\u015Fle\u015Fmez. Listeyi kurarken eleyin.`
|
|
330
|
-
);
|
|
459
|
+
throw new Error(`${caller}(${table}): where.${col}.in listesinde undefined var \u2014 sessizce NULL'a ba\u011Flan\u0131r ve o eleman hi\xE7bir sat\u0131rla e\u015Fle\u015Fmez. Listeyi kurarken eleyin.`);
|
|
331
460
|
}
|
|
332
461
|
continue;
|
|
333
462
|
}
|
|
463
|
+
if (REFUSED_OPS[op] !== void 0) {
|
|
464
|
+
throw new Error(`${caller}(${table}): where.${col}.${op} bu filtre dilinde yok \u2014 ${REFUSED_OPS[op]}`);
|
|
465
|
+
}
|
|
334
466
|
if (!KNOWN_OPS.has(op)) {
|
|
335
|
-
throw new Error(
|
|
336
|
-
`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in)`
|
|
337
|
-
);
|
|
467
|
+
throw new Error(`${caller}(${table}): where.${col} bilinmeyen operat\xF6r "${op}" (gt/gte/lt/lte/neq/in/contains/icontains/startsWith/endsWith/isNull)`);
|
|
338
468
|
}
|
|
339
469
|
if (v === void 0) {
|
|
340
|
-
throw new Error(
|
|
341
|
-
`${caller}(${table}): where.${col}.${op} de\u011Feri undefined \u2014 kar\u015F\u0131la\u015Ft\u0131rman\u0131n sa\u011F taraf\u0131 NULL olur ve sonu\xE7 hi\xE7bir sat\u0131ra uymaz. Ko\u015Fulu kurmay\u0131n.`
|
|
342
|
-
);
|
|
470
|
+
throw new Error(`${caller}(${table}): where.${col}.${op} de\u011Feri undefined \u2014 kar\u015F\u0131la\u015Ft\u0131rman\u0131n sa\u011F taraf\u0131 NULL olur ve sonu\xE7 hi\xE7bir sat\u0131ra uymaz. Ko\u015Fulu kurmay\u0131n.`);
|
|
343
471
|
}
|
|
344
472
|
}
|
|
345
473
|
}
|
|
346
474
|
}
|
|
475
|
+
__name(assertUsableFilter, "assertUsableFilter");
|
|
347
476
|
function assertUsableWriteValues(caller, table, cols, data) {
|
|
348
477
|
for (const c of cols) {
|
|
349
478
|
if (data[c] === void 0) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`${caller}(${table}): "${c}" de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Kolonu bo\u015Faltmak istiyorsan null yaz; kolonu de\u011Fi\u015Ftirmek istemiyorsan nesneye hi\xE7 koyma (bir eksik istek alan\u0131 sessizce NULL yaz\u0131yordu \u2014 FR-016).`
|
|
352
|
-
);
|
|
479
|
+
throw new Error(`${caller}(${table}): "${c}" de\u011Feri undefined \u2014 bu bir yazma de\u011Feri de\u011Fil. Kolonu bo\u015Faltmak istiyorsan null yaz; kolonu de\u011Fi\u015Ftirmek istemiyorsan nesneye hi\xE7 koyma (bir eksik istek alan\u0131 sessizce NULL yaz\u0131yordu \u2014 FR-016).`);
|
|
353
480
|
}
|
|
354
481
|
}
|
|
355
482
|
}
|
|
483
|
+
__name(assertUsableWriteValues, "assertUsableWriteValues");
|
|
484
|
+
|
|
485
|
+
// src/db/tx-plan.ts
|
|
486
|
+
var TxRefError = class extends Error {
|
|
487
|
+
static {
|
|
488
|
+
__name(this, "TxRefError");
|
|
489
|
+
}
|
|
490
|
+
constructor(message) {
|
|
491
|
+
super(message);
|
|
492
|
+
this.name = "TxRefError";
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
var TxPlanError = class extends Error {
|
|
496
|
+
static {
|
|
497
|
+
__name(this, "TxPlanError");
|
|
498
|
+
}
|
|
499
|
+
constructor(message) {
|
|
500
|
+
super(message);
|
|
501
|
+
this.name = "TxPlanError";
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
var EXPR = /* @__PURE__ */ Symbol.for("palbase.tx.expr");
|
|
505
|
+
var REF = /* @__PURE__ */ Symbol.for("palbase.tx.ref");
|
|
506
|
+
var ROW = /* @__PURE__ */ Symbol.for("palbase.tx.row");
|
|
507
|
+
var ROWS = /* @__PURE__ */ Symbol.for("palbase.tx.rows");
|
|
508
|
+
var TRAPPED_PROPS = [
|
|
509
|
+
"then",
|
|
510
|
+
"valueOf",
|
|
511
|
+
"toString",
|
|
512
|
+
"toJSON",
|
|
513
|
+
Symbol.toPrimitive
|
|
514
|
+
];
|
|
515
|
+
function trap(prop, what, hint) {
|
|
516
|
+
const name = typeof prop === "symbol" ? prop.description ?? String(prop) : prop;
|
|
517
|
+
throw new TxRefError(`${what} was used as a value (via \`${name}\`). Nothing in a transaction callback has run yet, so there is no value to read. ${hint}`);
|
|
518
|
+
}
|
|
519
|
+
__name(trap, "trap");
|
|
520
|
+
function columnExprOf(v) {
|
|
521
|
+
return exprOf(v);
|
|
522
|
+
}
|
|
523
|
+
__name(columnExprOf, "columnExprOf");
|
|
524
|
+
function makeRef(op, field) {
|
|
525
|
+
const target = {
|
|
526
|
+
[REF]: {
|
|
527
|
+
op,
|
|
528
|
+
field
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
return new Proxy(target, {
|
|
532
|
+
get(t, prop) {
|
|
533
|
+
if (prop === REF) return t[REF];
|
|
534
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
535
|
+
trap(prop, `\`${field}\` of a row this transaction has not written yet`, "Pass it to another operation in the same plan, or return it from the callback and read it after `transaction()` resolves.");
|
|
536
|
+
}
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
__name(makeRef, "makeRef");
|
|
542
|
+
function makeRowHandle(op) {
|
|
543
|
+
const target = {
|
|
544
|
+
[ROW]: op
|
|
545
|
+
};
|
|
546
|
+
return new Proxy(target, {
|
|
547
|
+
get(t, prop) {
|
|
548
|
+
if (prop === ROW) return t[ROW];
|
|
549
|
+
if (TRAPPED_PROPS.includes(prop)) {
|
|
550
|
+
trap(prop, "A row this transaction has not written yet", "Read one of its columns to reference it, or return the row from the callback and read it after `transaction()` resolves.");
|
|
551
|
+
}
|
|
552
|
+
if (typeof prop === "symbol") return void 0;
|
|
553
|
+
return makeRef(op, prop);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
__name(makeRowHandle, "makeRowHandle");
|
|
558
|
+
function exprOf(v) {
|
|
559
|
+
if (typeof v !== "object" || v === null) return null;
|
|
560
|
+
const e = v[EXPR];
|
|
561
|
+
return typeof e === "object" && e !== null ? e : null;
|
|
562
|
+
}
|
|
563
|
+
__name(exprOf, "exprOf");
|
|
564
|
+
var SKIPPED_OP = -1;
|
|
565
|
+
var TxRowsImpl = class TxRowsImpl2 {
|
|
566
|
+
static {
|
|
567
|
+
__name(this, "TxRowsImpl");
|
|
568
|
+
}
|
|
569
|
+
builder;
|
|
570
|
+
opIndex;
|
|
571
|
+
what;
|
|
572
|
+
// Present so `isRowsHandle` recognises the object; never read for its value.
|
|
573
|
+
[ROWS] = true;
|
|
574
|
+
guarded = false;
|
|
575
|
+
constructor(builder, opIndex, what) {
|
|
576
|
+
this.builder = builder;
|
|
577
|
+
this.opIndex = opIndex;
|
|
578
|
+
this.what = what;
|
|
579
|
+
}
|
|
580
|
+
// The type-level `await` guard made real: TS rejects `await rows` at compile
|
|
581
|
+
// time, and reaching this means someone called `.then(...)` by hand.
|
|
582
|
+
then() {
|
|
583
|
+
throw new TxRefError(`${this.what} cannot be awaited: a transaction callback builds a plan, it does not run statements. Remove the \`await\`.`);
|
|
584
|
+
}
|
|
585
|
+
expectOne(error) {
|
|
586
|
+
this.declareGuard("one", 1, error);
|
|
587
|
+
if (this.opIndex === SKIPPED_OP) throw error;
|
|
588
|
+
return makeRowHandle(this.opIndex);
|
|
589
|
+
}
|
|
590
|
+
expectNone(error) {
|
|
591
|
+
this.declareGuard("none", 0, error);
|
|
592
|
+
}
|
|
593
|
+
expectAtLeast(n, error) {
|
|
594
|
+
assertGuardCount(n, "expectAtLeast");
|
|
595
|
+
this.declareGuard("atLeast", n, error);
|
|
596
|
+
if (this.opIndex === SKIPPED_OP && n > 0) throw error;
|
|
597
|
+
}
|
|
598
|
+
expectAtMost(n, error) {
|
|
599
|
+
assertGuardCount(n, "expectAtMost");
|
|
600
|
+
this.declareGuard("atMost", n, error);
|
|
601
|
+
}
|
|
602
|
+
declareGuard(kind, n, error) {
|
|
603
|
+
if (!(error instanceof Error)) {
|
|
604
|
+
throw new TxPlanError(`${this.what}: an expectation needs the Error to throw when it does not hold (e.g. \`.expect\u2026(new Conflict("already accepted"))\`).`);
|
|
605
|
+
}
|
|
606
|
+
if (this.guarded) {
|
|
607
|
+
throw new TxPlanError(`${this.what} already has an expectation. One operation carries one expectation; declare the second one on its own operation.`);
|
|
608
|
+
}
|
|
609
|
+
this.guarded = true;
|
|
610
|
+
if (this.opIndex === SKIPPED_OP) return;
|
|
611
|
+
this.builder.attachGuard(this.opIndex, kind, n, error);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
function assertGuardCount(n, fn) {
|
|
615
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
616
|
+
throw new TxPlanError(`${fn}(n) needs a non-negative integer, got ${String(n)}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
__name(assertGuardCount, "assertGuardCount");
|
|
356
620
|
|
|
357
621
|
// src/__tests__/helpers/mock-db.ts
|
|
622
|
+
function refuseFragment(caller, table, where) {
|
|
623
|
+
if (isSqlFragment(where)) {
|
|
624
|
+
throw new Error(`${caller}(${table}): fakeDatabase bir sqlFragment'i de\u011Ferlendiremez \u2014 sahte depo SQL \xE7al\u0131\u015Ft\u0131rmaz. Filtreyi tipli filtre diliyle kurun (gt/gte/lt/lte/neq/in/contains/isNull, OR/AND/NOT), ya da bu testi ger\xE7ek bir Postgres'e kar\u015F\u0131 yaz\u0131n.`);
|
|
625
|
+
}
|
|
626
|
+
if (where === null || typeof where !== "object") return;
|
|
627
|
+
for (const [k, v] of Object.entries(where)) {
|
|
628
|
+
if (k === "OR" || k === "AND") {
|
|
629
|
+
for (const branch of Array.isArray(v) ? v : []) refuseFragment(caller, table, branch);
|
|
630
|
+
} else if (k === "NOT") {
|
|
631
|
+
refuseFragment(caller, table, v);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
__name(refuseFragment, "refuseFragment");
|
|
636
|
+
function addDecimal(cell, by, sign) {
|
|
637
|
+
if (cell === null || cell === void 0) return null;
|
|
638
|
+
if (typeof cell === "number" && typeof by === "number") return cell + sign * by;
|
|
639
|
+
const a = String(cell);
|
|
640
|
+
const b = String(by);
|
|
641
|
+
const parse = /* @__PURE__ */ __name((x) => {
|
|
642
|
+
const m = /^([+-]?)(\d*)(?:\.(\d*))?$/.exec(x.trim());
|
|
643
|
+
if (m === null || m[2] === "" && (m[3] ?? "") === "") return null;
|
|
644
|
+
const frac = m[3] ?? "";
|
|
645
|
+
const unit = BigInt(`${m[1] === "-" ? "-" : ""}${m[2] === "" ? "0" : m[2]}${frac}`);
|
|
646
|
+
return {
|
|
647
|
+
unit,
|
|
648
|
+
scale: frac.length
|
|
649
|
+
};
|
|
650
|
+
}, "parse");
|
|
651
|
+
const pa = parse(a);
|
|
652
|
+
const pb = parse(b);
|
|
653
|
+
if (pa === null || pb === null) {
|
|
654
|
+
throw new Error(`fakeDatabase: increment()/decrement() say\u0131sal olmayan bir de\u011Fere uyguland\u0131 ("${a}") \u2014 Postgres bunu "operator does not exist" ile reddeder.`);
|
|
655
|
+
}
|
|
656
|
+
const scale = Math.max(pa.scale, pb.scale);
|
|
657
|
+
const lift = /* @__PURE__ */ __name((v) => v.unit * 10n ** BigInt(scale - v.scale), "lift");
|
|
658
|
+
const total = lift(pa) + BigInt(sign) * lift(pb);
|
|
659
|
+
if (scale === 0) return typeof cell === "number" ? Number(total) : total.toString();
|
|
660
|
+
const neg = total < 0n;
|
|
661
|
+
const digits = (neg ? -total : total).toString().padStart(scale + 1, "0");
|
|
662
|
+
const out = `${neg ? "-" : ""}${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
|
|
663
|
+
return typeof cell === "number" ? Number(out) : out;
|
|
664
|
+
}
|
|
665
|
+
__name(addDecimal, "addDecimal");
|
|
666
|
+
function rowMatchesFilter(caller, table, row, f) {
|
|
667
|
+
return Object.entries(f).every(([k, c]) => {
|
|
668
|
+
if (k === "OR") return c.some((b) => rowMatchesFilter(caller, table, row, b));
|
|
669
|
+
if (k === "AND") return c.every((b) => rowMatchesFilter(caller, table, row, b));
|
|
670
|
+
if (k === "NOT") return !rowMatchesFilter(caller, table, row, c);
|
|
671
|
+
if (k === "has") {
|
|
672
|
+
throw new Error(`${caller}(${table}): fakeDatabase bir \`has\` filtresini \xE7\xF6zemez \u2014 ili\u015Fki grafi\u011Fi bildirimden t\xFCretiliyor ve sahte deponun bildirimi yok. Bu testi ger\xE7ek bir Postgres'e kar\u015F\u0131 yaz\u0131n, ya da ili\u015Fkiyi filtrede A\xC7IK\xC7A kurun (\xF6nce ili\u015Fki tablosunu okuyup { id: { in: [...] } } ile s\xFCz\xFCn).`);
|
|
673
|
+
}
|
|
674
|
+
return matchesCell(caller, table, row, k, c);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
__name(rowMatchesFilter, "rowMatchesFilter");
|
|
678
|
+
function cmp(a, b, op) {
|
|
679
|
+
if (a === null || a === void 0 || b === null || b === void 0) return false;
|
|
680
|
+
const l = a instanceof Date ? a.getTime() : a;
|
|
681
|
+
const r = b instanceof Date ? b.getTime() : b;
|
|
682
|
+
switch (op) {
|
|
683
|
+
case "eq":
|
|
684
|
+
return l === r;
|
|
685
|
+
case "neq":
|
|
686
|
+
return l !== r;
|
|
687
|
+
case "gt":
|
|
688
|
+
return l > r;
|
|
689
|
+
case "gte":
|
|
690
|
+
return l >= r;
|
|
691
|
+
case "lt":
|
|
692
|
+
return l < r;
|
|
693
|
+
case "lte":
|
|
694
|
+
return l <= r;
|
|
695
|
+
default:
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
__name(cmp, "cmp");
|
|
700
|
+
function matchesCell(caller, table, row, key, cond) {
|
|
701
|
+
if (isColRef(cond)) return cmp(row[key], row[cond.$col], "eq");
|
|
702
|
+
if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
|
|
703
|
+
return Object.entries(cond).every(([op, v]) => {
|
|
704
|
+
const cell = row[key];
|
|
705
|
+
if (isColRef(v)) {
|
|
706
|
+
if (![
|
|
707
|
+
"neq",
|
|
708
|
+
"gt",
|
|
709
|
+
"gte",
|
|
710
|
+
"lt",
|
|
711
|
+
"lte"
|
|
712
|
+
].includes(op)) {
|
|
713
|
+
throw new Error(`${caller}(${table}): where.${key}.${op} col() ile kullan\u0131lamaz`);
|
|
714
|
+
}
|
|
715
|
+
return cmp(cell, row[v.$col], op);
|
|
716
|
+
}
|
|
717
|
+
switch (op) {
|
|
718
|
+
case "in":
|
|
719
|
+
if (!Array.isArray(v)) throw new Error(`${caller}(${table}): where.${key}.in bir dizi olmal\u0131`);
|
|
720
|
+
if (v.some(isColRef)) {
|
|
721
|
+
throw new Error(`${caller}(${table}): where.${key}.in col() ile kullan\u0131lamaz \u2014 kolon kar\u015F\u0131la\u015Ft\u0131rmas\u0131 i\xE7in gt/gte/lt/lte/neq kullan\u0131n`);
|
|
722
|
+
}
|
|
723
|
+
return v.includes(cell);
|
|
724
|
+
case "neq":
|
|
725
|
+
case "gt":
|
|
726
|
+
case "gte":
|
|
727
|
+
case "lt":
|
|
728
|
+
case "lte":
|
|
729
|
+
return cmp(cell, v, op);
|
|
730
|
+
// K1 metin operatörleri (FR-005) ve null testi (FR-006). Bunlar BURADA
|
|
731
|
+
// da olmak zorunda: guard onları KABUL ettiği anda fake sessizce yanlış
|
|
732
|
+
// cevap verir ve yazarın testi, üretimde farklı davranan koda karşı
|
|
733
|
+
// yeşil verirdi (input-guards.ts'in uyardığı tam sınıf).
|
|
734
|
+
case "isNull":
|
|
735
|
+
if (typeof v !== "boolean") throw new Error(`${caller}(${table}): where.${key}.isNull bir boolean olmal\u0131`);
|
|
736
|
+
return v ? cell == null : cell != null;
|
|
737
|
+
case "contains":
|
|
738
|
+
case "icontains":
|
|
739
|
+
case "startsWith":
|
|
740
|
+
case "endsWith": {
|
|
741
|
+
if (typeof v !== "string") throw new Error(`${caller}(${table}): where.${key}.${op} bir string olmal\u0131`);
|
|
742
|
+
if (typeof cell !== "string") return false;
|
|
743
|
+
if (op === "contains") return cell.includes(v);
|
|
744
|
+
if (op === "icontains") return cell.toLowerCase().includes(v.toLowerCase());
|
|
745
|
+
if (op === "startsWith") return cell.startsWith(v);
|
|
746
|
+
return cell.endsWith(v);
|
|
747
|
+
}
|
|
748
|
+
default:
|
|
749
|
+
throw new Error(`${caller}(${table}): where.${key} bilinmeyen operat\xF6r "${op}"`);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return row[key] === cond;
|
|
754
|
+
}
|
|
755
|
+
__name(matchesCell, "matchesCell");
|
|
358
756
|
function createMockDB() {
|
|
359
757
|
const store = /* @__PURE__ */ new Map();
|
|
360
758
|
const tracked = {
|
|
@@ -370,11 +768,15 @@ function createMockDB() {
|
|
|
370
768
|
}
|
|
371
769
|
return rows;
|
|
372
770
|
}
|
|
771
|
+
__name(rowsOf, "rowsOf");
|
|
373
772
|
function track(map, table, row) {
|
|
374
773
|
const list = map.get(table);
|
|
375
774
|
if (list) list.push(row);
|
|
376
|
-
else map.set(table, [
|
|
775
|
+
else map.set(table, [
|
|
776
|
+
row
|
|
777
|
+
]);
|
|
377
778
|
}
|
|
779
|
+
__name(track, "track");
|
|
378
780
|
const ops = {
|
|
379
781
|
// The bulk ops and count run against the SAME in-memory store the direct
|
|
380
782
|
// ops write to, so a test that writes three rows and counts them gets 3 —
|
|
@@ -383,26 +785,38 @@ function createMockDB() {
|
|
|
383
785
|
async updateMany(table, where, set) {
|
|
384
786
|
if (Object.keys(where).length === 0) throw new Error(`updateMany(${table}): bo\u015F filtre`);
|
|
385
787
|
assertUsableFilter("updateMany", table, where);
|
|
788
|
+
refuseFragment("updateMany", table, where);
|
|
386
789
|
assertUsableWriteValues("updateMany", table, Object.keys(set), set);
|
|
387
|
-
const hit = (store.get(table) ?? []).filter(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
790
|
+
const hit = (store.get(table) ?? []).filter((r) => rowMatchesFilter("updateMany", table, r, where));
|
|
791
|
+
for (const row of hit) {
|
|
792
|
+
for (const [k, v] of Object.entries(set)) {
|
|
793
|
+
const expr = columnExprOf(v);
|
|
794
|
+
if (expr !== null && expr.fn === "now") {
|
|
795
|
+
row[k] = /* @__PURE__ */ new Date();
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if (expr !== null) {
|
|
799
|
+
row[k] = addDecimal(row[k], expr.by, expr.fn === "inc" ? 1 : -1);
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
row[k] = v;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
391
805
|
return hit;
|
|
392
806
|
},
|
|
393
807
|
async deleteMany(table, where) {
|
|
394
808
|
if (Object.keys(where).length === 0) throw new Error(`deleteMany(${table}): bo\u015F filtre`);
|
|
395
809
|
assertUsableFilter("deleteMany", table, where);
|
|
810
|
+
refuseFragment("deleteMany", table, where);
|
|
396
811
|
const list = store.get(table) ?? [];
|
|
397
|
-
const keep = list.filter((r) => !
|
|
812
|
+
const keep = list.filter((r) => !rowMatchesFilter("deleteMany", table, r, where));
|
|
398
813
|
store.set(table, keep);
|
|
399
814
|
return list.length - keep.length;
|
|
400
815
|
},
|
|
401
816
|
async count(table, where = {}) {
|
|
402
817
|
assertUsableFilter("count", table, where);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
).length;
|
|
818
|
+
refuseFragment("count", table, where);
|
|
819
|
+
return (store.get(table) ?? []).filter((r) => rowMatchesFilter("count", table, r, where)).length;
|
|
406
820
|
},
|
|
407
821
|
async search(_table, _params) {
|
|
408
822
|
return [];
|
|
@@ -417,25 +831,90 @@ function createMockDB() {
|
|
|
417
831
|
return [];
|
|
418
832
|
},
|
|
419
833
|
async supersede(_table, _id, row) {
|
|
420
|
-
return {
|
|
834
|
+
return {
|
|
835
|
+
id: crypto.randomUUID(),
|
|
836
|
+
...row
|
|
837
|
+
};
|
|
421
838
|
},
|
|
422
839
|
async query(_sql, _params) {
|
|
423
840
|
return [];
|
|
424
841
|
},
|
|
425
842
|
async insert(table, data) {
|
|
426
843
|
assertUsableWriteValues("insert", table, Object.keys(data), data);
|
|
427
|
-
|
|
844
|
+
assertNoExpressionHandles("insert", table, Object.keys(data), data);
|
|
845
|
+
const record = {
|
|
846
|
+
id: crypto.randomUUID(),
|
|
847
|
+
...data
|
|
848
|
+
};
|
|
428
849
|
rowsOf(table).push(record);
|
|
429
850
|
track(tracked.inserted, table, record);
|
|
430
851
|
return record;
|
|
431
852
|
},
|
|
853
|
+
/**
|
|
854
|
+
* `claim` — motorla PARİTE (FR-033).
|
|
855
|
+
*
|
|
856
|
+
* Fake bunu sunmasaydı `claim` ile yazılmış bir servis, iskelenin yazarlara
|
|
857
|
+
* önerdiği test yolunda `undefined is not a function` verirdi; sunup da
|
|
858
|
+
* FARKLI davransaydı (ör. hep `inserted: true`) idempotency testi üretimde
|
|
859
|
+
* çalışmayan koda karşı yeşil olurdu.
|
|
860
|
+
*
|
|
861
|
+
* Anahtar ZATEN VARSA var olan satır dönüyor ve hiçbir şey yazılmıyor —
|
|
862
|
+
* motorun 23505 dalının aynısı.
|
|
863
|
+
*/
|
|
864
|
+
/**
|
|
865
|
+
* `lockRows` — sahte depoda kilit YOKTUR, ama çağrı da patlamamalı.
|
|
866
|
+
*
|
|
867
|
+
* Motorla parite burada "aynı SQL" değil, "aynı SÖZLEŞME": boş liste
|
|
868
|
+
* no-op, tekrar edenler tekilleşir, ve PK'sı olmayan tablo adıyla
|
|
869
|
+
* reddedilir. Kilidin kendisi tek işlemli bir sahte depoda anlamsız —
|
|
870
|
+
* ama sözleşmeyi bozan bir çağrı burada da hata almalı.
|
|
871
|
+
*/
|
|
872
|
+
async lockRows(table, ids) {
|
|
873
|
+
if (ids.length === 0) return;
|
|
874
|
+
const rows = store.get(table) ?? [];
|
|
875
|
+
const unique = [
|
|
876
|
+
...new Set(ids)
|
|
877
|
+
].sort();
|
|
878
|
+
const missing = unique.filter((id) => !rows.some((r) => r["id"] === id));
|
|
879
|
+
if (missing.length > 0 && rows.length > 0) {
|
|
880
|
+
throw new Error(`lockRows(${table}): \u015Fu id'ler yok: ${missing.join(", ")} \u2014 kilitlenecek sat\u0131r bulunamad\u0131.`);
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
/** Sahte depoda kilit yok; sözleşme (çağrı patlamaz) korunuyor. */
|
|
884
|
+
async advisoryXactLock(_key) {
|
|
885
|
+
return void 0;
|
|
886
|
+
},
|
|
887
|
+
async claim(table, unique, extra = {}) {
|
|
888
|
+
const keyCols = Object.keys(unique);
|
|
889
|
+
if (keyCols.length === 0) {
|
|
890
|
+
throw new Error(`claim(${table}): benzersiz alan verilmedi. claim, bir anahtar\u0131 sahiplenmektir; anahtar yoksa sahiplenecek bir \u015Fey de yok \u2014 insert(${table}, \u2026) kullan\u0131n.`);
|
|
891
|
+
}
|
|
892
|
+
assertUsableWriteValues("claim", table, keyCols, unique);
|
|
893
|
+
const existing = (store.get(table) ?? []).find((r) => keyCols.every((c) => r[c] === unique[c]));
|
|
894
|
+
if (existing) return {
|
|
895
|
+
inserted: false,
|
|
896
|
+
row: existing
|
|
897
|
+
};
|
|
898
|
+
const record = {
|
|
899
|
+
id: crypto.randomUUID(),
|
|
900
|
+
...unique,
|
|
901
|
+
...extra
|
|
902
|
+
};
|
|
903
|
+
rowsOf(table).push(record);
|
|
904
|
+
track(tracked.inserted, table, record);
|
|
905
|
+
return {
|
|
906
|
+
inserted: true,
|
|
907
|
+
row: record
|
|
908
|
+
};
|
|
909
|
+
},
|
|
432
910
|
// Same semantics the engine's SQL has: match on the conflict columns, update
|
|
433
911
|
// everything else, and return the resulting row either way.
|
|
434
|
-
async
|
|
912
|
+
async put(table, data, opts) {
|
|
435
913
|
if (opts.onConflict.length === 0) {
|
|
436
|
-
throw new Error(`
|
|
914
|
+
throw new Error(`put into ${table}: onConflict en az bir kolon ad\u0131 ister`);
|
|
437
915
|
}
|
|
438
916
|
assertUsableWriteValues("upsert", table, Object.keys(data), data);
|
|
917
|
+
assertNoExpressionHandles("upsert", table, Object.keys(data), data);
|
|
439
918
|
const rows = rowsOf(table);
|
|
440
919
|
const existing = rows.find((r) => opts.onConflict.every((c) => r[c] === data[c]));
|
|
441
920
|
if (existing) {
|
|
@@ -444,16 +923,26 @@ function createMockDB() {
|
|
|
444
923
|
}
|
|
445
924
|
return existing;
|
|
446
925
|
}
|
|
447
|
-
const record = {
|
|
926
|
+
const record = {
|
|
927
|
+
id: crypto.randomUUID(),
|
|
928
|
+
...data
|
|
929
|
+
};
|
|
448
930
|
rows.push(record);
|
|
449
931
|
track(tracked.inserted, table, record);
|
|
450
932
|
return record;
|
|
451
933
|
},
|
|
452
934
|
async update(table, id, data) {
|
|
453
935
|
assertUsableWriteValues("update", table, Object.keys(data), data);
|
|
936
|
+
assertNoExpressionHandles("update", table, Object.keys(data), data);
|
|
454
937
|
const rows = store.get(table) ?? [];
|
|
455
938
|
const idx = rows.findIndex((r) => r["id"] === id);
|
|
456
|
-
const updated = idx >= 0 ? {
|
|
939
|
+
const updated = idx >= 0 ? {
|
|
940
|
+
...rows[idx],
|
|
941
|
+
...data
|
|
942
|
+
} : {
|
|
943
|
+
id,
|
|
944
|
+
...data
|
|
945
|
+
};
|
|
457
946
|
if (idx >= 0) {
|
|
458
947
|
rows[idx] = updated;
|
|
459
948
|
}
|
|
@@ -466,7 +955,9 @@ function createMockDB() {
|
|
|
466
955
|
if (idx >= 0) rows.splice(idx, 1);
|
|
467
956
|
const list = tracked.deleted.get(table);
|
|
468
957
|
if (list) list.push(id);
|
|
469
|
-
else tracked.deleted.set(table, [
|
|
958
|
+
else tracked.deleted.set(table, [
|
|
959
|
+
id
|
|
960
|
+
]);
|
|
470
961
|
},
|
|
471
962
|
async findById(table, id) {
|
|
472
963
|
const rows = store.get(table) ?? [];
|
|
@@ -478,51 +969,60 @@ function createMockDB() {
|
|
|
478
969
|
// thing a stand-in must never do.
|
|
479
970
|
async findMany(table, query, opts) {
|
|
480
971
|
assertUsableFilter("findMany", table, query);
|
|
972
|
+
refuseFragment("findMany", table, query);
|
|
481
973
|
const rows = store.get(table) ?? [];
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
974
|
+
let out = query ? rows.filter((row) => rowMatchesFilter("findMany", table, row, query)) : [
|
|
975
|
+
...rows
|
|
976
|
+
];
|
|
977
|
+
const orderSpecs = opts?.orderBy === void 0 ? [] : Array.isArray(opts.orderBy) ? opts.orderBy : [
|
|
978
|
+
opts.orderBy
|
|
979
|
+
];
|
|
980
|
+
if (orderSpecs.length > 0) {
|
|
981
|
+
out = [
|
|
982
|
+
...out
|
|
983
|
+
].sort((a, b) => {
|
|
984
|
+
for (const o of orderSpecs) {
|
|
985
|
+
const dir = o.direction === "desc" ? -1 : 1;
|
|
986
|
+
const x = a[o.column];
|
|
987
|
+
const y = b[o.column];
|
|
988
|
+
const xNull = x === null || x === void 0;
|
|
989
|
+
const yNull = y === null || y === void 0;
|
|
990
|
+
if (xNull || yNull) {
|
|
991
|
+
if (xNull && yNull) continue;
|
|
992
|
+
const nullsFirst = o.nulls === void 0 ? dir === -1 : o.nulls === "first";
|
|
993
|
+
return (xNull ? 1 : -1) * (nullsFirst ? -1 : 1);
|
|
501
994
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
let out = query ? rows.filter((row) => Object.entries(query).every(([k, c]) => matches2(row, k, c))) : [...rows];
|
|
507
|
-
const order = opts?.orderBy;
|
|
508
|
-
if (order) {
|
|
509
|
-
const dir = order.direction === "desc" ? -1 : 1;
|
|
510
|
-
out = [...out].sort((a, b) => {
|
|
511
|
-
const x = a[order.column];
|
|
512
|
-
const y = b[order.column];
|
|
513
|
-
return x === y ? 0 : (x < y ? -1 : 1) * dir;
|
|
995
|
+
if (x === y) continue;
|
|
996
|
+
return (x < y ? -1 : 1) * dir;
|
|
997
|
+
}
|
|
998
|
+
return 0;
|
|
514
999
|
});
|
|
515
1000
|
}
|
|
516
|
-
|
|
1001
|
+
const page = opts?.limit === void 0 ? out : out.slice(0, opts.limit);
|
|
1002
|
+
const cols = opts?.select;
|
|
1003
|
+
if (cols === void 0 || cols.length === 0) return page;
|
|
1004
|
+
return page.map((row) => Object.fromEntries(cols.map((c) => [
|
|
1005
|
+
c,
|
|
1006
|
+
row[c]
|
|
1007
|
+
])));
|
|
517
1008
|
}
|
|
518
1009
|
};
|
|
519
1010
|
async function txPlan(plan) {
|
|
520
1011
|
const snapshot = /* @__PURE__ */ new Map();
|
|
521
|
-
for (const [table, rows] of store) snapshot.set(table, [
|
|
1012
|
+
for (const [table, rows] of store) snapshot.set(table, [
|
|
1013
|
+
...rows
|
|
1014
|
+
]);
|
|
522
1015
|
const trackedSnapshot = {
|
|
523
1016
|
inserted: cloneTracked(tracked.inserted),
|
|
524
1017
|
updated: cloneTracked(tracked.updated),
|
|
525
|
-
deleted: new Map([
|
|
1018
|
+
deleted: new Map([
|
|
1019
|
+
...tracked.deleted
|
|
1020
|
+
].map(([k, v]) => [
|
|
1021
|
+
k,
|
|
1022
|
+
[
|
|
1023
|
+
...v
|
|
1024
|
+
]
|
|
1025
|
+
]))
|
|
526
1026
|
};
|
|
527
1027
|
const results = [];
|
|
528
1028
|
try {
|
|
@@ -540,8 +1040,11 @@ function createMockDB() {
|
|
|
540
1040
|
tracked.deleted = trackedSnapshot.deleted;
|
|
541
1041
|
throw err;
|
|
542
1042
|
}
|
|
543
|
-
return {
|
|
1043
|
+
return {
|
|
1044
|
+
results
|
|
1045
|
+
};
|
|
544
1046
|
}
|
|
1047
|
+
__name(txPlan, "txPlan");
|
|
545
1048
|
function applyOp(op, results) {
|
|
546
1049
|
switch (op.op) {
|
|
547
1050
|
case "upsert": {
|
|
@@ -553,27 +1056,54 @@ function createMockDB() {
|
|
|
553
1056
|
for (const [key, value] of Object.entries(values)) {
|
|
554
1057
|
if (!conflict.includes(key)) hit[key] = value;
|
|
555
1058
|
}
|
|
556
|
-
return {
|
|
1059
|
+
return {
|
|
1060
|
+
rows: [
|
|
1061
|
+
hit
|
|
1062
|
+
],
|
|
1063
|
+
rows_affected: 1
|
|
1064
|
+
};
|
|
557
1065
|
}
|
|
558
|
-
const created = {
|
|
1066
|
+
const created = {
|
|
1067
|
+
id: crypto.randomUUID(),
|
|
1068
|
+
...values
|
|
1069
|
+
};
|
|
559
1070
|
rows.push(created);
|
|
560
1071
|
track(tracked.inserted, op.table, created);
|
|
561
|
-
return {
|
|
1072
|
+
return {
|
|
1073
|
+
rows: [
|
|
1074
|
+
created
|
|
1075
|
+
],
|
|
1076
|
+
rows_affected: 1
|
|
1077
|
+
};
|
|
562
1078
|
}
|
|
563
1079
|
case "insert": {
|
|
564
|
-
const record = {
|
|
1080
|
+
const record = {
|
|
1081
|
+
id: crypto.randomUUID(),
|
|
1082
|
+
...resolveMap(op.values ?? {}, results, null)
|
|
1083
|
+
};
|
|
565
1084
|
rowsOf(op.table).push(record);
|
|
566
1085
|
track(tracked.inserted, op.table, record);
|
|
567
|
-
return {
|
|
1086
|
+
return {
|
|
1087
|
+
rows: [
|
|
1088
|
+
record
|
|
1089
|
+
],
|
|
1090
|
+
rows_affected: 1
|
|
1091
|
+
};
|
|
568
1092
|
}
|
|
569
1093
|
case "insertMany": {
|
|
570
1094
|
const written = (op.rows ?? []).map((row) => {
|
|
571
|
-
const record = {
|
|
1095
|
+
const record = {
|
|
1096
|
+
id: crypto.randomUUID(),
|
|
1097
|
+
...resolveMap(row, results, null)
|
|
1098
|
+
};
|
|
572
1099
|
rowsOf(op.table).push(record);
|
|
573
1100
|
track(tracked.inserted, op.table, record);
|
|
574
1101
|
return record;
|
|
575
1102
|
});
|
|
576
|
-
return {
|
|
1103
|
+
return {
|
|
1104
|
+
rows: written,
|
|
1105
|
+
rows_affected: written.length
|
|
1106
|
+
};
|
|
577
1107
|
}
|
|
578
1108
|
case "update": {
|
|
579
1109
|
const rows = rowsOf(op.table);
|
|
@@ -582,12 +1112,18 @@ function createMockDB() {
|
|
|
582
1112
|
for (let i = 0; i < rows.length; i++) {
|
|
583
1113
|
const row = rows[i];
|
|
584
1114
|
if (!row || !matches(row, where)) continue;
|
|
585
|
-
const next = {
|
|
1115
|
+
const next = {
|
|
1116
|
+
...row,
|
|
1117
|
+
...resolveMap(op.set ?? {}, results, row)
|
|
1118
|
+
};
|
|
586
1119
|
rows[i] = next;
|
|
587
1120
|
track(tracked.updated, op.table, next);
|
|
588
1121
|
written.push(next);
|
|
589
1122
|
}
|
|
590
|
-
return {
|
|
1123
|
+
return {
|
|
1124
|
+
rows: written,
|
|
1125
|
+
rows_affected: written.length
|
|
1126
|
+
};
|
|
591
1127
|
}
|
|
592
1128
|
case "delete": {
|
|
593
1129
|
const rows = rowsOf(op.table);
|
|
@@ -599,23 +1135,32 @@ function createMockDB() {
|
|
|
599
1135
|
const list = tracked.deleted.get(op.table);
|
|
600
1136
|
const key = typeof id === "string" ? id : String(id);
|
|
601
1137
|
if (list) list.push(key);
|
|
602
|
-
else tracked.deleted.set(op.table, [
|
|
1138
|
+
else tracked.deleted.set(op.table, [
|
|
1139
|
+
key
|
|
1140
|
+
]);
|
|
603
1141
|
}
|
|
604
|
-
return {
|
|
1142
|
+
return {
|
|
1143
|
+
rows: removed,
|
|
1144
|
+
rows_affected: removed.length
|
|
1145
|
+
};
|
|
605
1146
|
}
|
|
606
1147
|
case "select": {
|
|
607
1148
|
const where = resolveMap(op.where ?? {}, results, null);
|
|
608
1149
|
let found = rowsOf(op.table).filter((row) => matches(row, where));
|
|
609
1150
|
if (op.limit !== void 0) found = found.slice(0, op.limit);
|
|
610
|
-
return {
|
|
1151
|
+
return {
|
|
1152
|
+
rows: found,
|
|
1153
|
+
rows_affected: found.length
|
|
1154
|
+
};
|
|
611
1155
|
}
|
|
612
1156
|
}
|
|
613
1157
|
}
|
|
1158
|
+
__name(applyOp, "applyOp");
|
|
614
1159
|
const client = {
|
|
615
1160
|
...ops,
|
|
616
1161
|
// No real savepoint in memory: the fake runs the callback against the SAME
|
|
617
1162
|
// store. An assertion about rollback here would be asserting the fake.
|
|
618
|
-
attempt: (fn) => fn(ops),
|
|
1163
|
+
attempt: /* @__PURE__ */ __name((fn) => fn(ops), "attempt"),
|
|
619
1164
|
txPlan,
|
|
620
1165
|
// In tests there is no real DB role; `asService()` returns the same
|
|
621
1166
|
// in-memory client so RLS-bypass code paths still hit the same store and
|
|
@@ -634,14 +1179,25 @@ function createMockDB() {
|
|
|
634
1179
|
return tracked.deleted.get(table) ?? [];
|
|
635
1180
|
},
|
|
636
1181
|
seed(table, data) {
|
|
637
|
-
store.set(table, [
|
|
1182
|
+
store.set(table, [
|
|
1183
|
+
...data
|
|
1184
|
+
]);
|
|
638
1185
|
}
|
|
639
1186
|
};
|
|
640
1187
|
return client;
|
|
641
1188
|
}
|
|
1189
|
+
__name(createMockDB, "createMockDB");
|
|
642
1190
|
function cloneTracked(map) {
|
|
643
|
-
return new Map([
|
|
1191
|
+
return new Map([
|
|
1192
|
+
...map
|
|
1193
|
+
].map(([k, v]) => [
|
|
1194
|
+
k,
|
|
1195
|
+
[
|
|
1196
|
+
...v
|
|
1197
|
+
]
|
|
1198
|
+
]));
|
|
644
1199
|
}
|
|
1200
|
+
__name(cloneTracked, "cloneTracked");
|
|
645
1201
|
function resolveValue(value, results, current, column) {
|
|
646
1202
|
if (typeof value !== "object" || value === null) return value;
|
|
647
1203
|
const tagged = value;
|
|
@@ -663,6 +1219,7 @@ function resolveValue(value, results, current, column) {
|
|
|
663
1219
|
}
|
|
664
1220
|
return value;
|
|
665
1221
|
}
|
|
1222
|
+
__name(resolveValue, "resolveValue");
|
|
666
1223
|
function resolveMap(map, results, current) {
|
|
667
1224
|
const out = {};
|
|
668
1225
|
for (const [key, value] of Object.entries(map)) {
|
|
@@ -670,11 +1227,11 @@ function resolveMap(map, results, current) {
|
|
|
670
1227
|
}
|
|
671
1228
|
return out;
|
|
672
1229
|
}
|
|
1230
|
+
__name(resolveMap, "resolveMap");
|
|
673
1231
|
function matches(row, where) {
|
|
674
|
-
return Object.entries(where).every(
|
|
675
|
-
([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value
|
|
676
|
-
);
|
|
1232
|
+
return Object.entries(where).every(([key, value]) => value === null ? row[key] === null || row[key] === void 0 : row[key] === value);
|
|
677
1233
|
}
|
|
1234
|
+
__name(matches, "matches");
|
|
678
1235
|
function guardFailure(guard, count) {
|
|
679
1236
|
if (!guard) return null;
|
|
680
1237
|
const ok = guard.kind === "one" ? count === 1 : guard.kind === "none" ? count === 0 : guard.kind === "atLeast" ? count >= guard.n : count <= guard.n;
|
|
@@ -684,6 +1241,7 @@ function guardFailure(guard, count) {
|
|
|
684
1241
|
message: `expected ${guard.kind} ${guard.n} row(s), got ${count}`
|
|
685
1242
|
});
|
|
686
1243
|
}
|
|
1244
|
+
__name(guardFailure, "guardFailure");
|
|
687
1245
|
function txRejection(status, code, extra) {
|
|
688
1246
|
const err = new Error(extra.message);
|
|
689
1247
|
err.status = status;
|
|
@@ -691,31 +1249,37 @@ function txRejection(status, code, extra) {
|
|
|
691
1249
|
if (extra.slot !== void 0) err.slot = extra.slot;
|
|
692
1250
|
return err;
|
|
693
1251
|
}
|
|
1252
|
+
__name(txRejection, "txRejection");
|
|
694
1253
|
|
|
695
1254
|
// src/test/fake-db.ts
|
|
696
1255
|
function fakeDatabase() {
|
|
697
1256
|
const mock = createMockDB();
|
|
698
1257
|
const queries = [];
|
|
699
1258
|
const db = Object.assign(Object.create(Object.getPrototypeOf(mock)), mock, {
|
|
700
|
-
query: async (sql, params = []) => {
|
|
701
|
-
queries.push({
|
|
1259
|
+
query: /* @__PURE__ */ __name(async (sql, params = []) => {
|
|
1260
|
+
queries.push({
|
|
1261
|
+
sql,
|
|
1262
|
+
params
|
|
1263
|
+
});
|
|
702
1264
|
return mock.query(sql, params);
|
|
703
|
-
}
|
|
1265
|
+
}, "query")
|
|
704
1266
|
});
|
|
705
1267
|
return {
|
|
706
1268
|
db,
|
|
707
1269
|
queries,
|
|
708
|
-
seed: (table, rows) => mock.seed(table, rows),
|
|
709
|
-
inserted: (table) => mock.inserted(table),
|
|
710
|
-
updated: (table) => mock.updated(table),
|
|
711
|
-
deleted: (table) => mock.deleted(table)
|
|
1270
|
+
seed: /* @__PURE__ */ __name((table, rows) => mock.seed(table, rows), "seed"),
|
|
1271
|
+
inserted: /* @__PURE__ */ __name((table) => mock.inserted(table), "inserted"),
|
|
1272
|
+
updated: /* @__PURE__ */ __name((table) => mock.updated(table), "updated"),
|
|
1273
|
+
deleted: /* @__PURE__ */ __name((table) => mock.deleted(table), "deleted")
|
|
712
1274
|
};
|
|
713
1275
|
}
|
|
1276
|
+
__name(fakeDatabase, "fakeDatabase");
|
|
714
1277
|
// Annotate the CommonJS export names for ESM import in node:
|
|
715
1278
|
0 && (module.exports = {
|
|
716
1279
|
TestApiError,
|
|
717
1280
|
api,
|
|
718
1281
|
createTestApi,
|
|
719
|
-
fakeDatabase
|
|
1282
|
+
fakeDatabase,
|
|
1283
|
+
isolated
|
|
720
1284
|
});
|
|
721
1285
|
//# sourceMappingURL=index.cjs.map
|