@palbase/backend 25.0.4 → 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.
Files changed (104) hide show
  1. package/dist/bin/palbase-backend.cjs +2622 -1012
  2. package/dist/bin/palbase-backend.cjs.map +1 -1
  3. package/dist/bin/palbase-backend.js +87 -51
  4. package/dist/bin/palbase-backend.js.map +1 -1
  5. package/dist/chunk-CGNN2PUH.js +213 -0
  6. package/dist/chunk-CGNN2PUH.js.map +1 -0
  7. package/dist/chunk-DRZFQRJI.js +370 -0
  8. package/dist/chunk-DRZFQRJI.js.map +1 -0
  9. package/dist/chunk-GYK6QYS4.js +708 -0
  10. package/dist/chunk-GYK6QYS4.js.map +1 -0
  11. package/dist/chunk-I3C4PFIW.js +930 -0
  12. package/dist/chunk-I3C4PFIW.js.map +1 -0
  13. package/dist/{chunk-AILPKEK5.js → chunk-OO7R25AI.js} +1433 -563
  14. package/dist/chunk-OO7R25AI.js.map +1 -0
  15. package/dist/chunk-TS4U7NBD.js +885 -0
  16. package/dist/chunk-TS4U7NBD.js.map +1 -0
  17. package/dist/{chunk-YOY5DFQS.js → chunk-TWX6JTGJ.js} +76 -34
  18. package/dist/{chunk-YOY5DFQS.js.map → chunk-TWX6JTGJ.js.map} +1 -1
  19. package/dist/{chunk-35PNTIRN.js → chunk-VVMJEVQP.js} +63 -162
  20. package/dist/chunk-VVMJEVQP.js.map +1 -0
  21. package/dist/{chunk-7D4SUZUM.js → chunk-VXPNPVAG.js} +3 -1
  22. package/dist/db/env.cjs.map +1 -1
  23. package/dist/db/env.d.cts +2 -2
  24. package/dist/db/env.d.ts +2 -2
  25. package/dist/db/index.cjs +754 -344
  26. package/dist/db/index.cjs.map +1 -1
  27. package/dist/db/index.d.cts +2 -2
  28. package/dist/db/index.d.ts +2 -2
  29. package/dist/db/index.js +7 -4
  30. package/dist/engine/index.cjs +2562 -981
  31. package/dist/engine/index.cjs.map +1 -1
  32. package/dist/engine/index.d.cts +6 -6
  33. package/dist/engine/index.d.ts +6 -6
  34. package/dist/engine/index.js +7 -6
  35. package/dist/index-BrvvxSpn.d.ts +4844 -0
  36. package/dist/index-Bve7BBTL.d.cts +4844 -0
  37. package/dist/{index-CUomTA3e.d.ts → index-NuzRCuxe.d.ts} +171 -296
  38. package/dist/{index-ClpDeSos.d.cts → index-VtToZmUm.d.cts} +171 -296
  39. package/dist/index.cjs +2694 -1169
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +165 -19
  42. package/dist/index.d.ts +165 -19
  43. package/dist/index.js +738 -477
  44. package/dist/index.js.map +1 -1
  45. package/dist/module-Dl1KFVtc.d.cts +54 -0
  46. package/dist/module-Dl1KFVtc.d.ts +54 -0
  47. package/dist/openapi/index.cjs +1330 -484
  48. package/dist/openapi/index.cjs.map +1 -1
  49. package/dist/openapi/index.d.cts +4 -2
  50. package/dist/openapi/index.d.ts +4 -2
  51. package/dist/openapi/index.js +1264 -474
  52. package/dist/openapi/index.js.map +1 -1
  53. package/dist/{registry-dZZ5JKYg.d.ts → registry-B0eyOF9x.d.ts} +1 -1
  54. package/dist/{registry-CC0WBQq6.d.cts → registry-Bk9_rbNd.d.cts} +1 -1
  55. package/dist/stack.cjs.map +1 -1
  56. package/dist/test/index.cjs +705 -141
  57. package/dist/test/index.cjs.map +1 -1
  58. package/dist/test/index.d.cts +30 -4
  59. package/dist/test/index.d.ts +30 -4
  60. package/dist/test/index.js +472 -124
  61. package/dist/test/index.js.map +1 -1
  62. package/docs/README.md +34 -18
  63. package/docs/auth.md +1 -1
  64. package/docs/background.md +2 -2
  65. package/docs/database.md +221 -50
  66. package/docs/endpoints.md +3 -4
  67. package/docs/events.md +3 -3
  68. package/docs/getting-started.md +1 -1
  69. package/docs/llms-full.txt +402 -117
  70. package/docs/migrations.md +2 -2
  71. package/docs/schema.md +19 -10
  72. package/docs/services.md +116 -26
  73. package/package.json +8 -4
  74. package/stager/generics.js +205 -0
  75. package/stager/stage.js +39 -3
  76. package/template/AGENTS.md +110 -70
  77. package/template/db/public.ts +1 -1
  78. package/template/{controllers → modules/health}/health.controller.ts +1 -1
  79. package/template/modules/health/health.module.ts +24 -0
  80. package/template/modules/notes/note.service.test.ts +49 -0
  81. package/template/modules/notes/note.service.ts +108 -0
  82. package/template/{controllers → modules/notes}/notes.controller.ts +17 -11
  83. package/template/modules/notes/notes.module.ts +37 -0
  84. package/template/package.json +5 -3
  85. package/template/scripts/test.sh +33 -0
  86. package/template/tsconfig.json +29 -30
  87. package/dist/chunk-35PNTIRN.js.map +0 -1
  88. package/dist/chunk-AILPKEK5.js.map +0 -1
  89. package/dist/chunk-CJSKYY76.js +0 -627
  90. package/dist/chunk-CJSKYY76.js.map +0 -1
  91. package/dist/chunk-CRQKCRGF.js +0 -276
  92. package/dist/chunk-CRQKCRGF.js.map +0 -1
  93. package/dist/chunk-G4R6BTLV.js +0 -662
  94. package/dist/chunk-G4R6BTLV.js.map +0 -1
  95. package/dist/chunk-XABBC7JP.js +0 -55
  96. package/dist/chunk-XABBC7JP.js.map +0 -1
  97. package/dist/endpoint-CTEHhb7A.d.ts +0 -2386
  98. package/dist/endpoint-DYHMo6cC.d.cts +0 -2386
  99. package/dist/index-CW21M9Z3.d.ts +0 -1222
  100. package/dist/index-CmBK76nx.d.cts +0 -1222
  101. package/template/services/note.service.test.ts +0 -45
  102. package/template/services/note.service.ts +0 -74
  103. /package/dist/{chunk-7D4SUZUM.js.map → chunk-VXPNPVAG.js.map} +0 -0
  104. /package/template/{models/notes → modules/notes/dto}/create.ts +0 -0
