@spfn/core 0.2.0-beta.8 → 0.3.0-beta.1

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 +444 -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 +107 -0
  62. package/dist/ops/index.js +476 -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-ukNdAZcN.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-Bvvig_tT.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
@@ -1,3 +1,5 @@
1
+ import { format } from 'util';
2
+
1
3
  var __defProp = Object.defineProperty;
2
4
  var __export = (target, all) => {
3
5
  for (var name in all)
@@ -68,7 +70,8 @@ var ErrorRegistry = class _ErrorRegistry {
68
70
  if (!ErrorClass) {
69
71
  throw new Error(`Unknown error type: ${data.__type}`);
70
72
  }
71
- return new ErrorClass(data);
73
+ const { __type: _type, error: _envelope, ...fields } = data;
74
+ return new ErrorClass(fields);
72
75
  }
73
76
  /**
74
77
  * Try to deserialize error, return null if type unknown
@@ -112,7 +115,394 @@ __export(http_errors_exports, {
112
115
  ValidationError: () => ValidationError
113
116
  });
114
117
 
118
+ // src/logger/types.ts
119
+ var LOG_LEVEL_PRIORITY = {
120
+ debug: 0,
121
+ info: 1,
122
+ warn: 2,
123
+ error: 3,
124
+ fatal: 4
125
+ };
126
+
127
+ // src/logger/formatters.ts
128
+ var SENSITIVE_KEYS = [
129
+ "password",
130
+ "passwd",
131
+ "pwd",
132
+ "secret",
133
+ "token",
134
+ "apikey",
135
+ "api_key",
136
+ "accesstoken",
137
+ "access_token",
138
+ "refreshtoken",
139
+ "refresh_token",
140
+ "authorization",
141
+ "auth",
142
+ "cookie",
143
+ "session",
144
+ "sessionid",
145
+ "session_id",
146
+ "privatekey",
147
+ "private_key",
148
+ "creditcard",
149
+ "credit_card",
150
+ "cardnumber",
151
+ "card_number",
152
+ "cvv",
153
+ "ssn",
154
+ "pin"
155
+ ];
156
+ var MASKED_VALUE = "***MASKED***";
157
+ function isSensitiveKey(key) {
158
+ const lowerKey = key.toLowerCase();
159
+ return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive));
160
+ }
161
+ function maskSensitiveData(data, seen = /* @__PURE__ */ new WeakSet()) {
162
+ if (data === null || data === void 0) {
163
+ return data;
164
+ }
165
+ if (typeof data !== "object") {
166
+ return data;
167
+ }
168
+ if (seen.has(data)) {
169
+ return "[Circular]";
170
+ }
171
+ seen.add(data);
172
+ if (Array.isArray(data)) {
173
+ return data.map((item) => maskSensitiveData(item, seen));
174
+ }
175
+ const masked = {};
176
+ for (const [key, value] of Object.entries(data)) {
177
+ if (isSensitiveKey(key)) {
178
+ masked[key] = MASKED_VALUE;
179
+ } else if (typeof value === "object" && value !== null) {
180
+ masked[key] = maskSensitiveData(value, seen);
181
+ } else {
182
+ masked[key] = value;
183
+ }
184
+ }
185
+ return masked;
186
+ }
187
+ var COLORS = {
188
+ reset: "\x1B[0m",
189
+ bright: "\x1B[1m",
190
+ dim: "\x1B[2m",
191
+ // 로그 레벨 컬러
192
+ debug: "\x1B[36m",
193
+ // cyan
194
+ info: "\x1B[32m",
195
+ // green
196
+ warn: "\x1B[33m",
197
+ // yellow
198
+ error: "\x1B[31m",
199
+ // red
200
+ fatal: "\x1B[35m",
201
+ // magenta
202
+ // 추가 컬러
203
+ gray: "\x1B[90m"
204
+ };
205
+ function formatTimestampHuman(date) {
206
+ const year = date.getFullYear();
207
+ const month = String(date.getMonth() + 1).padStart(2, "0");
208
+ const day = String(date.getDate()).padStart(2, "0");
209
+ const hours = String(date.getHours()).padStart(2, "0");
210
+ const minutes = String(date.getMinutes()).padStart(2, "0");
211
+ const seconds = String(date.getSeconds()).padStart(2, "0");
212
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
213
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
214
+ }
215
+ function formatError(error) {
216
+ const lines = [];
217
+ lines.push(`${error.name}: ${error.message}`);
218
+ if (error.stack) {
219
+ const stackLines = error.stack.split("\n").slice(1);
220
+ lines.push(...stackLines);
221
+ }
222
+ if (error.cause instanceof Error) {
223
+ lines.push(`Caused by: ${formatError(error.cause)}`);
224
+ } else if (error.cause !== void 0) {
225
+ lines.push(`Caused by: ${String(error.cause)}`);
226
+ }
227
+ return lines.join("\n");
228
+ }
229
+ function formatConsole(metadata, colorize = true) {
230
+ const parts = [];
231
+ const timestamp = formatTimestampHuman(metadata.timestamp);
232
+ if (colorize) {
233
+ parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);
234
+ } else {
235
+ parts.push(`[${timestamp}]`);
236
+ }
237
+ const pid = process.pid;
238
+ if (colorize) {
239
+ parts.push(`${COLORS.dim}[pid=${pid}]${COLORS.reset}`);
240
+ } else {
241
+ parts.push(`[pid=${pid}]`);
242
+ }
243
+ if (metadata.module) {
244
+ if (colorize) {
245
+ parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);
246
+ } else {
247
+ parts.push(`[module=${metadata.module}]`);
248
+ }
249
+ }
250
+ if (metadata.context && Object.keys(metadata.context).length > 0) {
251
+ Object.entries(metadata.context).forEach(([key, value]) => {
252
+ let valueStr;
253
+ if (typeof value === "string") {
254
+ valueStr = value;
255
+ } else if (typeof value === "object" && value !== null) {
256
+ try {
257
+ valueStr = JSON.stringify(value);
258
+ } catch (error) {
259
+ valueStr = "[circular]";
260
+ }
261
+ } else {
262
+ valueStr = String(value);
263
+ }
264
+ if (colorize) {
265
+ parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);
266
+ } else {
267
+ parts.push(`[${key}=${valueStr}]`);
268
+ }
269
+ });
270
+ }
271
+ const levelStr = metadata.level.toUpperCase();
272
+ if (colorize) {
273
+ const color = COLORS[metadata.level];
274
+ parts.push(`${color}(${levelStr})${COLORS.reset}:`);
275
+ } else {
276
+ parts.push(`(${levelStr}):`);
277
+ }
278
+ if (colorize) {
279
+ parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);
280
+ } else {
281
+ parts.push(metadata.message);
282
+ }
283
+ let output = parts.join(" ");
284
+ if (metadata.error) {
285
+ output += "\n" + formatError(metadata.error);
286
+ }
287
+ return output;
288
+ }
289
+
290
+ // src/logger/logger.ts
291
+ var FORMAT_PATTERN = /%[sdifjoOc%]/;
292
+ var Logger = class _Logger {
293
+ config;
294
+ module;
295
+ constructor(config) {
296
+ this.config = config;
297
+ this.module = config.module;
298
+ }
299
+ /**
300
+ * Convert unknown error to Error object
301
+ */
302
+ toError(error) {
303
+ if (error instanceof Error) return error;
304
+ if (typeof error === "string") return new Error(error);
305
+ if (typeof error === "object" && error !== null) {
306
+ return new Error(JSON.stringify(error));
307
+ }
308
+ return new Error(String(error));
309
+ }
310
+ /**
311
+ * Check if value is a context object (not an error)
312
+ */
313
+ isContext(value) {
314
+ if (typeof value !== "object" || value === null) return false;
315
+ if (value instanceof Error) return false;
316
+ const hasStack = "stack" in value && typeof value.stack === "string";
317
+ if (hasStack) {
318
+ return false;
319
+ }
320
+ return true;
321
+ }
322
+ /**
323
+ * Get current log level
324
+ */
325
+ get level() {
326
+ return this.config.level;
327
+ }
328
+ /**
329
+ * Create child logger (per module)
330
+ */
331
+ child(module) {
332
+ return new _Logger({
333
+ ...this.config,
334
+ module
335
+ });
336
+ }
337
+ /**
338
+ * Common log method with error/context detection
339
+ */
340
+ logWithLevel(level, message, errorOrContext, context) {
341
+ if (errorOrContext !== void 0 && FORMAT_PATTERN.test(message)) {
342
+ this.log(level, format(message, errorOrContext), void 0, context);
343
+ return;
344
+ }
345
+ if (errorOrContext instanceof Error) {
346
+ this.log(level, message, errorOrContext, context);
347
+ } else if (errorOrContext !== void 0 && typeof errorOrContext === "object" && !this.isContext(errorOrContext)) {
348
+ this.log(level, message, this.toError(errorOrContext), context);
349
+ } else if (typeof errorOrContext === "string" || typeof errorOrContext === "number" || typeof errorOrContext === "boolean") {
350
+ this.log(level, message, this.toError(errorOrContext), context);
351
+ } else {
352
+ this.log(level, message, void 0, errorOrContext);
353
+ }
354
+ }
355
+ debug(message, errorOrContext, context) {
356
+ this.logWithLevel("debug", message, errorOrContext, context);
357
+ }
358
+ info(message, errorOrContext, context) {
359
+ this.logWithLevel("info", message, errorOrContext, context);
360
+ }
361
+ warn(message, errorOrContext, context) {
362
+ this.logWithLevel("warn", message, errorOrContext, context);
363
+ }
364
+ error(message, errorOrContext, context) {
365
+ this.logWithLevel("error", message, errorOrContext, context);
366
+ }
367
+ fatal(message, errorOrContext, context) {
368
+ this.logWithLevel("fatal", message, errorOrContext, context);
369
+ }
370
+ /**
371
+ * Log processing (internal)
372
+ */
373
+ log(level, message, error, context) {
374
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level]) {
375
+ return;
376
+ }
377
+ const metadata = {
378
+ timestamp: /* @__PURE__ */ new Date(),
379
+ level,
380
+ message,
381
+ module: this.module,
382
+ error,
383
+ // Mask sensitive information in context to prevent credential leaks
384
+ context: context ? maskSensitiveData(context) : void 0
385
+ };
386
+ this.processTransports(metadata);
387
+ }
388
+ /**
389
+ * Process Transports
390
+ */
391
+ processTransports(metadata) {
392
+ const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
393
+ Promise.all(promises).catch((error) => {
394
+ const errorMessage = error instanceof Error ? error.message : String(error);
395
+ process.stderr.write(`[Logger] Transport error: ${errorMessage}
396
+ `);
397
+ });
398
+ }
399
+ /**
400
+ * Transport log (error-safe)
401
+ */
402
+ async safeTransportLog(transport, metadata) {
403
+ try {
404
+ await transport.log(metadata);
405
+ } catch (error) {
406
+ const errorMessage = error instanceof Error ? error.message : String(error);
407
+ process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
408
+ `);
409
+ }
410
+ }
411
+ /**
412
+ * Close all Transports
413
+ */
414
+ async close() {
415
+ const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
416
+ await Promise.all(closePromises);
417
+ }
418
+ };
419
+
420
+ // src/logger/transports/console.ts
421
+ var ConsoleTransport = class {
422
+ name = "console";
423
+ level;
424
+ enabled;
425
+ colorize;
426
+ constructor(config) {
427
+ this.level = config.level;
428
+ this.enabled = config.enabled;
429
+ this.colorize = config.colorize ?? true;
430
+ }
431
+ async log(metadata) {
432
+ if (!this.enabled) {
433
+ return;
434
+ }
435
+ if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
436
+ return;
437
+ }
438
+ const message = formatConsole(metadata, this.colorize);
439
+ if (metadata.level === "warn" || metadata.level === "error" || metadata.level === "fatal") {
440
+ console.error(message);
441
+ } else {
442
+ console.log(message);
443
+ }
444
+ }
445
+ };
446
+
447
+ // src/logger/config.ts
448
+ function getConsoleConfig() {
449
+ const isProduction = process.env.NODE_ENV === "production";
450
+ return {
451
+ level: "debug",
452
+ enabled: true,
453
+ colorize: !isProduction
454
+ // Dev: colored output, Production: plain text
455
+ };
456
+ }
457
+ function validateEnvironment() {
458
+ const nodeEnv = process.env.NODE_ENV;
459
+ if (!nodeEnv) {
460
+ process.stderr.write(
461
+ "[Logger] Warning: NODE_ENV is not set. Defaulting to test environment.\n"
462
+ );
463
+ }
464
+ }
465
+ function validateConfig() {
466
+ validateEnvironment();
467
+ }
468
+
469
+ // src/logger/factory.ts
470
+ function initializeTransports() {
471
+ const transports = [];
472
+ const consoleConfig = getConsoleConfig();
473
+ transports.push(new ConsoleTransport(consoleConfig));
474
+ return transports;
475
+ }
476
+ function getLogLevel() {
477
+ const envLevel = process.env.SPFN_LOG_LEVEL || process.env.NEXT_PUBLIC_SPFN_LOG_LEVEL || "info";
478
+ if (envLevel in LOG_LEVEL_PRIORITY) {
479
+ return envLevel;
480
+ }
481
+ process.stderr.write(
482
+ `[Logger] Invalid log level "${envLevel}", defaulting to "info"
483
+ `
484
+ );
485
+ return "info";
486
+ }
487
+ function initializeLogger() {
488
+ validateConfig();
489
+ return new Logger({
490
+ level: getLogLevel(),
491
+ transports: initializeTransports()
492
+ });
493
+ }
494
+ var logger = initializeLogger();
495
+
115
496
  // src/errors/serializable-error.ts
497
+ var RESERVED_RESPONSE_KEYS = /* @__PURE__ */ new Set(["__type", "message", "error"]);
498
+ var AUTHORING_ENVIRONMENTS = /* @__PURE__ */ new Set(["local", "development", "test"]);
499
+ function refuseReservedKey(className, key) {
500
+ const detail = `${className} declares the reserved field "${key}". Reserved response keys: ${[...RESERVED_RESPONSE_KEYS].join(", ")}. Rename the field.`;
501
+ if (AUTHORING_ENVIRONMENTS.has(process.env.NODE_ENV ?? "local")) {
502
+ throw new Error(detail);
503
+ }
504
+ logger.error(`[SerializableError] ${detail} The field is omitted from the response.`);
505
+ }
116
506
  var SerializableError = class extends Error {
117
507
  /**
118
508
  * Serialize error to JSON-compatible object
@@ -128,9 +518,14 @@ var SerializableError = class extends Error {
128
518
  message: this.message
129
519
  };
130
520
  for (const key of Object.keys(this)) {
131
- if (key !== "name" && key !== "message" && key !== "stack" && key !== "statusCode") {
132
- json[key] = this[key];
521
+ if (key === "name" || key === "stack" || key === "statusCode") {
522
+ continue;
133
523
  }
524
+ if (RESERVED_RESPONSE_KEYS.has(key)) {
525
+ refuseReservedKey(this.constructor.name, key);
526
+ continue;
527
+ }
528
+ json[key] = this[key];
134
529
  }
135
530
  return json;
136
531
  }
@@ -321,6 +716,16 @@ var DatabaseError = class extends SerializableError {
321
716
  this.details = data.details;
322
717
  Error.captureStackTrace(this, this.constructor);
323
718
  }
719
+ /**
720
+ * Whether this error's message/details are derived from the raw database
721
+ * driver (SQL text, table/column/constraint names, parameter values) and
722
+ * therefore must NOT be exposed to clients in production. Defined as a
723
+ * prototype getter so it is never serialized into the response by toJSON().
724
+ * Subclasses with a safe, constructed message override this to `false`.
725
+ */
726
+ get internal() {
727
+ return true;
728
+ }
324
729
  };
325
730
  var ConnectionError = class extends DatabaseError {
326
731
  constructor(data) {
@@ -351,6 +756,10 @@ var EntityNotFoundError = class extends QueryError {
351
756
  this.resource = data.resource;
352
757
  this.id = data.id;
353
758
  }
759
+ // Message/details are constructed from the resource + id, not driver text
760
+ get internal() {
761
+ return false;
762
+ }
354
763
  };
355
764
  var ConstraintViolationError = class extends QueryError {
356
765
  constructor(data) {
@@ -379,14 +788,18 @@ var DuplicateEntryError = class extends QueryError {
379
788
  value;
380
789
  constructor(data) {
381
790
  super({
382
- message: `${data.field} '${data.value}' already exists`,
791
+ message: `${data.field} already exists`,
383
792
  statusCode: 409,
384
- details: { field: data.field, value: data.value }
793
+ details: { field: data.field }
385
794
  });
386
795
  this.name = "DuplicateEntryError";
387
796
  this.field = data.field;
388
797
  this.value = data.value;
389
798
  }
799
+ // Field-only message/details — safe to surface to the client (not internal)
800
+ get internal() {
801
+ return false;
802
+ }
390
803
  };
391
804
 
392
805
  // src/errors/error-utils.ts