aegisnode 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 +2461 -0
- package/bin/aegisnode.js +9 -0
- package/package.json +56 -0
- package/scripts/smoke-test.js +1831 -0
- package/src/cli/commands/createapp.js +191 -0
- package/src/cli/commands/doctor.js +199 -0
- package/src/cli/commands/generate.js +266 -0
- package/src/cli/commands/runserver.js +17 -0
- package/src/cli/commands/startproject.js +72 -0
- package/src/cli/commands/updatedeps.js +355 -0
- package/src/cli/index.js +151 -0
- package/src/cli/utils/fs.js +53 -0
- package/src/cli/utils/project.js +67 -0
- package/src/cli/utils/scaffolds.js +596 -0
- package/src/index.js +20 -0
- package/src/runtime/auth.js +2291 -0
- package/src/runtime/cache.js +37 -0
- package/src/runtime/config.js +482 -0
- package/src/runtime/container.js +43 -0
- package/src/runtime/database.js +195 -0
- package/src/runtime/events.js +33 -0
- package/src/runtime/helpers.js +575 -0
- package/src/runtime/kernel.js +3713 -0
- package/src/runtime/loaders.js +46 -0
- package/src/runtime/logger.js +56 -0
- package/src/runtime/mail.js +225 -0
- package/src/runtime/upload.js +272 -0
- package/src/runtime/views/default-install.ejs +183 -0
- package/src/runtime/views/default-maintenance.ejs +148 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import mongoose from 'mongoose';
|
|
3
|
+
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return Boolean(value) && Object.prototype.toString.call(value) === '[object Object]';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function asNonEmptyString(value, fallback = '') {
|
|
9
|
+
if (typeof value !== 'string') {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function asFiniteNumber(value, fallback = 0) {
|
|
18
|
+
const parsed = Number(value);
|
|
19
|
+
if (Number.isFinite(parsed)) {
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeMoneyDefaults(rawDefaults) {
|
|
26
|
+
const defaults = isPlainObject(rawDefaults) ? rawDefaults : {};
|
|
27
|
+
const normalized = {};
|
|
28
|
+
|
|
29
|
+
const locale = asNonEmptyString(defaults.locale);
|
|
30
|
+
if (locale) {
|
|
31
|
+
normalized.locale = locale;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const currency = asNonEmptyString(defaults.currency);
|
|
35
|
+
if (currency) {
|
|
36
|
+
normalized.currency = currency.toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const currencyDisplay = asNonEmptyString(defaults.currencyDisplay);
|
|
40
|
+
if (currencyDisplay) {
|
|
41
|
+
normalized.currencyDisplay = currencyDisplay;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (Number.isInteger(defaults.minimumFractionDigits) && defaults.minimumFractionDigits >= 0) {
|
|
45
|
+
normalized.minimumFractionDigits = defaults.minimumFractionDigits;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Number.isInteger(defaults.maximumFractionDigits) && defaults.maximumFractionDigits >= 0) {
|
|
49
|
+
normalized.maximumFractionDigits = defaults.maximumFractionDigits;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return normalized;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveRuntimeHelperDefaults(config = null) {
|
|
56
|
+
const source = isPlainObject(config) ? config : {};
|
|
57
|
+
const helperConfig = isPlainObject(source.helpers) ? source.helpers : {};
|
|
58
|
+
const moneyConfig = isPlainObject(helperConfig.money) ? helperConfig.money : {};
|
|
59
|
+
const i18nConfig = isPlainObject(source.i18n) ? source.i18n : {};
|
|
60
|
+
const appConfig = isPlainObject(source.app) ? source.app : {};
|
|
61
|
+
|
|
62
|
+
const locale = asNonEmptyString(
|
|
63
|
+
helperConfig.locale,
|
|
64
|
+
asNonEmptyString(
|
|
65
|
+
moneyConfig.locale,
|
|
66
|
+
asNonEmptyString(i18nConfig.defaultLocale, 'en-US'),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const currency = asNonEmptyString(
|
|
71
|
+
moneyConfig.currency,
|
|
72
|
+
asNonEmptyString(
|
|
73
|
+
helperConfig.currency,
|
|
74
|
+
asNonEmptyString(
|
|
75
|
+
appConfig.currency,
|
|
76
|
+
asNonEmptyString(
|
|
77
|
+
appConfig.Currency,
|
|
78
|
+
asNonEmptyString(source.currency, asNonEmptyString(source.Currency, 'USD')),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
money: normalizeMoneyDefaults({
|
|
86
|
+
...moneyConfig,
|
|
87
|
+
locale,
|
|
88
|
+
currency,
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function toDate(value) {
|
|
94
|
+
if (value instanceof Date) {
|
|
95
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof value === 'number') {
|
|
99
|
+
const fromNumber = new Date(value);
|
|
100
|
+
return Number.isNaN(fromNumber.getTime()) ? null : fromNumber;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
104
|
+
const fromString = new Date(value);
|
|
105
|
+
return Number.isNaN(fromString.getTime()) ? null : fromString;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function toUnixSeconds(value, fallback = Math.floor(Date.now() / 1000)) {
|
|
112
|
+
if (value instanceof Date) {
|
|
113
|
+
const asSeconds = Math.floor(value.getTime() / 1000);
|
|
114
|
+
return Number.isFinite(asSeconds) ? asSeconds : fallback;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
118
|
+
if (Math.abs(value) >= 1e12) {
|
|
119
|
+
return Math.floor(value / 1000);
|
|
120
|
+
}
|
|
121
|
+
return Math.floor(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
125
|
+
const fromNumber = Number(value);
|
|
126
|
+
if (Number.isFinite(fromNumber)) {
|
|
127
|
+
return toUnixSeconds(fromNumber, fallback);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const fromDate = new Date(value);
|
|
131
|
+
if (!Number.isNaN(fromDate.getTime())) {
|
|
132
|
+
return Math.floor(fromDate.getTime() / 1000);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function pad2(value) {
|
|
140
|
+
const parsed = Math.floor(Number(value));
|
|
141
|
+
if (!Number.isFinite(parsed)) {
|
|
142
|
+
return '00';
|
|
143
|
+
}
|
|
144
|
+
return String(parsed).padStart(2, '0');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function formatDateDmy(unixSeconds) {
|
|
148
|
+
const date = new Date(unixSeconds * 1000);
|
|
149
|
+
if (Number.isNaN(date.getTime())) {
|
|
150
|
+
return '';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return `${pad2(date.getDate())}-${pad2(date.getMonth() + 1)}-${date.getFullYear()}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getMongooseObjectIdCtor() {
|
|
157
|
+
return mongoose?.Types?.ObjectId || null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function normalizeObjectIdString(value) {
|
|
161
|
+
if (typeof value !== 'string') {
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
return value.trim();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function isObjectId(value) {
|
|
168
|
+
const ObjectIdCtor = getMongooseObjectIdCtor();
|
|
169
|
+
if (!ObjectIdCtor) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (value instanceof ObjectIdCtor) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const candidate = normalizeObjectIdString(value);
|
|
178
|
+
if (candidate.length === 0) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof mongoose?.isValidObjectId === 'function') {
|
|
183
|
+
return mongoose.isValidObjectId(candidate);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return /^[a-fA-F0-9]{24}$/.test(candidate);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function toObjectId(value) {
|
|
190
|
+
const ObjectIdCtor = getMongooseObjectIdCtor();
|
|
191
|
+
if (!ObjectIdCtor) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (value instanceof ObjectIdCtor) {
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const candidate = normalizeObjectIdString(value);
|
|
200
|
+
if (!isObjectId(candidate)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
return new ObjectIdCtor(candidate);
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function money(value, options = {}, defaults = null) {
|
|
212
|
+
const amount = asFiniteNumber(value, Number.NaN);
|
|
213
|
+
if (!Number.isFinite(amount)) {
|
|
214
|
+
return '';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const safeOptions = isPlainObject(options) ? options : {};
|
|
218
|
+
const safeDefaults = isPlainObject(defaults) ? defaults : {};
|
|
219
|
+
const locale = asNonEmptyString(safeOptions.locale, asNonEmptyString(safeDefaults.locale, 'en-US'));
|
|
220
|
+
const currency = asNonEmptyString(safeOptions.currency, asNonEmptyString(safeDefaults.currency, 'USD')).toUpperCase();
|
|
221
|
+
const currencyDisplay = asNonEmptyString(
|
|
222
|
+
safeOptions.currencyDisplay,
|
|
223
|
+
asNonEmptyString(safeDefaults.currencyDisplay, 'symbol'),
|
|
224
|
+
);
|
|
225
|
+
const minimumFractionDigits = Number.isInteger(safeOptions.minimumFractionDigits)
|
|
226
|
+
? safeOptions.minimumFractionDigits
|
|
227
|
+
: (Number.isInteger(safeDefaults.minimumFractionDigits) ? safeDefaults.minimumFractionDigits : undefined);
|
|
228
|
+
const maximumFractionDigits = Number.isInteger(safeOptions.maximumFractionDigits)
|
|
229
|
+
? safeOptions.maximumFractionDigits
|
|
230
|
+
: (Number.isInteger(safeDefaults.maximumFractionDigits) ? safeDefaults.maximumFractionDigits : undefined);
|
|
231
|
+
|
|
232
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
233
|
+
style: 'currency',
|
|
234
|
+
currency,
|
|
235
|
+
currencyDisplay,
|
|
236
|
+
minimumFractionDigits,
|
|
237
|
+
maximumFractionDigits,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return formatter.format(amount);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function number(value, options = {}) {
|
|
244
|
+
const parsed = asFiniteNumber(value, Number.NaN);
|
|
245
|
+
if (!Number.isFinite(parsed)) {
|
|
246
|
+
return '';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const locale = asNonEmptyString(options.locale, 'en-US');
|
|
250
|
+
const formatter = new Intl.NumberFormat(locale, options);
|
|
251
|
+
return formatter.format(parsed);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function dateTime(value, options = {}) {
|
|
255
|
+
const date = toDate(value);
|
|
256
|
+
if (!date) {
|
|
257
|
+
return '';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const locale = asNonEmptyString(options.locale, 'en-US');
|
|
261
|
+
const formatOptions = { ...options };
|
|
262
|
+
delete formatOptions.locale;
|
|
263
|
+
|
|
264
|
+
if (Object.keys(formatOptions).length === 0) {
|
|
265
|
+
formatOptions.dateStyle = 'medium';
|
|
266
|
+
formatOptions.timeStyle = 'short';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return new Intl.DateTimeFormat(locale, formatOptions).format(date);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const ELAPSED_UNITS = [
|
|
273
|
+
['year', 365 * 24 * 60 * 60],
|
|
274
|
+
['month', 30 * 24 * 60 * 60],
|
|
275
|
+
['week', 7 * 24 * 60 * 60],
|
|
276
|
+
['day', 24 * 60 * 60],
|
|
277
|
+
['hour', 60 * 60],
|
|
278
|
+
['minute', 60],
|
|
279
|
+
['second', 1],
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
function timeElapsedIntl(value, options = {}) {
|
|
283
|
+
const date = toDate(value);
|
|
284
|
+
if (!date) {
|
|
285
|
+
return '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const locale = asNonEmptyString(options.locale, 'en-US');
|
|
289
|
+
const nowValue = options.now instanceof Date
|
|
290
|
+
? options.now.getTime()
|
|
291
|
+
: (typeof options.now === 'number' ? options.now : Date.now());
|
|
292
|
+
const now = Number.isFinite(nowValue) ? nowValue : Date.now();
|
|
293
|
+
const diffSeconds = Math.round((now - date.getTime()) / 1000);
|
|
294
|
+
|
|
295
|
+
if (Math.abs(diffSeconds) < 5) {
|
|
296
|
+
return 'just now';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: options.numeric || 'auto' });
|
|
300
|
+
const absSeconds = Math.abs(diffSeconds);
|
|
301
|
+
|
|
302
|
+
for (const [unit, unitSeconds] of ELAPSED_UNITS) {
|
|
303
|
+
if (absSeconds >= unitSeconds || unit === 'second') {
|
|
304
|
+
const count = Math.floor(absSeconds / unitSeconds);
|
|
305
|
+
return rtf.format(diffSeconds > 0 ? -count : count, unit);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return 'just now';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function trimSpaces(value) {
|
|
313
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function formatElapsedStyle(time, short = false) {
|
|
317
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
318
|
+
const targetSeconds = toUnixSeconds(time, nowSeconds);
|
|
319
|
+
const endText = targetSeconds > nowSeconds ? '' : 'ago';
|
|
320
|
+
|
|
321
|
+
let diff = Math.abs(nowSeconds - targetSeconds);
|
|
322
|
+
|
|
323
|
+
const difYear = Math.floor(diff / 31536000);
|
|
324
|
+
diff -= difYear * 31536000;
|
|
325
|
+
|
|
326
|
+
const difMonth = Math.floor(diff / 2592000);
|
|
327
|
+
diff -= difMonth * 2592000;
|
|
328
|
+
|
|
329
|
+
const difWeek = Math.floor(diff / 604800);
|
|
330
|
+
diff -= difWeek * 604800;
|
|
331
|
+
|
|
332
|
+
const difDay = Math.floor(diff / 86400);
|
|
333
|
+
diff -= difDay * 86400;
|
|
334
|
+
|
|
335
|
+
const difHour = Math.floor(diff / 3600);
|
|
336
|
+
diff -= difHour * 3600;
|
|
337
|
+
|
|
338
|
+
const difMin = Math.floor(diff / 60);
|
|
339
|
+
diff -= difMin * 60;
|
|
340
|
+
|
|
341
|
+
const difSec = Math.floor(diff);
|
|
342
|
+
|
|
343
|
+
if (short) {
|
|
344
|
+
if (difMonth > 0 && difMonth < 13) {
|
|
345
|
+
return trimSpaces(`${difMonth} Month ${endText}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (difWeek > 0 && difWeek < 5) {
|
|
349
|
+
return trimSpaces(`${difWeek} Week ${endText}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (difDay === 0) {
|
|
353
|
+
if (difHour > 5 && difHour < 24) {
|
|
354
|
+
return trimSpaces(`Today ${difHour} heure`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (difHour > 0) {
|
|
358
|
+
return trimSpaces(`Just ${difHour} heures`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (difMin > 0) {
|
|
362
|
+
return trimSpaces(`Just ${difMin} mn`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return difSec === 0 ? 'Just Now' : trimSpaces(`Just ${difSec} secondes`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (difDay === 1) {
|
|
369
|
+
return 'Yesterday';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (difDay > 1 && difDay < 7) {
|
|
373
|
+
return trimSpaces(`${difDay} days ${endText}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return formatDateDmy(targetSeconds);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (difMonth > 0 && difMonth < 13) {
|
|
380
|
+
if (difDay === 0) {
|
|
381
|
+
return trimSpaces(`${difMonth} Month ${endText}`);
|
|
382
|
+
}
|
|
383
|
+
return trimSpaces(`${difMonth} Month ${difDay} Days ${endText}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (difWeek > 0 && difWeek < 5) {
|
|
387
|
+
if (difDay === 0) {
|
|
388
|
+
return trimSpaces(`${difWeek} Week ${endText}`);
|
|
389
|
+
}
|
|
390
|
+
return trimSpaces(`${difWeek} Week ${difDay} Days ${endText}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (difDay === 0) {
|
|
394
|
+
if (difHour > 5 && difHour < 24) {
|
|
395
|
+
return trimSpaces(`Today ${difHour} heure ${difMin} mn`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (difHour > 0) {
|
|
399
|
+
return trimSpaces(`Just ${difHour} heures ${difMin} mn`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (difMin > 0) {
|
|
403
|
+
return trimSpaces(`Just ${difMin} mn`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return difSec === 0 ? 'Just Now' : trimSpaces(`Just ${difSec} secondes`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (difDay === 1) {
|
|
410
|
+
return 'Yesterday';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (difDay > 1 && difDay < 7) {
|
|
414
|
+
return trimSpaces(`${difDay} days ${endText}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return formatDateDmy(targetSeconds);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function timeElapsed(time, shortOrOptions = false) {
|
|
421
|
+
if (isPlainObject(shortOrOptions)) {
|
|
422
|
+
return timeElapsedIntl(time, shortOrOptions);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return formatElapsedStyle(time, Boolean(shortOrOptions));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function timeDifference(today, start, end, rounded = true) {
|
|
429
|
+
const q = Math.abs(asFiniteNumber(today, 0) - asFiniteNumber(start, 0));
|
|
430
|
+
const d = Math.abs(asFiniteNumber(end, 0) - asFiniteNumber(start, 0));
|
|
431
|
+
const raw = (q / d) * 100;
|
|
432
|
+
const value = rounded ? Math.round(raw) : raw;
|
|
433
|
+
return Number.isFinite(value) ? value : 0;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function breakStr(str, nb, endText = '', spBreak = false) {
|
|
437
|
+
const source = String(str ?? '');
|
|
438
|
+
const limit = Math.max(0, Math.floor(asFiniteNumber(nb, 0)));
|
|
439
|
+
if (limit <= 0 || source.length <= limit) {
|
|
440
|
+
return source;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let clipped = source.slice(0, limit);
|
|
444
|
+
if (spBreak) {
|
|
445
|
+
const spacePosition = clipped.lastIndexOf(' ');
|
|
446
|
+
if (spacePosition > 0) {
|
|
447
|
+
clipped = clipped.slice(0, spacePosition);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return clipped + String(endText ?? '');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function createHelpers(defaults = {}) {
|
|
455
|
+
const sourceDefaults = isPlainObject(defaults) ? defaults : {};
|
|
456
|
+
const moneyDefaults = normalizeMoneyDefaults({
|
|
457
|
+
locale: sourceDefaults.locale,
|
|
458
|
+
...(isPlainObject(sourceDefaults.money) ? sourceDefaults.money : {}),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
money(value, options = {}) {
|
|
463
|
+
return money(value, options, moneyDefaults);
|
|
464
|
+
},
|
|
465
|
+
number,
|
|
466
|
+
dateTime,
|
|
467
|
+
timeElapsed,
|
|
468
|
+
timeDifference,
|
|
469
|
+
breakStr,
|
|
470
|
+
isObjectId,
|
|
471
|
+
toObjectId,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function fallbackSecret(length = 64) {
|
|
476
|
+
const normalizedLength = Number.isFinite(Number(length)) && Number(length) > 0
|
|
477
|
+
? Math.floor(Number(length))
|
|
478
|
+
: 64;
|
|
479
|
+
return crypto.randomBytes(Math.ceil(normalizedLength / 2)).toString('hex').slice(0, normalizedLength);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function createUnavailableJlive(reason = 'jlive is not installed') {
|
|
483
|
+
const fail = (methodName) => {
|
|
484
|
+
const error = new Error(`jlive is unavailable. Install "jlive" to use "${methodName}".`);
|
|
485
|
+
error.code = 'JLIVE_UNAVAILABLE';
|
|
486
|
+
error.reason = reason;
|
|
487
|
+
throw error;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
available: false,
|
|
492
|
+
reason,
|
|
493
|
+
module: null,
|
|
494
|
+
JliveEncrypt: null,
|
|
495
|
+
generate: (length = 64) => fallbackSecret(length),
|
|
496
|
+
encrypt: () => fail('encrypt'),
|
|
497
|
+
decrypt: () => fail('decrypt'),
|
|
498
|
+
hash: () => fail('hash'),
|
|
499
|
+
verify: () => fail('verify'),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function pickFunction(candidates, names) {
|
|
504
|
+
for (const candidate of candidates) {
|
|
505
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
for (const name of names) {
|
|
509
|
+
const fn = candidate[name];
|
|
510
|
+
if (typeof fn === 'function') {
|
|
511
|
+
return fn.bind(candidate);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function createAvailableJlive(loadedModule) {
|
|
519
|
+
const moduleObject = loadedModule?.default ?? loadedModule;
|
|
520
|
+
const jliveEncrypt = loadedModule?.JliveEncrypt
|
|
521
|
+
|| moduleObject?.JliveEncrypt
|
|
522
|
+
|| null;
|
|
523
|
+
const candidates = [
|
|
524
|
+
jliveEncrypt,
|
|
525
|
+
moduleObject,
|
|
526
|
+
loadedModule,
|
|
527
|
+
].filter(Boolean);
|
|
528
|
+
|
|
529
|
+
const generateFn = pickFunction(candidates, ['generate', 'createSecret']);
|
|
530
|
+
const encryptFn = pickFunction(candidates, ['encrypt']);
|
|
531
|
+
const decryptFn = pickFunction(candidates, ['decrypt']);
|
|
532
|
+
const hashFn = pickFunction(candidates, ['hash']);
|
|
533
|
+
const verifyFn = pickFunction(candidates, ['verify', 'compare']);
|
|
534
|
+
|
|
535
|
+
const fail = (methodName) => {
|
|
536
|
+
const error = new Error(`jlive does not expose method "${methodName}" in this environment.`);
|
|
537
|
+
error.code = 'JLIVE_METHOD_UNAVAILABLE';
|
|
538
|
+
throw error;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
available: true,
|
|
543
|
+
reason: '',
|
|
544
|
+
module: moduleObject,
|
|
545
|
+
JliveEncrypt: jliveEncrypt,
|
|
546
|
+
generate: (length = 64) => (generateFn ? generateFn(length) : fallbackSecret(length)),
|
|
547
|
+
encrypt: (...args) => (encryptFn ? encryptFn(...args) : fail('encrypt')),
|
|
548
|
+
decrypt: (...args) => (decryptFn ? decryptFn(...args) : fail('decrypt')),
|
|
549
|
+
hash: (...args) => (hashFn ? hashFn(...args) : fail('hash')),
|
|
550
|
+
verify: (...args) => (verifyFn ? verifyFn(...args) : fail('verify')),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export async function loadJlive(logger = null) {
|
|
555
|
+
try {
|
|
556
|
+
const loadedModule = await import('jlive');
|
|
557
|
+
return createAvailableJlive(loadedModule);
|
|
558
|
+
} catch (error) {
|
|
559
|
+
if (logger && typeof logger.debug === 'function') {
|
|
560
|
+
logger.debug('jlive unavailable, using fallback bridge: %s', error?.message || String(error));
|
|
561
|
+
}
|
|
562
|
+
return createUnavailableJlive(error?.message || 'jlive import failed');
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export async function createRuntimeHelpers({ logger = null, config = null } = {}) {
|
|
567
|
+
const helperDefaults = resolveRuntimeHelperDefaults(config);
|
|
568
|
+
const helpers = createHelpers(helperDefaults);
|
|
569
|
+
const jlive = await loadJlive(logger);
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
helpers,
|
|
573
|
+
jlive,
|
|
574
|
+
};
|
|
575
|
+
}
|