@@ -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 { id, prefix, difficulty };
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
- var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
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 ? { "x-palbase-candidate": 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({ method, path, status: res.status, ms: Date.now() - startedAt });
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
- `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(", ")}` : (
237
- // Not "you declared none": from here the two causes are
238
- // indistinguishable, and blaming the customer's config for a
239
- // platform failure sends them to look in the wrong file. The
240
- // deploy log names which one it was.
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 { id: identity.id ?? "", email: identity.email };
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
- "POST",
254
- "/auth/login",
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 ?? { id: "" };
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 KNOWN_OPS = /* @__PURE__ */ new Set(["gt", "gte", "lt", "lte", "neq", "eq", "in"]);
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, [row]);
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
- (r) => Object.entries(where).every(([k, v]) => r[k] === v)
389
- );
390
- for (const row of hit) Object.assign(row, set);
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) => !Object.entries(where).every(([k, v]) => r[k] === v));
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
- return (store.get(table) ?? []).filter(
404
- (r) => Object.entries(where).every(([k, v]) => r[k] === v)
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 { id: crypto.randomUUID(), ...row };
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
- const record = { id: crypto.randomUUID(), ...data };
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 upsert(table, data, opts) {
912
+ async put(table, data, opts) {
435
913
  if (opts.onConflict.length === 0) {
436
- throw new Error(`upsert into ${table}: onConflict en az bir kolon ad\u0131 ister`);
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 = { id: crypto.randomUUID(), ...data };
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 ? { ...rows[idx], ...data } : { id, ...data };
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, [id]);
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
- const matches2 = (row, key, cond) => {
483
- if (cond !== null && typeof cond === "object" && !Array.isArray(cond)) {
484
- return Object.entries(cond).every(([op, v]) => {
485
- const cell = row[key];
486
- switch (op) {
487
- case "in":
488
- return Array.isArray(v) && v.includes(cell);
489
- case "neq":
490
- return cell !== v;
491
- case "gt":
492
- return cell > v;
493
- case "gte":
494
- return cell >= v;
495
- case "lt":
496
- return cell < v;
497
- case "lte":
498
- return cell <= v;
499
- default:
500
- throw new Error(`findMany(${table}): where.${key} bilinmeyen operat\xF6r "${op}"`);
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
- return row[key] === cond;
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
- return opts?.limit === void 0 ? out : out.slice(0, opts.limit);
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, [...rows]);
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([...tracked.deleted].map(([k, v]) => [k, [...v]]))
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 { results };
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 { rows: [hit], rows_affected: 1 };
1059
+ return {
1060
+ rows: [
1061
+ hit
1062
+ ],
1063
+ rows_affected: 1
1064
+ };
557
1065
  }
558
- const created = { id: crypto.randomUUID(), ...values };
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 { rows: [created], rows_affected: 1 };
1072
+ return {
1073
+ rows: [
1074
+ created
1075
+ ],
1076
+ rows_affected: 1
1077
+ };
562
1078
  }
563
1079
  case "insert": {
564
- const record = { id: crypto.randomUUID(), ...resolveMap(op.values ?? {}, results, null) };
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 { rows: [record], rows_affected: 1 };
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 = { id: crypto.randomUUID(), ...resolveMap(row, results, null) };
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 { rows: written, rows_affected: written.length };
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 = { ...row, ...resolveMap(op.set ?? {}, results, row) };
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 { rows: written, rows_affected: written.length };
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, [key]);
1138
+ else tracked.deleted.set(op.table, [
1139
+ key
1140
+ ]);
603
1141
  }
604
- return { rows: removed, rows_affected: removed.length };
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 { rows: found, rows_affected: found.length };
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, [...data]);
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([...map].map(([k, v]) => [k, [...v]]));
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({ sql, params });
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