@spfn/core 0.2.0-beta.9 → 0.3.0-beta.2

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 (95) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +467 -305
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +810 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-D16fO2oG.d.ts} +41 -1
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +121 -13
  10. package/dist/codegen/index.js +212 -15
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +615 -6
  13. package/dist/config/index.js +124 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/contract/index.d.ts +220 -0
  16. package/dist/contract/index.js +558 -0
  17. package/dist/contract/index.js.map +1 -0
  18. package/dist/db/index.d.ts +528 -85
  19. package/dist/db/index.js +831 -122
  20. package/dist/db/index.js.map +1 -1
  21. package/dist/define-middleware-DfDP39Nq.d.ts +167 -0
  22. package/dist/env/index.d.ts +26 -2
  23. package/dist/env/index.js +15 -5
  24. package/dist/env/index.js.map +1 -1
  25. package/dist/env/loader.d.ts +26 -19
  26. package/dist/env/loader.js +32 -25
  27. package/dist/env/loader.js.map +1 -1
  28. package/dist/errors/index.d.ts +10 -0
  29. package/dist/errors/index.js +418 -5
  30. package/dist/errors/index.js.map +1 -1
  31. package/dist/event/index.d.ts +33 -3
  32. package/dist/event/index.js +24 -3
  33. package/dist/event/index.js.map +1 -1
  34. package/dist/event/sse/client.d.ts +42 -3
  35. package/dist/event/sse/client.js +128 -45
  36. package/dist/event/sse/client.js.map +1 -1
  37. package/dist/event/sse/index.d.ts +12 -5
  38. package/dist/event/sse/index.js +280 -32
  39. package/dist/event/sse/index.js.map +1 -1
  40. package/dist/event/ws/client.d.ts +59 -0
  41. package/dist/event/ws/client.js +273 -0
  42. package/dist/event/ws/client.js.map +1 -0
  43. package/dist/event/ws/index.d.ts +94 -0
  44. package/dist/event/ws/index.js +272 -0
  45. package/dist/event/ws/index.js.map +1 -0
  46. package/dist/job/index.d.ts +2 -2
  47. package/dist/job/index.js +155 -42
  48. package/dist/job/index.js.map +1 -1
  49. package/dist/logger/index.d.ts +5 -0
  50. package/dist/logger/index.js +14 -0
  51. package/dist/logger/index.js.map +1 -1
  52. package/dist/middleware/index.d.ts +347 -9
  53. package/dist/middleware/index.js +1462 -15
  54. package/dist/middleware/index.js.map +1 -1
  55. package/dist/nextjs/index.d.ts +2 -2
  56. package/dist/nextjs/index.js +42 -28
  57. package/dist/nextjs/index.js.map +1 -1
  58. package/dist/nextjs/server.d.ts +35 -51
  59. package/dist/nextjs/server.js +126 -60
  60. package/dist/nextjs/server.js.map +1 -1
  61. package/dist/ops/index.d.ts +152 -0
  62. package/dist/ops/index.js +500 -0
  63. package/dist/ops/index.js.map +1 -0
  64. package/dist/route/index.d.ts +8 -694
  65. package/dist/route/index.js +111 -22
  66. package/dist/route/index.js.map +1 -1
  67. package/dist/router-Qbssr11H.d.ts +676 -0
  68. package/dist/security/index.d.ts +83 -0
  69. package/dist/security/index.js +173 -0
  70. package/dist/security/index.js.map +1 -0
  71. package/dist/server/index.d.ts +491 -22
  72. package/dist/server/index.js +1887 -308
  73. package/dist/server/index.js.map +1 -1
  74. package/dist/token-manager-BT5EnUAR.d.ts +278 -0
  75. package/dist/types-2AbaW4Ie.d.ts +205 -0
  76. package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
  77. package/dist/types-D1c57Ko-.d.ts +115 -0
  78. package/dist/types-ZQODsBft.d.ts +282 -0
  79. package/package.json +244 -208
  80. package/dist/router-Di7ENoah.d.ts +0 -151
  81. package/dist/types-B-e_f2dQ.d.ts +0 -121
  82. package/docs/cache.md +0 -133
  83. package/docs/codegen.md +0 -74
  84. package/docs/database.md +0 -346
  85. package/docs/entity.md +0 -539
  86. package/docs/env.md +0 -477
  87. package/docs/errors.md +0 -319
  88. package/docs/event.md +0 -116
  89. package/docs/job.md +0 -131
  90. package/docs/logger.md +0 -108
  91. package/docs/middleware.md +0 -337
  92. package/docs/nextjs.md +0 -241
  93. package/docs/repository.md +0 -496
  94. package/docs/route.md +0 -497
  95. package/docs/server.md +0 -307
