@nilejs/nile 0.0.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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.cjs +1450 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +646 -0
- package/dist/index.d.ts +646 -0
- package/dist/index.js +1411 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1450 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc2) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
createAction: () => createAction,
|
|
34
|
+
createActions: () => createActions,
|
|
35
|
+
createLog: () => createLog,
|
|
36
|
+
createLogger: () => createLogger,
|
|
37
|
+
createModel: () => createModel,
|
|
38
|
+
createNileServer: () => createNileServer,
|
|
39
|
+
createService: () => createService,
|
|
40
|
+
createServices: () => createServices,
|
|
41
|
+
createTransactionVariant: () => createTransactionVariant,
|
|
42
|
+
getContext: () => getContext,
|
|
43
|
+
getLogs: () => getLogs,
|
|
44
|
+
getZodSchema: () => getZodSchema,
|
|
45
|
+
handleError: () => handleError
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/engine/create-action.ts
|
|
50
|
+
function createAction(config) {
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
function createActions(configs) {
|
|
54
|
+
return configs;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/engine/create-service.ts
|
|
58
|
+
function createService(config) {
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
function createServices(configs) {
|
|
62
|
+
return configs;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/logging/logger.ts
|
|
66
|
+
var import_node_fs = require("fs");
|
|
67
|
+
var import_node_path = require("path");
|
|
68
|
+
var import_nanoid = require("nanoid");
|
|
69
|
+
var import_pino = __toESM(require("pino"), 1);
|
|
70
|
+
var getMode = () => {
|
|
71
|
+
if (!process.env.MODE) {
|
|
72
|
+
throw new Error("Missing MODE environment variable");
|
|
73
|
+
}
|
|
74
|
+
return process.env.MODE;
|
|
75
|
+
};
|
|
76
|
+
var logDir = (0, import_node_path.join)(process.cwd(), "logs");
|
|
77
|
+
if (!(0, import_node_fs.existsSync)(logDir)) {
|
|
78
|
+
(0, import_node_fs.mkdirSync)(logDir);
|
|
79
|
+
}
|
|
80
|
+
var MONTHLY_PATTERN = /^(\d{4})-(\d{2})$/;
|
|
81
|
+
var DAILY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
82
|
+
var WEEKLY_PATTERN = /^(\d{4})-W(\d{2})$/;
|
|
83
|
+
function resolveLogPath(appName, config) {
|
|
84
|
+
const chunking = config?.chunking ?? "none";
|
|
85
|
+
if (chunking === "none") {
|
|
86
|
+
return (0, import_node_path.join)(logDir, `${appName}.log`);
|
|
87
|
+
}
|
|
88
|
+
const appDir = (0, import_node_path.join)(logDir, appName);
|
|
89
|
+
if (!(0, import_node_fs.existsSync)(appDir)) {
|
|
90
|
+
(0, import_node_fs.mkdirSync)(appDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
const now = /* @__PURE__ */ new Date();
|
|
93
|
+
const chunk = formatChunkName(now, chunking);
|
|
94
|
+
return (0, import_node_path.join)(appDir, `${chunk}.log`);
|
|
95
|
+
}
|
|
96
|
+
function formatChunkName(date, chunking) {
|
|
97
|
+
const year = date.getFullYear();
|
|
98
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
99
|
+
if (chunking === "monthly") {
|
|
100
|
+
return `${year}-${month}`;
|
|
101
|
+
}
|
|
102
|
+
if (chunking === "daily") {
|
|
103
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
104
|
+
return `${year}-${month}-${day}`;
|
|
105
|
+
}
|
|
106
|
+
const weekNum = getISOWeekNumber(date);
|
|
107
|
+
return `${year}-W${String(weekNum).padStart(2, "0")}`;
|
|
108
|
+
}
|
|
109
|
+
function getISOWeekNumber(date) {
|
|
110
|
+
const target = new Date(date.valueOf());
|
|
111
|
+
const dayNum = target.getDay() || 7;
|
|
112
|
+
target.setDate(target.getDate() + 4 - dayNum);
|
|
113
|
+
const yearStart = new Date(target.getFullYear(), 0, 1);
|
|
114
|
+
return Math.ceil(
|
|
115
|
+
((target.getTime() - yearStart.getTime()) / 864e5 + 1) / 7
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
function createLoggerForApp(appName, config) {
|
|
119
|
+
const logFile = resolveLogPath(appName, config);
|
|
120
|
+
const transport = import_pino.default.transport({
|
|
121
|
+
targets: [
|
|
122
|
+
{
|
|
123
|
+
level: "info",
|
|
124
|
+
target: "pino/file",
|
|
125
|
+
options: {
|
|
126
|
+
destination: logFile,
|
|
127
|
+
mkdir: true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
return (0, import_pino.default)(
|
|
133
|
+
{
|
|
134
|
+
base: null,
|
|
135
|
+
timestamp: () => `,"time":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
|
|
136
|
+
formatters: {
|
|
137
|
+
level(label) {
|
|
138
|
+
return { level: label };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
transport
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
var createLog = (log, config) => {
|
|
146
|
+
if (!log.appName) {
|
|
147
|
+
throw new Error(`Missing appName in log: ${JSON.stringify(log)}`);
|
|
148
|
+
}
|
|
149
|
+
const level = log.level || "info";
|
|
150
|
+
const log_id = log.log_id || (0, import_nanoid.nanoid)(6);
|
|
151
|
+
const logRecord = {
|
|
152
|
+
log_id,
|
|
153
|
+
appName: log.appName,
|
|
154
|
+
atFunction: log.atFunction,
|
|
155
|
+
message: log.message,
|
|
156
|
+
data: log.data ?? null,
|
|
157
|
+
level,
|
|
158
|
+
time: (/* @__PURE__ */ new Date()).toISOString()
|
|
159
|
+
};
|
|
160
|
+
const mode = getMode();
|
|
161
|
+
if (mode === "prod" || process.env.NODE_ENV === "test") {
|
|
162
|
+
const logFile = resolveLogPath(log.appName, config);
|
|
163
|
+
if (process.env.NODE_ENV === "test") {
|
|
164
|
+
(0, import_node_fs.appendFileSync)(logFile, `${JSON.stringify(logRecord)}
|
|
165
|
+
`, "utf-8");
|
|
166
|
+
} else {
|
|
167
|
+
const appLogger = createLoggerForApp(log.appName, config);
|
|
168
|
+
appLogger[level](logRecord);
|
|
169
|
+
}
|
|
170
|
+
return log_id;
|
|
171
|
+
}
|
|
172
|
+
if (mode === "agentic") {
|
|
173
|
+
return JSON.stringify(logRecord);
|
|
174
|
+
}
|
|
175
|
+
console.log({
|
|
176
|
+
...logRecord,
|
|
177
|
+
data: JSON.stringify(logRecord.data, null, 2)
|
|
178
|
+
});
|
|
179
|
+
return "dev-mode, see your dev console!";
|
|
180
|
+
};
|
|
181
|
+
var getLogs = (filters = {}, config) => {
|
|
182
|
+
const chunking = config?.chunking ?? "none";
|
|
183
|
+
const filesToRead = resolveLogFiles(filters, chunking);
|
|
184
|
+
const logs = readAndParseLogFiles(filesToRead);
|
|
185
|
+
return applyLogFilters(logs, filters);
|
|
186
|
+
};
|
|
187
|
+
function resolveLogFiles(filters, chunking) {
|
|
188
|
+
if (chunking === "none") {
|
|
189
|
+
const logFile = filters.appName ? (0, import_node_path.join)(logDir, `${filters.appName}.log`) : (0, import_node_path.join)(logDir, "app.log");
|
|
190
|
+
return (0, import_node_fs.existsSync)(logFile) ? [logFile] : [];
|
|
191
|
+
}
|
|
192
|
+
if (!filters.appName) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
const appDir = (0, import_node_path.join)(logDir, filters.appName);
|
|
196
|
+
if (!(0, import_node_fs.existsSync)(appDir)) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const allFiles = (0, import_node_fs.readdirSync)(appDir).filter((f) => f.endsWith(".log")).sort();
|
|
200
|
+
if (!(filters.from || filters.to)) {
|
|
201
|
+
return allFiles.map((f) => (0, import_node_path.join)(appDir, f));
|
|
202
|
+
}
|
|
203
|
+
return allFiles.filter((filename) => isChunkRelevant(filename, chunking, filters)).map((f) => (0, import_node_path.join)(appDir, f));
|
|
204
|
+
}
|
|
205
|
+
function isChunkRelevant(filename, chunking, filters) {
|
|
206
|
+
const chunkName = filename.replace(".log", "");
|
|
207
|
+
const range = getChunkDateRange(chunkName, chunking);
|
|
208
|
+
if (!range) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
const { start, end } = range;
|
|
212
|
+
if (filters.to && start > filters.to) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (filters.from && end < filters.from) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
function getChunkDateRange(chunkName, chunking) {
|
|
221
|
+
if (chunking === "monthly") {
|
|
222
|
+
const match2 = chunkName.match(MONTHLY_PATTERN);
|
|
223
|
+
if (!(match2?.[1] && match2[2])) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const year2 = Number(match2[1]);
|
|
227
|
+
const month = Number(match2[2]) - 1;
|
|
228
|
+
const start2 = new Date(year2, month, 1);
|
|
229
|
+
const end2 = new Date(year2, month + 1, 0, 23, 59, 59, 999);
|
|
230
|
+
return { start: start2, end: end2 };
|
|
231
|
+
}
|
|
232
|
+
if (chunking === "daily") {
|
|
233
|
+
const match2 = chunkName.match(DAILY_PATTERN);
|
|
234
|
+
if (!(match2?.[1] && match2[2] && match2[3])) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const year2 = Number(match2[1]);
|
|
238
|
+
const month = Number(match2[2]) - 1;
|
|
239
|
+
const day = Number(match2[3]);
|
|
240
|
+
const start2 = new Date(year2, month, day);
|
|
241
|
+
const end2 = new Date(year2, month, day, 23, 59, 59, 999);
|
|
242
|
+
return { start: start2, end: end2 };
|
|
243
|
+
}
|
|
244
|
+
const match = chunkName.match(WEEKLY_PATTERN);
|
|
245
|
+
if (!(match?.[1] && match[2])) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const year = Number(match[1]);
|
|
249
|
+
const week = Number(match[2]);
|
|
250
|
+
const start = getDateFromISOWeek(year, week);
|
|
251
|
+
const end = new Date(start);
|
|
252
|
+
end.setDate(end.getDate() + 6);
|
|
253
|
+
end.setHours(23, 59, 59, 999);
|
|
254
|
+
return { start, end };
|
|
255
|
+
}
|
|
256
|
+
function getDateFromISOWeek(year, week) {
|
|
257
|
+
const jan4 = new Date(year, 0, 4);
|
|
258
|
+
const dayOfWeek = jan4.getDay() || 7;
|
|
259
|
+
const monday = new Date(jan4);
|
|
260
|
+
monday.setDate(jan4.getDate() - dayOfWeek + 1);
|
|
261
|
+
monday.setDate(monday.getDate() + (week - 1) * 7);
|
|
262
|
+
monday.setHours(0, 0, 0, 0);
|
|
263
|
+
return monday;
|
|
264
|
+
}
|
|
265
|
+
function readAndParseLogFiles(files) {
|
|
266
|
+
const logs = [];
|
|
267
|
+
for (const file of files) {
|
|
268
|
+
if (!(0, import_node_fs.existsSync)(file)) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const content = (0, import_node_fs.readFileSync)(file, "utf-8").trim();
|
|
272
|
+
if (!content) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const lines = content.split("\n");
|
|
276
|
+
for (const line of lines) {
|
|
277
|
+
try {
|
|
278
|
+
const parsed = JSON.parse(line);
|
|
279
|
+
logs.push(parsed);
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return logs;
|
|
285
|
+
}
|
|
286
|
+
function applyLogFilters(logs, filters) {
|
|
287
|
+
return logs.filter((log) => {
|
|
288
|
+
if (filters.appName && log.appName !== filters.appName) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
if (filters.log_id && log.log_id !== filters.log_id) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
if (filters.level && log.level !== filters.level) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const time = new Date(log.time);
|
|
298
|
+
if (filters.from && time < filters.from) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
if (filters.to && time > filters.to) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
return true;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/logging/create-log.ts
|
|
309
|
+
var createLogger = (appName, config) => {
|
|
310
|
+
return {
|
|
311
|
+
info: (input) => createLog({ ...input, appName, level: "info" }, config),
|
|
312
|
+
warn: (input) => createLog({ ...input, appName, level: "warn" }, config),
|
|
313
|
+
error: (input) => createLog({ ...input, appName, level: "error" }, config)
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// src/nile/server.ts
|
|
318
|
+
var import_slang_ts7 = require("slang-ts");
|
|
319
|
+
|
|
320
|
+
// src/engine/engine.ts
|
|
321
|
+
var import_slang_ts4 = require("slang-ts");
|
|
322
|
+
|
|
323
|
+
// src/utils/db/create-model.ts
|
|
324
|
+
var import_drizzle_orm = require("drizzle-orm");
|
|
325
|
+
var import_slang_ts2 = require("slang-ts");
|
|
326
|
+
|
|
327
|
+
// src/utils/handle-error.ts
|
|
328
|
+
var import_slang_ts = require("slang-ts");
|
|
329
|
+
var CALLER_LINE_REGEX = /at\s+(\S+)\s+/;
|
|
330
|
+
function inferCallerName() {
|
|
331
|
+
const _err = new Error("capture stack trace");
|
|
332
|
+
const stack = _err.stack;
|
|
333
|
+
const callerLine = stack?.split("\n")[3] ?? "";
|
|
334
|
+
const match = callerLine.match(CALLER_LINE_REGEX);
|
|
335
|
+
return match?.[1] ?? "unknown";
|
|
336
|
+
}
|
|
337
|
+
function resolveLogger(explicit) {
|
|
338
|
+
if (explicit) {
|
|
339
|
+
return explicit;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const ctx = getContext();
|
|
343
|
+
if (ctx.resources?.logger) {
|
|
344
|
+
return ctx.resources.logger;
|
|
345
|
+
}
|
|
346
|
+
} catch (_) {
|
|
347
|
+
}
|
|
348
|
+
throw new Error(
|
|
349
|
+
"handleError: No logger available. Provide a logger param or set resources.logger on server config."
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
function handleError(params) {
|
|
353
|
+
const atFunction = params.atFunction ?? inferCallerName();
|
|
354
|
+
const logger = resolveLogger(params.logger);
|
|
355
|
+
const logId = logger.error({
|
|
356
|
+
atFunction,
|
|
357
|
+
message: params.message,
|
|
358
|
+
data: params.data
|
|
359
|
+
});
|
|
360
|
+
return (0, import_slang_ts.Err)(`[${logId}] ${params.message}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/utils/db/create-transaction-variant.ts
|
|
364
|
+
function createTransactionVariant(fn) {
|
|
365
|
+
return async (params) => {
|
|
366
|
+
const { dbx, ...rest } = params;
|
|
367
|
+
if (!dbx) {
|
|
368
|
+
const result2 = await fn(params);
|
|
369
|
+
if (result2.isErr) {
|
|
370
|
+
throw new Error(String(result2.error));
|
|
371
|
+
}
|
|
372
|
+
return result2;
|
|
373
|
+
}
|
|
374
|
+
const hasTransaction = typeof dbx?.transaction === "function";
|
|
375
|
+
if (hasTransaction) {
|
|
376
|
+
return await dbx.transaction(async (tx) => {
|
|
377
|
+
const result2 = await fn({ ...rest, dbx: tx });
|
|
378
|
+
if (result2.isErr) {
|
|
379
|
+
throw new Error(String(result2.error));
|
|
380
|
+
}
|
|
381
|
+
return result2;
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const result = await fn({ ...rest, dbx });
|
|
385
|
+
if (result.isErr) {
|
|
386
|
+
throw new Error(String(result.error));
|
|
387
|
+
}
|
|
388
|
+
return result;
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/utils/db/get-zod-schema.ts
|
|
393
|
+
var import_drizzle_zod = require("drizzle-zod");
|
|
394
|
+
function getZodSchema(table) {
|
|
395
|
+
const isRelation = Object.hasOwn(table, "config") && Object.hasOwn(table, "table");
|
|
396
|
+
if (isRelation) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`${String(table)} is a relation schema, not a table schema`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const insertSchema = (0, import_drizzle_zod.createInsertSchema)(
|
|
402
|
+
table
|
|
403
|
+
);
|
|
404
|
+
const updateSchema = (0, import_drizzle_zod.createUpdateSchema)(
|
|
405
|
+
table
|
|
406
|
+
);
|
|
407
|
+
const selectSchema = (0, import_drizzle_zod.createSelectSchema)(
|
|
408
|
+
table
|
|
409
|
+
);
|
|
410
|
+
return {
|
|
411
|
+
insert: insertSchema,
|
|
412
|
+
update: updateSchema,
|
|
413
|
+
select: selectSchema
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/utils/db/create-model.ts
|
|
418
|
+
function asDb(db) {
|
|
419
|
+
return db;
|
|
420
|
+
}
|
|
421
|
+
function resolveDb(explicitDb) {
|
|
422
|
+
if (explicitDb) {
|
|
423
|
+
return explicitDb;
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const ctx = getContext();
|
|
427
|
+
if (ctx.resources?.database) {
|
|
428
|
+
return ctx.resources.database;
|
|
429
|
+
}
|
|
430
|
+
} catch (_) {
|
|
431
|
+
}
|
|
432
|
+
throw new Error(
|
|
433
|
+
"createModel: No database available. Pass db in ModelOptions or set resources.database on server config."
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
function findTimestampColumn(table) {
|
|
437
|
+
if ("created_at" in table) {
|
|
438
|
+
return "created_at";
|
|
439
|
+
}
|
|
440
|
+
if ("createdAt" in table) {
|
|
441
|
+
return "createdAt";
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
function createModel(table, options) {
|
|
446
|
+
const { name, cursorColumn = "id" } = options;
|
|
447
|
+
const entityName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
448
|
+
const schemas = getZodSchema(table);
|
|
449
|
+
const tableRef = table;
|
|
450
|
+
const getDb = () => asDb(resolveDb(options.db));
|
|
451
|
+
const create = async ({ data, dbx }) => {
|
|
452
|
+
const parsed = schemas.insert.safeParse(data);
|
|
453
|
+
if (!parsed.success) {
|
|
454
|
+
return handleError({
|
|
455
|
+
message: `Invalid ${name} data`,
|
|
456
|
+
data: { errors: parsed.error },
|
|
457
|
+
atFunction: `${name}.create`
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
const db = dbx ? asDb(dbx) : getDb();
|
|
461
|
+
const result = await (0, import_slang_ts2.safeTry)(
|
|
462
|
+
() => db.insert(table).values(data).returning()
|
|
463
|
+
);
|
|
464
|
+
if (result.isErr) {
|
|
465
|
+
return handleError({
|
|
466
|
+
message: `Error creating ${name}`,
|
|
467
|
+
data: { error: result.error },
|
|
468
|
+
atFunction: `${name}.create`
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const row = result.value?.[0] ?? null;
|
|
472
|
+
if (!row) {
|
|
473
|
+
return handleError({
|
|
474
|
+
message: `${entityName} creation returned no data`,
|
|
475
|
+
atFunction: `${name}.create`
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return (0, import_slang_ts2.Ok)(row);
|
|
479
|
+
};
|
|
480
|
+
const update = async ({ id, data, dbx }) => {
|
|
481
|
+
const parsed = schemas.update.safeParse(data);
|
|
482
|
+
if (!parsed.success) {
|
|
483
|
+
return handleError({
|
|
484
|
+
message: `Invalid ${name} data`,
|
|
485
|
+
data: { errors: parsed.error },
|
|
486
|
+
atFunction: `${name}.update`
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const db = dbx ? asDb(dbx) : getDb();
|
|
490
|
+
const idCol = tableRef.id;
|
|
491
|
+
const result = await (0, import_slang_ts2.safeTry)(
|
|
492
|
+
() => db.update(table).set(data).where((0, import_drizzle_orm.eq)(idCol, id)).returning()
|
|
493
|
+
);
|
|
494
|
+
if (result.isErr) {
|
|
495
|
+
return handleError({
|
|
496
|
+
message: `Error updating ${name}`,
|
|
497
|
+
data: { id, error: result.error },
|
|
498
|
+
atFunction: `${name}.update`
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const row = result.value?.[0] ?? null;
|
|
502
|
+
if (!row) {
|
|
503
|
+
return handleError({
|
|
504
|
+
message: `${entityName} not found`,
|
|
505
|
+
data: { id },
|
|
506
|
+
atFunction: `${name}.update`
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return (0, import_slang_ts2.Ok)(row);
|
|
510
|
+
};
|
|
511
|
+
const createTx = createTransactionVariant(
|
|
512
|
+
create
|
|
513
|
+
);
|
|
514
|
+
const updateTx = createTransactionVariant(
|
|
515
|
+
update
|
|
516
|
+
);
|
|
517
|
+
const findById = async (id) => {
|
|
518
|
+
const db = getDb();
|
|
519
|
+
const idCol = tableRef.id;
|
|
520
|
+
const result = await (0, import_slang_ts2.safeTry)(
|
|
521
|
+
() => db.select().from(table).where((0, import_drizzle_orm.eq)(idCol, id))
|
|
522
|
+
);
|
|
523
|
+
if (result.isErr) {
|
|
524
|
+
return handleError({
|
|
525
|
+
message: `Error getting ${name}`,
|
|
526
|
+
data: { id, error: result.error },
|
|
527
|
+
atFunction: `${name}.findById`
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
const row = result.value?.[0] ?? null;
|
|
531
|
+
if (!row) {
|
|
532
|
+
return handleError({
|
|
533
|
+
message: `${entityName} not found`,
|
|
534
|
+
data: { id },
|
|
535
|
+
atFunction: `${name}.findById`
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return (0, import_slang_ts2.Ok)(row);
|
|
539
|
+
};
|
|
540
|
+
const deleteFn = async (id) => {
|
|
541
|
+
const db = getDb();
|
|
542
|
+
const idCol = tableRef.id;
|
|
543
|
+
const result = await (0, import_slang_ts2.safeTry)(
|
|
544
|
+
() => db.delete(table).where((0, import_drizzle_orm.eq)(idCol, id)).returning()
|
|
545
|
+
);
|
|
546
|
+
if (result.isErr) {
|
|
547
|
+
return handleError({
|
|
548
|
+
message: `Error deleting ${name}`,
|
|
549
|
+
data: { id, error: result.error },
|
|
550
|
+
atFunction: `${name}.delete`
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const row = result.value?.[0] ?? null;
|
|
554
|
+
if (!row) {
|
|
555
|
+
return handleError({
|
|
556
|
+
message: `${entityName} not found`,
|
|
557
|
+
data: { id },
|
|
558
|
+
atFunction: `${name}.delete`
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
return (0, import_slang_ts2.Ok)(row);
|
|
562
|
+
};
|
|
563
|
+
const findAll = async () => {
|
|
564
|
+
const db = getDb();
|
|
565
|
+
const tsCol = findTimestampColumn(tableRef);
|
|
566
|
+
const result = await (0, import_slang_ts2.safeTry)(() => {
|
|
567
|
+
const query = db.select().from(table);
|
|
568
|
+
if (!tsCol) {
|
|
569
|
+
return query;
|
|
570
|
+
}
|
|
571
|
+
return query.orderBy((0, import_drizzle_orm.desc)(tableRef[tsCol]));
|
|
572
|
+
});
|
|
573
|
+
if (result.isErr) {
|
|
574
|
+
return handleError({
|
|
575
|
+
message: `Error getting all ${name}s`,
|
|
576
|
+
data: { error: result.error },
|
|
577
|
+
atFunction: `${name}.findAll`
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
return (0, import_slang_ts2.Ok)(result.value ?? []);
|
|
581
|
+
};
|
|
582
|
+
const findOffsetPage = async (limit, offset) => {
|
|
583
|
+
const db = getDb();
|
|
584
|
+
const tsCol = findTimestampColumn(tableRef);
|
|
585
|
+
const itemsResult = await (0, import_slang_ts2.safeTry)(() => {
|
|
586
|
+
const query = db.select().from(table);
|
|
587
|
+
const ordered = tsCol ? query.orderBy((0, import_drizzle_orm.desc)(tableRef[tsCol])) : query;
|
|
588
|
+
return ordered.limit(limit).offset(offset);
|
|
589
|
+
});
|
|
590
|
+
if (itemsResult.isErr) {
|
|
591
|
+
return handleError({
|
|
592
|
+
message: `Error getting paginated ${name}s`,
|
|
593
|
+
data: { limit, offset, error: itemsResult.error },
|
|
594
|
+
atFunction: `${name}.findPaginated`
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
const countResult = await (0, import_slang_ts2.safeTry)(
|
|
598
|
+
() => db.select({ total: (0, import_drizzle_orm.count)() }).from(table)
|
|
599
|
+
);
|
|
600
|
+
if (countResult.isErr) {
|
|
601
|
+
return handleError({
|
|
602
|
+
message: `Error getting ${name} count`,
|
|
603
|
+
data: { error: countResult.error },
|
|
604
|
+
atFunction: `${name}.findPaginated`
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const items = itemsResult.value ?? [];
|
|
608
|
+
const total = countResult.value?.[0]?.total ?? 0;
|
|
609
|
+
return (0, import_slang_ts2.Ok)({
|
|
610
|
+
items,
|
|
611
|
+
total,
|
|
612
|
+
hasMore: offset + items.length < total
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
const findCursorPage = async (limit, cursor, colName) => {
|
|
616
|
+
const db = getDb();
|
|
617
|
+
const column = tableRef[colName];
|
|
618
|
+
if (!column) {
|
|
619
|
+
return handleError({
|
|
620
|
+
message: `Cursor column '${colName}' does not exist on ${name} table`,
|
|
621
|
+
atFunction: `${name}.findPaginated`
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
const typedColumn = column;
|
|
625
|
+
const result = await (0, import_slang_ts2.safeTry)(
|
|
626
|
+
() => db.select().from(table).where((0, import_drizzle_orm.lt)(typedColumn, cursor)).orderBy((0, import_drizzle_orm.desc)(typedColumn)).limit(limit + 1)
|
|
627
|
+
);
|
|
628
|
+
if (result.isErr) {
|
|
629
|
+
return handleError({
|
|
630
|
+
message: `Error getting paginated ${name}s`,
|
|
631
|
+
data: { cursor, cursorColumn: colName, error: result.error },
|
|
632
|
+
atFunction: `${name}.findPaginated`
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
const rows = result.value ?? [];
|
|
636
|
+
const hasMore = rows.length > limit;
|
|
637
|
+
const items = hasMore ? rows.slice(0, limit) : rows;
|
|
638
|
+
const lastItem = items.at(-1);
|
|
639
|
+
const nextCursor = lastItem ? String(lastItem[colName] ?? "") || null : null;
|
|
640
|
+
return (0, import_slang_ts2.Ok)({
|
|
641
|
+
items,
|
|
642
|
+
nextCursor,
|
|
643
|
+
hasMore
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
const findPaginated = (opts = {}) => {
|
|
647
|
+
const limit = opts.limit ?? 50;
|
|
648
|
+
if ("cursor" in opts && opts.cursor) {
|
|
649
|
+
const colName = opts.cursorColumn ?? cursorColumn;
|
|
650
|
+
return findCursorPage(limit, opts.cursor, colName);
|
|
651
|
+
}
|
|
652
|
+
const offset = opts.offset ?? 0;
|
|
653
|
+
return findOffsetPage(limit, offset);
|
|
654
|
+
};
|
|
655
|
+
return {
|
|
656
|
+
create,
|
|
657
|
+
createTx,
|
|
658
|
+
findById,
|
|
659
|
+
update,
|
|
660
|
+
updateTx,
|
|
661
|
+
delete: deleteFn,
|
|
662
|
+
findAll,
|
|
663
|
+
findPaginated,
|
|
664
|
+
table,
|
|
665
|
+
schemas
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/utils/diagnostics-log.ts
|
|
670
|
+
function isNileLogger(logger) {
|
|
671
|
+
return "warn" in logger && "error" in logger;
|
|
672
|
+
}
|
|
673
|
+
function createDiagnosticsLog(prefix, params) {
|
|
674
|
+
if (!params.diagnostics) {
|
|
675
|
+
return () => {
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const { logger } = params;
|
|
679
|
+
return (message, data) => {
|
|
680
|
+
if (!logger) {
|
|
681
|
+
console.log(`[${prefix}] ${message}`, data ?? "");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (isNileLogger(logger)) {
|
|
685
|
+
logger.info({
|
|
686
|
+
atFunction: prefix,
|
|
687
|
+
message: `[${prefix}] ${message}`,
|
|
688
|
+
data
|
|
689
|
+
});
|
|
690
|
+
} else {
|
|
691
|
+
logger.info(`[${prefix}] ${message}`, data);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/engine/pipeline.ts
|
|
697
|
+
var import_slang_ts3 = require("slang-ts");
|
|
698
|
+
var import_zod = require("zod");
|
|
699
|
+
async function runHook(hookDef, hookAction, input, nileContext) {
|
|
700
|
+
const result = await (0, import_slang_ts3.safeTry)(
|
|
701
|
+
() => hookAction.handler(input, nileContext)
|
|
702
|
+
);
|
|
703
|
+
return {
|
|
704
|
+
result,
|
|
705
|
+
logEntry: {
|
|
706
|
+
name: `${hookDef.service}.${hookDef.action}`,
|
|
707
|
+
input,
|
|
708
|
+
output: result.isOk ? result.value : result.error,
|
|
709
|
+
passed: result.isOk
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
async function processHooks(hooks, initialValue, getAction, nileContext, logTarget, log) {
|
|
714
|
+
let currentValue = initialValue;
|
|
715
|
+
for (const hookDef of hooks) {
|
|
716
|
+
const hookActionResult = getAction(hookDef.service, hookDef.action);
|
|
717
|
+
if (hookActionResult.isErr) {
|
|
718
|
+
const errorMsg = `${logTarget} hook '${hookDef.service}.${hookDef.action}' not found`;
|
|
719
|
+
log(errorMsg);
|
|
720
|
+
if (hookDef.isCritical) {
|
|
721
|
+
nileContext.setHookError(errorMsg);
|
|
722
|
+
return (0, import_slang_ts3.Err)(errorMsg);
|
|
723
|
+
}
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
const { result, logEntry } = await runHook(
|
|
727
|
+
hookDef,
|
|
728
|
+
hookActionResult.value,
|
|
729
|
+
currentValue,
|
|
730
|
+
nileContext
|
|
731
|
+
);
|
|
732
|
+
nileContext.addHookLog(logTarget, logEntry);
|
|
733
|
+
if (result.isErr) {
|
|
734
|
+
const errorMsg = String(result.error);
|
|
735
|
+
log(
|
|
736
|
+
`${logTarget} hook '${hookDef.service}.${hookDef.action}' failed`,
|
|
737
|
+
result.error
|
|
738
|
+
);
|
|
739
|
+
if (hookDef.isCritical) {
|
|
740
|
+
nileContext.setHookError(errorMsg);
|
|
741
|
+
return (0, import_slang_ts3.Err)(errorMsg);
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
currentValue = result.value;
|
|
746
|
+
}
|
|
747
|
+
return (0, import_slang_ts3.Ok)(currentValue);
|
|
748
|
+
}
|
|
749
|
+
async function runGlobalBeforeHook(handler, nileContext, action, payload, log) {
|
|
750
|
+
if (!handler) {
|
|
751
|
+
return (0, import_slang_ts3.Ok)(true);
|
|
752
|
+
}
|
|
753
|
+
const result = await (0, import_slang_ts3.safeTry)(() => handler({ nileContext, action, payload }));
|
|
754
|
+
if (result.isErr) {
|
|
755
|
+
log(`Global before hook failed for ${action.name}`);
|
|
756
|
+
nileContext.setHookError(result.error);
|
|
757
|
+
return (0, import_slang_ts3.Err)(result.error);
|
|
758
|
+
}
|
|
759
|
+
return (0, import_slang_ts3.Ok)(true);
|
|
760
|
+
}
|
|
761
|
+
async function runGlobalAfterHook(handler, nileContext, action, payload, currentResult, log) {
|
|
762
|
+
if (!handler) {
|
|
763
|
+
return (0, import_slang_ts3.Ok)(currentResult);
|
|
764
|
+
}
|
|
765
|
+
const result = await (0, import_slang_ts3.safeTry)(
|
|
766
|
+
() => handler({
|
|
767
|
+
nileContext,
|
|
768
|
+
action,
|
|
769
|
+
payload,
|
|
770
|
+
result: (0, import_slang_ts3.Ok)(currentResult)
|
|
771
|
+
})
|
|
772
|
+
);
|
|
773
|
+
if (result.isErr) {
|
|
774
|
+
log(`Global after hook failed for ${action.name}`);
|
|
775
|
+
nileContext.setHookError(result.error);
|
|
776
|
+
return (0, import_slang_ts3.Err)(result.error);
|
|
777
|
+
}
|
|
778
|
+
return (0, import_slang_ts3.Ok)(result.value);
|
|
779
|
+
}
|
|
780
|
+
function validatePayload(action, payload, nileContext, log) {
|
|
781
|
+
if (!action.validation) {
|
|
782
|
+
return (0, import_slang_ts3.Ok)(payload);
|
|
783
|
+
}
|
|
784
|
+
const parseResult = action.validation.safeParse(payload);
|
|
785
|
+
if (!parseResult.success) {
|
|
786
|
+
const validationError = (0, import_zod.prettifyError)(parseResult.error);
|
|
787
|
+
log(`Validation failed for ${action.name}`, validationError);
|
|
788
|
+
nileContext.setHookError(validationError);
|
|
789
|
+
return (0, import_slang_ts3.Err)(`Validation failed: ${validationError}`);
|
|
790
|
+
}
|
|
791
|
+
return (0, import_slang_ts3.Ok)(parseResult.data);
|
|
792
|
+
}
|
|
793
|
+
async function runHandler(action, payload, nileContext, log) {
|
|
794
|
+
const result = await (0, import_slang_ts3.safeTry)(
|
|
795
|
+
() => action.handler(payload, nileContext)
|
|
796
|
+
);
|
|
797
|
+
if (result.isErr) {
|
|
798
|
+
log(`Handler failed for ${action.name}`, result.error);
|
|
799
|
+
nileContext.setHookError(result.error);
|
|
800
|
+
return (0, import_slang_ts3.Err)(result.error);
|
|
801
|
+
}
|
|
802
|
+
nileContext.setHookOutput(result.value);
|
|
803
|
+
return (0, import_slang_ts3.Ok)(result.value);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// src/engine/engine.ts
|
|
807
|
+
function createEngine(options) {
|
|
808
|
+
const { diagnostics, services, logger } = options;
|
|
809
|
+
const log = createDiagnosticsLog("Engine", {
|
|
810
|
+
diagnostics,
|
|
811
|
+
logger
|
|
812
|
+
});
|
|
813
|
+
const serviceSummaries = [];
|
|
814
|
+
const serviceActionsStore = {};
|
|
815
|
+
const actionStore = {};
|
|
816
|
+
const initStartTime = performance.now();
|
|
817
|
+
for (const service of services) {
|
|
818
|
+
const actionNames = [];
|
|
819
|
+
serviceActionsStore[service.name] = [];
|
|
820
|
+
actionStore[service.name] = {};
|
|
821
|
+
for (const action of service.actions) {
|
|
822
|
+
actionNames.push(action.name);
|
|
823
|
+
serviceActionsStore[service.name]?.push({
|
|
824
|
+
name: action.name,
|
|
825
|
+
description: action.description,
|
|
826
|
+
isProtected: !!action.isProtected,
|
|
827
|
+
validation: !!action.validation,
|
|
828
|
+
accessControl: action.accessControl || []
|
|
829
|
+
});
|
|
830
|
+
const serviceActions = actionStore[service.name];
|
|
831
|
+
if (serviceActions) {
|
|
832
|
+
serviceActions[action.name] = action;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
serviceSummaries.push({
|
|
836
|
+
name: service.name,
|
|
837
|
+
description: service.description,
|
|
838
|
+
meta: service.meta,
|
|
839
|
+
actions: actionNames
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
log(
|
|
843
|
+
`Initialized in ${performance.now() - initStartTime}ms. Loaded ${services.length} services.`
|
|
844
|
+
);
|
|
845
|
+
const getServices = () => (0, import_slang_ts4.Ok)(serviceSummaries);
|
|
846
|
+
const getServiceActions = (serviceName) => {
|
|
847
|
+
const actions = serviceActionsStore[serviceName];
|
|
848
|
+
return actions ? (0, import_slang_ts4.Ok)(actions) : (0, import_slang_ts4.Err)(`Service '${serviceName}' not found`);
|
|
849
|
+
};
|
|
850
|
+
const getAction = (serviceName, actionName) => {
|
|
851
|
+
const serviceMap = actionStore[serviceName];
|
|
852
|
+
if (!serviceMap) {
|
|
853
|
+
return (0, import_slang_ts4.Err)(`Service '${serviceName}' not found`);
|
|
854
|
+
}
|
|
855
|
+
const action = serviceMap[actionName];
|
|
856
|
+
return action ? (0, import_slang_ts4.Ok)(action) : (0, import_slang_ts4.Err)(`Action '${actionName}' not found in service '${serviceName}'`);
|
|
857
|
+
};
|
|
858
|
+
const executeAction = async (serviceName, actionName, payload, nileContext) => {
|
|
859
|
+
const { onBeforeActionHandler, onAfterActionHandler } = options;
|
|
860
|
+
const actionResult = getAction(serviceName, actionName);
|
|
861
|
+
if (actionResult.isErr) {
|
|
862
|
+
return (0, import_slang_ts4.Err)(actionResult.error);
|
|
863
|
+
}
|
|
864
|
+
const action = actionResult.value;
|
|
865
|
+
nileContext.resetHookContext(`${serviceName}.${actionName}`, payload);
|
|
866
|
+
const globalBeforeResult = await runGlobalBeforeHook(
|
|
867
|
+
onBeforeActionHandler,
|
|
868
|
+
nileContext,
|
|
869
|
+
action,
|
|
870
|
+
payload,
|
|
871
|
+
log
|
|
872
|
+
);
|
|
873
|
+
if (globalBeforeResult.isErr) {
|
|
874
|
+
return (0, import_slang_ts4.Err)(globalBeforeResult.error);
|
|
875
|
+
}
|
|
876
|
+
const beforeHooksResult = await processHooks(
|
|
877
|
+
action.hooks?.before ?? [],
|
|
878
|
+
payload,
|
|
879
|
+
getAction,
|
|
880
|
+
nileContext,
|
|
881
|
+
"before",
|
|
882
|
+
log
|
|
883
|
+
);
|
|
884
|
+
if (beforeHooksResult.isErr) {
|
|
885
|
+
return (0, import_slang_ts4.Err)(beforeHooksResult.error);
|
|
886
|
+
}
|
|
887
|
+
const validationResult = validatePayload(
|
|
888
|
+
action,
|
|
889
|
+
beforeHooksResult.value,
|
|
890
|
+
nileContext,
|
|
891
|
+
log
|
|
892
|
+
);
|
|
893
|
+
if (validationResult.isErr) {
|
|
894
|
+
return (0, import_slang_ts4.Err)(validationResult.error);
|
|
895
|
+
}
|
|
896
|
+
const handlerResult = await runHandler(
|
|
897
|
+
action,
|
|
898
|
+
validationResult.value,
|
|
899
|
+
nileContext,
|
|
900
|
+
log
|
|
901
|
+
);
|
|
902
|
+
if (handlerResult.isErr) {
|
|
903
|
+
return (0, import_slang_ts4.Err)(handlerResult.error);
|
|
904
|
+
}
|
|
905
|
+
const afterHooksResult = await processHooks(
|
|
906
|
+
action.hooks?.after ?? [],
|
|
907
|
+
handlerResult.value,
|
|
908
|
+
getAction,
|
|
909
|
+
nileContext,
|
|
910
|
+
"after",
|
|
911
|
+
log
|
|
912
|
+
);
|
|
913
|
+
if (afterHooksResult.isErr) {
|
|
914
|
+
return (0, import_slang_ts4.Err)(afterHooksResult.error);
|
|
915
|
+
}
|
|
916
|
+
const globalAfterResult = await runGlobalAfterHook(
|
|
917
|
+
onAfterActionHandler,
|
|
918
|
+
nileContext,
|
|
919
|
+
action,
|
|
920
|
+
validationResult.value,
|
|
921
|
+
afterHooksResult.value,
|
|
922
|
+
log
|
|
923
|
+
);
|
|
924
|
+
if (globalAfterResult.isErr) {
|
|
925
|
+
return (0, import_slang_ts4.Err)(globalAfterResult.error);
|
|
926
|
+
}
|
|
927
|
+
return action.result?.pipeline ? (0, import_slang_ts4.Ok)({
|
|
928
|
+
data: globalAfterResult.value,
|
|
929
|
+
pipeline: nileContext.hookContext.log
|
|
930
|
+
}) : (0, import_slang_ts4.Ok)(globalAfterResult.value);
|
|
931
|
+
};
|
|
932
|
+
return {
|
|
933
|
+
getServices,
|
|
934
|
+
getServiceActions,
|
|
935
|
+
getAction,
|
|
936
|
+
executeAction
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// src/rest/rest.ts
|
|
941
|
+
var import_hono = require("hono");
|
|
942
|
+
var import_zod3 = __toESM(require("zod"), 1);
|
|
943
|
+
|
|
944
|
+
// src/cors/cors.ts
|
|
945
|
+
var import_cors = require("hono/cors");
|
|
946
|
+
var buildDefaultCorsOptions = (config) => {
|
|
947
|
+
const getDefaultOrigin = (reqOrigin) => {
|
|
948
|
+
if (config.allowedOrigins.length > 0) {
|
|
949
|
+
return config.allowedOrigins.includes(reqOrigin ?? "") ? reqOrigin : "";
|
|
950
|
+
}
|
|
951
|
+
return "*";
|
|
952
|
+
};
|
|
953
|
+
return {
|
|
954
|
+
origin: config.cors?.defaults?.origin ?? getDefaultOrigin,
|
|
955
|
+
credentials: config.cors?.defaults?.credentials ?? true,
|
|
956
|
+
allowHeaders: config.cors?.defaults?.allowHeaders ?? [
|
|
957
|
+
"Content-Type",
|
|
958
|
+
"Authorization"
|
|
959
|
+
],
|
|
960
|
+
allowMethods: config.cors?.defaults?.allowMethods ?? [
|
|
961
|
+
"POST",
|
|
962
|
+
"GET",
|
|
963
|
+
"OPTIONS"
|
|
964
|
+
],
|
|
965
|
+
exposeHeaders: config.cors?.defaults?.exposeHeaders ?? ["Content-Length"],
|
|
966
|
+
maxAge: config.cors?.defaults?.maxAge ?? 600
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
var applyCorsConfig = (app, config) => {
|
|
970
|
+
const corsEnabled = config.cors?.enabled ?? "default";
|
|
971
|
+
if (corsEnabled === false) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const defaultCorsOpts = buildDefaultCorsOptions(config);
|
|
975
|
+
const corsRules = config.cors?.addCors ?? [];
|
|
976
|
+
for (const rule of corsRules) {
|
|
977
|
+
applyRouteCorsRule(app, rule, defaultCorsOpts);
|
|
978
|
+
}
|
|
979
|
+
app.use("*", (0, import_cors.cors)(defaultCorsOpts));
|
|
980
|
+
};
|
|
981
|
+
var applyRouteCorsRule = (app, rule, defaultOpts) => {
|
|
982
|
+
if (rule.resolver) {
|
|
983
|
+
applyResolverBasedCors(app, rule.path, rule.resolver, defaultOpts);
|
|
984
|
+
} else if (rule.options) {
|
|
985
|
+
applyStaticCors(app, rule.path, rule.options, defaultOpts);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
var applyResolverBasedCors = (app, path, resolver, defaultOpts) => {
|
|
989
|
+
if (!resolver) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
app.use(path, (c, next) => {
|
|
993
|
+
const reqOrigin = c.req.header("origin") ?? "";
|
|
994
|
+
const corsOpts = evaluateResolver(resolver, reqOrigin, c, defaultOpts);
|
|
995
|
+
return (0, import_cors.cors)(corsOpts)(c, next);
|
|
996
|
+
});
|
|
997
|
+
};
|
|
998
|
+
var applyStaticCors = (app, path, options, defaultOpts) => {
|
|
999
|
+
app.use(
|
|
1000
|
+
path,
|
|
1001
|
+
(0, import_cors.cors)({ ...defaultOpts, ...options })
|
|
1002
|
+
);
|
|
1003
|
+
};
|
|
1004
|
+
var evaluateResolver = (resolver, origin, c, defaultOpts) => {
|
|
1005
|
+
if (!resolver) {
|
|
1006
|
+
return defaultOpts;
|
|
1007
|
+
}
|
|
1008
|
+
try {
|
|
1009
|
+
const result = resolver(origin, c);
|
|
1010
|
+
if (result === true) {
|
|
1011
|
+
return { ...defaultOpts, origin: origin || "*" };
|
|
1012
|
+
}
|
|
1013
|
+
if (result === false) {
|
|
1014
|
+
return { ...defaultOpts, origin: "" };
|
|
1015
|
+
}
|
|
1016
|
+
if (result && typeof result === "object") {
|
|
1017
|
+
return { ...defaultOpts, ...result };
|
|
1018
|
+
}
|
|
1019
|
+
return defaultOpts;
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error("CORS resolver error:", error);
|
|
1022
|
+
return { ...defaultOpts, origin: "" };
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
// src/rest/intent-handlers.ts
|
|
1027
|
+
var import_slang_ts5 = require("slang-ts");
|
|
1028
|
+
var import_zod2 = __toESM(require("zod"), 1);
|
|
1029
|
+
function toExternalResponse(result, successMessage) {
|
|
1030
|
+
if (result.isOk) {
|
|
1031
|
+
const value = result.value;
|
|
1032
|
+
const data = value !== null && typeof value === "object" && !Array.isArray(value) ? value : { result: value };
|
|
1033
|
+
return { status: true, message: successMessage, data };
|
|
1034
|
+
}
|
|
1035
|
+
return { status: false, message: result.error, data: {} };
|
|
1036
|
+
}
|
|
1037
|
+
function handleExplore(engine, request) {
|
|
1038
|
+
const { service, action } = request;
|
|
1039
|
+
if (service === "*") {
|
|
1040
|
+
return toExternalResponse(engine.getServices(), "Available services");
|
|
1041
|
+
}
|
|
1042
|
+
if (action === "*") {
|
|
1043
|
+
return toExternalResponse(
|
|
1044
|
+
engine.getServiceActions(service),
|
|
1045
|
+
`Actions for '${service}'`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
const actionResult = engine.getAction(service, action);
|
|
1049
|
+
if (actionResult.isErr) {
|
|
1050
|
+
return toExternalResponse(actionResult, "");
|
|
1051
|
+
}
|
|
1052
|
+
const act = actionResult.value;
|
|
1053
|
+
return toExternalResponse(
|
|
1054
|
+
(0, import_slang_ts5.Ok)({
|
|
1055
|
+
name: act.name,
|
|
1056
|
+
description: act.description,
|
|
1057
|
+
isProtected: act.isProtected ?? false,
|
|
1058
|
+
accessControl: act.accessControl,
|
|
1059
|
+
hooks: act.hooks ? { before: act.hooks.before ?? [], after: act.hooks.after ?? [] } : null,
|
|
1060
|
+
meta: act.meta ?? null
|
|
1061
|
+
}),
|
|
1062
|
+
`Details for '${service}.${action}'`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
async function handleExecute(engine, request, nileContext) {
|
|
1066
|
+
const { service, action, payload } = request;
|
|
1067
|
+
if (service === "*" || action === "*") {
|
|
1068
|
+
return {
|
|
1069
|
+
status: false,
|
|
1070
|
+
message: "Execute intent requires specific service and action, wildcards not allowed",
|
|
1071
|
+
data: {}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
const result = await engine.executeAction(
|
|
1075
|
+
service,
|
|
1076
|
+
action,
|
|
1077
|
+
payload,
|
|
1078
|
+
nileContext
|
|
1079
|
+
);
|
|
1080
|
+
return toExternalResponse(result, `Action '${service}.${action}' executed`);
|
|
1081
|
+
}
|
|
1082
|
+
function handleSchema(engine, request) {
|
|
1083
|
+
const { service, action } = request;
|
|
1084
|
+
if (service === "*") {
|
|
1085
|
+
const servicesResult = engine.getServices();
|
|
1086
|
+
if (servicesResult.isErr) {
|
|
1087
|
+
return toExternalResponse(servicesResult, "");
|
|
1088
|
+
}
|
|
1089
|
+
const schemas = {};
|
|
1090
|
+
for (const svc of servicesResult.value) {
|
|
1091
|
+
const actionsResult = engine.getServiceActions(svc.name);
|
|
1092
|
+
if (actionsResult.isErr) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
schemas[svc.name] = buildServiceSchemas(
|
|
1096
|
+
engine,
|
|
1097
|
+
svc.name,
|
|
1098
|
+
actionsResult.value.map((a) => a.name)
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
return toExternalResponse((0, import_slang_ts5.Ok)(schemas), "All service schemas");
|
|
1102
|
+
}
|
|
1103
|
+
if (action === "*") {
|
|
1104
|
+
const actionsResult = engine.getServiceActions(service);
|
|
1105
|
+
if (actionsResult.isErr) {
|
|
1106
|
+
return toExternalResponse(actionsResult, "");
|
|
1107
|
+
}
|
|
1108
|
+
const schemas = buildServiceSchemas(
|
|
1109
|
+
engine,
|
|
1110
|
+
service,
|
|
1111
|
+
actionsResult.value.map((a) => a.name)
|
|
1112
|
+
);
|
|
1113
|
+
return toExternalResponse((0, import_slang_ts5.Ok)(schemas), `Schemas for '${service}'`);
|
|
1114
|
+
}
|
|
1115
|
+
const actionResult = engine.getAction(service, action);
|
|
1116
|
+
if (actionResult.isErr) {
|
|
1117
|
+
return toExternalResponse(actionResult, "");
|
|
1118
|
+
}
|
|
1119
|
+
const schema = extractActionSchema(actionResult.value);
|
|
1120
|
+
return toExternalResponse(
|
|
1121
|
+
(0, import_slang_ts5.Ok)({ [action]: schema }),
|
|
1122
|
+
`Schema for '${service}.${action}'`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
function buildServiceSchemas(engine, serviceName, actionNames) {
|
|
1126
|
+
const schemas = {};
|
|
1127
|
+
for (const actionName of actionNames) {
|
|
1128
|
+
const actionResult = engine.getAction(serviceName, actionName);
|
|
1129
|
+
if (actionResult.isErr) {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
schemas[actionName] = extractActionSchema(actionResult.value);
|
|
1133
|
+
}
|
|
1134
|
+
return schemas;
|
|
1135
|
+
}
|
|
1136
|
+
function extractActionSchema(action) {
|
|
1137
|
+
const schema = action.validation;
|
|
1138
|
+
if (!schema) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
const { err, result } = safeTrySync(
|
|
1142
|
+
() => import_zod2.default.toJSONSchema(schema, { unrepresentable: "any" })
|
|
1143
|
+
);
|
|
1144
|
+
return err ? null : result;
|
|
1145
|
+
}
|
|
1146
|
+
function safeTrySync(fn) {
|
|
1147
|
+
try {
|
|
1148
|
+
return { err: null, result: fn() };
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
return { err: error, result: null };
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
var intentHandlers = {
|
|
1154
|
+
explore: (engine, request) => handleExplore(engine, request),
|
|
1155
|
+
execute: (engine, request, nileContext) => handleExecute(engine, request, nileContext),
|
|
1156
|
+
schema: (engine, request) => handleSchema(engine, request)
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
// src/rest/middleware.ts
|
|
1160
|
+
var import_hono_rate_limiter = require("hono-rate-limiter");
|
|
1161
|
+
var import_slang_ts6 = require("slang-ts");
|
|
1162
|
+
var ASSETS_REGEX = /^\/assets\//;
|
|
1163
|
+
var DEFAULT_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
|
|
1164
|
+
var DEFAULT_RATE_LIMIT_MAX = 100;
|
|
1165
|
+
var UNKNOWN_CLIENT_KEY = "__unknown_client__";
|
|
1166
|
+
function applyRateLimiting(app, config, log) {
|
|
1167
|
+
if (!config.rateLimiting?.limitingHeader) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const { rateLimiting } = config;
|
|
1171
|
+
app.use(
|
|
1172
|
+
(0, import_hono_rate_limiter.rateLimiter)({
|
|
1173
|
+
windowMs: rateLimiting.windowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS,
|
|
1174
|
+
limit: rateLimiting.limit ?? DEFAULT_RATE_LIMIT_MAX,
|
|
1175
|
+
standardHeaders: rateLimiting.standardHeaders ?? true,
|
|
1176
|
+
keyGenerator: (c) => {
|
|
1177
|
+
const key = c.req.header(rateLimiting.limitingHeader);
|
|
1178
|
+
if (!key) {
|
|
1179
|
+
log(
|
|
1180
|
+
`Rate limiting header '${rateLimiting.limitingHeader}' missing from request`
|
|
1181
|
+
);
|
|
1182
|
+
return UNKNOWN_CLIENT_KEY;
|
|
1183
|
+
}
|
|
1184
|
+
return key;
|
|
1185
|
+
},
|
|
1186
|
+
store: rateLimiting.store ?? void 0
|
|
1187
|
+
})
|
|
1188
|
+
);
|
|
1189
|
+
log(
|
|
1190
|
+
`Rate limiting enabled: ${rateLimiting.limit ?? DEFAULT_RATE_LIMIT_MAX} requests per ${rateLimiting.windowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS}ms window`
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
function applyStaticServing(app, config, runtime, log) {
|
|
1194
|
+
if (!config.enableStatic) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (runtime !== "bun") {
|
|
1198
|
+
log(`Static file serving not yet supported for runtime: ${runtime}`);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
let cachedHandler = null;
|
|
1202
|
+
let importFailed = false;
|
|
1203
|
+
app.use("/assets/*", async (c, next) => {
|
|
1204
|
+
if (importFailed) {
|
|
1205
|
+
return next();
|
|
1206
|
+
}
|
|
1207
|
+
if (!cachedHandler) {
|
|
1208
|
+
const importResult = await (0, import_slang_ts6.safeTry)(async () => {
|
|
1209
|
+
const mod = await import("hono/bun");
|
|
1210
|
+
return mod.serveStatic({
|
|
1211
|
+
root: "./assets",
|
|
1212
|
+
rewriteRequestPath: (path) => path.replace(ASSETS_REGEX, "")
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
if (importResult.isErr) {
|
|
1216
|
+
log("Failed to load static file adapter", importResult.error);
|
|
1217
|
+
importFailed = true;
|
|
1218
|
+
return next();
|
|
1219
|
+
}
|
|
1220
|
+
cachedHandler = importResult.value;
|
|
1221
|
+
}
|
|
1222
|
+
if (cachedHandler) {
|
|
1223
|
+
return cachedHandler(c, next);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
log("Static file serving enabled at /assets/*");
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/rest/rest.ts
|
|
1230
|
+
var externalRequestSchema = import_zod3.default.object({
|
|
1231
|
+
intent: import_zod3.default.enum(["explore", "execute", "schema"]),
|
|
1232
|
+
service: import_zod3.default.string().min(1),
|
|
1233
|
+
action: import_zod3.default.string().min(1),
|
|
1234
|
+
payload: import_zod3.default.record(import_zod3.default.string(), import_zod3.default.unknown())
|
|
1235
|
+
});
|
|
1236
|
+
function createRestApp(params) {
|
|
1237
|
+
const { config, engine, nileContext, serverName, runtime } = params;
|
|
1238
|
+
const app = new import_hono.Hono();
|
|
1239
|
+
const log = createDiagnosticsLog("REST", {
|
|
1240
|
+
diagnostics: config.diagnostics,
|
|
1241
|
+
logger: nileContext.resources?.logger
|
|
1242
|
+
});
|
|
1243
|
+
applyCorsConfig(app, config);
|
|
1244
|
+
applyRateLimiting(app, config, log);
|
|
1245
|
+
applyStaticServing(app, config, runtime, log);
|
|
1246
|
+
const servicesPath = `${config.baseUrl}/services`;
|
|
1247
|
+
app.post(servicesPath, async (c) => {
|
|
1248
|
+
const body = await c.req.json().catch(() => null);
|
|
1249
|
+
if (!body) {
|
|
1250
|
+
return c.json(
|
|
1251
|
+
{
|
|
1252
|
+
status: false,
|
|
1253
|
+
message: "Invalid or missing JSON body",
|
|
1254
|
+
data: {}
|
|
1255
|
+
},
|
|
1256
|
+
400
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
const parsed = externalRequestSchema.safeParse(body);
|
|
1260
|
+
if (!parsed.success) {
|
|
1261
|
+
return c.json(
|
|
1262
|
+
{
|
|
1263
|
+
status: false,
|
|
1264
|
+
message: "Invalid request format",
|
|
1265
|
+
data: { errors: parsed.error.issues }
|
|
1266
|
+
},
|
|
1267
|
+
400
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
const request = parsed.data;
|
|
1271
|
+
log(`${request.intent} -> ${request.service}.${request.action}`);
|
|
1272
|
+
const handler = intentHandlers[request.intent];
|
|
1273
|
+
const response = await handler(engine, request, nileContext);
|
|
1274
|
+
const statusCode = response.status ? 200 : 400;
|
|
1275
|
+
return c.json(response, statusCode);
|
|
1276
|
+
});
|
|
1277
|
+
if (config.enableStatus) {
|
|
1278
|
+
app.get("/status", (c) => {
|
|
1279
|
+
return c.json({
|
|
1280
|
+
status: true,
|
|
1281
|
+
message: `${serverName} is running`,
|
|
1282
|
+
data: {}
|
|
1283
|
+
});
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
app.notFound((c) => {
|
|
1287
|
+
return c.json(
|
|
1288
|
+
{
|
|
1289
|
+
status: false,
|
|
1290
|
+
message: `Route not found. Use POST ${servicesPath} for all operations.`,
|
|
1291
|
+
data: {}
|
|
1292
|
+
},
|
|
1293
|
+
404
|
|
1294
|
+
);
|
|
1295
|
+
});
|
|
1296
|
+
log(`REST interface ready at ${servicesPath}`);
|
|
1297
|
+
return app;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/nile/nile.ts
|
|
1301
|
+
function createNileContext(params) {
|
|
1302
|
+
const store = /* @__PURE__ */ new Map();
|
|
1303
|
+
const interfaceContext = params?.interfaceContext;
|
|
1304
|
+
const sessions = {};
|
|
1305
|
+
const context = {
|
|
1306
|
+
rest: interfaceContext?.rest,
|
|
1307
|
+
ws: interfaceContext?.ws,
|
|
1308
|
+
rpc: interfaceContext?.rpc,
|
|
1309
|
+
resources: params?.resources,
|
|
1310
|
+
sessions,
|
|
1311
|
+
_store: store,
|
|
1312
|
+
get(key) {
|
|
1313
|
+
return store.get(key);
|
|
1314
|
+
},
|
|
1315
|
+
set(key, value) {
|
|
1316
|
+
store.set(key, value);
|
|
1317
|
+
},
|
|
1318
|
+
getSession(name) {
|
|
1319
|
+
return sessions[name];
|
|
1320
|
+
},
|
|
1321
|
+
setSession(name, data) {
|
|
1322
|
+
sessions[name] = data;
|
|
1323
|
+
},
|
|
1324
|
+
hookContext: {
|
|
1325
|
+
actionName: "",
|
|
1326
|
+
input: null,
|
|
1327
|
+
state: {},
|
|
1328
|
+
log: { before: [], after: [] }
|
|
1329
|
+
},
|
|
1330
|
+
updateHookState(key, value) {
|
|
1331
|
+
context.hookContext.state[key] = value;
|
|
1332
|
+
},
|
|
1333
|
+
addHookLog(phase, logEntry) {
|
|
1334
|
+
context.hookContext.log[phase].push(logEntry);
|
|
1335
|
+
},
|
|
1336
|
+
setHookError(error) {
|
|
1337
|
+
context.hookContext.error = error;
|
|
1338
|
+
},
|
|
1339
|
+
setHookOutput(output) {
|
|
1340
|
+
context.hookContext.output = output;
|
|
1341
|
+
},
|
|
1342
|
+
resetHookContext(actionName, input) {
|
|
1343
|
+
context.hookContext = {
|
|
1344
|
+
actionName,
|
|
1345
|
+
input,
|
|
1346
|
+
state: {},
|
|
1347
|
+
log: { before: [], after: [] }
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
return context;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// src/nile/server.ts
|
|
1355
|
+
var _nileContext = null;
|
|
1356
|
+
function getContext() {
|
|
1357
|
+
if (!_nileContext) {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
"getContext: Server not initialized. Call createNileServer first."
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
return _nileContext;
|
|
1363
|
+
}
|
|
1364
|
+
function createNileServer(config) {
|
|
1365
|
+
if (!config.services?.length) {
|
|
1366
|
+
throw new Error(
|
|
1367
|
+
"createNileServer requires at least one service in config.services"
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
const log = createDiagnosticsLog("NileServer", {
|
|
1371
|
+
diagnostics: config.diagnostics,
|
|
1372
|
+
logger: config.resources?.logger
|
|
1373
|
+
});
|
|
1374
|
+
const nileContext = createNileContext({
|
|
1375
|
+
resources: config.resources
|
|
1376
|
+
});
|
|
1377
|
+
_nileContext = nileContext;
|
|
1378
|
+
const engine = createEngine({
|
|
1379
|
+
services: config.services,
|
|
1380
|
+
diagnostics: config.diagnostics,
|
|
1381
|
+
logger: config.resources?.logger,
|
|
1382
|
+
onBeforeActionHandler: config.onBeforeActionHandler,
|
|
1383
|
+
onAfterActionHandler: config.onAfterActionHandler
|
|
1384
|
+
});
|
|
1385
|
+
log(`Engine initialized with ${config.services.length} service(s)`);
|
|
1386
|
+
if (config.logServices !== false) {
|
|
1387
|
+
const servicesResult = engine.getServices();
|
|
1388
|
+
if (servicesResult.isOk) {
|
|
1389
|
+
const table = servicesResult.value.map((s) => ({
|
|
1390
|
+
Service: s.name,
|
|
1391
|
+
Description: s.description,
|
|
1392
|
+
Actions: s.actions.length
|
|
1393
|
+
}));
|
|
1394
|
+
console.table(table);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
const server = {
|
|
1398
|
+
config,
|
|
1399
|
+
engine,
|
|
1400
|
+
context: nileContext
|
|
1401
|
+
};
|
|
1402
|
+
if (config.rest) {
|
|
1403
|
+
const app = createRestApp({
|
|
1404
|
+
config: config.rest,
|
|
1405
|
+
engine,
|
|
1406
|
+
nileContext,
|
|
1407
|
+
serverName: config.serverName,
|
|
1408
|
+
runtime: config.runtime ?? "bun"
|
|
1409
|
+
});
|
|
1410
|
+
server.rest = { app, config: config.rest };
|
|
1411
|
+
const host = config.rest.host ?? "localhost";
|
|
1412
|
+
const port = config.rest.port ?? 3e3;
|
|
1413
|
+
const base = `http://${host}:${port}`;
|
|
1414
|
+
console.log(`
|
|
1415
|
+
POST ${base}${config.rest.baseUrl}/services`);
|
|
1416
|
+
if (config.rest.enableStatus) {
|
|
1417
|
+
console.log(` GET ${base}/status`);
|
|
1418
|
+
}
|
|
1419
|
+
console.log("");
|
|
1420
|
+
}
|
|
1421
|
+
if (config.onBoot) {
|
|
1422
|
+
const { fn } = config.onBoot;
|
|
1423
|
+
const _boot = (async () => {
|
|
1424
|
+
const result = await (0, import_slang_ts7.safeTry)(() => fn(nileContext));
|
|
1425
|
+
if (result.isErr) {
|
|
1426
|
+
console.error("[NileServer] onBoot failed:", result.error);
|
|
1427
|
+
}
|
|
1428
|
+
})();
|
|
1429
|
+
_boot;
|
|
1430
|
+
}
|
|
1431
|
+
log(`${config.serverName} server ready`);
|
|
1432
|
+
return server;
|
|
1433
|
+
}
|
|
1434
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1435
|
+
0 && (module.exports = {
|
|
1436
|
+
createAction,
|
|
1437
|
+
createActions,
|
|
1438
|
+
createLog,
|
|
1439
|
+
createLogger,
|
|
1440
|
+
createModel,
|
|
1441
|
+
createNileServer,
|
|
1442
|
+
createService,
|
|
1443
|
+
createServices,
|
|
1444
|
+
createTransactionVariant,
|
|
1445
|
+
getContext,
|
|
1446
|
+
getLogs,
|
|
1447
|
+
getZodSchema,
|
|
1448
|
+
handleError
|
|
1449
|
+
});
|
|
1450
|
+
//# sourceMappingURL=index.cjs.map
|