@spfn/core 0.2.0-beta.67 → 0.2.0-beta.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -116
- package/dist/authz/index.js +398 -3
- package/dist/authz/index.js.map +1 -1
- package/dist/codegen/index.d.ts +114 -8
- package/dist/codegen/index.js +162 -3
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +288 -0
- package/dist/contract/index.js +534 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/{define-middleware-DuXD8Hvu.d.ts → define-middleware-B9bFuXVU.d.ts} +1 -1
- package/dist/errors/index.js +398 -3
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +3 -3
- package/dist/event/sse/client.d.ts +2 -2
- package/dist/event/sse/index.d.ts +4 -4
- package/dist/event/sse/index.js +9 -0
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +2 -2
- package/dist/event/ws/index.d.ts +3 -3
- package/dist/middleware/index.d.ts +108 -11
- package/dist/middleware/index.js +769 -632
- package/dist/middleware/index.js.map +1 -1
- package/dist/route/index.d.ts +8 -552
- package/dist/route/index.js +36 -0
- package/dist/route/index.js.map +1 -1
- package/dist/router-DhvbMhef.d.ts +641 -0
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +9 -0
- package/dist/server/index.js.map +1 -1
- package/dist/{token-manager-jKD_EsSE.d.ts → token-manager-vZeqBbtA.d.ts} +7 -0
- package/dist/{types-DVjf37yO.d.ts → types-CF-37KAG.d.ts} +1 -1
- package/dist/{types-BFB72jbM.d.ts → types-D9uMxeQS.d.ts} +1 -1
- package/package.json +11 -9
package/dist/authz/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { format } from 'util';
|
|
2
|
+
|
|
1
3
|
// src/errors/error-registry.ts
|
|
2
4
|
var ErrorRegistry = class _ErrorRegistry {
|
|
3
5
|
errors = /* @__PURE__ */ new Map();
|
|
@@ -62,7 +64,8 @@ var ErrorRegistry = class _ErrorRegistry {
|
|
|
62
64
|
if (!ErrorClass) {
|
|
63
65
|
throw new Error(`Unknown error type: ${data.__type}`);
|
|
64
66
|
}
|
|
65
|
-
|
|
67
|
+
const { __type: _type, error: _envelope, ...fields } = data;
|
|
68
|
+
return new ErrorClass(fields);
|
|
66
69
|
}
|
|
67
70
|
/**
|
|
68
71
|
* Try to deserialize error, return null if type unknown
|
|
@@ -88,7 +91,394 @@ var ErrorRegistry = class _ErrorRegistry {
|
|
|
88
91
|
}
|
|
89
92
|
};
|
|
90
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
|
+
|
|
91
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
|
+
}
|
|
92
482
|
var SerializableError = class extends Error {
|
|
93
483
|
/**
|
|
94
484
|
* Serialize error to JSON-compatible object
|
|
@@ -104,9 +494,14 @@ var SerializableError = class extends Error {
|
|
|
104
494
|
message: this.message
|
|
105
495
|
};
|
|
106
496
|
for (const key of Object.keys(this)) {
|
|
107
|
-
if (key
|
|
108
|
-
|
|
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;
|
|
109
503
|
}
|
|
504
|
+
json[key] = this[key];
|
|
110
505
|
}
|
|
111
506
|
return json;
|
|
112
507
|
}
|