@@ -0,0 +1,810 @@
1
+ import { format } from 'util';
2
+
3
+ // src/errors/error-registry.ts
4
+ var ErrorRegistry = class _ErrorRegistry {
5
+ errors = /* @__PURE__ */ new Map();
6
+ constructor(initialErrors) {
7
+ if (initialErrors) {
8
+ for (const input of initialErrors) {
9
+ if (input instanceof _ErrorRegistry) {
10
+ this.concat(input);
11
+ } else if (Array.isArray(input)) {
12
+ this.append(input);
13
+ } else {
14
+ this.append(input);
15
+ }
16
+ }
17
+ }
18
+ }
19
+ /**
20
+ * Append error class(es) to the registry
21
+ *
22
+ * @param input - Error constructor or array of constructors
23
+ * @returns This registry for chaining
24
+ */
25
+ append(input) {
26
+ if (Array.isArray(input)) {
27
+ for (const ErrorClass of input) {
28
+ this.errors.set(ErrorClass.name, ErrorClass);
29
+ }
30
+ } else {
31
+ this.errors.set(input.name, input);
32
+ }
33
+ return this;
34
+ }
35
+ /**
36
+ * Concatenate another ErrorRegistry into this one
37
+ *
38
+ * @param registry - Another ErrorRegistry to merge
39
+ * @returns This registry for chaining
40
+ */
41
+ concat(registry) {
42
+ for (const [name, ErrorClass] of registry.errors) {
43
+ this.errors.set(name, ErrorClass);
44
+ }
45
+ return this;
46
+ }
47
+ /**
48
+ * Check if error type is registered
49
+ *
50
+ * @param name - Error class name
51
+ */
52
+ has(name) {
53
+ return this.errors.has(name);
54
+ }
55
+ /**
56
+ * Deserialize error from JSON data
57
+ *
58
+ * @param data - Serialized error data with __type field
59
+ * @returns Deserialized error instance
60
+ * @throws Error if error type is not registered
61
+ */
62
+ deserialize(data) {
63
+ const ErrorClass = this.errors.get(data.__type);
64
+ if (!ErrorClass) {
65
+ throw new Error(`Unknown error type: ${data.__type}`);
66
+ }
67
+ const { __type: _type, error: _envelope, ...fields } = data;
68
+ return new ErrorClass(fields);
69
+ }
70
+ /**
71
+ * Try to deserialize error, return null if type unknown
72
+ *
73
+ * @param data - Serialized error data
74
+ * @returns Deserialized error or null
75
+ */
76
+ tryDeserialize(data) {
77
+ if (!data.__type || !this.has(data.__type)) {
78
+ return null;
79
+ }
80
+ try {
81
+ return this.deserialize(data);
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Get all registered error types
88
+ */
89
+ getRegisteredTypes() {
90
+ return Array.from(this.errors.keys());
91
+ }
92
+ };
93
+
94
+ // src/logger/types.ts
95
+ var LOG_LEVEL_PRIORITY = {
96
+ debug: 0,
97
+ info: 1,
98
+ warn: 2,
99
+ error: 3,
100
+ fatal: 4
101
+ };
102
+
103
+ // src/logger/formatters.ts
104
+ var SENSITIVE_KEYS = [
105
+ "password",
106
+ "passwd",
107
+ "pwd",
108
+ "secret",
109
+ "token",
110
+ "apikey",
111
+ "api_key",
112
+ "accesstoken",
113
+ "access_token",
114
+ "refreshtoken",
115
+ "refresh_token",
116
+ "authorization",
117
+ "auth",
118
+ "cookie",
119
+ "session",
120
+ "sessionid",
121
+ "session_id",
122
+ "privatekey",
123
+ "private_key",
124
+ "creditcard",
125
+ "credit_card",
126
+ "cardnumber",
127
+ "card_number",
128
+ "cvv",
129
+ "ssn",
130
+ "pin"
131
+ ];
132
+ var MASKED_VALUE = "***MASKED***";
133
+ function isSensitiveKey(key) {
134
+ const lowerKey = key.toLowerCase();
135
+ return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive));
136
+ }
137
+ function maskSensitiveData(data, seen = /* @__PURE__ */ new WeakSet()) {
138
+ if (data === null || data === void 0) {
139
+ return data;
140
+ }
141
+ if (typeof data !== "object") {
142
+ return data;
143
+ }
144
+ if (seen.has(data)) {
145
+ return "[Circular]";
146
+ }
147
+ seen.add(data);
148
+ if (Array.isArray(data)) {
149
+ return data.map((item) => maskSensitiveData(item, seen));
150
+ }
151
+ const masked = {};
152
+ for (const [key, value] of Object.entries(data)) {
153
+ if (isSensitiveKey(key)) {
154
+ masked[key] = MASKED_VALUE;
155
+ } else if (typeof value === "object" && value !== null) {
156
+ masked[key] = maskSensitiveData(value, seen);
157
+ } else {
158
+ masked[key] = value;
159
+ }
160
+ }
161
+ return masked;
162
+ }
163
+ var COLORS = {
164
+ reset: "\x1B[0m",
165
+ bright: "\x1B[1m",
166
+ dim: "\x1B[2m",
167
+ // 로그 레벨 컬러
168
+ debug: "\x1B[36m",
169
+ // cyan
170
+ info: "\x1B[32m",
171
+ // green
172
+ warn: "\x1B[33m",
173
+ // yellow
174
+ error: "\x1B[31m",
175
+ // red
176
+ fatal: "\x1B[35m",
177
+ // magenta
178
+ // 추가 컬러
179
+ gray: "\x1B[90m"
180
+ };
181
+ function formatTimestampHuman(date) {
182
+ const year = date.getFullYear();
183
+ const month = String(date.getMonth() + 1).padStart(2, "0");
184
+ const day = String(date.getDate()).padStart(2, "0");
185
+ const hours = String(date.getHours()).padStart(2, "0");
186
+ const minutes = String(date.getMinutes()).padStart(2, "0");
187
+ const seconds = String(date.getSeconds()).padStart(2, "0");
188
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
189
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
190
+ }
191
+ function formatError(error) {
192
+ const lines = [];
193
+ lines.push(`${error.name}: ${error.message}`);
194
+ if (error.stack) {
195
+ const stackLines = error.stack.split("\n").slice(1);
196
+ lines.push(...stackLines);
197
+ }
198
+ if (error.cause instanceof Error) {
199
+ lines.push(`Caused by: ${formatError(error.cause)}`);
200
+ } else if (error.cause !== void 0) {
201
+ lines.push(`Caused by: ${String(error.cause)}`);
202
+ }
203
+ return lines.join("\n");
204
+ }
205
+ function formatConsole(metadata, colorize = true) {
206
+ const parts = [];
207
+ const timestamp = formatTimestampHuman(metadata.timestamp);
208
+ if (colorize) {
209
+ parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);
210
+ } else {
211
+ parts.push(`[${timestamp}]`);
212
+ }
213
+ const pid = process.pid;
214
+ if (colorize) {
215
+ parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);
216
+ } else {
217
+ parts.push(`[pid=${pid}]`);
218
+ }
219
+ if (metadata.module) {
220
+ if (colorize) {
221
+ parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);
222
+ } else {
223
+ parts.push(`[module=${metadata.module}]`);
224
+ }
225
+ }
226
+ if (metadata.context && Object.keys(metadata.context).length > 0) {
227
+ Object.entries(metadata.context).forEach(([key, value]) => {
228
+ let valueStr;
229
+ if (typeof value === "string") {
230
+ valueStr = value;
231
+ } else if (typeof value === "object" && value !== null) {
232
+ try {
233
+ valueStr = JSON.stringify(value);
234
+ } catch (error) {
235
+ valueStr = "[circular]";
236
+ }
237
+ } else {
238
+ valueStr = String(value);
239
+ }
240
+ if (colorize) {
241
+ parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);
242
+ } else {
243
+ parts.push(`[${key}=${valueStr}]`);
244
+ }
245
+ });
246
+ }
247
+ const levelStr = metadata.level.toUpperCase();
248
+ if (colorize) {
249
+ const color = COLORS[metadata.level];
250
+ parts.push(`${color}(${levelStr})${COLORS.reset}:`);
251
+ } else {
252
+ parts.push(`(${levelStr}):`);
253
+ }
254
+ if (colorize) {
255
+ parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);
256
+ } else {
257
+ parts.push(metadata.message);
258
+ }
259
+ let output = parts.join(" ");
260
+ if (metadata.error) {
261
+ output += "\n" + formatError(metadata.error);
262
+ }
263
+ return output;
264
+ }
265
+
266
+ // src/logger/logger.ts
267
+ var FORMAT_PATTERN = /%[sdifjoOc%]/;
268
+ var Logger = class _Logger {
269
+ config;
270
+ module;
271
+ constructor(config) {
272
+ this.config = config;
273
+ this.module = config.module;
274
+ }
275
+ /**
276
+ * Convert unknown error to Error object
277
+ */
278
+ toError(error) {
279
+ if (error instanceof Error) return error;
280
+ if (typeof error === "string") return new Error(error);
281
+ if (typeof error === "object" && error !== null) {
282
+ return new Error(JSON.stringify(error));
283
+ }
284
+ return new Error(String(error));
285
+ }
286
+ /**
287
+ * Check if value is a context object (not an error)
288
+ */
289
+ isContext(value) {
290
+ if (typeof value !== "object" || value === null) return false;
291
+ if (value instanceof Error) return false;
292
+ const hasStack = "stack" in value && typeof value.stack === "string";
293
+ if (hasStack) {
294
+ return false;
295
+ }
296
+ return true;
297
+ }
298
+ /**
299
+ * Get current log level
300
+ */
301
+ get level() {
302
+ return this.config.level;
303
+ }
304
+ /**
305
+ * Create child logger (per module)
306
+ */
307
+ child(module) {
308
+ return new _Logger({
309
+ ...this.config,
310
+ module
311
+ });
312
+ }
313
+ /**
314
+ * Common log method with error/context detection
315
+ */
316
+ logWithLevel(level, message, errorOrContext, context) {
317
+ if (errorOrContext !== void 0 && FORMAT_PATTERN.test(message)) {
318
+ this.log(level, format(message, errorOrContext), void 0, context);
319
+ return;
320
+ }
321
+ if (errorOrContext instanceof Error) {
322
+ this.log(level, message, errorOrContext, context);
323
+ } else if (errorOrContext !== void 0 && typeof errorOrContext === "object" && !this.isContext(errorOrContext)) {
324
+ this.log(level, message, this.toError(errorOrContext), context);
325
+ } else if (typeof errorOrContext === "string" || typeof errorOrContext === "number" || typeof errorOrContext === "boolean") {
326
+ this.log(level, message, this.toError(errorOrContext), context);
327
+ } else {
328
+ this.log(level, message, void 0, errorOrContext);
329
+ }
330
+ }
331
+ debug(message, errorOrContext, context) {
332
+ this.logWithLevel("debug", message, errorOrContext, context);
333
+ }
334
+ info(message, errorOrContext, context) {
335
+ this.logWithLevel("info", message, errorOrContext, context);
336
+ }
337
+ warn(message, errorOrContext, context) {
338
+ this.logWithLevel("warn", message, errorOrContext, context);
339
+ }
340
+ error(message, errorOrContext, context) {
341
+ this.logWithLevel("error", message, errorOrContext, context);
342
+ }
343
+ fatal(message, errorOrContext, context) {
344
+ this.logWithLevel("fatal", message, errorOrContext, context);
345
+ }
346
+ /**
347
+ * Log processing (internal)
348
+ */
349
+ log(level, message, error, context) {
350
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level]) {
351
+ return;
352
+ }
353
+ const metadata = {
354
+ timestamp: /* @__PURE__ */ new Date(),
355
+ level,
356
+ message,
357
+ module: this.module,
358
+ error,
359
+ // Mask sensitive information in context to prevent credential leaks
360
+ context: context ? maskSensitiveData(context) : void 0
361
+ };
362
+ this.processTransports(metadata);
363
+ }
364
+ /**
365
+ * Process Transports
366
+ */
367
+ processTransports(metadata) {
368
+ const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
369
+ Promise.all(promises).catch((error) => {
370
+ const errorMessage = error instanceof Error ? error.message : String(error);
371
+ process.stderr.write(`[Logger] Transport error: ${errorMessage}
372
+ `);
373
+ });
374
+ }
375
+ /**
376
+ * Transport log (error-safe)
377
+ */
378
+ async safeTransportLog(transport, metadata) {
379
+ try {
380
+ await transport.log(metadata);
381
+ } catch (error) {
382
+ const errorMessage = error instanceof Error ? error.message : String(error);
383
+ process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
384
+ `);
385
+ }
386
+ }
387
+ /**
388
+ * Close all Transports
389
+ */
390
+ async close() {
391
+ const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
392
+ await Promise.all(closePromises);
393
+ }
394
+ };
395
+
396
+ // src/logger/transports/console.ts
397
+ var ConsoleTransport = class {
398
+ name = "console";
399
+ level;
400
+ enabled;
401
+ colorize;
402
+ constructor(config) {
403
+ this.level = config.level;
404
+ this.enabled = config.enabled;
405
+ this.colorize = config.colorize ?? true;
406
+ }
407
+ async log(metadata) {
408
+ if (!this.enabled) {
409
+ return;
410
+ }
411
+ if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
412
+ return;
413
+ }
414
+ const message = formatConsole(metadata, this.colorize);
415
+ if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
416
+ console.error(message);
417
+ } else {
418
+ console.log(message);
419
+ }
420
+ }
421
+ };
422
+
423
+ // src/logger/config.ts
424
+ function getConsoleConfig() {
425
+ const isProduction = process.env.NODE_ENV === "production";
426
+ return {
427
+ level: "debug",
428
+ enabled: true,
429
+ colorize: !isProduction
430
+ // Dev: colored output, Production: plain text
431
+ };
432
+ }
433
+ function validateEnvironment() {
434
+ const nodeEnv = process.env.NODE_ENV;
435
+ if (!nodeEnv) {
436
+ process.stderr.write(
437
+ "[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\n"
438
+ );
439
+ }
440
+ }
441
+ function validateConfig() {
442
+ validateEnvironment();
443
+ }
444
+
445
+ // src/logger/factory.ts
446
+ function initializeTransports() {
447
+ const transports = [];
448
+ const consoleConfig = getConsoleConfig();
449
+ transports.push(new ConsoleTransport(consoleConfig));
450
+ return transports;
451
+ }
452
+ function getLogLevel() {
453
+ const envLevel = process.env.SPFN_LOG_LEVEL || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL || "info";
454
+ if (envLevel in LOG_LEVEL_PRIORITY) {
455
+ return envLevel;
456
+ }
457
+ process.stderr.write(
458
+ `[Logger] Invalid log level "${envLevel}", defaulting to "info"
459
+ `
460
+ );
461
+ return "info";
462
+ }
463
+ function initializeLogger() {
464
+ validateConfig();
465
+ return new Logger({
466
+ level: getLogLevel(),
467
+ transports: initializeTransports()
468
+ });
469
+ }
470
+ var logger = initializeLogger();
471
+
472
+ // src/errors/serializable-error.ts
473
+ var RESERVED_RESPONSE_KEYS = /* @__PURE__ */ new Set(["__type", "message", "error"]);
474
+ var AUTHORING_ENVIRONMENTS = /* @__PURE__ */ new Set(["local", "development", "test"]);
475
+ function refuseReservedKey(className, key) {
476
+ const detail = `${className} declares the reserved field "${key}". Reserved response keys: ${[...RESERVED_RESPONSE_KEYS].join(", ")}. Rename the field.`;
477
+ if (AUTHORING_ENVIRONMENTS.has(process.env.NODE_ENV ?? "local")) {
478
+ throw new Error(detail);
479
+ }
480
+ logger.error(`[SerializableError] ${detail} The field is omitted from the response.`);
481
+ }
482
+ var SerializableError = class extends Error {
483
+ /**
484
+ * Serialize error to JSON-compatible object
485
+ *
486
+ * Automatically includes:
487
+ * - __type: Constructor name for deserialization
488
+ * - message: Error message
489
+ * - All public instance properties (except name, stack)
490
+ */
491
+ toJSON() {
492
+ const json = {
493
+ __type: this.constructor.name,
494
+ message: this.message
495
+ };
496
+ for (const key of Object.keys(this)) {
497
+ if (key === "name" || key === "stack" || key === "statusCode") {
498
+ continue;
499
+ }
500
+ if (RESERVED_RESPONSE_KEYS.has(key)) {
501
+ refuseReservedKey(this.constructor.name, key);
502
+ continue;
503
+ }
504
+ json[key] = this[key];
505
+ }
506
+ return json;
507
+ }
508
+ };
509
+
510
+ // src/errors/http-errors.ts
511
+ var HttpError = class extends SerializableError {
512
+ statusCode;
513
+ details;
514
+ constructor(data) {
515
+ super(data.message);
516
+ this.name = "HttpError";
517
+ this.statusCode = data.statusCode;
518
+ if (data.details) {
519
+ this.details = data.details;
520
+ }
521
+ Error.captureStackTrace(this, this.constructor);
522
+ }
523
+ };
524
+ var BadRequestError = class extends HttpError {
525
+ constructor(data = {}) {
526
+ super({
527
+ message: data.message || "Bad request",
528
+ statusCode: 400,
529
+ details: data.details
530
+ });
531
+ this.name = "BadRequestError";
532
+ }
533
+ };
534
+ var ValidationError = class extends HttpError {
535
+ fields;
536
+ constructor(data) {
537
+ super({
538
+ message: data.message,
539
+ statusCode: 400,
540
+ details: data.details
541
+ });
542
+ this.name = "ValidationError";
543
+ if (data.fields) {
544
+ this.fields = data.fields;
545
+ }
546
+ }
547
+ };
548
+ var UnauthorizedError = class extends HttpError {
549
+ constructor(data = {}) {
550
+ super({
551
+ message: data.message || "Authentication required",
552
+ statusCode: 401,
553
+ details: data.details
554
+ });
555
+ this.name = "UnauthorizedError";
556
+ }
557
+ };
558
+ var ForbiddenError = class extends HttpError {
559
+ constructor(data = {}) {
560
+ super({
561
+ message: data.message || "Access forbidden",
562
+ statusCode: 403,
563
+ details: data.details
564
+ });
565
+ this.name = "ForbiddenError";
566
+ }
567
+ };
568
+ var NotFoundError = class extends HttpError {
569
+ resource;
570
+ constructor(data = {}) {
571
+ super({
572
+ message: data.message || "Resource not found",
573
+ statusCode: 404,
574
+ details: data.details
575
+ });
576
+ this.name = "NotFoundError";
577
+ if (data.resource) {
578
+ this.resource = data.resource;
579
+ }
580
+ }
581
+ };
582
+ var ConflictError = class extends HttpError {
583
+ constructor(data = {}) {
584
+ super({
585
+ message: data.message || "Resource conflict",
586
+ statusCode: 409,
587
+ details: data.details
588
+ });
589
+ this.name = "ConflictError";
590
+ }
591
+ };
592
+ var GoneError = class extends HttpError {
593
+ resource;
594
+ constructor(data = {}) {
595
+ super({
596
+ message: data.message || "Resource permanently deleted",
597
+ statusCode: 410,
598
+ details: data.details
599
+ });
600
+ this.name = "GoneError";
601
+ if (data.resource) {
602
+ this.resource = data.resource;
603
+ }
604
+ }
605
+ };
606
+ var TooManyRequestsError = class extends HttpError {
607
+ retryAfter;
608
+ constructor(data = {}) {
609
+ super({
610
+ message: data.message || "Too many requests",
611
+ statusCode: 429,
612
+ details: data.details
613
+ });
614
+ this.name = "TooManyRequestsError";
615
+ if (data.retryAfter) {
616
+ this.retryAfter = data.retryAfter;
617
+ }
618
+ }
619
+ };
620
+ var InternalServerError = class extends HttpError {
621
+ constructor(data = {}) {
622
+ super({
623
+ message: data.message || "Internal server error",
624
+ statusCode: 500,
625
+ details: data.details
626
+ });
627
+ this.name = "InternalServerError";
628
+ }
629
+ };
630
+ var UnsupportedMediaTypeError = class extends HttpError {
631
+ mediaType;
632
+ supportedTypes;
633
+ constructor(data = {}) {
634
+ super({
635
+ message: data.message || "Unsupported media type",
636
+ statusCode: 415,
637
+ details: data.details
638
+ });
639
+ this.name = "UnsupportedMediaTypeError";
640
+ if (data.mediaType) {
641
+ this.mediaType = data.mediaType;
642
+ }
643
+ if (data.supportedTypes) {
644
+ this.supportedTypes = data.supportedTypes;
645
+ }
646
+ }
647
+ };
648
+ var UnprocessableEntityError = class extends HttpError {
649
+ constructor(data = {}) {
650
+ super({
651
+ message: data.message || "Unprocessable entity",
652
+ statusCode: 422,
653
+ details: data.details
654
+ });
655
+ this.name = "UnprocessableEntityError";
656
+ }
657
+ };
658
+ var ServiceUnavailableError = class extends HttpError {
659
+ retryAfter;
660
+ constructor(data = {}) {
661
+ super({
662
+ message: data.message || "Service unavailable",
663
+ statusCode: 503,
664
+ details: data.details
665
+ });
666
+ this.name = "ServiceUnavailableError";
667
+ if (data.retryAfter) {
668
+ this.retryAfter = data.retryAfter;
669
+ }
670
+ }
671
+ };
672
+
673
+ // src/errors/database-errors.ts
674
+ var DatabaseError = class extends SerializableError {
675
+ statusCode;
676
+ details;
677
+ constructor(data) {
678
+ super(data.message);
679
+ this.name = "DatabaseError";
680
+ this.statusCode = data.statusCode ?? 500;
681
+ this.details = data.details;
682
+ Error.captureStackTrace(this, this.constructor);
683
+ }
684
+ /**
685
+ * Whether this error's message/details are derived from the raw database
686
+ * driver (SQL text, table/column/constraint names, parameter values) and
687
+ * therefore must NOT be exposed to clients in production. Defined as a
688
+ * prototype getter so it is never serialized into the response by toJSON().
689
+ * Subclasses with a safe, constructed message override this to `false`.
690
+ */
691
+ get internal() {
692
+ return true;
693
+ }
694
+ };
695
+ var ConnectionError = class extends DatabaseError {
696
+ constructor(data) {
697
+ super({ message: data.message, statusCode: 503, details: data.details });
698
+ this.name = "ConnectionError";
699
+ }
700
+ };
701
+ var QueryError = class extends DatabaseError {
702
+ constructor(data) {
703
+ super({
704
+ message: data.message,
705
+ statusCode: data.statusCode ?? 500,
706
+ details: data.details
707
+ });
708
+ this.name = "QueryError";
709
+ }
710
+ };
711
+ var EntityNotFoundError = class extends QueryError {
712
+ resource;
713
+ id;
714
+ constructor(data) {
715
+ super({
716
+ message: `${data.resource} with id ${data.id} not found`,
717
+ statusCode: 404,
718
+ details: { resource: data.resource, id: data.id }
719
+ });
720
+ this.name = "EntityNotFoundError";
721
+ this.resource = data.resource;
722
+ this.id = data.id;
723
+ }
724
+ // Message/details are constructed from the resource + id, not driver text
725
+ get internal() {
726
+ return false;
727
+ }
728
+ };
729
+ var ConstraintViolationError = class extends QueryError {
730
+ constructor(data) {
731
+ super({ message: data.message, statusCode: 400, details: data.details });
732
+ this.name = "ConstraintViolationError";
733
+ }
734
+ };
735
+ var TransactionError = class extends DatabaseError {
736
+ constructor(data) {
737
+ super({
738
+ message: data.message,
739
+ statusCode: data.statusCode ?? 500,
740
+ details: data.details
741
+ });
742
+ this.name = "TransactionError";
743
+ }
744
+ };
745
+ var DeadlockError = class extends TransactionError {
746
+ constructor(data) {
747
+ super({ message: data.message, statusCode: 409, details: data.details });
748
+ this.name = "DeadlockError";
749
+ }
750
+ };
751
+ var DuplicateEntryError = class extends QueryError {
752
+ field;
753
+ value;
754
+ constructor(data) {
755
+ super({
756
+ message: `${data.field} already exists`,
757
+ statusCode: 409,
758
+ details: { field: data.field }
759
+ });
760
+ this.name = "DuplicateEntryError";
761
+ this.field = data.field;
762
+ this.value = data.value;
763
+ }
764
+ // Field-only message/details — safe to surface to the client (not internal)
765
+ get internal() {
766
+ return false;
767
+ }
768
+ };
769
+
770
+ // src/errors/index.ts
771
+ var errorRegistry = new ErrorRegistry();
772
+ errorRegistry.append([
773
+ HttpError,
774
+ BadRequestError,
775
+ ValidationError,
776
+ UnauthorizedError,
777
+ ForbiddenError,
778
+ NotFoundError,
779
+ ConflictError,
780
+ GoneError,
781
+ TooManyRequestsError,
782
+ UnsupportedMediaTypeError,
783
+ UnprocessableEntityError,
784
+ InternalServerError,
785
+ ServiceUnavailableError
786
+ ]);
787
+ errorRegistry.append([
788
+ DatabaseError,
789
+ ConnectionError,
790
+ QueryError,
791
+ EntityNotFoundError,
792
+ ConstraintViolationError,
793
+ TransactionError,
794
+ DeadlockError,
795
+ DuplicateEntryError
796
+ ]);
797
+
798
+ // src/authz/index.ts
799
+ function requireOwner(resource, ownerId, options = {}) {
800
+ const ownerKey = options.ownerKey ?? "ownerId";
801
+ const value = resource ? resource[ownerKey] : void 0;
802
+ if (!resource || value === null || value === void 0 || String(value) !== String(ownerId)) {
803
+ throw new NotFoundError({ message: options.message ?? "not found" });
804
+ }
805
+ return resource;
806
+ }
807
+
808
+ export { requireOwner };
809
+ //# sourceMappingURL=index.js.map
810
+ //# sourceMappingURL=index.js.map