@rayadesu/dsh-llm-billing 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +594 -3169
- package/lib/invariant.js +3 -3
- package/lib/typert.host.js +31 -19
- package/lib/typert.remote-client.d.ts +3 -4
- package/lib/typert.remote-client.js +31 -19
- package/lib/types/balance.d.ts +14 -6
- package/lib/types/balance.js +9 -5
- package/lib/types/billing.d.ts +88 -7
- package/lib/types/billing.js +171 -62
- package/lib/types/index.d.ts +22 -5
- package/lib/types/index.js +64 -53
- package/lib/types/invariant.d.ts +2 -2
- package/lib/types/invariant.js +3 -3
- package/lib/types/projection.d.ts +69 -0
- package/lib/types/projection.js +88 -0
- package/lib/types/today-spend.d.ts +138 -0
- package/lib/types/today-spend.js +246 -0
- package/lib/types/types.d.ts +3 -3
- package/lib/types/types.js +1 -1
- package/package.json +20 -17
package/lib/index.js
CHANGED
|
@@ -1,3008 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
/** Return true for non-array object values. */
|
|
8
|
-
function isPlainObject(data) {
|
|
9
|
-
return data && typeof data === "object" && !Array.isArray(data);
|
|
10
|
-
}
|
|
11
|
-
/** Filter object entries and return a new object. */
|
|
12
|
-
function filterKeys(object, filter) {
|
|
13
|
-
return Object.fromEntries(Object.entries(object).filter(([key, value]) => filter(key, value)));
|
|
14
|
-
}
|
|
15
|
-
/** Map object values while preserving the original key set. */
|
|
16
|
-
function mapValues(object, transform) {
|
|
17
|
-
return Object.fromEntries(Object.entries(object).map(([key, value]) => [key, transform(value, key)]));
|
|
18
|
-
}
|
|
19
|
-
/** Pick selected keys from an object, optionally including `undefined` values. */
|
|
20
|
-
function pick(source, keys, forced) {
|
|
21
|
-
if (!keys) return { ...source };
|
|
22
|
-
const result = {};
|
|
23
|
-
for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
/** Define a non-enumerable writable property and return the object. */
|
|
27
|
-
function defineProperty(object, key, value) {
|
|
28
|
-
return Object.defineProperty(object, key, {
|
|
29
|
-
writable: true,
|
|
30
|
-
value,
|
|
31
|
-
enumerable: false
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region ../../../vendor/cosmokit/src/types.ts
|
|
36
|
-
/** Test values using `instanceof` with a `toStringTag` fallback. */
|
|
37
|
-
function is(type, value) {
|
|
38
|
-
if (arguments.length === 1) return (value) => is(type, value);
|
|
39
|
-
return type in globalThis && value instanceof globalThis[type] || Object.prototype.toString.call(value).slice(8, -1) === type;
|
|
40
|
-
}
|
|
41
|
-
function isArrayBufferLike(value) {
|
|
42
|
-
return is("ArrayBuffer", value) || is("SharedArrayBuffer", value);
|
|
43
|
-
}
|
|
44
|
-
function isArrayBufferSource(value) {
|
|
45
|
-
return isArrayBufferLike(value) || ArrayBuffer.isView(value);
|
|
46
|
-
}
|
|
47
|
-
let Binary;
|
|
48
|
-
(function(_Binary) {
|
|
49
|
-
_Binary.is = isArrayBufferLike;
|
|
50
|
-
_Binary.isSource = isArrayBufferSource;
|
|
51
|
-
function fromSource(source) {
|
|
52
|
-
if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
|
|
53
|
-
else return source;
|
|
54
|
-
}
|
|
55
|
-
_Binary.fromSource = fromSource;
|
|
56
|
-
function toBase64(source) {
|
|
57
|
-
source = fromSource(source);
|
|
58
|
-
if (typeof Buffer !== "undefined") return Buffer.from(source).toString("base64");
|
|
59
|
-
let binary = "";
|
|
60
|
-
const bytes = new Uint8Array(source);
|
|
61
|
-
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
|
|
62
|
-
return btoa(binary);
|
|
63
|
-
}
|
|
64
|
-
_Binary.toBase64 = toBase64;
|
|
65
|
-
function fromBase64(source) {
|
|
66
|
-
if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "base64"));
|
|
67
|
-
return Uint8Array.from(atob(source), (c) => c.charCodeAt(0));
|
|
68
|
-
}
|
|
69
|
-
_Binary.fromBase64 = fromBase64;
|
|
70
|
-
function toHex(source) {
|
|
71
|
-
source = fromSource(source);
|
|
72
|
-
if (typeof Buffer !== "undefined") return Buffer.from(source).toString("hex");
|
|
73
|
-
return Array.from(new Uint8Array(source), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
74
|
-
}
|
|
75
|
-
_Binary.toHex = toHex;
|
|
76
|
-
function fromHex(source) {
|
|
77
|
-
if (typeof Buffer !== "undefined") return fromSource(Buffer.from(source, "hex"));
|
|
78
|
-
const hex = source.length % 2 === 0 ? source : source.slice(0, source.length - 1);
|
|
79
|
-
const buffer = [];
|
|
80
|
-
for (let i = 0; i < hex.length; i += 2) buffer.push(parseInt(`${hex[i]}${hex[i + 1]}`, 16));
|
|
81
|
-
return Uint8Array.from(buffer).buffer;
|
|
82
|
-
}
|
|
83
|
-
_Binary.fromHex = fromHex;
|
|
84
|
-
})(Binary || (Binary = {}));
|
|
85
|
-
Binary.fromBase64;
|
|
86
|
-
Binary.toBase64;
|
|
87
|
-
Binary.fromHex;
|
|
88
|
-
Binary.toHex;
|
|
89
|
-
/** Deep-clone common JavaScript values while preserving prototypes and cycles. */
|
|
90
|
-
function clone(source, refs = /* @__PURE__ */ new Map()) {
|
|
91
|
-
if (!source || typeof source !== "object") return source;
|
|
92
|
-
if (is("Date", source)) return new Date(source.valueOf());
|
|
93
|
-
if (is("RegExp", source)) return new RegExp(source.source, source.flags);
|
|
94
|
-
if (isArrayBufferLike(source)) return source.slice(0);
|
|
95
|
-
if (ArrayBuffer.isView(source)) return source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
|
|
96
|
-
const cached = refs.get(source);
|
|
97
|
-
if (cached) return cached;
|
|
98
|
-
if (Array.isArray(source)) {
|
|
99
|
-
const result = [];
|
|
100
|
-
refs.set(source, result);
|
|
101
|
-
source.forEach((value, index) => {
|
|
102
|
-
result[index] = Reflect.apply(clone, null, [value, refs]);
|
|
103
|
-
});
|
|
104
|
-
return result;
|
|
105
|
-
}
|
|
106
|
-
const result = Object.create(Object.getPrototypeOf(source));
|
|
107
|
-
refs.set(source, result);
|
|
108
|
-
for (const key of Reflect.ownKeys(source)) {
|
|
109
|
-
const descriptor = { ...Reflect.getOwnPropertyDescriptor(source, key) };
|
|
110
|
-
if ("value" in descriptor) descriptor.value = Reflect.apply(clone, null, [descriptor.value, refs]);
|
|
111
|
-
Reflect.defineProperty(result, key, descriptor);
|
|
112
|
-
}
|
|
113
|
-
return result;
|
|
114
|
-
}
|
|
115
|
-
/** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
|
|
116
|
-
function deepEqual(a, b, strict) {
|
|
117
|
-
if (a === b) return true;
|
|
118
|
-
if (!strict && isNullable(a) && isNullable(b)) return true;
|
|
119
|
-
if (typeof a !== typeof b) return false;
|
|
120
|
-
if (typeof a !== "object") return false;
|
|
121
|
-
if (!a || !b) return false;
|
|
122
|
-
function check(test, then) {
|
|
123
|
-
return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
|
|
124
|
-
}
|
|
125
|
-
return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
|
|
126
|
-
if (a.byteLength !== b.byteLength) return false;
|
|
127
|
-
const viewA = new Uint8Array(a);
|
|
128
|
-
const viewB = new Uint8Array(b);
|
|
129
|
-
for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
|
|
130
|
-
return true;
|
|
131
|
-
}) ?? Object.keys({
|
|
132
|
-
...a,
|
|
133
|
-
...b
|
|
134
|
-
}).every((key) => deepEqual(a[key], b[key], strict));
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region ../../../vendor/cosmokit/src/string.ts
|
|
138
|
-
function tokenize(source, delimiters, delimiter) {
|
|
139
|
-
const output = [];
|
|
140
|
-
let state = 0;
|
|
141
|
-
for (let i = 0; i < source.length; i++) {
|
|
142
|
-
const code = source.charCodeAt(i);
|
|
143
|
-
if (code >= 65 && code <= 90) {
|
|
144
|
-
if (state === 1) {
|
|
145
|
-
const next = source.charCodeAt(i + 1);
|
|
146
|
-
if (next >= 97 && next <= 122) output.push(delimiter);
|
|
147
|
-
output.push(code + 32);
|
|
148
|
-
} else {
|
|
149
|
-
if (state !== 0) output.push(delimiter);
|
|
150
|
-
output.push(code + 32);
|
|
151
|
-
}
|
|
152
|
-
state = 1;
|
|
153
|
-
} else if (code >= 97 && code <= 122) {
|
|
154
|
-
output.push(code);
|
|
155
|
-
state = 2;
|
|
156
|
-
} else if (delimiters.includes(code)) {
|
|
157
|
-
if (state !== 0) output.push(delimiter);
|
|
158
|
-
state = 0;
|
|
159
|
-
} else output.push(code);
|
|
160
|
-
}
|
|
161
|
-
return String.fromCharCode(...output);
|
|
162
|
-
}
|
|
163
|
-
/** Convert text to dash-delimited parameter case. */
|
|
164
|
-
function paramCase(source) {
|
|
165
|
-
return tokenize(source, [45, 95], 45);
|
|
166
|
-
}
|
|
167
|
-
/** Runtime alias for `paramCase`. */
|
|
168
|
-
const hyphenate = paramCase;
|
|
169
|
-
//#endregion
|
|
170
|
-
//#region ../../../vendor/cosmokit/src/time.ts
|
|
171
|
-
let Time;
|
|
172
|
-
(function(_Time) {
|
|
173
|
-
_Time.millisecond = 1;
|
|
174
|
-
const second = _Time.second = 1e3;
|
|
175
|
-
const minute = _Time.minute = second * 60;
|
|
176
|
-
const hour = _Time.hour = minute * 60;
|
|
177
|
-
const day = _Time.day = hour * 24;
|
|
178
|
-
const week = _Time.week = day * 7;
|
|
179
|
-
let timezoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
180
|
-
function setTimezoneOffset(offset) {
|
|
181
|
-
timezoneOffset = offset;
|
|
182
|
-
}
|
|
183
|
-
_Time.setTimezoneOffset = setTimezoneOffset;
|
|
184
|
-
function getTimezoneOffset() {
|
|
185
|
-
return timezoneOffset;
|
|
186
|
-
}
|
|
187
|
-
_Time.getTimezoneOffset = getTimezoneOffset;
|
|
188
|
-
function getDateNumber(date = /* @__PURE__ */ new Date(), offset) {
|
|
189
|
-
if (typeof date === "number") date = new Date(date);
|
|
190
|
-
if (offset === void 0) offset = timezoneOffset;
|
|
191
|
-
return Math.floor((date.valueOf() / minute - offset) / 1440);
|
|
192
|
-
}
|
|
193
|
-
_Time.getDateNumber = getDateNumber;
|
|
194
|
-
function fromDateNumber(value, offset) {
|
|
195
|
-
const date = new Date(value * day);
|
|
196
|
-
if (offset === void 0) offset = timezoneOffset;
|
|
197
|
-
return new Date(+date + offset * minute);
|
|
198
|
-
}
|
|
199
|
-
_Time.fromDateNumber = fromDateNumber;
|
|
200
|
-
const numeric = /\d+(?:\.\d+)?/.source;
|
|
201
|
-
const timeRegExp = new RegExp(`^${[
|
|
202
|
-
"w(?:eek(?:s)?)?",
|
|
203
|
-
"d(?:ay(?:s)?)?",
|
|
204
|
-
"h(?:our(?:s)?)?",
|
|
205
|
-
"m(?:in(?:ute)?(?:s)?)?",
|
|
206
|
-
"s(?:ec(?:ond)?(?:s)?)?"
|
|
207
|
-
].map((unit) => `(${numeric}${unit})?`).join("")}$`);
|
|
208
|
-
function parseTime(source) {
|
|
209
|
-
const capture = timeRegExp.exec(source);
|
|
210
|
-
if (!capture) return 0;
|
|
211
|
-
return (parseFloat(capture[1]) * week || 0) + (parseFloat(capture[2]) * day || 0) + (parseFloat(capture[3]) * hour || 0) + (parseFloat(capture[4]) * minute || 0) + (parseFloat(capture[5]) * second || 0);
|
|
212
|
-
}
|
|
213
|
-
_Time.parseTime = parseTime;
|
|
214
|
-
function parseDate(date) {
|
|
215
|
-
const parsed = parseTime(date);
|
|
216
|
-
if (parsed) date = Date.now() + parsed;
|
|
217
|
-
else if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).toLocaleDateString()}-${date}`;
|
|
218
|
-
else if (/^\d{1,2}-\d{1,2}-\d{1,2}(:\d{1,2}){1,2}$/.test(date)) date = `${(/* @__PURE__ */ new Date()).getFullYear()}-${date}`;
|
|
219
|
-
return date ? new Date(date) : /* @__PURE__ */ new Date();
|
|
220
|
-
}
|
|
221
|
-
_Time.parseDate = parseDate;
|
|
222
|
-
function format(ms) {
|
|
223
|
-
const abs = Math.abs(ms);
|
|
224
|
-
if (abs >= day - hour / 2) return Math.round(ms / day) + "d";
|
|
225
|
-
else if (abs >= hour - minute / 2) return Math.round(ms / hour) + "h";
|
|
226
|
-
else if (abs >= minute - second / 2) return Math.round(ms / minute) + "m";
|
|
227
|
-
else if (abs >= second) return Math.round(ms / second) + "s";
|
|
228
|
-
return ms + "ms";
|
|
229
|
-
}
|
|
230
|
-
_Time.format = format;
|
|
231
|
-
function toDigits(source, length = 2) {
|
|
232
|
-
return source.toString().padStart(length, "0");
|
|
233
|
-
}
|
|
234
|
-
_Time.toDigits = toDigits;
|
|
235
|
-
function template(template, time = /* @__PURE__ */ new Date()) {
|
|
236
|
-
return template.replace("yyyy", time.getFullYear().toString()).replace("yy", time.getFullYear().toString().slice(2)).replace("MM", toDigits(time.getMonth() + 1)).replace("dd", toDigits(time.getDate())).replace("hh", toDigits(time.getHours())).replace("mm", toDigits(time.getMinutes())).replace("ss", toDigits(time.getSeconds())).replace("SSS", toDigits(time.getMilliseconds(), 3));
|
|
237
|
-
}
|
|
238
|
-
_Time.template = template;
|
|
239
|
-
})(Time || (Time = {}));
|
|
240
|
-
//#endregion
|
|
241
|
-
//#region ../../../vendor/schemastery/src/index.ts
|
|
242
|
-
const kSchema = Symbol.for("schemastery");
|
|
243
|
-
const kValidationError$1 = Symbol.for("ValidationError");
|
|
244
|
-
globalThis.__schemastery_index__ ??= 0;
|
|
245
|
-
globalThis.__schemastery_refs__ = void 0;
|
|
246
|
-
var ValidationError$1 = class extends TypeError {
|
|
247
|
-
options;
|
|
248
|
-
name = "ValidationError";
|
|
249
|
-
constructor(message, options) {
|
|
250
|
-
let prefix = "$";
|
|
251
|
-
for (const segment of options.path || []) if (typeof segment === "string") prefix += "." + segment;
|
|
252
|
-
else if (typeof segment === "number") prefix += "[" + segment + "]";
|
|
253
|
-
else if (typeof segment === "symbol") prefix += `[Symbol(${segment.toString()})]`;
|
|
254
|
-
if (prefix.startsWith(".")) prefix = prefix.slice(1);
|
|
255
|
-
super((prefix === "$" ? "" : `${prefix} `) + message);
|
|
256
|
-
this.options = options;
|
|
257
|
-
}
|
|
258
|
-
static is(error) {
|
|
259
|
-
return !!error?.[kValidationError$1];
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
Object.defineProperty(ValidationError$1.prototype, kValidationError$1, { value: true });
|
|
263
|
-
const Schema = function(options) {
|
|
264
|
-
const schema = function(data, options = {}) {
|
|
265
|
-
return Schema.resolve(data, schema, options)[0];
|
|
266
|
-
};
|
|
267
|
-
if (options.refs) {
|
|
268
|
-
const refs = mapValues(options.refs, (options) => new Schema(options));
|
|
269
|
-
const getRef = (uid) => refs[uid];
|
|
270
|
-
for (const key in refs) {
|
|
271
|
-
const options = refs[key];
|
|
272
|
-
options.sKey = getRef(options.sKey);
|
|
273
|
-
options.inner = getRef(options.inner);
|
|
274
|
-
options.list = options.list && options.list.map(getRef);
|
|
275
|
-
options.dict = options.dict && mapValues(options.dict, getRef);
|
|
276
|
-
}
|
|
277
|
-
return refs[options.uid];
|
|
278
|
-
}
|
|
279
|
-
Object.assign(schema, options);
|
|
280
|
-
if (typeof schema.callback === "string") try {
|
|
281
|
-
schema.callback = new Function("return " + schema.callback)();
|
|
282
|
-
} catch {}
|
|
283
|
-
Object.defineProperty(schema, "uid", { value: globalThis.__schemastery_index__++ });
|
|
284
|
-
Object.setPrototypeOf(schema, Schema.prototype);
|
|
285
|
-
schema.meta ||= {};
|
|
286
|
-
schema.toString = schema.toString.bind(schema);
|
|
287
|
-
return schema;
|
|
288
|
-
};
|
|
289
|
-
Schema.prototype = Object.create(Function.prototype);
|
|
290
|
-
Schema.prototype[kSchema] = true;
|
|
291
|
-
Object.defineProperty(Schema.prototype, "~standard", { get() {
|
|
292
|
-
return {
|
|
293
|
-
version: 1,
|
|
294
|
-
vendor: "schemastery",
|
|
295
|
-
validate: (value) => {
|
|
296
|
-
try {
|
|
297
|
-
return { value: Schema.resolve(value, this, {})[0] };
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (ValidationError$1.is(error)) return { issues: [{
|
|
300
|
-
message: error.message,
|
|
301
|
-
path: error.options.path
|
|
302
|
-
}] };
|
|
303
|
-
throw error;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
} });
|
|
308
|
-
Schema.ValidationError = ValidationError$1;
|
|
309
|
-
Schema.prototype.toJSON = function toJSON() {
|
|
310
|
-
if (globalThis.__schemastery_refs__) {
|
|
311
|
-
globalThis.__schemastery_refs__[this.uid] ??= JSON.parse(JSON.stringify({ ...this }));
|
|
312
|
-
return this.uid;
|
|
313
|
-
}
|
|
314
|
-
globalThis.__schemastery_refs__ = { [this.uid]: { ...this } };
|
|
315
|
-
globalThis.__schemastery_refs__[this.uid] = JSON.parse(JSON.stringify({ ...this }));
|
|
316
|
-
const result = {
|
|
317
|
-
uid: this.uid,
|
|
318
|
-
refs: globalThis.__schemastery_refs__
|
|
319
|
-
};
|
|
320
|
-
globalThis.__schemastery_refs__ = void 0;
|
|
321
|
-
return result;
|
|
322
|
-
};
|
|
323
|
-
Schema.prototype.set = function set(key, value) {
|
|
324
|
-
this.dict[key] = value;
|
|
325
|
-
return this;
|
|
326
|
-
};
|
|
327
|
-
Schema.prototype.push = function push(value) {
|
|
328
|
-
this.list.push(value);
|
|
329
|
-
return this;
|
|
330
|
-
};
|
|
331
|
-
function mergeDesc(original, messages) {
|
|
332
|
-
const result = typeof original === "string" ? { "": original } : { ...original };
|
|
333
|
-
for (const locale in messages) {
|
|
334
|
-
const value = messages[locale];
|
|
335
|
-
if (value?.$description || value?.$desc) result[locale] = value.$description || value.$desc;
|
|
336
|
-
else if (typeof value === "string") result[locale] = value;
|
|
337
|
-
}
|
|
338
|
-
return result;
|
|
339
|
-
}
|
|
340
|
-
function getInner(value) {
|
|
341
|
-
return value?.$value ?? value?.$inner;
|
|
342
|
-
}
|
|
343
|
-
function extractKeys(data) {
|
|
344
|
-
return filterKeys(data ?? {}, (key) => !key.startsWith("$"));
|
|
345
|
-
}
|
|
346
|
-
Schema.prototype.i18n = function i18n(messages) {
|
|
347
|
-
const schema = Schema(this);
|
|
348
|
-
const desc = mergeDesc(schema.meta.description, messages);
|
|
349
|
-
if (Object.keys(desc).length) schema.meta.description = desc;
|
|
350
|
-
if (schema.dict) schema.dict = mapValues(schema.dict, (inner, key) => {
|
|
351
|
-
return inner.i18n(mapValues(messages, (data) => getInner(data)?.[key] ?? data?.[key]));
|
|
352
|
-
});
|
|
353
|
-
if (schema.list) schema.list = schema.list.map((inner, index) => {
|
|
354
|
-
return inner.i18n(mapValues(messages, (data = {}) => {
|
|
355
|
-
if (Array.isArray(getInner(data))) return getInner(data)[index];
|
|
356
|
-
if (Array.isArray(data)) return data[index];
|
|
357
|
-
return extractKeys(data);
|
|
358
|
-
}));
|
|
359
|
-
});
|
|
360
|
-
if (schema.inner) schema.inner = schema.inner.i18n(mapValues(messages, (data) => {
|
|
361
|
-
if (getInner(data)) return getInner(data);
|
|
362
|
-
return extractKeys(data);
|
|
363
|
-
}));
|
|
364
|
-
if (schema.sKey) schema.sKey = schema.sKey.i18n(mapValues(messages, (data) => data?.$key));
|
|
365
|
-
return schema;
|
|
366
|
-
};
|
|
367
|
-
Schema.prototype.extra = function extra(key, value) {
|
|
368
|
-
const schema = Schema(this);
|
|
369
|
-
schema.meta = {
|
|
370
|
-
...schema.meta,
|
|
371
|
-
[key]: value
|
|
372
|
-
};
|
|
373
|
-
return schema;
|
|
374
|
-
};
|
|
375
|
-
for (const key of [
|
|
376
|
-
"required",
|
|
377
|
-
"disabled",
|
|
378
|
-
"collapse",
|
|
379
|
-
"hidden",
|
|
380
|
-
"loose"
|
|
381
|
-
]) Object.assign(Schema.prototype, { [key](value = true) {
|
|
382
|
-
const schema = Schema(this);
|
|
383
|
-
schema.meta = {
|
|
384
|
-
...schema.meta,
|
|
385
|
-
[key]: value
|
|
386
|
-
};
|
|
387
|
-
return schema;
|
|
388
|
-
} });
|
|
389
|
-
Schema.prototype.deprecated = function deprecated() {
|
|
390
|
-
const schema = Schema(this);
|
|
391
|
-
schema.meta.badges ||= [];
|
|
392
|
-
schema.meta.badges.push({
|
|
393
|
-
text: "deprecated",
|
|
394
|
-
type: "danger"
|
|
395
|
-
});
|
|
396
|
-
return schema;
|
|
397
|
-
};
|
|
398
|
-
Schema.prototype.experimental = function experimental() {
|
|
399
|
-
const schema = Schema(this);
|
|
400
|
-
schema.meta.badges ||= [];
|
|
401
|
-
schema.meta.badges.push({
|
|
402
|
-
text: "experimental",
|
|
403
|
-
type: "warning"
|
|
404
|
-
});
|
|
405
|
-
return schema;
|
|
406
|
-
};
|
|
407
|
-
Schema.prototype.pattern = function pattern(regexp) {
|
|
408
|
-
const schema = Schema(this);
|
|
409
|
-
const pattern = pick(regexp, ["source", "flags"]);
|
|
410
|
-
schema.meta = {
|
|
411
|
-
...schema.meta,
|
|
412
|
-
pattern
|
|
413
|
-
};
|
|
414
|
-
return schema;
|
|
415
|
-
};
|
|
416
|
-
Schema.prototype.simplify = function simplify(value) {
|
|
417
|
-
if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
|
|
418
|
-
if (isNullable(value)) return value;
|
|
419
|
-
if (this.type === "object" || this.type === "dict") {
|
|
420
|
-
const result = {};
|
|
421
|
-
for (const key in value) {
|
|
422
|
-
const item = (this.type === "object" ? this.dict[key] : this.inner)?.simplify(value[key]);
|
|
423
|
-
if (this.type === "dict" || !isNullable(item)) result[key] = item;
|
|
424
|
-
}
|
|
425
|
-
if (deepEqual(result, this.meta.default, this.type === "dict")) return null;
|
|
426
|
-
return result;
|
|
427
|
-
} else if (this.type === "array" || this.type === "tuple") {
|
|
428
|
-
const result = [];
|
|
429
|
-
value.forEach((value, index) => {
|
|
430
|
-
const schema = this.type === "array" ? this.inner : this.list[index];
|
|
431
|
-
const item = schema ? schema.simplify(value) : value;
|
|
432
|
-
result.push(item);
|
|
433
|
-
});
|
|
434
|
-
return result;
|
|
435
|
-
} else if (this.type === "intersect") {
|
|
436
|
-
const result = {};
|
|
437
|
-
for (const item of this.list) Object.assign(result, item.simplify(value));
|
|
438
|
-
return result;
|
|
439
|
-
} else if (this.type === "union") for (const schema of this.list) try {
|
|
440
|
-
Schema.resolve(value, schema, {});
|
|
441
|
-
return schema.simplify(value);
|
|
442
|
-
} catch {}
|
|
443
|
-
return value;
|
|
444
|
-
};
|
|
445
|
-
Schema.prototype.toString = function toString(inline) {
|
|
446
|
-
return formatters[this.type]?.(this, inline) ?? `Schema<${this.type}>`;
|
|
447
|
-
};
|
|
448
|
-
Schema.prototype.role = function role(role, extra) {
|
|
449
|
-
const schema = Schema(this);
|
|
450
|
-
schema.meta = {
|
|
451
|
-
...schema.meta,
|
|
452
|
-
role,
|
|
453
|
-
extra
|
|
454
|
-
};
|
|
455
|
-
return schema;
|
|
456
|
-
};
|
|
457
|
-
for (const key of [
|
|
458
|
-
"default",
|
|
459
|
-
"link",
|
|
460
|
-
"comment",
|
|
461
|
-
"description",
|
|
462
|
-
"max",
|
|
463
|
-
"min",
|
|
464
|
-
"step"
|
|
465
|
-
]) Object.assign(Schema.prototype, { [key](value) {
|
|
466
|
-
const schema = Schema(this);
|
|
467
|
-
schema.meta = {
|
|
468
|
-
...schema.meta,
|
|
469
|
-
[key]: value
|
|
470
|
-
};
|
|
471
|
-
return schema;
|
|
472
|
-
} });
|
|
473
|
-
const resolvers = {};
|
|
474
|
-
Schema.extend = function extend(type, resolve) {
|
|
475
|
-
resolvers[type] = resolve;
|
|
476
|
-
};
|
|
477
|
-
Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
|
|
478
|
-
if (!schema) return [data];
|
|
479
|
-
if (options.ignore?.(data, schema)) return [data];
|
|
480
|
-
if (isNullable(data) && schema.type !== "lazy") {
|
|
481
|
-
if (schema.meta.required) throw new ValidationError$1(`missing required value`, options);
|
|
482
|
-
let current = schema;
|
|
483
|
-
let fallback = schema.meta.default;
|
|
484
|
-
while (current?.type === "intersect" && isNullable(fallback)) {
|
|
485
|
-
current = current.list[0];
|
|
486
|
-
fallback = current?.meta.default;
|
|
487
|
-
}
|
|
488
|
-
if (isNullable(fallback)) return [data];
|
|
489
|
-
data = clone(fallback);
|
|
490
|
-
}
|
|
491
|
-
const callback = resolvers[schema.type];
|
|
492
|
-
if (!callback) throw new ValidationError$1(`unsupported type "${schema.type}"`, options);
|
|
493
|
-
try {
|
|
494
|
-
return callback(data, schema, options, strict);
|
|
495
|
-
} catch (error) {
|
|
496
|
-
if (!schema.meta.loose) throw error;
|
|
497
|
-
return [schema.meta.default];
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
Schema.from = function from(source) {
|
|
501
|
-
if (isNullable(source)) return Schema.any();
|
|
502
|
-
else if ([
|
|
503
|
-
"string",
|
|
504
|
-
"number",
|
|
505
|
-
"boolean"
|
|
506
|
-
].includes(typeof source)) return Schema.const(source).required();
|
|
507
|
-
else if (source[kSchema]) return source;
|
|
508
|
-
else if (typeof source === "function") switch (source) {
|
|
509
|
-
case String: return Schema.string().required();
|
|
510
|
-
case Number: return Schema.number().required();
|
|
511
|
-
case Boolean: return Schema.boolean().required();
|
|
512
|
-
case Function: return Schema.function().required();
|
|
513
|
-
default: return Schema.is(source).required();
|
|
514
|
-
}
|
|
515
|
-
else throw new TypeError(`cannot infer schema from ${source}`);
|
|
516
|
-
};
|
|
517
|
-
Schema.lazy = function lazy(builder) {
|
|
518
|
-
const toJSON = () => {
|
|
519
|
-
if (!schema.inner[kSchema]) {
|
|
520
|
-
schema.inner = schema.builder();
|
|
521
|
-
schema.inner.meta = {
|
|
522
|
-
...schema.meta,
|
|
523
|
-
...schema.inner.meta
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
return schema.inner.toJSON();
|
|
527
|
-
};
|
|
528
|
-
const schema = new Schema({
|
|
529
|
-
type: "lazy",
|
|
530
|
-
builder,
|
|
531
|
-
inner: { toJSON }
|
|
532
|
-
});
|
|
533
|
-
return schema;
|
|
534
|
-
};
|
|
535
|
-
Schema.natural = function natural() {
|
|
536
|
-
return Schema.number().step(1).min(0);
|
|
537
|
-
};
|
|
538
|
-
Schema.percent = function percent() {
|
|
539
|
-
return Schema.number().step(.01).min(0).max(1).role("slider");
|
|
540
|
-
};
|
|
541
|
-
Schema.date = function date() {
|
|
542
|
-
return Schema.union([Schema.is(Date), Schema.transform(Schema.string().role("datetime"), (value, options) => {
|
|
543
|
-
const date = new Date(value);
|
|
544
|
-
if (isNaN(+date)) throw new ValidationError$1(`invalid date "${value}"`, options);
|
|
545
|
-
return date;
|
|
546
|
-
}, true)]);
|
|
547
|
-
};
|
|
548
|
-
Schema.regExp = function regExp(flag = "") {
|
|
549
|
-
return Schema.union([Schema.is(RegExp), Schema.transform(Schema.string().role("regexp", { flag }), (value, options) => {
|
|
550
|
-
try {
|
|
551
|
-
return new RegExp(value, flag);
|
|
552
|
-
} catch (e) {
|
|
553
|
-
throw new ValidationError$1(e.message, options);
|
|
554
|
-
}
|
|
555
|
-
}, true)]);
|
|
556
|
-
};
|
|
557
|
-
Schema.arrayBuffer = function arrayBuffer(encoding) {
|
|
558
|
-
return Schema.union([
|
|
559
|
-
Schema.is(ArrayBuffer),
|
|
560
|
-
Schema.is(SharedArrayBuffer),
|
|
561
|
-
Schema.transform(Schema.any(), (value, options) => {
|
|
562
|
-
if (Binary.isSource(value)) return Binary.fromSource(value);
|
|
563
|
-
throw new ValidationError$1(`expected ArrayBufferSource but got ${value}`, options);
|
|
564
|
-
}, true),
|
|
565
|
-
...encoding ? [Schema.transform(Schema.string(), (value, options) => {
|
|
566
|
-
try {
|
|
567
|
-
return encoding === "base64" ? Binary.fromBase64(value) : Binary.fromHex(value);
|
|
568
|
-
} catch (e) {
|
|
569
|
-
throw new ValidationError$1(e.message, options);
|
|
570
|
-
}
|
|
571
|
-
}, true)] : []
|
|
572
|
-
]);
|
|
573
|
-
};
|
|
574
|
-
Schema.extend("lazy", (data, schema, options, strict) => {
|
|
575
|
-
if (!schema.inner[kSchema]) {
|
|
576
|
-
schema.inner = schema.builder();
|
|
577
|
-
schema.inner.meta = {
|
|
578
|
-
...schema.meta,
|
|
579
|
-
...schema.inner.meta
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
return Schema.resolve(data, schema.inner, options, strict);
|
|
583
|
-
});
|
|
584
|
-
Schema.extend("any", (data) => {
|
|
585
|
-
return [data];
|
|
586
|
-
});
|
|
587
|
-
Schema.extend("never", (data, _, options) => {
|
|
588
|
-
throw new ValidationError$1(`expected nullable but got ${data}`, options);
|
|
589
|
-
});
|
|
590
|
-
Schema.extend("const", (data, { value }, options) => {
|
|
591
|
-
if (deepEqual(data, value)) return [value];
|
|
592
|
-
throw new ValidationError$1(`expected ${value} but got ${data}`, options);
|
|
593
|
-
});
|
|
594
|
-
function checkWithinRange(data, meta, description, options, skipMin = false) {
|
|
595
|
-
const { max = Infinity, min = -Infinity } = meta;
|
|
596
|
-
if (data > max) throw new ValidationError$1(`expected ${description} <= ${max} but got ${data}`, options);
|
|
597
|
-
if (data < min && !skipMin) throw new ValidationError$1(`expected ${description} >= ${min} but got ${data}`, options);
|
|
598
|
-
}
|
|
599
|
-
Schema.extend("string", (data, { meta }, options) => {
|
|
600
|
-
if (typeof data !== "string") throw new ValidationError$1(`expected string but got ${data}`, options);
|
|
601
|
-
if (meta.pattern) {
|
|
602
|
-
const regexp = new RegExp(meta.pattern.source, meta.pattern.flags);
|
|
603
|
-
if (!regexp.test(data)) throw new ValidationError$1(`expect string to match regexp ${regexp}`, options);
|
|
604
|
-
}
|
|
605
|
-
checkWithinRange(data.length, meta, "string length", options);
|
|
606
|
-
return [data];
|
|
607
|
-
});
|
|
608
|
-
function decimalShift(data, digits) {
|
|
609
|
-
const str = data.toString();
|
|
610
|
-
if (str.includes("e")) return data * Math.pow(10, digits);
|
|
611
|
-
const index = str.indexOf(".");
|
|
612
|
-
if (index === -1) return data * Math.pow(10, digits);
|
|
613
|
-
const frac = str.slice(index + 1);
|
|
614
|
-
const integer = str.slice(0, index);
|
|
615
|
-
if (frac.length <= digits) return +(integer + frac.padEnd(digits, "0"));
|
|
616
|
-
return +(integer + frac.slice(0, digits) + "." + frac.slice(digits));
|
|
617
|
-
}
|
|
618
|
-
function isMultipleOf(data, min, step) {
|
|
619
|
-
step = Math.abs(step);
|
|
620
|
-
if (!/^\d+\.\d+$/.test(step.toString())) return (data - min) % step === 0;
|
|
621
|
-
const index = step.toString().indexOf(".");
|
|
622
|
-
const digits = step.toString().slice(index + 1).length;
|
|
623
|
-
return Math.abs(decimalShift(data, digits) - decimalShift(min, digits)) % decimalShift(step, digits) === 0;
|
|
624
|
-
}
|
|
625
|
-
Schema.extend("number", (data, { meta }, options) => {
|
|
626
|
-
if (typeof data !== "number") throw new ValidationError$1(`expected number but got ${data}`, options);
|
|
627
|
-
checkWithinRange(data, meta, "number", options);
|
|
628
|
-
const { step } = meta;
|
|
629
|
-
if (step && !isMultipleOf(data, meta.min ?? 0, step)) throw new ValidationError$1(`expected number multiple of ${step} but got ${data}`, options);
|
|
630
|
-
return [data];
|
|
631
|
-
});
|
|
632
|
-
Schema.extend("boolean", (data, _, options) => {
|
|
633
|
-
if (typeof data === "boolean") return [data];
|
|
634
|
-
throw new ValidationError$1(`expected boolean but got ${data}`, options);
|
|
635
|
-
});
|
|
636
|
-
Schema.extend("bitset", (data, { bits, meta }, options) => {
|
|
637
|
-
let value = 0, keys = [];
|
|
638
|
-
if (typeof data === "number") {
|
|
639
|
-
value = data;
|
|
640
|
-
for (const key in bits) if (data & bits[key]) keys.push(key);
|
|
641
|
-
} else if (Array.isArray(data)) {
|
|
642
|
-
keys = data;
|
|
643
|
-
for (const key of keys) {
|
|
644
|
-
if (typeof key !== "string") throw new ValidationError$1(`expected string but got ${key}`, options);
|
|
645
|
-
if (key in bits) value |= bits[key];
|
|
646
|
-
}
|
|
647
|
-
} else throw new ValidationError$1(`expected number or array but got ${data}`, options);
|
|
648
|
-
if (value === meta.default) return [value];
|
|
649
|
-
return [value, keys];
|
|
650
|
-
});
|
|
651
|
-
Schema.extend("function", (data, _, options) => {
|
|
652
|
-
if (typeof data === "function") return [data];
|
|
653
|
-
throw new ValidationError$1(`expected function but got ${data}`, options);
|
|
654
|
-
});
|
|
655
|
-
Schema.extend("is", (data, { constructor }, options) => {
|
|
656
|
-
if (typeof constructor === "function") {
|
|
657
|
-
if (data instanceof constructor) return [data];
|
|
658
|
-
throw new ValidationError$1(`expected ${constructor.name} but got ${data}`, options);
|
|
659
|
-
} else {
|
|
660
|
-
if (isNullable(data)) throw new ValidationError$1(`expected ${constructor} but got ${data}`, options);
|
|
661
|
-
let prototype = Object.getPrototypeOf(data);
|
|
662
|
-
while (prototype) {
|
|
663
|
-
if (prototype.constructor?.name === constructor) return [data];
|
|
664
|
-
prototype = Object.getPrototypeOf(prototype);
|
|
665
|
-
}
|
|
666
|
-
throw new ValidationError$1(`expected ${constructor} but got ${data}`, options);
|
|
667
|
-
}
|
|
668
|
-
});
|
|
669
|
-
function property(data, key, schema, options) {
|
|
670
|
-
try {
|
|
671
|
-
const [value, adapted] = Schema.resolve(data[key], schema, {
|
|
672
|
-
...options,
|
|
673
|
-
path: [...options.path || [], key]
|
|
674
|
-
});
|
|
675
|
-
if (adapted !== void 0) data[key] = adapted;
|
|
676
|
-
return value;
|
|
677
|
-
} catch (e) {
|
|
678
|
-
if (!options?.autofix) throw e;
|
|
679
|
-
delete data[key];
|
|
680
|
-
return schema.meta.default;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
Schema.extend("array", (data, { inner, meta }, options) => {
|
|
684
|
-
if (!Array.isArray(data)) throw new ValidationError$1(`expected array but got ${data}`, options);
|
|
685
|
-
checkWithinRange(data.length, meta, "array length", options, !isNullable(inner.meta.default));
|
|
686
|
-
return [data.map((_, index) => property(data, index, inner, options))];
|
|
687
|
-
});
|
|
688
|
-
Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
|
|
689
|
-
if (!isPlainObject(data)) throw new ValidationError$1(`expected object but got ${data}`, options);
|
|
690
|
-
const result = {};
|
|
691
|
-
for (const key in data) {
|
|
692
|
-
let rKey;
|
|
693
|
-
try {
|
|
694
|
-
rKey = Schema.resolve(key, sKey, options)[0];
|
|
695
|
-
} catch (error) {
|
|
696
|
-
if (strict) continue;
|
|
697
|
-
throw error;
|
|
698
|
-
}
|
|
699
|
-
result[rKey] = property(data, key, inner, options);
|
|
700
|
-
data[rKey] = data[key];
|
|
701
|
-
if (key !== rKey) delete data[key];
|
|
702
|
-
}
|
|
703
|
-
return [result];
|
|
704
|
-
});
|
|
705
|
-
Schema.extend("tuple", (data, { list }, options, strict) => {
|
|
706
|
-
if (!Array.isArray(data)) throw new ValidationError$1(`expected array but got ${data}`, options);
|
|
707
|
-
const result = list.map((inner, index) => property(data, index, inner, options));
|
|
708
|
-
if (strict) return [result];
|
|
709
|
-
result.push(...data.slice(list.length));
|
|
710
|
-
return [result];
|
|
711
|
-
});
|
|
712
|
-
function merge(result, data) {
|
|
713
|
-
for (const key in data) {
|
|
714
|
-
if (key in result) continue;
|
|
715
|
-
result[key] = data[key];
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
Schema.extend("object", (data, { dict }, options, strict) => {
|
|
719
|
-
if (!isPlainObject(data)) throw new ValidationError$1(`expected object but got ${data}`, options);
|
|
720
|
-
const result = {};
|
|
721
|
-
for (const key in dict) {
|
|
722
|
-
const value = property(data, key, dict[key], options);
|
|
723
|
-
if (!isNullable(value) || key in data) result[key] = value;
|
|
724
|
-
}
|
|
725
|
-
if (!strict) merge(result, data);
|
|
726
|
-
return [result];
|
|
727
|
-
});
|
|
728
|
-
Schema.extend("union", (data, { list, toString }, options, strict) => {
|
|
729
|
-
const messages = [];
|
|
730
|
-
for (const inner of list) try {
|
|
731
|
-
return Schema.resolve(data, inner, options, strict);
|
|
732
|
-
} catch (error) {
|
|
733
|
-
messages.push(error);
|
|
734
|
-
}
|
|
735
|
-
throw new ValidationError$1(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
|
|
736
|
-
});
|
|
737
|
-
Schema.extend("intersect", (data, { list, toString }, options, strict) => {
|
|
738
|
-
if (!list.length) return [data];
|
|
739
|
-
let result;
|
|
740
|
-
for (const inner of list) {
|
|
741
|
-
const value = Schema.resolve(data, inner, options, true)[0];
|
|
742
|
-
if (isNullable(value)) continue;
|
|
743
|
-
if (isNullable(result)) result = value;
|
|
744
|
-
else if (typeof result !== typeof value) throw new ValidationError$1(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
|
|
745
|
-
else if (typeof value === "object") merge(result ??= {}, value);
|
|
746
|
-
else if (result !== value) throw new ValidationError$1(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
|
|
747
|
-
}
|
|
748
|
-
if (!strict && isPlainObject(data)) merge(result, data);
|
|
749
|
-
return [result];
|
|
750
|
-
});
|
|
751
|
-
Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
|
|
752
|
-
const [result, adapted = data] = Schema.resolve(data, inner, options, true);
|
|
753
|
-
if (preserve) return [callback(result)];
|
|
754
|
-
else return [callback(result), callback(adapted)];
|
|
755
|
-
});
|
|
756
|
-
const formatters = {};
|
|
757
|
-
function defineMethod(name, keys, format) {
|
|
758
|
-
formatters[name] = format;
|
|
759
|
-
Object.assign(Schema, { [name](...args) {
|
|
760
|
-
const schema = new Schema({ type: name });
|
|
761
|
-
keys.forEach((key, index) => {
|
|
762
|
-
switch (key) {
|
|
763
|
-
case "sKey":
|
|
764
|
-
schema.sKey = args[index] ?? Schema.string();
|
|
765
|
-
break;
|
|
766
|
-
case "inner":
|
|
767
|
-
schema.inner = Schema.from(args[index]);
|
|
768
|
-
break;
|
|
769
|
-
case "list":
|
|
770
|
-
schema.list = args[index].map(Schema.from);
|
|
771
|
-
break;
|
|
772
|
-
case "dict":
|
|
773
|
-
schema.dict = mapValues(args[index], Schema.from);
|
|
774
|
-
break;
|
|
775
|
-
case "bits":
|
|
776
|
-
schema.bits = {};
|
|
777
|
-
for (const key in args[index]) {
|
|
778
|
-
if (typeof args[index][key] !== "number") continue;
|
|
779
|
-
schema.bits[key] = args[index][key];
|
|
780
|
-
}
|
|
781
|
-
break;
|
|
782
|
-
case "callback": {
|
|
783
|
-
const callback = schema.callback = args[index];
|
|
784
|
-
callback["toJSON"] ||= () => callback.toString();
|
|
785
|
-
break;
|
|
786
|
-
}
|
|
787
|
-
case "constructor": {
|
|
788
|
-
const constructor = schema.constructor = args[index];
|
|
789
|
-
if (typeof constructor === "function") constructor["toJSON"] ||= () => constructor["name"];
|
|
790
|
-
break;
|
|
791
|
-
}
|
|
792
|
-
default: schema[key] = args[index];
|
|
793
|
-
}
|
|
794
|
-
});
|
|
795
|
-
if (name === "object" || name === "dict") schema.meta.default = {};
|
|
796
|
-
else if (name === "array" || name === "tuple") schema.meta.default = [];
|
|
797
|
-
else if (name === "bitset") schema.meta.default = 0;
|
|
798
|
-
return schema;
|
|
799
|
-
} });
|
|
800
|
-
}
|
|
801
|
-
defineMethod("is", ["constructor"], ({ constructor }) => {
|
|
802
|
-
if (typeof constructor === "function") return constructor.name;
|
|
803
|
-
else return constructor;
|
|
804
|
-
});
|
|
805
|
-
defineMethod("any", [], () => "any");
|
|
806
|
-
defineMethod("never", [], () => "never");
|
|
807
|
-
defineMethod("const", ["value"], ({ value }) => typeof value === "string" ? JSON.stringify(value) : value);
|
|
808
|
-
defineMethod("string", [], () => "string");
|
|
809
|
-
defineMethod("number", [], () => "number");
|
|
810
|
-
defineMethod("boolean", [], () => "boolean");
|
|
811
|
-
defineMethod("bitset", ["bits"], () => "bitset");
|
|
812
|
-
defineMethod("function", [], () => "function");
|
|
813
|
-
defineMethod("array", ["inner"], ({ inner }) => `${inner.toString(true)}[]`);
|
|
814
|
-
defineMethod("dict", ["inner", "sKey"], ({ inner, sKey }) => `{ [key: ${sKey.toString()}]: ${inner.toString()} }`);
|
|
815
|
-
defineMethod("tuple", ["list"], ({ list }) => `[${list.map((inner) => inner.toString()).join(", ")}]`);
|
|
816
|
-
defineMethod("object", ["dict"], ({ dict }) => {
|
|
817
|
-
if (Object.keys(dict).length === 0) return "{}";
|
|
818
|
-
return `{ ${Object.entries(dict).map(([key, inner]) => {
|
|
819
|
-
return `${key}${inner.meta.required ? "" : "?"}: ${inner.toString()}`;
|
|
820
|
-
}).join(", ")} }`;
|
|
821
|
-
});
|
|
822
|
-
defineMethod("union", ["list"], ({ list }, inline) => {
|
|
823
|
-
const result = list.map(({ toString: format }) => format()).join(" | ");
|
|
824
|
-
return inline ? `(${result})` : result;
|
|
825
|
-
});
|
|
826
|
-
defineMethod("intersect", ["list"], ({ list }) => {
|
|
827
|
-
return `${list.map((inner) => inner.toString(true)).join(" & ")}`;
|
|
828
|
-
});
|
|
829
|
-
defineMethod("transform", [
|
|
830
|
-
"inner",
|
|
831
|
-
"callback",
|
|
832
|
-
"preserve"
|
|
833
|
-
], ({ inner }, isInner) => inner.toString(isInner));
|
|
834
|
-
//#endregion
|
|
835
|
-
//#region ../../../vendor/cordis/src/utils.ts
|
|
836
|
-
/** Ordered collection of disposable values with O(1) deletion by value. */
|
|
837
|
-
var DisposableList = class {
|
|
838
|
-
sn = 0;
|
|
839
|
-
map = /* @__PURE__ */ new Map();
|
|
840
|
-
weak = /* @__PURE__ */ new WeakMap();
|
|
841
|
-
get length() {
|
|
842
|
-
return this.map.size;
|
|
843
|
-
}
|
|
844
|
-
push(value) {
|
|
845
|
-
const sn = ++this.sn;
|
|
846
|
-
this.map.set(sn, value);
|
|
847
|
-
this.weak.set(value, sn);
|
|
848
|
-
return () => this.map.delete(sn);
|
|
849
|
-
}
|
|
850
|
-
delete(value) {
|
|
851
|
-
const sn = this.weak.get(value);
|
|
852
|
-
if (!sn) return false;
|
|
853
|
-
return this.map.delete(sn);
|
|
854
|
-
}
|
|
855
|
-
clear() {
|
|
856
|
-
const values = [...this.map.values()];
|
|
857
|
-
this.map.clear();
|
|
858
|
-
return values.reverse();
|
|
859
|
-
}
|
|
860
|
-
[Symbol.iterator]() {
|
|
861
|
-
return this.map.values();
|
|
862
|
-
}
|
|
863
|
-
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
864
|
-
return [...this];
|
|
865
|
-
}
|
|
866
|
-
};
|
|
867
|
-
/** Shared symbols used to avoid public property-name collisions. */
|
|
868
|
-
const symbols = {
|
|
869
|
-
shadow: Symbol.for("cordis.shadow"),
|
|
870
|
-
receiver: Symbol.for("cordis.receiver"),
|
|
871
|
-
original: Symbol.for("cordis.original"),
|
|
872
|
-
metadata: Symbol.for("cordis.metadata"),
|
|
873
|
-
initHooks: Symbol.for("cordis.initHooks"),
|
|
874
|
-
checkProto: Symbol.for("cordis.checkProto"),
|
|
875
|
-
effect: Symbol.for("cordis.effect"),
|
|
876
|
-
filter: Symbol.for("cordis.filter"),
|
|
877
|
-
isolate: Symbol.for("cordis.isolate"),
|
|
878
|
-
intercept: Symbol.for("cordis.intercept"),
|
|
879
|
-
init: Symbol.for("cordis.init"),
|
|
880
|
-
check: Symbol.for("cordis.check"),
|
|
881
|
-
config: Symbol.for("cordis.config"),
|
|
882
|
-
invoke: Symbol.for("cordis.invoke"),
|
|
883
|
-
extend: Symbol.for("cordis.extend"),
|
|
884
|
-
tracker: Symbol.for("cordis.tracker"),
|
|
885
|
-
resolveConfig: Symbol.for("cordis.resolveConfig")
|
|
886
|
-
};
|
|
887
|
-
const GeneratorFunction = function* () {}.constructor;
|
|
888
|
-
const AsyncGeneratorFunction = async function* () {}.constructor;
|
|
889
|
-
/** Return true when a plugin callback should be constructed with `new`. */
|
|
890
|
-
function isConstructor(func) {
|
|
891
|
-
if (!func.prototype) return false;
|
|
892
|
-
if (func instanceof GeneratorFunction) return false;
|
|
893
|
-
if (AsyncGeneratorFunction !== Function && func instanceof AsyncGeneratorFunction) return false;
|
|
894
|
-
return true;
|
|
895
|
-
}
|
|
896
|
-
/** Merge two prototype chains while preserving descriptors from `proto1`. */
|
|
897
|
-
function joinPrototype(proto1, proto2) {
|
|
898
|
-
if (proto1 === Object.prototype) return proto2;
|
|
899
|
-
const result = Object.create(joinPrototype(Object.getPrototypeOf(proto1), proto2));
|
|
900
|
-
for (const key of Reflect.ownKeys(proto1)) Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(proto1, key));
|
|
901
|
-
return result;
|
|
902
|
-
}
|
|
903
|
-
/** Return true for non-null objects and functions. */
|
|
904
|
-
function isObject(value) {
|
|
905
|
-
return value && (typeof value === "object" || typeof value === "function");
|
|
906
|
-
}
|
|
907
|
-
/** Find a property descriptor by walking an object's prototype chain. */
|
|
908
|
-
function getPropertyDescriptor(target, prop) {
|
|
909
|
-
let proto = target;
|
|
910
|
-
while (proto) {
|
|
911
|
-
const desc = Reflect.getOwnPropertyDescriptor(proto, prop);
|
|
912
|
-
if (desc) return desc;
|
|
913
|
-
proto = Object.getPrototypeOf(proto);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
/** Wrap services/functions so method calls see the caller's active context. */
|
|
917
|
-
function getTraceable(ctx, value) {
|
|
918
|
-
if (!isObject(value)) return value;
|
|
919
|
-
if (Object.hasOwn(value, symbols.shadow)) return Object.getPrototypeOf(value);
|
|
920
|
-
const tracker = value[symbols.tracker];
|
|
921
|
-
if (!tracker) return value;
|
|
922
|
-
return createTraceable(ctx, value, tracker);
|
|
923
|
-
}
|
|
924
|
-
/** Return a proxy that overlays readonly or writable properties onto a target. */
|
|
925
|
-
function withProps(target, props) {
|
|
926
|
-
if (!props) return target;
|
|
927
|
-
return new Proxy(target, {
|
|
928
|
-
get: (target, prop, receiver) => {
|
|
929
|
-
if (prop in props && prop !== "constructor") return Reflect.get(props, prop, receiver);
|
|
930
|
-
return Reflect.get(target, prop, receiver);
|
|
931
|
-
},
|
|
932
|
-
set: (target, prop, value, receiver) => {
|
|
933
|
-
if (prop in props && prop !== "constructor") return Reflect.set(props, prop, value, receiver);
|
|
934
|
-
return Reflect.set(target, prop, value, receiver);
|
|
935
|
-
}
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
function withProp(target, prop, value) {
|
|
939
|
-
return withProps(target, Object.defineProperty(Object.create(null), prop, {
|
|
940
|
-
value,
|
|
941
|
-
writable: false
|
|
942
|
-
}));
|
|
943
|
-
}
|
|
944
|
-
function createShadow(ctx, target, property, receiver) {
|
|
945
|
-
if (!property) return receiver;
|
|
946
|
-
const origin = Reflect.getOwnPropertyDescriptor(target, property)?.value;
|
|
947
|
-
if (!origin) return receiver;
|
|
948
|
-
return withProp(receiver, property, ctx.extend({ [symbols.shadow]: origin }));
|
|
949
|
-
}
|
|
950
|
-
function createShadowMethod(ctx, value, outer, shadow) {
|
|
951
|
-
return new Proxy(value, { apply: (target, thisArg, args) => {
|
|
952
|
-
if (thisArg === outer) thisArg = shadow;
|
|
953
|
-
return getTraceable(ctx, Reflect.apply(target, thisArg, args));
|
|
954
|
-
} });
|
|
955
|
-
}
|
|
956
|
-
function createTraceable(ctx, value, tracker) {
|
|
957
|
-
if (ctx[symbols.shadow] && !tracker.noShadow) ctx = Object.getPrototypeOf(ctx);
|
|
958
|
-
const proxy = new Proxy(value, {
|
|
959
|
-
get: (target, prop, receiver) => {
|
|
960
|
-
if (prop === symbols.original) return target;
|
|
961
|
-
if (prop === tracker.property) return ctx;
|
|
962
|
-
if (typeof prop === "symbol") return Reflect.get(target, prop, receiver);
|
|
963
|
-
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver));
|
|
964
|
-
let shadow, innerValue;
|
|
965
|
-
const desc = getPropertyDescriptor(target, prop);
|
|
966
|
-
if (desc && "value" in desc) innerValue = desc.value;
|
|
967
|
-
else {
|
|
968
|
-
shadow = createShadow(ctx, target, tracker.property, receiver);
|
|
969
|
-
innerValue = Reflect.get(target, prop, shadow);
|
|
970
|
-
}
|
|
971
|
-
const innerTracker = innerValue?.[symbols.tracker];
|
|
972
|
-
if (innerTracker) return createTraceable(ctx, innerValue, innerTracker);
|
|
973
|
-
else if (!tracker.noShadow && typeof innerValue === "function") {
|
|
974
|
-
shadow ??= createShadow(ctx, target, tracker.property, receiver);
|
|
975
|
-
return createShadowMethod(ctx, innerValue, receiver, shadow);
|
|
976
|
-
} else return innerValue;
|
|
977
|
-
},
|
|
978
|
-
set: (target, prop, value, receiver) => {
|
|
979
|
-
if (prop === symbols.original) return false;
|
|
980
|
-
if (prop === tracker.property) return false;
|
|
981
|
-
if (typeof prop === "symbol") return Reflect.set(target, prop, value, receiver);
|
|
982
|
-
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) return Reflect.set(ctx, `${tracker.associate}.${prop}`, value, withProp(ctx, symbols.receiver, receiver));
|
|
983
|
-
const shadow = createShadow(ctx, target, tracker.property, receiver);
|
|
984
|
-
return Reflect.set(target, prop, value, shadow);
|
|
985
|
-
},
|
|
986
|
-
apply: (target, thisArg, args) => {
|
|
987
|
-
return applyTraceable(proxy, target, thisArg, args);
|
|
988
|
-
}
|
|
989
|
-
});
|
|
990
|
-
return proxy;
|
|
991
|
-
}
|
|
992
|
-
function applyTraceable(proxy, value, thisArg, args) {
|
|
993
|
-
if (!value[symbols.invoke]) return Reflect.apply(value, thisArg, args);
|
|
994
|
-
return value[symbols.invoke].apply(proxy, args);
|
|
995
|
-
}
|
|
996
|
-
/** Create a callable service object that dispatches through `symbols.invoke`. */
|
|
997
|
-
function createCallable(name, proto, tracker) {
|
|
998
|
-
const self = function(...args) {
|
|
999
|
-
return applyTraceable(createTraceable(self["ctx"], self, tracker), self, this, args);
|
|
1000
|
-
};
|
|
1001
|
-
defineProperty(self, "name", name);
|
|
1002
|
-
return Object.setPrototypeOf(self, proto);
|
|
1003
|
-
}
|
|
1004
|
-
function handleError(info, reason, getOuterStack) {
|
|
1005
|
-
const innerLines = info.error.stack.split("\n");
|
|
1006
|
-
if (typeof reason?.stack !== "string") {
|
|
1007
|
-
const outerError = new Error(reason);
|
|
1008
|
-
const lines = outerError.stack.split("\n");
|
|
1009
|
-
lines.splice(1, Infinity, ...getOuterStack());
|
|
1010
|
-
outerError.stack = lines.join("\n");
|
|
1011
|
-
throw outerError;
|
|
1012
|
-
}
|
|
1013
|
-
const lines = reason.stack.split("\n");
|
|
1014
|
-
let index = lines.indexOf(innerLines[2]);
|
|
1015
|
-
if (index === -1) throw reason;
|
|
1016
|
-
index -= info.offset;
|
|
1017
|
-
while (index > 0) {
|
|
1018
|
-
if (!lines[index - 1].endsWith(" (<anonymous>)")) break;
|
|
1019
|
-
index -= 1;
|
|
1020
|
-
}
|
|
1021
|
-
lines.splice(index, Infinity, ...getOuterStack());
|
|
1022
|
-
reason.stack = lines.join("\n");
|
|
1023
|
-
throw reason;
|
|
1024
|
-
}
|
|
1025
|
-
/** Run a callback and splice outer call-site frames into thrown async errors. */
|
|
1026
|
-
function composeError(callback, getOuterStack = buildOuterStack()) {
|
|
1027
|
-
const info = {
|
|
1028
|
-
offset: 1,
|
|
1029
|
-
error: /* @__PURE__ */ new Error()
|
|
1030
|
-
};
|
|
1031
|
-
try {
|
|
1032
|
-
const result = callback(info);
|
|
1033
|
-
if (isObject(result) && "then" in result) return result.then(void 0, (reason) => handleError(info, reason, getOuterStack));
|
|
1034
|
-
else return result;
|
|
1035
|
-
} catch (reason) {
|
|
1036
|
-
handleError(info, reason, getOuterStack);
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
/** Capture a lazy stack-frame supplier for later error composition. */
|
|
1040
|
-
function buildOuterStack(offset = 0) {
|
|
1041
|
-
const outerError = /* @__PURE__ */ new Error();
|
|
1042
|
-
return () => outerError.stack.split("\n").slice(3 + offset);
|
|
1043
|
-
}
|
|
1044
|
-
//#endregion
|
|
1045
|
-
//#region ../../../vendor/cordis/src/events.ts
|
|
1046
|
-
/**
|
|
1047
|
-
* Return whether an event result should stop a bail-style dispatch.
|
|
1048
|
-
*
|
|
1049
|
-
* @param value — a listener's return value.
|
|
1050
|
-
* @returns `true` unless `value` is `null`, `false`, or `undefined`.
|
|
1051
|
-
*/
|
|
1052
|
-
function isBailed(value) {
|
|
1053
|
-
return value !== null && value !== false && value !== void 0;
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Event bus installed as `ctx.events` and mixed into every context.
|
|
1057
|
-
*
|
|
1058
|
-
* The service supports concurrent, synchronous, serial, bail, and waterfall
|
|
1059
|
-
* dispatch and automatically disposes listeners with their owning fiber.
|
|
1060
|
-
*/
|
|
1061
|
-
var EventsService = class {
|
|
1062
|
-
ctx;
|
|
1063
|
-
_hooks = {};
|
|
1064
|
-
constructor(ctx) {
|
|
1065
|
-
this.ctx = ctx;
|
|
1066
|
-
defineProperty(this, symbols.tracker, {
|
|
1067
|
-
property: "ctx",
|
|
1068
|
-
noShadow: true
|
|
1069
|
-
});
|
|
1070
|
-
this.on("internal/listener", function(name, listener, options) {
|
|
1071
|
-
if (name === "internal/update" && !options.global) return (this.fiber._hooks["internal/update"] ??= new DisposableList())[options.prepend ? "unshift" : "push"](listener);
|
|
1072
|
-
});
|
|
1073
|
-
this.on("internal/update", function(config, noSave, next) {
|
|
1074
|
-
const cbs = [...this._hooks["internal/update"] || []];
|
|
1075
|
-
const _next = () => {
|
|
1076
|
-
return (cbs.shift() ?? next).call(this, config, noSave, _next);
|
|
1077
|
-
};
|
|
1078
|
-
return _next();
|
|
1079
|
-
}, {
|
|
1080
|
-
global: true,
|
|
1081
|
-
prepend: true
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Resolve listeners for one dispatch and apply context filtering.
|
|
1086
|
-
*
|
|
1087
|
-
* @param type — the dispatch mode, reported on `internal/dispatch`.
|
|
1088
|
-
* @param args — the raw dispatch arguments; consumed up to the event name.
|
|
1089
|
-
* @returns the matching listener callbacks, bound to the dispatch `this`.
|
|
1090
|
-
*/
|
|
1091
|
-
dispatch(type, args) {
|
|
1092
|
-
const thisArg = typeof args[0] === "object" || typeof args[0] === "function" ? args.shift() : null;
|
|
1093
|
-
const name = args.shift();
|
|
1094
|
-
if (!name.startsWith("internal/")) this.emit("internal/dispatch", type, name, args, thisArg);
|
|
1095
|
-
const filter = thisArg?.[Context.filter];
|
|
1096
|
-
return (this._hooks[name] || []).filter((hook) => hook.global || !filter || filter.call(thisArg, hook.ctx)).map((hook) => hook.callback.bind(thisArg));
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Run listeners concurrently and wait for all of them.
|
|
1100
|
-
*
|
|
1101
|
-
* @param args — optional `this`, the event name, then listener arguments.
|
|
1102
|
-
* @returns a promise resolving once every listener has settled.
|
|
1103
|
-
*/
|
|
1104
|
-
async parallel(...args) {
|
|
1105
|
-
const errors = (await Promise.allSettled(this.dispatch("emit", args).map(async (cb) => cb(...args)))).filter((result) => result.status === "rejected");
|
|
1106
|
-
if (errors.length) throw new AggregateError(errors.map((error) => error.reason));
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Run listeners synchronously without waiting for returned promises.
|
|
1110
|
-
*
|
|
1111
|
-
* @param args — optional `this`, the event name, then listener arguments.
|
|
1112
|
-
*/
|
|
1113
|
-
emit(...args) {
|
|
1114
|
-
this.dispatch("emit", args).map((cb) => cb(...args));
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Run listeners in order, awaiting each, until one returns a bail value.
|
|
1118
|
-
*
|
|
1119
|
-
* @param args — optional `this`, the event name, then listener arguments.
|
|
1120
|
-
* @returns the first bail value (see {@link isBailed}), if any.
|
|
1121
|
-
*/
|
|
1122
|
-
async serial(...args) {
|
|
1123
|
-
for (const cb of this.dispatch("serial", args)) {
|
|
1124
|
-
const result = await cb(...args);
|
|
1125
|
-
if (isBailed(result)) return result;
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Run listeners synchronously until one returns a bail value.
|
|
1130
|
-
*
|
|
1131
|
-
* @param args — optional `this`, the event name, then listener arguments.
|
|
1132
|
-
* @returns the first bail value (see {@link isBailed}), if any.
|
|
1133
|
-
*/
|
|
1134
|
-
bail(...args) {
|
|
1135
|
-
for (const cb of this.dispatch("bail", args)) {
|
|
1136
|
-
const result = cb(...args);
|
|
1137
|
-
if (isBailed(result)) return result;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Compose listeners around the final `next` callback.
|
|
1142
|
-
*
|
|
1143
|
-
* The last dispatch argument is treated as the innermost `next`. Listeners
|
|
1144
|
-
* run outermost-first; a listener that does not call `next()` vetoes the
|
|
1145
|
-
* rest of the chain, including the built-in behavior.
|
|
1146
|
-
*
|
|
1147
|
-
* @param args — optional `this`, the event name, listener arguments, then `next`.
|
|
1148
|
-
* @returns the outermost listener's return value.
|
|
1149
|
-
*/
|
|
1150
|
-
waterfall(...args) {
|
|
1151
|
-
const cbs = this.dispatch("waterfall", args);
|
|
1152
|
-
const inner = args.pop();
|
|
1153
|
-
const next = () => {
|
|
1154
|
-
return (cbs.shift() ?? inner)(...args);
|
|
1155
|
-
};
|
|
1156
|
-
args.push(next);
|
|
1157
|
-
return next();
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Store a listener record as an effect on the current fiber.
|
|
1161
|
-
*
|
|
1162
|
-
* @param label — effect label shown in fiber diagnostics.
|
|
1163
|
-
* @param hooks — the listener list for one event.
|
|
1164
|
-
* @param callback — the listener to store.
|
|
1165
|
-
* @param options — placement and filtering options.
|
|
1166
|
-
* @returns a disposer that unregisters the listener.
|
|
1167
|
-
*/
|
|
1168
|
-
register(label, hooks, callback, options) {
|
|
1169
|
-
const method = options.prepend ? "unshift" : "push";
|
|
1170
|
-
return this.ctx.fiber.effect(() => {
|
|
1171
|
-
hooks[method]({
|
|
1172
|
-
ctx: this.ctx,
|
|
1173
|
-
callback,
|
|
1174
|
-
...options
|
|
1175
|
-
});
|
|
1176
|
-
return () => this.unregister(hooks, callback);
|
|
1177
|
-
}, label);
|
|
1178
|
-
}
|
|
1179
|
-
/**
|
|
1180
|
-
* Remove a stored listener record.
|
|
1181
|
-
*
|
|
1182
|
-
* @param hooks — the listener list for one event.
|
|
1183
|
-
* @param callback — the listener to remove.
|
|
1184
|
-
* @returns `true` if the listener was found and removed.
|
|
1185
|
-
*/
|
|
1186
|
-
unregister(hooks, callback) {
|
|
1187
|
-
const index = hooks.findIndex((hook) => hook.callback === callback);
|
|
1188
|
-
if (index >= 0) {
|
|
1189
|
-
hooks.splice(index, 1);
|
|
1190
|
-
return true;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
/**
|
|
1194
|
-
* Register an event listener owned by the current fiber.
|
|
1195
|
-
*
|
|
1196
|
-
* The listener is removed automatically when the fiber unloads. Throws
|
|
1197
|
-
* `CordisError('INACTIVE_EFFECT')` if the fiber is already disposed.
|
|
1198
|
-
*
|
|
1199
|
-
* @param name — the event name to listen for.
|
|
1200
|
-
* @param listener — called with the dispatch arguments.
|
|
1201
|
-
* @param options — listener options; a boolean is shorthand for `prepend`.
|
|
1202
|
-
* @returns a disposer removing the listener; `true` if it was still registered.
|
|
1203
|
-
*/
|
|
1204
|
-
on(name, listener, options) {
|
|
1205
|
-
if (typeof options !== "object") options = { prepend: options };
|
|
1206
|
-
this.ctx.fiber.assertActive();
|
|
1207
|
-
listener = this.ctx.reflect.bind(listener);
|
|
1208
|
-
const result = this.bail(this.ctx, "internal/listener", name, listener, options);
|
|
1209
|
-
if (result) return result;
|
|
1210
|
-
const hooks = this._hooks[name] ||= [];
|
|
1211
|
-
const label = `ctx.on(${typeof name === "string" ? JSON.stringify(name) : name.toString()})`;
|
|
1212
|
-
return this.register(label, hooks, listener, options);
|
|
1213
|
-
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Register an event listener that disposes itself after the first call.
|
|
1216
|
-
*
|
|
1217
|
-
* @param name — the event name to listen for.
|
|
1218
|
-
* @param listener — called at most once with the dispatch arguments.
|
|
1219
|
-
* @param options — listener options; a boolean is shorthand for `prepend`.
|
|
1220
|
-
* @returns a disposer removing the listener; `true` if it was still registered.
|
|
1221
|
-
*/
|
|
1222
|
-
once(name, listener, options) {
|
|
1223
|
-
const dispose = this.on(name, function(...args) {
|
|
1224
|
-
dispose();
|
|
1225
|
-
return listener.apply(this, args);
|
|
1226
|
-
}, options);
|
|
1227
|
-
return dispose;
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
//#endregion
|
|
1231
|
-
//#region ../../../vendor/cordis/src/logger.ts
|
|
1232
|
-
/** Built-in placeholder formatters used by `Logger.format()`. */
|
|
1233
|
-
const defaultFormatters = {
|
|
1234
|
-
s: (value) => String(value),
|
|
1235
|
-
d: (value) => Math.trunc(Number(value)),
|
|
1236
|
-
i: (value) => Math.trunc(Number(value)),
|
|
1237
|
-
f: (value) => Number(value),
|
|
1238
|
-
o: (value) => JSON.stringify(value),
|
|
1239
|
-
O: (value) => JSON.stringify(value),
|
|
1240
|
-
c: () => "",
|
|
1241
|
-
C: (value, exporter, message) => {
|
|
1242
|
-
return Logger.color(exporter, Logger.code(message.name, exporter.colors), value);
|
|
1243
|
-
}
|
|
1244
|
-
};
|
|
1245
|
-
function isAggregateError(error) {
|
|
1246
|
-
return error instanceof Error && Array.isArray(error["errors"]);
|
|
1247
|
-
}
|
|
1248
|
-
/** Logger facade for one named subsystem. */
|
|
1249
|
-
var Logger = class {
|
|
1250
|
-
service;
|
|
1251
|
-
static color(exporter, code, value, decoration = "") {
|
|
1252
|
-
if (!exporter.colors) return "" + value;
|
|
1253
|
-
return `\u001b[3${code < 8 ? code : "8;5;" + code}${exporter.colors >= 2 ? decoration : ""}m${value}\u001b[0m`;
|
|
1254
|
-
}
|
|
1255
|
-
static code(name, level) {
|
|
1256
|
-
let hash = 0;
|
|
1257
|
-
for (let i = 0; i < name.length; i++) {
|
|
1258
|
-
hash = (hash << 3) - hash + name.charCodeAt(i) + 13;
|
|
1259
|
-
hash |= 0;
|
|
1260
|
-
}
|
|
1261
|
-
const colors = !level ? [] : level >= 2 ? c256 : c16;
|
|
1262
|
-
return colors[Math.abs(hash) % colors.length];
|
|
1263
|
-
}
|
|
1264
|
-
static format(exporter, message) {
|
|
1265
|
-
const args = message.args.slice();
|
|
1266
|
-
if (args[0] instanceof Error) {
|
|
1267
|
-
args[0] = args[0].stack || args[0].message;
|
|
1268
|
-
args.unshift("%s");
|
|
1269
|
-
} else if (typeof args[0] !== "string") args.unshift("%o");
|
|
1270
|
-
let format = args.shift();
|
|
1271
|
-
format = format.replace(/%([a-zA-Z%])/g, (match, char) => {
|
|
1272
|
-
if (match === "%%") return "%";
|
|
1273
|
-
const formatter = exporter.formatters?.[char] ?? defaultFormatters[char];
|
|
1274
|
-
if (typeof formatter === "function") return formatter(args.shift(), exporter, message);
|
|
1275
|
-
return match;
|
|
1276
|
-
});
|
|
1277
|
-
const oFormatter = exporter.formatters?.o ?? defaultFormatters.o;
|
|
1278
|
-
for (let arg of args) {
|
|
1279
|
-
if (typeof arg === "object" && arg) arg = oFormatter(arg, exporter, message);
|
|
1280
|
-
format += " " + arg;
|
|
1281
|
-
}
|
|
1282
|
-
const { maxLength = 10240 } = exporter;
|
|
1283
|
-
return format.split(/\r?\n/g).map((line) => {
|
|
1284
|
-
return line.slice(0, maxLength) + (line.length > maxLength ? "..." : "");
|
|
1285
|
-
}).join("\n");
|
|
1286
|
-
}
|
|
1287
|
-
constructor(options, service) {
|
|
1288
|
-
this.service = service;
|
|
1289
|
-
Object.assign(this, options);
|
|
1290
|
-
this.error = this._method("error", 0);
|
|
1291
|
-
this.info = this._method("info", 1);
|
|
1292
|
-
this.warn = this._method("warn", 2);
|
|
1293
|
-
this.debug = this._method("debug", 3);
|
|
1294
|
-
}
|
|
1295
|
-
_method(type, level) {
|
|
1296
|
-
return (...args) => {
|
|
1297
|
-
if (args.length === 1 && args[0] instanceof Error) {
|
|
1298
|
-
if (args[0].cause) this[type](args[0].cause);
|
|
1299
|
-
else if (isAggregateError(args[0])) {
|
|
1300
|
-
args[0].errors.forEach((error) => this[type](error));
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
const sn = ++this.service._snMessage;
|
|
1305
|
-
const ts = Date.now();
|
|
1306
|
-
for (const exporter of this.service.exporters.values()) {
|
|
1307
|
-
if ((exporter.levels?.[this.name] ?? exporter.levels?.default ?? this.level ?? 1) < level) continue;
|
|
1308
|
-
const message = {
|
|
1309
|
-
sn,
|
|
1310
|
-
ts,
|
|
1311
|
-
type,
|
|
1312
|
-
level,
|
|
1313
|
-
name: this.name,
|
|
1314
|
-
...this.meta,
|
|
1315
|
-
args
|
|
1316
|
-
};
|
|
1317
|
-
exporter.export(message);
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
1320
|
-
}
|
|
1321
|
-
};
|
|
1322
|
-
/** ANSI 16-color palette indexes used for logger name coloring. */
|
|
1323
|
-
const c16 = [
|
|
1324
|
-
6,
|
|
1325
|
-
2,
|
|
1326
|
-
3,
|
|
1327
|
-
4,
|
|
1328
|
-
5,
|
|
1329
|
-
1
|
|
1330
|
-
];
|
|
1331
|
-
/** ANSI 256-color palette indexes used for logger name coloring. */
|
|
1332
|
-
const c256 = [
|
|
1333
|
-
20,
|
|
1334
|
-
21,
|
|
1335
|
-
26,
|
|
1336
|
-
27,
|
|
1337
|
-
32,
|
|
1338
|
-
33,
|
|
1339
|
-
38,
|
|
1340
|
-
39,
|
|
1341
|
-
40,
|
|
1342
|
-
41,
|
|
1343
|
-
42,
|
|
1344
|
-
43,
|
|
1345
|
-
44,
|
|
1346
|
-
45,
|
|
1347
|
-
56,
|
|
1348
|
-
57,
|
|
1349
|
-
62,
|
|
1350
|
-
63,
|
|
1351
|
-
68,
|
|
1352
|
-
69,
|
|
1353
|
-
74,
|
|
1354
|
-
75,
|
|
1355
|
-
76,
|
|
1356
|
-
77,
|
|
1357
|
-
78,
|
|
1358
|
-
79,
|
|
1359
|
-
80,
|
|
1360
|
-
81,
|
|
1361
|
-
92,
|
|
1362
|
-
93,
|
|
1363
|
-
98,
|
|
1364
|
-
99,
|
|
1365
|
-
112,
|
|
1366
|
-
113,
|
|
1367
|
-
129,
|
|
1368
|
-
134,
|
|
1369
|
-
135,
|
|
1370
|
-
148,
|
|
1371
|
-
149,
|
|
1372
|
-
160,
|
|
1373
|
-
161,
|
|
1374
|
-
162,
|
|
1375
|
-
163,
|
|
1376
|
-
164,
|
|
1377
|
-
165,
|
|
1378
|
-
166,
|
|
1379
|
-
167,
|
|
1380
|
-
168,
|
|
1381
|
-
169,
|
|
1382
|
-
170,
|
|
1383
|
-
171,
|
|
1384
|
-
172,
|
|
1385
|
-
173,
|
|
1386
|
-
178,
|
|
1387
|
-
179,
|
|
1388
|
-
184,
|
|
1389
|
-
185,
|
|
1390
|
-
196,
|
|
1391
|
-
197,
|
|
1392
|
-
198,
|
|
1393
|
-
199,
|
|
1394
|
-
200,
|
|
1395
|
-
201,
|
|
1396
|
-
202,
|
|
1397
|
-
203,
|
|
1398
|
-
204,
|
|
1399
|
-
205,
|
|
1400
|
-
206,
|
|
1401
|
-
207,
|
|
1402
|
-
208,
|
|
1403
|
-
209,
|
|
1404
|
-
214,
|
|
1405
|
-
215,
|
|
1406
|
-
220,
|
|
1407
|
-
221
|
|
1408
|
-
];
|
|
1409
|
-
/**
|
|
1410
|
-
* Built-in logging service.
|
|
1411
|
-
*
|
|
1412
|
-
* Call `ctx.logger()` to create a named logger, or call `ctx.logger.info()`
|
|
1413
|
-
* directly to log with the current fiber-derived name.
|
|
1414
|
-
*/
|
|
1415
|
-
var LoggerService = class LoggerService {
|
|
1416
|
-
bufferSize = 1e3;
|
|
1417
|
-
buffer = [];
|
|
1418
|
-
ctx;
|
|
1419
|
-
_snMessage = 0;
|
|
1420
|
-
_snExporter = 0;
|
|
1421
|
-
exporters = /* @__PURE__ */ new Map();
|
|
1422
|
-
constructor(ctx) {
|
|
1423
|
-
const tracker = {
|
|
1424
|
-
property: "ctx",
|
|
1425
|
-
noShadow: true
|
|
1426
|
-
};
|
|
1427
|
-
const self = createCallable("logger", joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker);
|
|
1428
|
-
Object.assign(self, this);
|
|
1429
|
-
self.ctx = ctx;
|
|
1430
|
-
defineProperty(self, symbols.tracker, tracker);
|
|
1431
|
-
self.exporter({
|
|
1432
|
-
colors: 3,
|
|
1433
|
-
export: (message) => {
|
|
1434
|
-
self.buffer.push(message);
|
|
1435
|
-
if (self.buffer.length > self.bufferSize) self.buffer = self.buffer.slice(-self.bufferSize);
|
|
1436
|
-
}
|
|
1437
|
-
});
|
|
1438
|
-
return self;
|
|
1439
|
-
}
|
|
1440
|
-
/**
|
|
1441
|
-
* Register an exporter and dispose it with the current fiber.
|
|
1442
|
-
*
|
|
1443
|
-
* @param exporter — the sink that receives structured log messages.
|
|
1444
|
-
* @returns a disposer that removes the exporter.
|
|
1445
|
-
*/
|
|
1446
|
-
exporter(exporter) {
|
|
1447
|
-
return this.ctx.effect(() => {
|
|
1448
|
-
this.exporters.set(++this._snExporter, exporter);
|
|
1449
|
-
return () => this.exporters.delete(this._snExporter);
|
|
1450
|
-
}, "ctx.logger.exporter()");
|
|
1451
|
-
}
|
|
1452
|
-
_resolveConfig() {
|
|
1453
|
-
let intercept = this.ctx[symbols.intercept];
|
|
1454
|
-
const configs = [];
|
|
1455
|
-
while ("logger" in intercept) {
|
|
1456
|
-
if (Object.hasOwn(intercept, "logger")) configs.unshift(intercept["logger"]);
|
|
1457
|
-
intercept = Object.getPrototypeOf(intercept);
|
|
1458
|
-
}
|
|
1459
|
-
return Object.assign({}, ...configs);
|
|
1460
|
-
}
|
|
1461
|
-
[symbols.invoke](name) {
|
|
1462
|
-
const config = this._resolveConfig();
|
|
1463
|
-
const fiber = (this.ctx[symbols.shadow] ?? this.ctx).fiber;
|
|
1464
|
-
name ??= config.name;
|
|
1465
|
-
name ??= hyphenate(fiber.name);
|
|
1466
|
-
return new Logger({
|
|
1467
|
-
name,
|
|
1468
|
-
level: config.level,
|
|
1469
|
-
meta: { fiber: new WeakRef(fiber) }
|
|
1470
|
-
}, this);
|
|
1471
|
-
}
|
|
1472
|
-
static {
|
|
1473
|
-
for (const type of [
|
|
1474
|
-
"error",
|
|
1475
|
-
"info",
|
|
1476
|
-
"warn",
|
|
1477
|
-
"debug"
|
|
1478
|
-
]) LoggerService.prototype[type] = function(...args) {
|
|
1479
|
-
return this()[type](...args);
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
};
|
|
1483
|
-
//#endregion
|
|
1484
|
-
//#region ../../../vendor/cordis/src/fiber.ts
|
|
1485
|
-
const kValidationError = Symbol.for("ValidationError");
|
|
1486
|
-
/** Error raised when plugin configuration fails standard-schema validation. */
|
|
1487
|
-
var ValidationError = class extends TypeError {
|
|
1488
|
-
name = "ValidationError";
|
|
1489
|
-
/**
|
|
1490
|
-
* Build the aggregated message from schema issues.
|
|
1491
|
-
*
|
|
1492
|
-
* @param issues — the standard-schema issues, one message line each.
|
|
1493
|
-
*/
|
|
1494
|
-
constructor(issues) {
|
|
1495
|
-
super(`invalid config:\n` + issues.map((issue) => {
|
|
1496
|
-
if (issue.path) return ` - ${issue.message} (at ${issue.path.join(".")})`;
|
|
1497
|
-
else return ` - ${issue.message}`;
|
|
1498
|
-
}).join("\n"));
|
|
1499
|
-
}
|
|
1500
|
-
};
|
|
1501
|
-
Object.defineProperty(ValidationError.prototype, kValidationError, { value: true });
|
|
1502
|
-
/**
|
|
1503
|
-
* Validate and normalize config for a plugin runtime before it starts.
|
|
1504
|
-
*
|
|
1505
|
-
* @param runtime — the plugin runtime whose `Config` schema to apply.
|
|
1506
|
-
* @param config — the raw user config.
|
|
1507
|
-
* @returns the validated config, or `config` unchanged if the runtime has no schema.
|
|
1508
|
-
* @throws {ValidationError} when validation reports issues.
|
|
1509
|
-
*/
|
|
1510
|
-
function resolveConfig(runtime, config) {
|
|
1511
|
-
if (!runtime.Config) return config;
|
|
1512
|
-
const result = runtime.Config["~standard"].validate(config);
|
|
1513
|
-
if ("then" in result) throw new TypeError("Async config validation is not supported");
|
|
1514
|
-
if (result.issues) throw new ValidationError(result.issues);
|
|
1515
|
-
else return result.value;
|
|
1516
|
-
}
|
|
1517
|
-
const effectInertia = /* @__PURE__ */ new WeakMap();
|
|
1518
|
-
function runDisposable(dispose) {
|
|
1519
|
-
const result = dispose();
|
|
1520
|
-
return effectInertia.get(dispose)?.() ?? result;
|
|
1521
|
-
}
|
|
1522
|
-
/** Notify plugin teardown without allowing one observer to break ownership cleanup. */
|
|
1523
|
-
function emitPluginDisposed(context, fiber) {
|
|
1524
|
-
const args = ["internal/plugin", fiber];
|
|
1525
|
-
let callbacks;
|
|
1526
|
-
try {
|
|
1527
|
-
callbacks = context.events.dispatch("emit", args);
|
|
1528
|
-
} catch (error) {
|
|
1529
|
-
context.logger.error(error);
|
|
1530
|
-
return;
|
|
1531
|
-
}
|
|
1532
|
-
for (const callback of callbacks) try {
|
|
1533
|
-
const returned = callback(...args);
|
|
1534
|
-
Promise.resolve(returned).catch((error) => context.logger.error(error));
|
|
1535
|
-
} catch (error) {
|
|
1536
|
-
context.logger.error(error);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
/** Framework error with a stable machine-readable code. */
|
|
1540
|
-
var CordisError = class CordisError extends Error {
|
|
1541
|
-
code;
|
|
1542
|
-
/**
|
|
1543
|
-
* @param code — the stable error code; also the default message.
|
|
1544
|
-
* @param message — optional human-readable override.
|
|
1545
|
-
*/
|
|
1546
|
-
constructor(code, message) {
|
|
1547
|
-
super(message ?? CordisError.Code[code]);
|
|
1548
|
-
this.code = code;
|
|
1549
|
-
}
|
|
1550
|
-
};
|
|
1551
|
-
(function(_CordisError) {
|
|
1552
|
-
_CordisError.Code = { INACTIVE_EFFECT: "cannot create effect on inactive context" };
|
|
1553
|
-
})(CordisError || (CordisError = {}));
|
|
1554
|
-
const INACTIVE = "__INACTIVE__";
|
|
1555
|
-
/**
|
|
1556
|
-
* Runtime instance of one plugin application.
|
|
1557
|
-
*
|
|
1558
|
-
* A fiber tracks dependency state, validated config, lifecycle effects, and
|
|
1559
|
-
* cleanup for the plugin context returned by `ctx.plugin()`.
|
|
1560
|
-
*/
|
|
1561
|
-
var Fiber = class {
|
|
1562
|
-
parent;
|
|
1563
|
-
inject;
|
|
1564
|
-
runtime;
|
|
1565
|
-
/** Unique id within the registry; 0 for the root fiber, `null` once disposed. */
|
|
1566
|
-
uid;
|
|
1567
|
-
/** The context this fiber's plugin runs in (extends the parent context). */
|
|
1568
|
-
ctx;
|
|
1569
|
-
/** The validated plugin config (updated by `update()`). */
|
|
1570
|
-
config;
|
|
1571
|
-
/** The raw plugin config, re-resolved before each activation. */
|
|
1572
|
-
_config;
|
|
1573
|
-
/** Current lifecycle state; transitions emit `internal/status`. */
|
|
1574
|
-
state = 0;
|
|
1575
|
-
/** Dispose this fiber: unload the plugin, then settle once cleanup finished. */
|
|
1576
|
-
dispose;
|
|
1577
|
-
/** Snapshot of required service implementations while loaded; `undefined` otherwise. */
|
|
1578
|
-
store;
|
|
1579
|
-
/** The in-flight load/unload transition, if one is currently running. */
|
|
1580
|
-
inertia;
|
|
1581
|
-
_hooks = Object.create(null);
|
|
1582
|
-
_disposables = new DisposableList();
|
|
1583
|
-
context;
|
|
1584
|
-
_error;
|
|
1585
|
-
_runner;
|
|
1586
|
-
_store = Object.create(null);
|
|
1587
|
-
/**
|
|
1588
|
-
* Create a fiber. Plugin authors normally obtain fibers from `ctx.plugin()`
|
|
1589
|
-
* rather than constructing them directly.
|
|
1590
|
-
*
|
|
1591
|
-
* @param parent — the context the plugin was loaded from.
|
|
1592
|
-
* @param config — raw config, validated against the runtime's schema.
|
|
1593
|
-
* @param inject — resolved dependency map (service name → intercept config).
|
|
1594
|
-
* @param runtime — the shared plugin runtime, or `null` for the root fiber.
|
|
1595
|
-
* @param getOuterStack — captures the caller stack for effect diagnostics.
|
|
1596
|
-
*/
|
|
1597
|
-
constructor(parent, config, inject, runtime, getOuterStack) {
|
|
1598
|
-
this.parent = parent;
|
|
1599
|
-
this.inject = inject;
|
|
1600
|
-
this.runtime = runtime;
|
|
1601
|
-
this._config = config;
|
|
1602
|
-
const collect = (dispose) => {
|
|
1603
|
-
this._disposables.push(dispose);
|
|
1604
|
-
};
|
|
1605
|
-
if (runtime) {
|
|
1606
|
-
this.uid = parent.registry.counter;
|
|
1607
|
-
this.ctx = this.context = parent.extend({ fiber: this });
|
|
1608
|
-
const injectEntries = Object.entries(this.inject);
|
|
1609
|
-
if (injectEntries.length) {
|
|
1610
|
-
this.ctx[Context.intercept] = Object.create(parent[Context.intercept]);
|
|
1611
|
-
for (const [name, config] of injectEntries) {
|
|
1612
|
-
if (isNullable(config)) continue;
|
|
1613
|
-
this.ctx[Context.intercept][name] = config;
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
this._runner = {
|
|
1617
|
-
epoch: INACTIVE,
|
|
1618
|
-
getOuterStack,
|
|
1619
|
-
execute: function() {
|
|
1620
|
-
if (isConstructor(runtime.callback)) {
|
|
1621
|
-
const instance = new runtime.callback(this.ctx, this.config);
|
|
1622
|
-
for (const hook of instance?.[symbols.initHooks] ?? []) hook();
|
|
1623
|
-
return instance?.[symbols.init]?.();
|
|
1624
|
-
} else return runtime.callback(this.ctx, this.config);
|
|
1625
|
-
},
|
|
1626
|
-
collect
|
|
1627
|
-
};
|
|
1628
|
-
this.dispose = parent.fiber.effect(() => {
|
|
1629
|
-
const remove = runtime.fibers.push(this);
|
|
1630
|
-
return async () => {
|
|
1631
|
-
this.uid = null;
|
|
1632
|
-
emitPluginDisposed(this.context, this);
|
|
1633
|
-
if (this.ctx.registry.has(runtime.callback)) {
|
|
1634
|
-
remove();
|
|
1635
|
-
if (!runtime.fibers.length) this.ctx.registry.delete(runtime.callback);
|
|
1636
|
-
}
|
|
1637
|
-
this._setEpoch(INACTIVE);
|
|
1638
|
-
if (!this.inertia) this._updateState(() => {
|
|
1639
|
-
this.inertia = this._unload();
|
|
1640
|
-
return 5;
|
|
1641
|
-
});
|
|
1642
|
-
while (this.inertia) await this.inertia;
|
|
1643
|
-
};
|
|
1644
|
-
}, "ctx.plugin()");
|
|
1645
|
-
try {
|
|
1646
|
-
this.context.emit("internal/plugin", this);
|
|
1647
|
-
} catch (error) {
|
|
1648
|
-
Promise.resolve(this.dispose()).catch((reason) => this.ctx.logger.error(reason));
|
|
1649
|
-
throw error;
|
|
1650
|
-
}
|
|
1651
|
-
if (this.uid !== null && parent.fiber.state !== 5) {
|
|
1652
|
-
for (const name of Object.keys(this.inject)) this._checkImpl(name);
|
|
1653
|
-
this._refresh();
|
|
1654
|
-
}
|
|
1655
|
-
} else {
|
|
1656
|
-
this.uid = 0;
|
|
1657
|
-
this.ctx = this.context = parent;
|
|
1658
|
-
this.state = 2;
|
|
1659
|
-
this.store = Object.create(null);
|
|
1660
|
-
this._runner = {
|
|
1661
|
-
epoch: "",
|
|
1662
|
-
getOuterStack,
|
|
1663
|
-
execute: () => {},
|
|
1664
|
-
collect
|
|
1665
|
-
};
|
|
1666
|
-
this.dispose = () => this.restart();
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
/** The plugin's display name, inherited from the nearest named ancestor, else `'root'`. */
|
|
1670
|
-
get name() {
|
|
1671
|
-
let fiber = this;
|
|
1672
|
-
do {
|
|
1673
|
-
if (fiber.runtime?.name) return fiber.runtime.name;
|
|
1674
|
-
fiber = fiber.parent.fiber;
|
|
1675
|
-
} while (fiber !== fiber.parent.fiber);
|
|
1676
|
-
return "root";
|
|
1677
|
-
}
|
|
1678
|
-
/**
|
|
1679
|
-
* Throw if the fiber has already been disposed.
|
|
1680
|
-
*
|
|
1681
|
-
* @returns nothing when the fiber is still active.
|
|
1682
|
-
* @throws {CordisError} `INACTIVE_EFFECT` when the fiber's uid has been cleared.
|
|
1683
|
-
*/
|
|
1684
|
-
assertActive() {
|
|
1685
|
-
if (this.uid !== null) return;
|
|
1686
|
-
throw new CordisError("INACTIVE_EFFECT");
|
|
1687
|
-
}
|
|
1688
|
-
_execute(runner) {
|
|
1689
|
-
const oldEpoch = runner.epoch;
|
|
1690
|
-
return composeError((info) => {
|
|
1691
|
-
const safeCollect = (dispose) => {
|
|
1692
|
-
if (typeof dispose === "function") runner.collect(dispose);
|
|
1693
|
-
else if (!isNullable(dispose)) throw new TypeError("Invalid effect");
|
|
1694
|
-
};
|
|
1695
|
-
const effect = runner.execute.call(this);
|
|
1696
|
-
if (typeof effect === "function") return runner.collect(effect);
|
|
1697
|
-
else if (isNullable(effect)) {} else if (!isObject(effect)) throw new TypeError("Invalid effect");
|
|
1698
|
-
else if ("then" in effect) return effect.then(safeCollect);
|
|
1699
|
-
else if (Symbol.iterator in effect) {
|
|
1700
|
-
info.error = /* @__PURE__ */ new Error();
|
|
1701
|
-
const iter = effect[Symbol.iterator]();
|
|
1702
|
-
while (true) {
|
|
1703
|
-
const result = iter.next();
|
|
1704
|
-
safeCollect(result.value);
|
|
1705
|
-
if (result.done) return;
|
|
1706
|
-
}
|
|
1707
|
-
} else if (Symbol.asyncIterator in effect) {
|
|
1708
|
-
const iter = effect[Symbol.asyncIterator]();
|
|
1709
|
-
return (async () => {
|
|
1710
|
-
await Promise.resolve();
|
|
1711
|
-
info.error = /* @__PURE__ */ new Error();
|
|
1712
|
-
while (true) {
|
|
1713
|
-
if (runner.epoch !== oldEpoch) return;
|
|
1714
|
-
const result = await iter.next();
|
|
1715
|
-
safeCollect(result.value);
|
|
1716
|
-
if (result.done) return;
|
|
1717
|
-
}
|
|
1718
|
-
})();
|
|
1719
|
-
} else throw new TypeError("Invalid effect");
|
|
1720
|
-
}, runner.getOuterStack);
|
|
1721
|
-
}
|
|
1722
|
-
effect(execute, label = "anonymous") {
|
|
1723
|
-
this.assertActive();
|
|
1724
|
-
if (this.state === 5) throw new CordisError("INACTIVE_EFFECT");
|
|
1725
|
-
const disposables = [];
|
|
1726
|
-
let disposing = false;
|
|
1727
|
-
let disposalTask;
|
|
1728
|
-
const dispose = () => {
|
|
1729
|
-
if (disposing) return disposalTask;
|
|
1730
|
-
disposing = true;
|
|
1731
|
-
let task;
|
|
1732
|
-
for (const disposable of disposables.splice(0).reverse()) if (task) task = task.then(() => runDisposable(disposable));
|
|
1733
|
-
else {
|
|
1734
|
-
const result = runDisposable(disposable);
|
|
1735
|
-
if (isObject(result) && "then" in result) task = result;
|
|
1736
|
-
}
|
|
1737
|
-
return disposalTask = task;
|
|
1738
|
-
};
|
|
1739
|
-
const meta = {
|
|
1740
|
-
label,
|
|
1741
|
-
children: []
|
|
1742
|
-
};
|
|
1743
|
-
const runner = {
|
|
1744
|
-
execute,
|
|
1745
|
-
epoch: true,
|
|
1746
|
-
collect: (dispose) => {
|
|
1747
|
-
disposables.push(dispose);
|
|
1748
|
-
this._disposables.delete(dispose);
|
|
1749
|
-
if (dispose[symbols.effect]) meta.children.push(dispose[symbols.effect]);
|
|
1750
|
-
},
|
|
1751
|
-
getOuterStack: buildOuterStack()
|
|
1752
|
-
};
|
|
1753
|
-
let task;
|
|
1754
|
-
let executing = true;
|
|
1755
|
-
let resolveSetup;
|
|
1756
|
-
let rejectSetup;
|
|
1757
|
-
let setupBarrier;
|
|
1758
|
-
let setupFailed = false;
|
|
1759
|
-
let inFlight;
|
|
1760
|
-
let removeWrapper = () => false;
|
|
1761
|
-
const waitForSetup = () => {
|
|
1762
|
-
setupBarrier ??= new Promise((resolve, reject) => {
|
|
1763
|
-
resolveSetup = resolve;
|
|
1764
|
-
rejectSetup = reject;
|
|
1765
|
-
});
|
|
1766
|
-
return setupBarrier;
|
|
1767
|
-
};
|
|
1768
|
-
const disposeAfter = (setup) => {
|
|
1769
|
-
return Promise.resolve(setup).then(() => dispose(), async (reason) => {
|
|
1770
|
-
await dispose();
|
|
1771
|
-
throw reason;
|
|
1772
|
-
});
|
|
1773
|
-
};
|
|
1774
|
-
const finalizeDisposal = (callback) => {
|
|
1775
|
-
let result;
|
|
1776
|
-
try {
|
|
1777
|
-
result = callback();
|
|
1778
|
-
} catch (error) {
|
|
1779
|
-
removeWrapper();
|
|
1780
|
-
throw error;
|
|
1781
|
-
}
|
|
1782
|
-
if (isObject(result) && "then" in result) {
|
|
1783
|
-
const pending = Promise.resolve(result).finally(() => {
|
|
1784
|
-
removeWrapper();
|
|
1785
|
-
if (inFlight === pending) inFlight = void 0;
|
|
1786
|
-
});
|
|
1787
|
-
return inFlight = pending;
|
|
1788
|
-
}
|
|
1789
|
-
removeWrapper();
|
|
1790
|
-
return result;
|
|
1791
|
-
};
|
|
1792
|
-
const wrapper = defineProperty(() => {
|
|
1793
|
-
if (!runner.epoch) return setupFailed ? inFlight : void 0;
|
|
1794
|
-
runner.epoch = false;
|
|
1795
|
-
return finalizeDisposal(() => {
|
|
1796
|
-
if (executing) return disposeAfter(waitForSetup());
|
|
1797
|
-
return task ? disposeAfter(task) : dispose();
|
|
1798
|
-
});
|
|
1799
|
-
}, symbols.effect, meta);
|
|
1800
|
-
effectInertia.set(wrapper, () => inFlight);
|
|
1801
|
-
removeWrapper = this._disposables.push(wrapper);
|
|
1802
|
-
try {
|
|
1803
|
-
task = this._execute(runner);
|
|
1804
|
-
} catch (reason) {
|
|
1805
|
-
executing = false;
|
|
1806
|
-
setupFailed = true;
|
|
1807
|
-
runner.epoch = false;
|
|
1808
|
-
let cleanup;
|
|
1809
|
-
try {
|
|
1810
|
-
cleanup = finalizeDisposal(dispose);
|
|
1811
|
-
} finally {
|
|
1812
|
-
rejectSetup?.(reason);
|
|
1813
|
-
}
|
|
1814
|
-
if (isObject(cleanup) && "then" in cleanup) cleanup.catch((error) => this.ctx.logger.error(error));
|
|
1815
|
-
throw reason;
|
|
1816
|
-
}
|
|
1817
|
-
executing = false;
|
|
1818
|
-
if (setupBarrier) Promise.resolve(task).then(resolveSetup, rejectSetup);
|
|
1819
|
-
task?.catch(() => {
|
|
1820
|
-
if (!runner.epoch) return dispose();
|
|
1821
|
-
return finalizeDisposal(dispose);
|
|
1822
|
-
}).catch((error) => this.ctx.logger.error(error));
|
|
1823
|
-
const disposeAsync = () => {
|
|
1824
|
-
if (!runner.epoch) return;
|
|
1825
|
-
runner.epoch = false;
|
|
1826
|
-
return finalizeDisposal(dispose);
|
|
1827
|
-
};
|
|
1828
|
-
wrapper.then = async (onFulfilled, onRejected) => {
|
|
1829
|
-
return Promise.resolve(task).then(() => disposeAsync).then(onFulfilled, onRejected);
|
|
1830
|
-
};
|
|
1831
|
-
return wrapper;
|
|
1832
|
-
}
|
|
1833
|
-
/**
|
|
1834
|
-
* Return metadata for currently registered effects.
|
|
1835
|
-
*
|
|
1836
|
-
* @returns one {@link EffectMeta} tree per labeled live effect.
|
|
1837
|
-
*/
|
|
1838
|
-
getEffects() {
|
|
1839
|
-
return [...this._disposables].map((dispose) => dispose[symbols.effect]).filter(Boolean);
|
|
1840
|
-
}
|
|
1841
|
-
_getState() {
|
|
1842
|
-
if (this.uid === null) return 4;
|
|
1843
|
-
if (this._error) return 3;
|
|
1844
|
-
if (this._runner.epoch !== INACTIVE) return 2;
|
|
1845
|
-
return 0;
|
|
1846
|
-
}
|
|
1847
|
-
_updateState(callback) {
|
|
1848
|
-
const oldState = this.state;
|
|
1849
|
-
this.state = callback() ?? this._getState();
|
|
1850
|
-
if (oldState === this.state) return;
|
|
1851
|
-
this.context.emit("internal/status", this, oldState);
|
|
1852
|
-
if (oldState !== 2 && this.state !== 2) return;
|
|
1853
|
-
for (const key of Reflect.ownKeys(this.ctx.reflect.store)) {
|
|
1854
|
-
const impl = this.ctx.reflect.store[key];
|
|
1855
|
-
if (impl.fiber !== this) continue;
|
|
1856
|
-
this.ctx.reflect.notify([impl.name]);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
_checkImpl(name) {
|
|
1860
|
-
const impl = this.ctx.reflect._getImpl(name, true);
|
|
1861
|
-
if (!impl) return delete this._store[name];
|
|
1862
|
-
try {
|
|
1863
|
-
if (impl.check && !impl.check.call(getTraceable(this.ctx, impl.value))) return delete this._store[name];
|
|
1864
|
-
} catch (error) {
|
|
1865
|
-
impl.fiber.ctx.logger.error(error);
|
|
1866
|
-
return delete this._store[name];
|
|
1867
|
-
}
|
|
1868
|
-
this._store[name] = impl;
|
|
1869
|
-
}
|
|
1870
|
-
_refresh() {
|
|
1871
|
-
let epoch = false;
|
|
1872
|
-
epoch = "";
|
|
1873
|
-
for (const name of Object.keys(this.inject)) {
|
|
1874
|
-
const impl = this._store[name];
|
|
1875
|
-
if (!impl) {
|
|
1876
|
-
epoch = INACTIVE;
|
|
1877
|
-
break;
|
|
1878
|
-
}
|
|
1879
|
-
epoch += ":" + impl.fiber.uid;
|
|
1880
|
-
}
|
|
1881
|
-
this._setEpoch(epoch);
|
|
1882
|
-
}
|
|
1883
|
-
_setEpoch(epoch) {
|
|
1884
|
-
const oldEpoch = this._runner.epoch;
|
|
1885
|
-
if (epoch === oldEpoch) return;
|
|
1886
|
-
this._runner.epoch = epoch;
|
|
1887
|
-
if (this.inertia) return;
|
|
1888
|
-
this._updateState(() => {
|
|
1889
|
-
if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
|
|
1890
|
-
this.inertia = this._reload();
|
|
1891
|
-
return 1;
|
|
1892
|
-
} else {
|
|
1893
|
-
this.inertia = this._unload();
|
|
1894
|
-
return 5;
|
|
1895
|
-
}
|
|
1896
|
-
});
|
|
1897
|
-
}
|
|
1898
|
-
_resolveConfig(config) {
|
|
1899
|
-
config = this.context.waterfall(this, "internal/config", config, () => config);
|
|
1900
|
-
return this.runtime ? resolveConfig(this.runtime, config) : config;
|
|
1901
|
-
}
|
|
1902
|
-
async _reload() {
|
|
1903
|
-
this.store = { ...this._store };
|
|
1904
|
-
const oldEpoch = this._runner.epoch;
|
|
1905
|
-
try {
|
|
1906
|
-
await Promise.resolve();
|
|
1907
|
-
if (this._runner.epoch === oldEpoch) {
|
|
1908
|
-
this.config = this._resolveConfig(this._config);
|
|
1909
|
-
await this._execute(this._runner);
|
|
1910
|
-
this._error = void 0;
|
|
1911
|
-
}
|
|
1912
|
-
} catch (reason) {
|
|
1913
|
-
this.ctx.logger.error(reason);
|
|
1914
|
-
this._error = reason;
|
|
1915
|
-
this._runner.epoch = INACTIVE;
|
|
1916
|
-
}
|
|
1917
|
-
this._updateState(() => {
|
|
1918
|
-
if (this._runner.epoch === oldEpoch) this.inertia = void 0;
|
|
1919
|
-
else {
|
|
1920
|
-
this.inertia = this._unload();
|
|
1921
|
-
return 5;
|
|
1922
|
-
}
|
|
1923
|
-
});
|
|
1924
|
-
}
|
|
1925
|
-
async _unload() {
|
|
1926
|
-
await Promise.all(this._disposables.clear().map(async (dispose) => {
|
|
1927
|
-
try {
|
|
1928
|
-
await composeError(async (info) => {
|
|
1929
|
-
await Promise.resolve();
|
|
1930
|
-
info.error = /* @__PURE__ */ new Error();
|
|
1931
|
-
await runDisposable(dispose);
|
|
1932
|
-
}, this._runner.getOuterStack);
|
|
1933
|
-
} catch (reason) {
|
|
1934
|
-
this.ctx.logger.error(reason);
|
|
1935
|
-
}
|
|
1936
|
-
}));
|
|
1937
|
-
this.store = void 0;
|
|
1938
|
-
this._updateState(() => {
|
|
1939
|
-
if (this._runner.epoch === INACTIVE) this.inertia = void 0;
|
|
1940
|
-
else {
|
|
1941
|
-
this.inertia = this._reload();
|
|
1942
|
-
return 1;
|
|
1943
|
-
}
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
/**
|
|
1947
|
-
* Wait for current lifecycle work and rethrow startup errors.
|
|
1948
|
-
*
|
|
1949
|
-
* @returns this fiber, once it has settled into a stable state.
|
|
1950
|
-
* @throws the config-validation or plugin-startup error, if any.
|
|
1951
|
-
*/
|
|
1952
|
-
async await() {
|
|
1953
|
-
while (this.inertia) await this.inertia;
|
|
1954
|
-
if (this._error) throw this._error;
|
|
1955
|
-
return this;
|
|
1956
|
-
}
|
|
1957
|
-
/**
|
|
1958
|
-
* Dispose and immediately reload this plugin with its current config.
|
|
1959
|
-
*
|
|
1960
|
-
* @returns a promise resolving once the reload settled.
|
|
1961
|
-
* @throws {CordisError} `INACTIVE_EFFECT` when the fiber is already disposed.
|
|
1962
|
-
*/
|
|
1963
|
-
async restart() {
|
|
1964
|
-
this.assertActive();
|
|
1965
|
-
this._setEpoch(INACTIVE);
|
|
1966
|
-
this._refresh();
|
|
1967
|
-
await this.await();
|
|
1968
|
-
}
|
|
1969
|
-
/**
|
|
1970
|
-
* Validate and apply new config, then restart the plugin.
|
|
1971
|
-
*
|
|
1972
|
-
* Runs the `internal/update` waterfall first, so update hooks (and HMR)
|
|
1973
|
-
* can veto or replace the restart.
|
|
1974
|
-
*
|
|
1975
|
-
* @param config — the new raw config; validated before anything restarts.
|
|
1976
|
-
* @param noSave — hint for persistence hooks not to write the change back.
|
|
1977
|
-
* @returns the update waterfall result; the default restart returns a promise.
|
|
1978
|
-
* @throws when validation, an update listener, or the restarted plugin fails.
|
|
1979
|
-
*/
|
|
1980
|
-
update(config, noSave = false) {
|
|
1981
|
-
this.assertActive();
|
|
1982
|
-
this._config = config;
|
|
1983
|
-
if (this.state !== 2) {
|
|
1984
|
-
this._error = void 0;
|
|
1985
|
-
this._setEpoch(INACTIVE);
|
|
1986
|
-
this._refresh();
|
|
1987
|
-
return;
|
|
1988
|
-
}
|
|
1989
|
-
config = this._resolveConfig(config);
|
|
1990
|
-
return this.context.waterfall(this, "internal/update", config, noSave, () => {
|
|
1991
|
-
this.config = config;
|
|
1992
|
-
this._error = void 0;
|
|
1993
|
-
return this.restart();
|
|
1994
|
-
});
|
|
1995
|
-
}
|
|
1996
|
-
};
|
|
1997
|
-
//#endregion
|
|
1998
|
-
//#region ../../../vendor/cordis/src/reflect.ts
|
|
1999
|
-
function enhanceError(error) {
|
|
2000
|
-
const lines = error.stack.split("\n");
|
|
2001
|
-
lines.splice(0, 2, `Error: ${error.message}`);
|
|
2002
|
-
error.stack = lines.join("\n");
|
|
2003
|
-
return error;
|
|
2004
|
-
}
|
|
2005
|
-
const RESERVED_WORDS = ["prototype", "then"];
|
|
2006
|
-
function isSpecialProperty(prop) {
|
|
2007
|
-
return typeof prop === "symbol" || RESERVED_WORDS.includes(prop) || parseInt(prop).toString() === prop || prop.startsWith("_");
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Reflection and service-resolution layer installed as `ctx.reflect`.
|
|
2011
|
-
*
|
|
2012
|
-
* This service powers the context proxy, service registration, accessors, and
|
|
2013
|
-
* the mixins that expose core service methods directly on `ctx`.
|
|
2014
|
-
*/
|
|
2015
|
-
var ReflectService = class {
|
|
2016
|
-
ctx;
|
|
2017
|
-
/** Proxy traps implementing service resolution for every context object. */
|
|
2018
|
-
static handler = {
|
|
2019
|
-
get: (target, prop, ctx) => {
|
|
2020
|
-
if (isSpecialProperty(prop)) return Reflect.get(target, prop, ctx);
|
|
2021
|
-
if (Reflect.has(target, prop)) return getTraceable(ctx, Reflect.get(target, prop, ctx));
|
|
2022
|
-
const error = /* @__PURE__ */ new Error(`cannot get property "${prop}" without inject`);
|
|
2023
|
-
try {
|
|
2024
|
-
const def = target.reflect.props[prop];
|
|
2025
|
-
if (def?.type === "accessor") return def.get.call(ctx, ctx[symbols.receiver], error);
|
|
2026
|
-
if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false);
|
|
2027
|
-
return ctx.events.waterfall("internal/get", ctx, prop, error, () => {
|
|
2028
|
-
const key = target[symbols.isolate][prop];
|
|
2029
|
-
let fiber = (ctx[symbols.shadow] ?? ctx).fiber;
|
|
2030
|
-
while (true) {
|
|
2031
|
-
const impl = fiber.store?.[prop];
|
|
2032
|
-
if (impl) return getTraceable(ctx, impl.value);
|
|
2033
|
-
if (prop in fiber.inject) {
|
|
2034
|
-
error.message = `cannot get required service "${prop}" in inactive context`;
|
|
2035
|
-
throw error;
|
|
2036
|
-
}
|
|
2037
|
-
if (!fiber.runtime) throw error;
|
|
2038
|
-
if (fiber.parent[symbols.isolate][prop] !== key) throw error;
|
|
2039
|
-
fiber = fiber.parent.fiber;
|
|
2040
|
-
}
|
|
2041
|
-
});
|
|
2042
|
-
} catch (e) {
|
|
2043
|
-
throw e === error ? enhanceError(e) : e;
|
|
2044
|
-
}
|
|
2045
|
-
},
|
|
2046
|
-
set: (target, prop, value, ctx) => {
|
|
2047
|
-
if (isSpecialProperty(prop)) return Reflect.set(target, prop, value, ctx);
|
|
2048
|
-
const error = /* @__PURE__ */ new Error(`cannot set property "${prop}" without provide`);
|
|
2049
|
-
const def = target.reflect.props[prop];
|
|
2050
|
-
if (!def) {
|
|
2051
|
-
if (!ctx.fiber.runtime) return Reflect.set(target, prop, value, ctx);
|
|
2052
|
-
throw enhanceError(error);
|
|
2053
|
-
}
|
|
2054
|
-
try {
|
|
2055
|
-
if (def.type === "accessor") {
|
|
2056
|
-
if (!def.set) return false;
|
|
2057
|
-
return def.set.call(ctx, value, ctx[symbols.receiver], error);
|
|
2058
|
-
}
|
|
2059
|
-
return ctx.events.waterfall("internal/set", ctx, prop, value, error, () => {
|
|
2060
|
-
return ctx.reflect.set(prop, value, error);
|
|
2061
|
-
});
|
|
2062
|
-
} catch (e) {
|
|
2063
|
-
throw e === error ? enhanceError(e) : e;
|
|
2064
|
-
}
|
|
2065
|
-
},
|
|
2066
|
-
has: (target, prop) => {
|
|
2067
|
-
if (isSpecialProperty(prop)) return Reflect.has(target, prop);
|
|
2068
|
-
if (Reflect.has(target, prop)) return true;
|
|
2069
|
-
return !!target.reflect.props[prop];
|
|
2070
|
-
}
|
|
2071
|
-
};
|
|
2072
|
-
/** Service implementations, keyed by isolation label. */
|
|
2073
|
-
store = Object.create(null);
|
|
2074
|
-
/** Declared context properties (services and accessors), by name. */
|
|
2075
|
-
props = Object.create(null);
|
|
2076
|
-
constructor(ctx) {
|
|
2077
|
-
this.ctx = ctx;
|
|
2078
|
-
defineProperty(this, symbols.tracker, {
|
|
2079
|
-
property: "ctx",
|
|
2080
|
-
noShadow: true
|
|
2081
|
-
});
|
|
2082
|
-
this.mixin("reflect", [
|
|
2083
|
-
"get",
|
|
2084
|
-
"set",
|
|
2085
|
-
"provide",
|
|
2086
|
-
"accessor",
|
|
2087
|
-
"mixin"
|
|
2088
|
-
]);
|
|
2089
|
-
this.mixin("fiber", ["runtime", "effect"]);
|
|
2090
|
-
this.mixin("registry", ["inject", "plugin"]);
|
|
2091
|
-
this.mixin("events", [
|
|
2092
|
-
"on",
|
|
2093
|
-
"once",
|
|
2094
|
-
"parallel",
|
|
2095
|
-
"emit",
|
|
2096
|
-
"serial",
|
|
2097
|
-
"bail",
|
|
2098
|
-
"waterfall"
|
|
2099
|
-
]);
|
|
2100
|
-
}
|
|
2101
|
-
/**
|
|
2102
|
-
* Read a service from the store without the inject requirement.
|
|
2103
|
-
*
|
|
2104
|
-
* @param name — the service name.
|
|
2105
|
-
* @param strict — when `true`, only return implementations whose providing
|
|
2106
|
-
* fiber is currently active.
|
|
2107
|
-
* @returns the service value, or `undefined` when not (yet) provided.
|
|
2108
|
-
*/
|
|
2109
|
-
get(name, strict = true) {
|
|
2110
|
-
return getTraceable(this.ctx, this._getImpl(name, strict)?.value);
|
|
2111
|
-
}
|
|
2112
|
-
_getImpl(name, strict = true) {
|
|
2113
|
-
const key = this.ctx[symbols.isolate][name];
|
|
2114
|
-
const impl = key && this.store[key];
|
|
2115
|
-
if (!impl) return;
|
|
2116
|
-
if (strict && impl.fiber.state !== 2) return;
|
|
2117
|
-
return impl;
|
|
2118
|
-
}
|
|
2119
|
-
/**
|
|
2120
|
-
* Overwrite a provided service's value.
|
|
2121
|
-
*
|
|
2122
|
-
* @param name — the service name.
|
|
2123
|
-
* @param value — the new service value.
|
|
2124
|
-
* @param error — carrier for the caller stack in diagnostics.
|
|
2125
|
-
* @returns `true` on success.
|
|
2126
|
-
* @throws when `name` was never provided, or was provided by another fiber.
|
|
2127
|
-
*/
|
|
2128
|
-
set(name, value, error) {
|
|
2129
|
-
const key = this.ctx[symbols.isolate][name];
|
|
2130
|
-
const impl = this.store[key];
|
|
2131
|
-
if (!impl) throw new Error(`cannot set property "${name}" without provide`);
|
|
2132
|
-
if (impl.fiber !== this.ctx.fiber) throw new Error(`cannot set property "${name}" in multiple fibers`);
|
|
2133
|
-
impl.value = value;
|
|
2134
|
-
return true;
|
|
2135
|
-
}
|
|
2136
|
-
/**
|
|
2137
|
-
* Register a service implementation owned by the current fiber.
|
|
2138
|
-
*
|
|
2139
|
-
* See the `ctx.provide()` overload above for the full contract.
|
|
2140
|
-
*
|
|
2141
|
-
* @param name — the service name.
|
|
2142
|
-
* @param value — the service value.
|
|
2143
|
-
* @param check — optional availability predicate for dependents.
|
|
2144
|
-
* @returns a disposer that unregisters the service.
|
|
2145
|
-
*/
|
|
2146
|
-
provide(name, value, check) {
|
|
2147
|
-
return this.ctx.fiber.effect(() => {
|
|
2148
|
-
if (!this.props[name]) this.props[name] ??= { type: "service" };
|
|
2149
|
-
else if (this.props[name].type !== "service") throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
|
|
2150
|
-
this.props[name] = { type: "service" };
|
|
2151
|
-
this.ctx.root[symbols.isolate][name] ??= Symbol(name);
|
|
2152
|
-
const key = this.ctx[symbols.isolate][name];
|
|
2153
|
-
const impl = {
|
|
2154
|
-
name,
|
|
2155
|
-
value,
|
|
2156
|
-
fiber: this.ctx.fiber,
|
|
2157
|
-
check
|
|
2158
|
-
};
|
|
2159
|
-
if (this.store[key]) throw new Error(`service "${name}" has been registered at <${this.store[key].fiber.name}>`);
|
|
2160
|
-
this.store[key] = impl;
|
|
2161
|
-
this.ctx.fiber.store[name] = impl;
|
|
2162
|
-
if (this.ctx.fiber.state === 2) this.notify([name]);
|
|
2163
|
-
return async () => {
|
|
2164
|
-
delete this.store[key];
|
|
2165
|
-
const fibers = this.notify([name]);
|
|
2166
|
-
await Promise.allSettled(fibers.map((fiber) => fiber.await()));
|
|
2167
|
-
delete this.ctx.fiber.store[name];
|
|
2168
|
-
};
|
|
2169
|
-
}, `ctx.provide(${JSON.stringify(name)})`);
|
|
2170
|
-
}
|
|
2171
|
-
/**
|
|
2172
|
-
* Re-evaluate every fiber that requires one of the given services.
|
|
2173
|
-
*
|
|
2174
|
-
* @param names — the service names that changed.
|
|
2175
|
-
* @param filter — restricts notification to matching isolation scopes.
|
|
2176
|
-
* @returns the fibers whose dependency state was refreshed.
|
|
2177
|
-
*/
|
|
2178
|
-
notify(names, filter = (ctx, name) => ctx[symbols.isolate][name] === this.ctx[symbols.isolate][name]) {
|
|
2179
|
-
const fibers = [];
|
|
2180
|
-
for (const runtime of this.ctx.registry.values()) for (const fiber of runtime.fibers) {
|
|
2181
|
-
let hasUpdate = false;
|
|
2182
|
-
for (const name of names) {
|
|
2183
|
-
if (!(name in fiber.inject)) continue;
|
|
2184
|
-
if (!filter(fiber.ctx, name)) continue;
|
|
2185
|
-
hasUpdate = true;
|
|
2186
|
-
fiber._checkImpl(name);
|
|
2187
|
-
}
|
|
2188
|
-
if (!hasUpdate) continue;
|
|
2189
|
-
fiber._refresh();
|
|
2190
|
-
fibers.push(fiber);
|
|
2191
|
-
}
|
|
2192
|
-
for (const name of names) {
|
|
2193
|
-
const self = Object.create(this.ctx);
|
|
2194
|
-
self[symbols.filter] = (target) => filter(target, name);
|
|
2195
|
-
this.ctx.events.emit(self, "internal/service", name, this._getImpl(name, false)?.value);
|
|
2196
|
-
}
|
|
2197
|
-
return fibers;
|
|
2198
|
-
}
|
|
2199
|
-
/**
|
|
2200
|
-
* Define a computed context property backed by get/set hooks.
|
|
2201
|
-
*
|
|
2202
|
-
* @param name — the context property name.
|
|
2203
|
-
* @param options — the `get` hook and optional `set` hook.
|
|
2204
|
-
* @returns a disposer that removes the accessor.
|
|
2205
|
-
*/
|
|
2206
|
-
accessor(name, options) {
|
|
2207
|
-
return this.ctx.fiber.effect(() => {
|
|
2208
|
-
if (name in this.props) throw new Error(`property "${name}" is already declared as ${this.props[name].type}`);
|
|
2209
|
-
this.props[name] = {
|
|
2210
|
-
type: "accessor",
|
|
2211
|
-
...options
|
|
2212
|
-
};
|
|
2213
|
-
return () => delete this.props[name];
|
|
2214
|
-
}, `ctx.accessor(${JSON.stringify(name)})`);
|
|
2215
|
-
}
|
|
2216
|
-
/**
|
|
2217
|
-
* Expose selected members of a service directly on `ctx`.
|
|
2218
|
-
*
|
|
2219
|
-
* See the `ctx.mixin()` overload above for the full contract.
|
|
2220
|
-
*
|
|
2221
|
-
* @param source — a context property name or a source object.
|
|
2222
|
-
* @param mixins — keys to forward, or a source-key → ctx-key map.
|
|
2223
|
-
* @returns a disposer that removes all created accessors.
|
|
2224
|
-
*/
|
|
2225
|
-
mixin(source, mixins) {
|
|
2226
|
-
const self = this;
|
|
2227
|
-
return this.ctx.fiber.effect(function* () {
|
|
2228
|
-
const entries = Array.isArray(mixins) ? mixins.map((key) => [key, key]) : Object.entries(mixins);
|
|
2229
|
-
const getTarget = (ctx, error) => {
|
|
2230
|
-
return ctx[source];
|
|
2231
|
-
};
|
|
2232
|
-
for (const [key, value] of entries) yield self.accessor(value, {
|
|
2233
|
-
get(receiver, error) {
|
|
2234
|
-
const service = getTarget(this, error);
|
|
2235
|
-
if (isNullable(service)) return service;
|
|
2236
|
-
const mixin = receiver ? withProps(receiver, service) : service;
|
|
2237
|
-
const value = Reflect.get(service, key, mixin);
|
|
2238
|
-
if (typeof value !== "function") return value;
|
|
2239
|
-
return value.bind(mixin ?? service);
|
|
2240
|
-
},
|
|
2241
|
-
set(value, receiver, error) {
|
|
2242
|
-
const service = getTarget(this, error);
|
|
2243
|
-
const mixin = receiver ? withProps(receiver, service) : service;
|
|
2244
|
-
return Reflect.set(service, key, value, mixin);
|
|
2245
|
-
}
|
|
2246
|
-
});
|
|
2247
|
-
}, `ctx.mixin(${JSON.stringify(source)})`);
|
|
2248
|
-
}
|
|
2249
|
-
/**
|
|
2250
|
-
* Attach this context's tracing wrapper to a value.
|
|
2251
|
-
*
|
|
2252
|
-
* @param value — the value to wrap.
|
|
2253
|
-
* @returns the traceable wrapper (or the value itself when not applicable).
|
|
2254
|
-
*/
|
|
2255
|
-
trace(value) {
|
|
2256
|
-
return getTraceable(this.ctx, value);
|
|
2257
|
-
}
|
|
2258
|
-
/**
|
|
2259
|
-
* Wrap a callback so calls trace `this` and arguments to this context.
|
|
2260
|
-
*
|
|
2261
|
-
* @param callback — the function to wrap.
|
|
2262
|
-
* @returns a proxy delegating to `callback` with traced values.
|
|
2263
|
-
*/
|
|
2264
|
-
bind(callback) {
|
|
2265
|
-
return new Proxy(callback, {
|
|
2266
|
-
apply: (target, thisArg, args) => {
|
|
2267
|
-
return Reflect.apply(target, this.trace(thisArg), args.map((arg) => this.trace(arg)));
|
|
2268
|
-
},
|
|
2269
|
-
construct: (target, args, newTarget) => {
|
|
2270
|
-
return Reflect.construct(target, args.map((arg) => this.trace(arg)), newTarget);
|
|
2271
|
-
}
|
|
2272
|
-
});
|
|
2273
|
-
}
|
|
2274
|
-
};
|
|
2275
|
-
//#endregion
|
|
2276
|
-
//#region ../../../vendor/cordis/src/registry.ts
|
|
2277
|
-
function isApplicable(object) {
|
|
2278
|
-
return object && typeof object === "object" && typeof object.apply === "function";
|
|
2279
|
-
}
|
|
2280
|
-
/**
|
|
2281
|
-
* Decorator for declaring service dependencies on classes or class methods.
|
|
2282
|
-
*
|
|
2283
|
-
* On classes it contributes to the plugin's static `inject` map. On methods it
|
|
2284
|
-
* delays the method call until the declared services are available.
|
|
2285
|
-
*/
|
|
2286
|
-
/**
|
|
2287
|
-
* @param name — the required service name.
|
|
2288
|
-
* @param config — optional intercept config applied for that service.
|
|
2289
|
-
* @returns the class or method decorator.
|
|
2290
|
-
*/
|
|
2291
|
-
function Inject(name, config) {
|
|
2292
|
-
return function(value, decorator) {
|
|
2293
|
-
if (decorator.kind === "class") {
|
|
2294
|
-
if (!Object.hasOwn(value, "inject")) {
|
|
2295
|
-
defineProperty(value, "inject", Object.create(Object.getPrototypeOf(value).inject ?? null));
|
|
2296
|
-
defineProperty(value.inject, symbols.checkProto, true);
|
|
2297
|
-
}
|
|
2298
|
-
value.inject[name] = config;
|
|
2299
|
-
} else if (decorator.kind === "method") {
|
|
2300
|
-
const inject = (value[symbols.metadata] ??= {}).inject ??= Object.create(null);
|
|
2301
|
-
inject[name] = config;
|
|
2302
|
-
decorator.addInitializer(function() {
|
|
2303
|
-
const property = this[symbols.tracker]?.property;
|
|
2304
|
-
(this[symbols.initHooks] ??= []).push(() => {
|
|
2305
|
-
this.ctx.inject(inject, (ctx) => {
|
|
2306
|
-
return value.call(property ? withProps(this, { [property]: ctx }) : this);
|
|
2307
|
-
});
|
|
2308
|
-
});
|
|
2309
|
-
});
|
|
2310
|
-
} else throw new Error("@Inject() can only be used on class or class methods");
|
|
2311
|
-
};
|
|
2312
|
-
}
|
|
2313
|
-
(function(_Inject) {
|
|
2314
|
-
function resolve(inject, result = Object.create(null)) {
|
|
2315
|
-
if (!inject) return result;
|
|
2316
|
-
if (Array.isArray(inject)) for (const name of inject) result[name] = null;
|
|
2317
|
-
else if (Reflect.has(inject, symbols.checkProto)) {
|
|
2318
|
-
Object.assign(result, resolve(Object.getPrototypeOf(inject)));
|
|
2319
|
-
for (const name of Object.keys(inject)) result[name] = inject[name] ?? null;
|
|
2320
|
-
} else for (const name of Object.keys(inject)) result[name] = inject[name] ?? null;
|
|
2321
|
-
return result;
|
|
2322
|
-
}
|
|
2323
|
-
_Inject.resolve = resolve;
|
|
2324
|
-
})(Inject || (Inject = {}));
|
|
2325
|
-
/**
|
|
2326
|
-
* Plugin registry installed as `ctx.registry` and mixed into every context.
|
|
2327
|
-
*
|
|
2328
|
-
* It normalizes plugin shapes, tracks plugin runtimes, starts fibers, and
|
|
2329
|
-
* exposes map-like inspection over active plugin callbacks.
|
|
2330
|
-
*/
|
|
2331
|
-
var RegistryService = class {
|
|
2332
|
-
ctx;
|
|
2333
|
-
_counter = 0;
|
|
2334
|
-
_internal = /* @__PURE__ */ new Map();
|
|
2335
|
-
constructor(ctx) {
|
|
2336
|
-
this.ctx = ctx;
|
|
2337
|
-
defineProperty(this, symbols.tracker, {
|
|
2338
|
-
property: "ctx",
|
|
2339
|
-
noShadow: true
|
|
2340
|
-
});
|
|
2341
|
-
}
|
|
2342
|
-
/** Allocate the next fiber uid (increments on every read). */
|
|
2343
|
-
get counter() {
|
|
2344
|
-
return ++this._counter;
|
|
2345
|
-
}
|
|
2346
|
-
/** Number of registered plugin runtimes. */
|
|
2347
|
-
get size() {
|
|
2348
|
-
return this._internal.size;
|
|
2349
|
-
}
|
|
2350
|
-
/**
|
|
2351
|
-
* Resolve a supported plugin shape to its executable callback.
|
|
2352
|
-
*
|
|
2353
|
-
* @param plugin — a function, class, or `{ apply }` object plugin.
|
|
2354
|
-
* @returns the callback identifying the plugin, or `undefined` if invalid.
|
|
2355
|
-
*/
|
|
2356
|
-
resolve(plugin) {
|
|
2357
|
-
try {
|
|
2358
|
-
if (typeof plugin === "function") return plugin;
|
|
2359
|
-
if (isApplicable(plugin)) return plugin.apply;
|
|
2360
|
-
} catch {}
|
|
2361
|
-
}
|
|
2362
|
-
/**
|
|
2363
|
-
* Look up the runtime record for a plugin.
|
|
2364
|
-
*
|
|
2365
|
-
* @param plugin — any supported plugin shape.
|
|
2366
|
-
* @returns the runtime, or `undefined` when the plugin is not registered.
|
|
2367
|
-
*/
|
|
2368
|
-
get(plugin) {
|
|
2369
|
-
const key = this.resolve(plugin);
|
|
2370
|
-
return key && this._internal.get(key);
|
|
2371
|
-
}
|
|
2372
|
-
/**
|
|
2373
|
-
* Check whether a plugin has a registered runtime.
|
|
2374
|
-
*
|
|
2375
|
-
* @param plugin — any supported plugin shape.
|
|
2376
|
-
* @returns `true` when at least one fiber of the plugin exists.
|
|
2377
|
-
*/
|
|
2378
|
-
has(plugin) {
|
|
2379
|
-
const key = this.resolve(plugin);
|
|
2380
|
-
return !!key && this._internal.has(key);
|
|
2381
|
-
}
|
|
2382
|
-
/**
|
|
2383
|
-
* Dispose every running fiber for a plugin and remove its runtime record.
|
|
2384
|
-
*
|
|
2385
|
-
* @param plugin — any supported plugin shape.
|
|
2386
|
-
* @returns the removed runtime, or `undefined` when none was registered.
|
|
2387
|
-
*/
|
|
2388
|
-
delete(plugin) {
|
|
2389
|
-
const key = this.resolve(plugin);
|
|
2390
|
-
const runtime = key && this._internal.get(key);
|
|
2391
|
-
if (!runtime) return;
|
|
2392
|
-
this._internal.delete(key);
|
|
2393
|
-
for (const fiber of runtime.fibers) fiber.dispose();
|
|
2394
|
-
return runtime;
|
|
2395
|
-
}
|
|
2396
|
-
/** Iterate the registered plugin callbacks. */
|
|
2397
|
-
keys() {
|
|
2398
|
-
return this._internal.keys();
|
|
2399
|
-
}
|
|
2400
|
-
/** Iterate the registered plugin runtimes. */
|
|
2401
|
-
values() {
|
|
2402
|
-
return this._internal.values();
|
|
2403
|
-
}
|
|
2404
|
-
/** Iterate `[callback, runtime]` pairs. */
|
|
2405
|
-
entries() {
|
|
2406
|
-
return this._internal.entries();
|
|
2407
|
-
}
|
|
2408
|
-
/**
|
|
2409
|
-
* Visit every registered runtime.
|
|
2410
|
-
*
|
|
2411
|
-
* @param callback — receives each runtime and its identifying callback.
|
|
2412
|
-
*/
|
|
2413
|
-
forEach(callback) {
|
|
2414
|
-
return this._internal.forEach(callback);
|
|
2415
|
-
}
|
|
2416
|
-
/**
|
|
2417
|
-
* Start a callback once the requested dependencies are available.
|
|
2418
|
-
*
|
|
2419
|
-
* @param inject — required services, as an array or a name → config map.
|
|
2420
|
-
* @param callback — plugin body called with `(ctx, config)`.
|
|
2421
|
-
* @returns the fiber; awaiting it settles once loading finished.
|
|
2422
|
-
*/
|
|
2423
|
-
inject(inject, callback) {
|
|
2424
|
-
return this.plugin({
|
|
2425
|
-
inject,
|
|
2426
|
-
apply: callback,
|
|
2427
|
-
name: callback.name
|
|
2428
|
-
});
|
|
2429
|
-
}
|
|
2430
|
-
/**
|
|
2431
|
-
* Start a plugin in the current context and return its fiber.
|
|
2432
|
-
*
|
|
2433
|
-
* Creates (or reuses) the plugin's runtime record, then starts a new fiber
|
|
2434
|
-
* under the current context. Throws if `plugin` is not a supported shape or
|
|
2435
|
-
* if the current fiber is already disposed.
|
|
2436
|
-
*
|
|
2437
|
-
* @param plugin — a function, class, or `{ apply }` object plugin.
|
|
2438
|
-
* @param config — the plugin config, validated against its `Config` schema.
|
|
2439
|
-
* @param getOuterStack — captures the caller stack for effect diagnostics.
|
|
2440
|
-
* @returns the fiber; awaiting it settles once loading finished.
|
|
2441
|
-
*/
|
|
2442
|
-
plugin(plugin, config, getOuterStack = buildOuterStack()) {
|
|
2443
|
-
const callback = this.resolve(plugin);
|
|
2444
|
-
if (!callback) throw new Error("invalid plugin, expect function or object with an \"apply\" method, received " + typeof plugin);
|
|
2445
|
-
this.ctx.fiber.assertActive();
|
|
2446
|
-
let runtime = this._internal.get(callback);
|
|
2447
|
-
if (!runtime) {
|
|
2448
|
-
let name = plugin.name;
|
|
2449
|
-
if (name === "apply") name = void 0;
|
|
2450
|
-
runtime = {
|
|
2451
|
-
name,
|
|
2452
|
-
callback,
|
|
2453
|
-
fibers: new DisposableList(),
|
|
2454
|
-
Config: plugin.Config
|
|
2455
|
-
};
|
|
2456
|
-
this._internal.set(callback, runtime);
|
|
2457
|
-
}
|
|
2458
|
-
const fiber = new Fiber(this.ctx, config, Inject.resolve(plugin.inject), runtime, getOuterStack);
|
|
2459
|
-
const wrapped = Object.create(fiber);
|
|
2460
|
-
wrapped.then = (onFulfilled, onRejected) => {
|
|
2461
|
-
return fiber.await().then(onFulfilled, onRejected);
|
|
2462
|
-
};
|
|
2463
|
-
return wrapped;
|
|
2464
|
-
}
|
|
2465
|
-
};
|
|
2466
|
-
//#endregion
|
|
2467
|
-
//#region ../../../vendor/cordis/src/context.ts
|
|
2468
|
-
/**
|
|
2469
|
-
* Root and child dependency containers for Cordis plugins.
|
|
2470
|
-
*
|
|
2471
|
-
* A context is a proxy: normal property reads go through the service resolver,
|
|
2472
|
-
* while `extend()`, `isolate()`, and `intercept()` create scoped child
|
|
2473
|
-
* contexts without mutating their parent.
|
|
2474
|
-
*/
|
|
2475
|
-
var Context = class Context {
|
|
2476
|
-
/** Symbol key under which a disposer exposes its {@link EffectMeta} diagnostics tree. */
|
|
2477
|
-
static effect = symbols.effect;
|
|
2478
|
-
/** Symbol key for a context's listener filter, consulted on every event dispatch. */
|
|
2479
|
-
static filter = symbols.filter;
|
|
2480
|
-
/** Symbol key of the isolation map (see the `Context[symbols.isolate]` property). */
|
|
2481
|
-
static isolate = symbols.isolate;
|
|
2482
|
-
/** Symbol key of the intercept map (see the `Context[symbols.intercept]` property). */
|
|
2483
|
-
static intercept = symbols.intercept;
|
|
2484
|
-
/**
|
|
2485
|
-
* Returns true for Cordis context proxies and context prototypes.
|
|
2486
|
-
*
|
|
2487
|
-
* Works across realms and across multiple copies of cordis, because the
|
|
2488
|
-
* brand is keyed by a global symbol rather than by `instanceof`.
|
|
2489
|
-
*
|
|
2490
|
-
* @param value — the value to test.
|
|
2491
|
-
* @returns `true` if `value` is a Cordis context, narrowing its type.
|
|
2492
|
-
*/
|
|
2493
|
-
static is(value) {
|
|
2494
|
-
return !!value?.[Context.is];
|
|
2495
|
-
}
|
|
2496
|
-
static {
|
|
2497
|
-
Context.is[Symbol.toPrimitive] = () => Symbol.for("cordis.is");
|
|
2498
|
-
Context.prototype[Context.is] = true;
|
|
2499
|
-
}
|
|
2500
|
-
/** Create the root context and install the built-in services. */
|
|
2501
|
-
constructor() {
|
|
2502
|
-
this[symbols.isolate] = Object.create(null);
|
|
2503
|
-
this[symbols.intercept] = Object.create(null);
|
|
2504
|
-
const self = new Proxy(this, ReflectService.handler);
|
|
2505
|
-
this.root = self;
|
|
2506
|
-
this.baseUrl = void 0;
|
|
2507
|
-
this.fiber = new Fiber(self, {}, Object.create(null), null, () => []);
|
|
2508
|
-
this.reflect = new ReflectService(self);
|
|
2509
|
-
this.registry = new RegistryService(self);
|
|
2510
|
-
this.events = new EventsService(self);
|
|
2511
|
-
this.logger = new LoggerService(self);
|
|
2512
|
-
this.fiber._disposables.clear();
|
|
2513
|
-
return self;
|
|
2514
|
-
}
|
|
2515
|
-
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
2516
|
-
return `Context <${this.fiber.name}>`;
|
|
2517
|
-
}
|
|
2518
|
-
/**
|
|
2519
|
-
* Create a child context with extra metadata on top of the current scope.
|
|
2520
|
-
*
|
|
2521
|
-
* The child prototypally inherits every property of this context; own
|
|
2522
|
-
* properties of `meta` shadow the inherited ones. The parent is not mutated.
|
|
2523
|
-
*
|
|
2524
|
-
* @param meta — own properties (including symbol keys) to define on the child.
|
|
2525
|
-
* @returns a child context inheriting from this one.
|
|
2526
|
-
*/
|
|
2527
|
-
extend(meta = {}) {
|
|
2528
|
-
const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value;
|
|
2529
|
-
const self = Object.create(getTraceable(this, this));
|
|
2530
|
-
for (const prop of Reflect.ownKeys(meta)) Object.defineProperty(self, prop, Reflect.getOwnPropertyDescriptor(meta, prop));
|
|
2531
|
-
if (!shadow) return self;
|
|
2532
|
-
return Object.assign(Object.create(self), { [symbols.shadow]: shadow });
|
|
2533
|
-
}
|
|
2534
|
-
/**
|
|
2535
|
-
* Create a child context with an independent service scope for `name`.
|
|
2536
|
-
*
|
|
2537
|
-
* Below the returned context, reads and writes of the service `name`
|
|
2538
|
-
* resolve against the new label instead of the parent's, so a different
|
|
2539
|
-
* implementation can be provided without affecting the parent scope.
|
|
2540
|
-
* Passing the same `label` to two `isolate()` calls joins their scopes.
|
|
2541
|
-
*
|
|
2542
|
-
* @param name — the service name to isolate.
|
|
2543
|
-
* @param label — scope label to join; defaults to a fresh unique symbol.
|
|
2544
|
-
* @returns a child context whose `name` service resolves in the new scope.
|
|
2545
|
-
*/
|
|
2546
|
-
isolate(name, label) {
|
|
2547
|
-
const shadow = Object.create(this[symbols.isolate]);
|
|
2548
|
-
shadow[name] = label ?? Symbol(name);
|
|
2549
|
-
return this.extend({ [symbols.isolate]: shadow });
|
|
2550
|
-
}
|
|
2551
|
-
intercept(name, config) {
|
|
2552
|
-
const intercept = Object.create(this[symbols.intercept]);
|
|
2553
|
-
intercept[name] = config;
|
|
2554
|
-
return this.extend({ [symbols.intercept]: intercept });
|
|
2555
|
-
}
|
|
2556
|
-
};
|
|
2557
|
-
//#endregion
|
|
2558
|
-
//#region ../../../vendor/cordis/src/service.ts
|
|
2559
|
-
/**
|
|
2560
|
-
* Base class for services that expose a named API on `ctx`.
|
|
2561
|
-
*
|
|
2562
|
-
* Subclasses call `super(ctx, name)` from their constructor. The service is
|
|
2563
|
-
* registered immediately and is automatically removed with the owning fiber.
|
|
2564
|
-
*/
|
|
2565
|
-
var Service = class Service {
|
|
2566
|
-
ctx;
|
|
2567
|
-
/** Symbol key of an instance method run after construction (class plugins). */
|
|
2568
|
-
static init = symbols.init;
|
|
2569
|
-
/** Symbol key of the availability predicate passed to `ctx.provide()`. */
|
|
2570
|
-
static check = symbols.check;
|
|
2571
|
-
/** Symbol key of the phantom intercept-config type parameter. */
|
|
2572
|
-
static config = symbols.config;
|
|
2573
|
-
/** Symbol key of the call body making a service callable (e.g. `ctx.logger()`). */
|
|
2574
|
-
static invoke = symbols.invoke;
|
|
2575
|
-
/** Symbol key of the helper deriving an extended service instance. */
|
|
2576
|
-
static extend = symbols.extend;
|
|
2577
|
-
/** Symbol key of the tracker metadata used for context tracing. */
|
|
2578
|
-
static tracker = symbols.tracker;
|
|
2579
|
-
/** Symbol key of the intercept-config resolution helper below. */
|
|
2580
|
-
static resolveConfig = symbols.resolveConfig;
|
|
2581
|
-
/** The service name this instance is registered under. */
|
|
2582
|
-
name;
|
|
2583
|
-
/**
|
|
2584
|
-
* Register this instance as `name` in the current context.
|
|
2585
|
-
*
|
|
2586
|
-
* Calls `ctx.reflect.provide(name, this, this[Service.check])`, so the
|
|
2587
|
-
* service is unregistered automatically when the owning fiber unloads.
|
|
2588
|
-
* Services with a `[Service.invoke]` body return a callable instance.
|
|
2589
|
-
*
|
|
2590
|
-
* @param ctx — the context to register in (stored as `this.ctx`).
|
|
2591
|
-
* @param name — the service name; defaults to the static `provide` field.
|
|
2592
|
-
*/
|
|
2593
|
-
constructor(ctx, name) {
|
|
2594
|
-
this.ctx = ctx;
|
|
2595
|
-
name ??= this.constructor["provide"];
|
|
2596
|
-
let self = this;
|
|
2597
|
-
const tracker = {
|
|
2598
|
-
associate: name,
|
|
2599
|
-
property: "ctx"
|
|
2600
|
-
};
|
|
2601
|
-
if (self[symbols.invoke]) self = createCallable(name, joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker);
|
|
2602
|
-
self.ctx = ctx;
|
|
2603
|
-
self.name = name;
|
|
2604
|
-
defineProperty(self, symbols.tracker, tracker);
|
|
2605
|
-
self.ctx.reflect.provide(name, self, this[symbols.check]);
|
|
2606
|
-
return self;
|
|
2607
|
-
}
|
|
2608
|
-
[symbols.filter](ctx) {
|
|
2609
|
-
return ctx[symbols.isolate][this.name] === this.ctx[symbols.isolate][this.name];
|
|
2610
|
-
}
|
|
2611
|
-
[symbols.extend](props) {
|
|
2612
|
-
let self;
|
|
2613
|
-
if (this[Service.invoke]) self = createCallable(this.name, this, this[symbols.tracker]);
|
|
2614
|
-
else self = Object.create(this);
|
|
2615
|
-
return Object.assign(self, props);
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Merge intercept config from ancestors with optional base and head values.
|
|
2619
|
-
*
|
|
2620
|
-
* Entries added closer to the root apply first; `base` is prepended and
|
|
2621
|
-
* `head` appended. Uses `Config.merge` when the service declares one,
|
|
2622
|
-
* otherwise a shallow `Object.assign`.
|
|
2623
|
-
*
|
|
2624
|
-
* @param base — lowest-precedence config merged before all intercepts.
|
|
2625
|
-
* @param head — highest-precedence config merged after all intercepts.
|
|
2626
|
-
* @returns the merged config.
|
|
2627
|
-
*/
|
|
2628
|
-
[symbols.resolveConfig](base, head) {
|
|
2629
|
-
let intercept = this.ctx[Context.intercept];
|
|
2630
|
-
const configs = [];
|
|
2631
|
-
while (this.name in intercept) {
|
|
2632
|
-
if (Object.hasOwn(intercept, this.name)) configs.unshift(intercept[this.name]);
|
|
2633
|
-
intercept = Object.getPrototypeOf(intercept);
|
|
2634
|
-
}
|
|
2635
|
-
if (base) configs.unshift(base);
|
|
2636
|
-
if (head) configs.push(head);
|
|
2637
|
-
if (this["Config"]?.merge) return this["Config"].merge(...configs);
|
|
2638
|
-
else return Object.assign({}, ...configs);
|
|
2639
|
-
}
|
|
2640
|
-
static [Symbol.hasInstance](instance) {
|
|
2641
|
-
if (!instance) return false;
|
|
2642
|
-
let constructor = instance.constructor;
|
|
2643
|
-
while (constructor) {
|
|
2644
|
-
constructor = constructor.prototype?.constructor;
|
|
2645
|
-
if (constructor === this) return true;
|
|
2646
|
-
constructor &&= Object.getPrototypeOf(constructor);
|
|
2647
|
-
}
|
|
2648
|
-
return false;
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
|
-
//#endregion
|
|
2652
|
-
//#region ../../util/timeout/src/index.ts
|
|
2653
|
-
/** Largest delay Node schedules without clamping it to one millisecond. */
|
|
2654
|
-
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
2655
|
-
//#endregion
|
|
2656
|
-
//#region ../llm/src/error.ts
|
|
2657
|
-
/**
|
|
2658
|
-
* Harness error base with a stable machine-routable code and chained cause.
|
|
2659
|
-
* Package errors extend it so tool results and replay can retain failure class.
|
|
2660
|
-
* @module @deepseek-ai/dsh-llm/error
|
|
2661
|
-
*/
|
|
2662
|
-
/**
|
|
2663
|
-
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
2664
|
-
* e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the
|
|
2665
|
-
* human-readable `message`, and supports `cause` chaining via the standard
|
|
2666
|
-
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
2667
|
-
*/
|
|
2668
|
-
var HarnessError = class extends Error {
|
|
2669
|
-
/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */
|
|
2670
|
-
code;
|
|
2671
|
-
constructor(message, code, options) {
|
|
2672
|
-
super(message, options);
|
|
2673
|
-
this.code = code;
|
|
2674
|
-
this.name = new.target.name;
|
|
2675
|
-
}
|
|
2676
|
-
};
|
|
2677
|
-
/**
|
|
2678
|
-
* Canonical provider-neutral code for a response that completed normally but
|
|
2679
|
-
* carried no content blocks at all. Providers occasionally emit a degenerate
|
|
2680
|
-
* completion (a terminal stop with zero output); adapters classify it as this
|
|
2681
|
-
* failure instead of yielding an empty assistant message, because an empty
|
|
2682
|
-
* message silently ends the turn with nothing for the user or the loop to act
|
|
2683
|
-
* on. The attempt produced nothing durable, so retry policy treats it as safe
|
|
2684
|
-
* to repeat.
|
|
2685
|
-
*/
|
|
2686
|
-
const EMPTY_RESPONSE_CODE = "EMPTY_RESPONSE";
|
|
2687
|
-
/**
|
|
2688
|
-
* Canonical provider-neutral code for a credential that was supplied but
|
|
2689
|
-
* cannot be used — malformed rather than absent. Distinct from
|
|
2690
|
-
* `MISSING_CREDENTIAL` because the fix differs: correct the stored value
|
|
2691
|
-
* rather than supply one. Deliberately outside the default retryable set —
|
|
2692
|
-
* a malformed credential fails identically on every attempt.
|
|
2693
|
-
*/
|
|
2694
|
-
const INVALID_CREDENTIAL_CODE = "INVALID_CREDENTIAL";
|
|
2695
|
-
new RegExp(String.raw`(?:^|[^a-z0-9])context[\s_-](?:length|window)[\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\s_-]exceeded)(?:$|[^a-z0-9])`, "i");
|
|
2696
|
-
new RegExp(String.raw`\b(?:request|prompt|input|messages?)\s+(?:is\s+|are\s+)?` + String.raw`too\s+(?:large|long)\s+for\s+(?:(?:this|the)\s+)?` + String.raw`(?:model(?:'s)?\s+)?context(?:\s+window)?\b`, "i");
|
|
2697
|
-
new RegExp(String.raw`\b(?:input|prompt|request|messages?)\b.{0,40}` + String.raw`\b(?:exceed(?:s|ed)?|overflows?|is\s+larger\s+than)\b.{0,40}` + String.raw`\b(?:the\s+)?(?:model(?:'s)?\s+)?context(?:\s+(?:length|window))?\b`, "i");
|
|
2698
|
-
//#endregion
|
|
2699
|
-
//#region ../llm/src/retry-policy.ts
|
|
2700
|
-
/**
|
|
2701
|
-
* Provider-owned request-retry policy configuration and resolution.
|
|
2702
|
-
*
|
|
2703
|
-
* Adapters expose one resolved policy per registered provider route; the
|
|
2704
|
-
* optional dsh-llm-retry plugin executes it on the agent's failed-step extension point.
|
|
2705
|
-
*
|
|
2706
|
-
* @module @deepseek-ai/dsh-llm/retry-policy
|
|
2707
|
-
*/
|
|
2708
|
-
const DEFAULT_MAX_RETRIES = 5;
|
|
2709
|
-
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
2710
|
-
const DEFAULT_MAX_DELAY_MS = 1e4;
|
|
2711
|
-
const DEFAULT_JITTER_RATIO = .1;
|
|
2712
|
-
const DEFAULT_RETRYABLE_CODES = Object.freeze([
|
|
2713
|
-
EMPTY_RESPONSE_CODE,
|
|
2714
|
-
"RATE_LIMIT",
|
|
2715
|
-
"SERVER",
|
|
2716
|
-
"TIMEOUT",
|
|
2717
|
-
"TRANSPORT"
|
|
2718
|
-
]);
|
|
2719
|
-
const backoffSchema = Schema.object({
|
|
2720
|
-
initialDelayMs: Schema.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),
|
|
2721
|
-
maxDelayMs: Schema.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),
|
|
2722
|
-
jitterRatio: Schema.number().min(0).max(1).default(DEFAULT_JITTER_RATIO)
|
|
2723
|
-
});
|
|
2724
|
-
const normalPolicySchema = Schema.object({
|
|
2725
|
-
mode: Schema.const("normal").required(),
|
|
2726
|
-
maxRetries: Schema.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),
|
|
2727
|
-
retryableCodes: Schema.array(Schema.string()).default([...DEFAULT_RETRYABLE_CODES]),
|
|
2728
|
-
backoff: backoffSchema
|
|
2729
|
-
});
|
|
2730
|
-
const alwaysPolicySchema = Schema.object({
|
|
2731
|
-
mode: Schema.const("always").required(),
|
|
2732
|
-
backoff: backoffSchema
|
|
2733
|
-
});
|
|
2734
|
-
Schema.union([normalPolicySchema, alwaysPolicySchema]);
|
|
2735
|
-
//#endregion
|
|
2736
|
-
//#region ../llm/src/api-key.ts
|
|
2737
|
-
/**
|
|
2738
|
-
* The one definition of a well-formed provider API key, shared by every
|
|
2739
|
-
* adapter that puts one in an HTTP header.
|
|
2740
|
-
* @module @deepseek-ai/dsh-llm/api-key
|
|
2741
|
-
*/
|
|
2742
|
-
/**
|
|
2743
|
-
* Characters an HTTP header value carries verbatim and every known provider
|
|
2744
|
-
* key uses: printable ASCII, space excluded. A key outside this set cannot
|
|
2745
|
-
* reach any provider — `fetch` refuses to build the header — so this is a
|
|
2746
|
-
* transport invariant rather than one provider's policy. Latin-1 is excluded
|
|
2747
|
-
* deliberately: a header could carry it, but no provider issues it, and
|
|
2748
|
-
* admitting it trades a local explained refusal for an opaque 401.
|
|
2749
|
-
*/
|
|
2750
|
-
const LEGAL_API_KEY = /^[\x21-\x7E]+$/;
|
|
2751
|
-
/**
|
|
2752
|
-
* Judge one *supplied* API key, trimming surrounding whitespace first.
|
|
2753
|
-
*
|
|
2754
|
-
* Trimming is silent because a padded key has one unambiguous reading; every
|
|
2755
|
-
* other defect is reported. Absence is a configuration state this function
|
|
2756
|
-
* never sees — a profile naming no credential authenticates through the
|
|
2757
|
-
* provider's own ambient discovery or OAuth — so callers decide whether a
|
|
2758
|
-
* value was supplied before asking.
|
|
2759
|
-
* @param raw - the key exactly as configured, stored, or typed.
|
|
2760
|
-
* @returns the trimmed key, or why it cannot be used.
|
|
2761
|
-
*/
|
|
2762
|
-
function normalizeApiKey(raw) {
|
|
2763
|
-
const value = raw.trim();
|
|
2764
|
-
if (value.length === 0) return {
|
|
2765
|
-
ok: false,
|
|
2766
|
-
reason: "empty"
|
|
2767
|
-
};
|
|
2768
|
-
if (!LEGAL_API_KEY.test(value)) return {
|
|
2769
|
-
ok: false,
|
|
2770
|
-
reason: "illegalCharacters"
|
|
2771
|
-
};
|
|
2772
|
-
return {
|
|
2773
|
-
ok: true,
|
|
2774
|
-
value
|
|
2775
|
-
};
|
|
2776
|
-
}
|
|
2777
|
-
//#endregion
|
|
2778
|
-
//#region ../llm/src/attribution.ts
|
|
2779
|
-
/**
|
|
2780
|
-
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
2781
|
-
* adapters from drifting. See
|
|
2782
|
-
* `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
|
|
2783
|
-
*
|
|
2784
|
-
* App-attribution vocabulary for provider requests.
|
|
2785
|
-
* @module @deepseek-ai/dsh-llm/attribution
|
|
2786
|
-
*/
|
|
2787
|
-
const { version } = createRequire(import.meta.url)("../package.json");
|
|
2788
|
-
//#endregion
|
|
2789
|
-
//#region ../llm/src/index.ts
|
|
2790
|
-
/**
|
|
2791
|
-
* Typed error for LLM-related failures. Extends {@link HarnessError}, so the
|
|
2792
|
-
* `code` string (e.g. `AUTH`, `RATE_LIMIT`, `NO_ADAPTER`) is shared taxonomy.
|
|
2793
|
-
*/
|
|
2794
|
-
var LlmError = class extends HarnessError {
|
|
2795
|
-
/** Serializable facts retained beside this live Error. */
|
|
2796
|
-
failure;
|
|
2797
|
-
/**
|
|
2798
|
-
* @param message - non-empty human-readable failure summary.
|
|
2799
|
-
* @param code - non-empty stable provider-neutral machine code.
|
|
2800
|
-
* @param options - optional cause and validated serializable provider facts.
|
|
2801
|
-
*/
|
|
2802
|
-
constructor(message, code, options) {
|
|
2803
|
-
if (typeof message !== "string" || message.length === 0) throw new Error("LlmError message must be a non-empty string");
|
|
2804
|
-
if (typeof code !== "string" || code.length === 0) throw new Error("LlmError code must be a non-empty string");
|
|
2805
|
-
if (options?.status !== void 0 && (!Number.isInteger(options.status) || options.status < 100 || options.status > 599)) throw new Error("LlmError status must be an integer from 100 through 599");
|
|
2806
|
-
if (options?.providerRetryAfterMs !== void 0 && (!Number.isFinite(options.providerRetryAfterMs) || options.providerRetryAfterMs <= 0)) throw new Error("LlmError providerRetryAfterMs must be a positive finite number");
|
|
2807
|
-
if (options?.requestId !== void 0 && (typeof options.requestId !== "string" || options.requestId.length === 0)) throw new Error("LlmError requestId must be a non-empty string");
|
|
2808
|
-
super(message, code, options);
|
|
2809
|
-
this.name = "LlmError";
|
|
2810
|
-
this.failure = Object.freeze({
|
|
2811
|
-
message,
|
|
2812
|
-
code,
|
|
2813
|
-
...options?.status === void 0 ? {} : { status: options.status },
|
|
2814
|
-
...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
|
|
2815
|
-
...options?.requestId === void 0 ? {} : { requestId: options.requestId }
|
|
2816
|
-
});
|
|
2817
|
-
}
|
|
2818
|
-
};
|
|
2819
|
-
/**
|
|
2820
|
-
* Accept one supplied credential, or refuse it as unusable.
|
|
2821
|
-
*
|
|
2822
|
-
* A stored key arrives from the credentials seam, a `.env` line, or a shell
|
|
2823
|
-
* export, all of which pick up surrounding whitespace, so trimming is silent.
|
|
2824
|
-
* Anything else fails here rather than inside `fetch`, whose ByteString
|
|
2825
|
-
* refusal names a UTF-16 code point instead of the setting to change. The key
|
|
2826
|
-
* never enters the message: `ref` names where to fix it, and echoing any part
|
|
2827
|
-
* of a secret into a log or a UI is the failure this diagnosis avoids.
|
|
2828
|
-
*
|
|
2829
|
-
* Lives beside {@link LlmError} rather than in `./api-key.ts` so the predicate
|
|
2830
|
-
* module stays dependency-free; both adapters share this one diagnosis instead
|
|
2831
|
-
* of keeping near-identical local copies.
|
|
2832
|
-
* @param raw - the credential exactly as supplied.
|
|
2833
|
-
* @param pkg - the refusing package name, prefixed to the diagnostic.
|
|
2834
|
-
* @param ref - the credential reference the value resolved through.
|
|
2835
|
-
* @returns the trimmed, usable key.
|
|
2836
|
-
*/
|
|
2837
|
-
function assertUsableApiKey(raw, pkg, ref) {
|
|
2838
|
-
const checked = normalizeApiKey(raw);
|
|
2839
|
-
if (checked.ok) return checked.value;
|
|
2840
|
-
throw new LlmError(checked.reason === "empty" ? `${pkg}: the API key resolved from ${ref} is blank; set ${ref} to the raw key (the web Models page writes it) or export it in the launching environment` : `${pkg}: the API key resolved from ${ref} contains characters no HTTP header can carry; set ${ref} to the raw key alone (the web Models page writes it)`, INVALID_CREDENTIAL_CODE);
|
|
2841
|
-
}
|
|
2842
|
-
//#endregion
|
|
2843
|
-
//#region ../../credentials/credentials/src/index.ts
|
|
2844
|
-
const REF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2845
|
-
/**
|
|
2846
|
-
* Brand a raw string as a {@link CredentialRef}.
|
|
2847
|
-
* @param value - candidate reference; a POSIX shell identifier such as `DEEPSEEK_API_KEY`.
|
|
2848
|
-
* @returns the branded reference.
|
|
2849
|
-
*/
|
|
2850
|
-
function credentialRef(value) {
|
|
2851
|
-
if (!REF_PATTERN.test(value)) throw new TypeError(`credential ref "${value}" must match ${String(REF_PATTERN)}`);
|
|
2852
|
-
return value;
|
|
2853
|
-
}
|
|
2854
|
-
//#endregion
|
|
2855
|
-
//#region ../../util/launch-environment/src/index.ts
|
|
2856
|
-
/** Layer order, most trusted first. */
|
|
2857
|
-
const SOURCE_ORDER = [
|
|
2858
|
-
"process",
|
|
2859
|
-
"project-env",
|
|
2860
|
-
"user-env"
|
|
2861
|
-
];
|
|
2862
|
-
/**
|
|
2863
|
-
* The map key one variable name resolves under. Windows treats environment
|
|
2864
|
-
* names case-insensitively; every other platform does not.
|
|
2865
|
-
* @param name - the variable name as written.
|
|
2866
|
-
* @returns the key to store and look up by.
|
|
2867
|
-
*/
|
|
2868
|
-
function lookupKey(name) {
|
|
2869
|
-
/* v8 ignore next -- native Windows coverage exercises the folding arm; POSIX covers the exact one */
|
|
2870
|
-
return process.platform === "win32" ? name.toUpperCase() : name;
|
|
2871
|
-
}
|
|
2872
|
-
/**
|
|
2873
|
-
* Build the snapshot from each layer's contents.
|
|
2874
|
-
* @param layers - the layers in any order; the result searches them by canonical trust order.
|
|
2875
|
-
* @returns the immutable snapshot.
|
|
2876
|
-
*/
|
|
2877
|
-
function createLaunchEnvironmentSnapshot(layers) {
|
|
2878
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
2879
|
-
for (const layer of layers) bySource.set(layer.source, {
|
|
2880
|
-
...layer.path === void 0 ? {} : { path: layer.path },
|
|
2881
|
-
values: new Map(Object.entries(layer.values).map(([name, value]) => [lookupKey(name), value]))
|
|
2882
|
-
});
|
|
2883
|
-
const getFrom = (name, sources) => {
|
|
2884
|
-
const key = lookupKey(name);
|
|
2885
|
-
for (const source of SOURCE_ORDER) {
|
|
2886
|
-
if (!sources.includes(source)) continue;
|
|
2887
|
-
const layer = bySource.get(source);
|
|
2888
|
-
const value = layer?.values.get(key);
|
|
2889
|
-
if (value === void 0) continue;
|
|
2890
|
-
return {
|
|
2891
|
-
value,
|
|
2892
|
-
source,
|
|
2893
|
-
...layer?.path === void 0 ? {} : { path: layer.path }
|
|
2894
|
-
};
|
|
2895
|
-
}
|
|
2896
|
-
};
|
|
2897
|
-
return {
|
|
2898
|
-
get: (name) => getFrom(name, SOURCE_ORDER),
|
|
2899
|
-
getFrom
|
|
2900
|
-
};
|
|
2901
|
-
}
|
|
2902
|
-
/**
|
|
2903
|
-
* Return the launcher's snapshot, or the inherited environment as the sole
|
|
2904
|
-
* layer when the host provided none.
|
|
2905
|
-
* @param ctx - the consuming plugin's context.
|
|
2906
|
-
* @returns the snapshot to resolve user-facing values against.
|
|
2907
|
-
*/
|
|
2908
|
-
function launchEnvironmentOf(ctx) {
|
|
2909
|
-
return ctx.get("launchEnvironment") ?? createLaunchEnvironmentSnapshot([{
|
|
2910
|
-
source: "process",
|
|
2911
|
-
values: process.env
|
|
2912
|
-
}]);
|
|
2913
|
-
}
|
|
2914
|
-
//#endregion
|
|
2915
|
-
//#region ../../typert/protocol/src/index.ts
|
|
2916
|
-
/**
|
|
2917
|
-
* Remote decorators and explicit Gateway bindings backed only by private
|
|
2918
|
-
* module state. Strict reflection remains a Typert compiler responsibility.
|
|
2919
|
-
* @module @deepseek-ai/dsh-typert-protocol
|
|
2920
|
-
*/
|
|
2921
|
-
const TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
|
|
2922
|
-
/**
|
|
2923
|
-
* Test one generated Remote name against the Connection endpoint grammar.
|
|
2924
|
-
* @param value - namespace, method, lookup, or Context segment.
|
|
2925
|
-
* @returns whether the value can cross the shared RPC carrier unchanged.
|
|
2926
|
-
*/
|
|
2927
|
-
function isTypertRemoteSegment(value) {
|
|
2928
|
-
return value !== "." && value !== ".." && TYPERT_REMOTE_SEGMENT_PATTERN.test(value);
|
|
2929
|
-
}
|
|
2930
|
-
const markers = /* @__PURE__ */ new WeakMap();
|
|
2931
|
-
/**
|
|
2932
|
-
* Bind one visible Service field to a Cordis key and Remote namespace.
|
|
2933
|
-
* @param service - owning Service instance, normally `this`.
|
|
2934
|
-
* @param serviceKey - exact Cordis service key.
|
|
2935
|
-
* @param options - optional distinct wire namespace.
|
|
2936
|
-
* @returns a frozen, inspectable binding with no compiler-injected metadata.
|
|
2937
|
-
*/
|
|
2938
|
-
function bindTypertRemote(service, serviceKey, options = {}) {
|
|
2939
|
-
validateName("service key", serviceKey);
|
|
2940
|
-
const namespace = options.namespace ?? serviceKey;
|
|
2941
|
-
validateName("namespace", namespace);
|
|
2942
|
-
return Object.freeze({
|
|
2943
|
-
service,
|
|
2944
|
-
serviceKey,
|
|
2945
|
-
namespace
|
|
2946
|
-
});
|
|
2947
|
-
}
|
|
2948
|
-
/** Cordis Service base that exposes its registered name through Typert Gateway. */
|
|
2949
|
-
var TypertRemoteService = class extends Service {
|
|
2950
|
-
/** Visible binding consumed by the Gateway's source-mode discovery. */
|
|
2951
|
-
typertRemote;
|
|
2952
|
-
/**
|
|
2953
|
-
* Register the Service and bind the same key to Typert Gateway.
|
|
2954
|
-
* @param ctx - owning Cordis Context.
|
|
2955
|
-
* @param serviceKey - exact Cordis service key and default wire namespace.
|
|
2956
|
-
* @param options - optional distinct wire namespace.
|
|
2957
|
-
*/
|
|
2958
|
-
constructor(ctx, serviceKey, options = {}) {
|
|
2959
|
-
super(ctx, serviceKey);
|
|
2960
|
-
this.typertRemote = bindTypertRemote(this, this.name, options);
|
|
2961
|
-
}
|
|
2962
|
-
};
|
|
2963
|
-
function Remote(methodOrExportName, context) {
|
|
2964
|
-
if (typeof methodOrExportName === "string") {
|
|
2965
|
-
validateName("Remote export name", methodOrExportName);
|
|
2966
|
-
return function(_method, decoratorContext) {
|
|
2967
|
-
addMarkerInitializer(decoratorContext, { kind: "direct" }, methodOrExportName);
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
if (context === void 0) throw new TypeError("typert-protocol: Remote decorator context is missing");
|
|
2971
|
-
addMarkerInitializer(context, { kind: "direct" });
|
|
2972
|
-
}
|
|
2973
|
-
function addMarkerInitializer(context, invocation, exportName) {
|
|
2974
|
-
if (context.private || context.static || typeof context.name !== "string") throw new TypeError("typert-protocol: Remote decorators require a public instance method with a string name");
|
|
2975
|
-
const method = context.name;
|
|
2976
|
-
context.addInitializer(function() {
|
|
2977
|
-
const prototype = Object.getPrototypeOf(this);
|
|
2978
|
-
if (prototype === null) throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
|
|
2979
|
-
mark(prototype, method, invocation, exportName);
|
|
2980
|
-
});
|
|
2981
|
-
}
|
|
2982
|
-
function mark(prototype, method, invocation, exportName) {
|
|
2983
|
-
let table = markers.get(prototype);
|
|
2984
|
-
if (table === void 0) {
|
|
2985
|
-
table = /* @__PURE__ */ new Map();
|
|
2986
|
-
markers.set(prototype, table);
|
|
2987
|
-
}
|
|
2988
|
-
const marker = {
|
|
2989
|
-
...exportName === void 0 || exportName === method ? {} : { exportName },
|
|
2990
|
-
invocation: Object.freeze(invocation)
|
|
2991
|
-
};
|
|
2992
|
-
const current = table.get(method);
|
|
2993
|
-
if (current !== void 0) {
|
|
2994
|
-
if (current.exportName === marker.exportName && sameInvocation(current.invocation, invocation)) return;
|
|
2995
|
-
throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
|
|
2996
|
-
}
|
|
2997
|
-
table.set(method, Object.freeze(marker));
|
|
2998
|
-
}
|
|
2999
|
-
function sameInvocation(left, right) {
|
|
3000
|
-
return left.kind === right.kind && (left.kind === "direct" || right.kind === "context" && left.context === right.context);
|
|
3001
|
-
}
|
|
3002
|
-
function validateName(subject, value) {
|
|
3003
|
-
if (!isTypertRemoteSegment(value)) throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
|
|
3004
|
-
}
|
|
3005
|
-
//#endregion
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { LlmError, assertUsableApiKey } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
4
|
+
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
5
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
|
+
import { z as z$1 } from "zod";
|
|
3006
7
|
//#region lib/types/balance.js
|
|
3007
8
|
/**
|
|
3008
9
|
* DeepSeek account-balance capability: the `GET /user/balance` transport and
|
|
@@ -3010,7 +11,7 @@ function validateName(subject, value) {
|
|
|
3010
11
|
* takes an already-resolved endpoint and bearer token so the registering
|
|
3011
12
|
* plugin stays the one owner of credential policy; the gateway carries only a
|
|
3012
13
|
* `fetchBalance` thunk for the same reason.
|
|
3013
|
-
* @module @
|
|
14
|
+
* @module @rayadesu/dsh-llm-billing/balance
|
|
3014
15
|
*/
|
|
3015
16
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
3016
17
|
var useValue = arguments.length > 2;
|
|
@@ -3044,8 +45,10 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
3044
45
|
if (_ = accept(result.get)) descriptor.get = _;
|
|
3045
46
|
if (_ = accept(result.set)) descriptor.set = _;
|
|
3046
47
|
if (_ = accept(result.init)) initializers.unshift(_);
|
|
3047
|
-
} else if (_ = accept(result))
|
|
3048
|
-
|
|
48
|
+
} else if (_ = accept(result)) {
|
|
49
|
+
if (kind === "field") initializers.unshift(_);
|
|
50
|
+
else descriptor[key] = _;
|
|
51
|
+
}
|
|
3049
52
|
}
|
|
3050
53
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
3051
54
|
done = true;
|
|
@@ -3196,7 +199,8 @@ let DeepSeekBalanceGateway = (() => {
|
|
|
3196
199
|
return this.options.fetchBalance();
|
|
3197
200
|
}
|
|
3198
201
|
/**
|
|
3199
|
-
* Read one session's billed spend, priced per event by its
|
|
202
|
+
* Read one session's billed spend, priced per event by its Beijing-time
|
|
203
|
+
* peak/off-peak hour and weekday (weekends are always off-peak).
|
|
3200
204
|
* @param sessionId - the session whose spend to compute.
|
|
3201
205
|
* @returns the session's total cost plus one row per priced model.
|
|
3202
206
|
*/
|
|
@@ -3205,11 +209,14 @@ let DeepSeekBalanceGateway = (() => {
|
|
|
3205
209
|
}
|
|
3206
210
|
/**
|
|
3207
211
|
* Read today's billed spend across every session, priced per event by its
|
|
3208
|
-
* Beijing-time calendar day and
|
|
212
|
+
* Beijing-time calendar day, hour, and weekday (weekends are always off-peak).
|
|
213
|
+
* @param force - bypass the host-side 60s cache (manual refresh); omitted
|
|
214
|
+
* means a cached read. Remote parameters cannot carry default values, so
|
|
215
|
+
* the thunk receives `undefined` for an omitted argument.
|
|
3209
216
|
* @returns today's total cost plus one row per priced model.
|
|
3210
217
|
*/
|
|
3211
|
-
getTodaySpend() {
|
|
3212
|
-
return this.options.fetchTodaySpend();
|
|
218
|
+
getTodaySpend(force) {
|
|
219
|
+
return this.options.fetchTodaySpend(force ?? false);
|
|
3213
220
|
}
|
|
3214
221
|
};
|
|
3215
222
|
})();
|
|
@@ -3220,9 +227,19 @@ let DeepSeekBalanceGateway = (() => {
|
|
|
3220
227
|
* pricing. Pure functions over session events and the pricing table, so the
|
|
3221
228
|
* Remote gateway stays transport-free and the whole spend is testable without
|
|
3222
229
|
* a key.
|
|
3223
|
-
*
|
|
230
|
+
*
|
|
231
|
+
* The per-event pricing lives in {@link priceEvent}, the one shared fold
|
|
232
|
+
* primitive: the events-scan paths ({@link computeSessionSpend},
|
|
233
|
+
* {@link computeTodaySpend}) and the session-projection unit
|
|
234
|
+
* (`billingTodaySpend` in projection.ts) all fold the same contribution, so a
|
|
235
|
+
* pricing-table change cannot drift one path from the others.
|
|
236
|
+
* @module @rayadesu/dsh-llm-billing/billing
|
|
237
|
+
*/
|
|
238
|
+
/**
|
|
239
|
+
* Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00,
|
|
240
|
+
* applied on weekdays (Monday–Friday) only — weekends are always off-peak
|
|
241
|
+
* (effective 2026-08-23).
|
|
3224
242
|
*/
|
|
3225
|
-
/** Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00. */
|
|
3226
243
|
const DEFAULT_PEAK_HOURS = [{
|
|
3227
244
|
start: 9,
|
|
3228
245
|
end: 12
|
|
@@ -3231,31 +248,47 @@ const DEFAULT_PEAK_HOURS = [{
|
|
|
3231
248
|
end: 18
|
|
3232
249
|
}];
|
|
3233
250
|
/** Official peak/off-peak rates (CNY per 1M tokens), effective 2026-08-17. */
|
|
3234
|
-
const DEFAULT_MODEL_PRICING = [
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
251
|
+
const DEFAULT_MODEL_PRICING = [
|
|
252
|
+
{
|
|
253
|
+
model: "deepseek-v4-flash",
|
|
254
|
+
peak: {
|
|
255
|
+
cacheHitInput: .1,
|
|
256
|
+
cacheMissInput: 3,
|
|
257
|
+
output: 9
|
|
258
|
+
},
|
|
259
|
+
offPeak: {
|
|
260
|
+
cacheHitInput: .05,
|
|
261
|
+
cacheMissInput: 1.5,
|
|
262
|
+
output: 4.5
|
|
263
|
+
}
|
|
3240
264
|
},
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
265
|
+
{
|
|
266
|
+
model: "deepseek-v4-pro",
|
|
267
|
+
peak: {
|
|
268
|
+
cacheHitInput: .3,
|
|
269
|
+
cacheMissInput: 9,
|
|
270
|
+
output: 27
|
|
271
|
+
},
|
|
272
|
+
offPeak: {
|
|
273
|
+
cacheHitInput: .15,
|
|
274
|
+
cacheMissInput: 4.5,
|
|
275
|
+
output: 13.5
|
|
276
|
+
}
|
|
3252
277
|
},
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
278
|
+
{
|
|
279
|
+
model: "deepseek-v4-flash-vision-exp",
|
|
280
|
+
peak: {
|
|
281
|
+
cacheHitInput: .1,
|
|
282
|
+
cacheMissInput: 3,
|
|
283
|
+
output: 9
|
|
284
|
+
},
|
|
285
|
+
offPeak: {
|
|
286
|
+
cacheHitInput: .05,
|
|
287
|
+
cacheMissInput: 1.5,
|
|
288
|
+
output: 4.5
|
|
289
|
+
}
|
|
3257
290
|
}
|
|
3258
|
-
|
|
291
|
+
];
|
|
3259
292
|
/**
|
|
3260
293
|
* Resolve optional configuration to a pricing table, defaulting omitted or
|
|
3261
294
|
* empty rows to the published rates. Schemastery materializes an absent
|
|
@@ -3280,25 +313,156 @@ function resolveBilling(config) {
|
|
|
3280
313
|
}
|
|
3281
314
|
/** The Beijing (Asia/Shanghai, UTC+8, no DST) hour of a timestamp. */
|
|
3282
315
|
function beijingHour(now) {
|
|
3283
|
-
return new Date(now.getTime() +
|
|
316
|
+
return new Date(now.getTime() + 288e5).getUTCHours();
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The Beijing (Asia/Shanghai, UTC+8, no DST) weekday of a timestamp, as
|
|
320
|
+
* `getUTCDay()`: `0` is Sunday, `6` is Saturday.
|
|
321
|
+
*/
|
|
322
|
+
function beijingWeekday(now) {
|
|
323
|
+
return new Date(now.getTime() + 288e5).getUTCDay();
|
|
3284
324
|
}
|
|
3285
325
|
/** The Beijing (Asia/Shanghai, UTC+8, no DST) calendar-day key of a timestamp. */
|
|
3286
326
|
function beijingDayKey(now) {
|
|
3287
|
-
return new Date(now.getTime() +
|
|
327
|
+
return new Date(now.getTime() + 288e5).toISOString().slice(0, 10);
|
|
3288
328
|
}
|
|
3289
329
|
/**
|
|
3290
|
-
* Whether a timestamp falls inside any peak-hour window (Beijing time
|
|
330
|
+
* Whether a timestamp falls inside any peak-hour window (Beijing time,
|
|
331
|
+
* weekdays Monday–Friday only). Weekends (Saturday and Sunday) are always
|
|
332
|
+
* off-peak, matching the published peak-hours rule.
|
|
3291
333
|
* @param billing - resolved pricing with peak-hour windows.
|
|
3292
334
|
* @param now - the moment to classify.
|
|
3293
|
-
* @returns true during peak
|
|
335
|
+
* @returns true during a weekday peak hour.
|
|
3294
336
|
*/
|
|
3295
337
|
function isPeak(billing, now) {
|
|
338
|
+
const weekday = beijingWeekday(now);
|
|
339
|
+
if (weekday === 0 || weekday === 6) return false;
|
|
3296
340
|
const hour = beijingHour(now);
|
|
3297
341
|
return billing.peakHours.some(({ start, end }) => hour >= start && hour < end);
|
|
3298
342
|
}
|
|
3299
343
|
/**
|
|
344
|
+
* Price one event at the official per-model rates, applying the peak/off-peak
|
|
345
|
+
* table by its Beijing-time hour and weekday (peak windows apply Monday–Friday
|
|
346
|
+
* only; weekends are off-peak). Each `assistant/message` event with usage
|
|
347
|
+
* contributes cache-hit input, cache-miss input (uncached input plus cache
|
|
348
|
+
* writes), and output (reasoning included) tokens at the rate of its own
|
|
349
|
+
* timestamp; a model with usage but no pricing row contributes nothing (the
|
|
350
|
+
* published table prices only the two V4 rows).
|
|
351
|
+
* @param event - the event to price.
|
|
352
|
+
* @param billing - resolved pricing with peak-hour windows.
|
|
353
|
+
* @param names - model id → display label.
|
|
354
|
+
* @returns the priced contribution, or `undefined` when the event has no priced usage.
|
|
355
|
+
*/
|
|
356
|
+
function priceEvent(event, billing, names) {
|
|
357
|
+
if (event.type !== "assistant/message") return void 0;
|
|
358
|
+
const reported = event.data.usage;
|
|
359
|
+
if (reported === void 0) return void 0;
|
|
360
|
+
const model = event.data.message.source.model;
|
|
361
|
+
const pricing = billing.models.get(model);
|
|
362
|
+
if (pricing === void 0) return void 0;
|
|
363
|
+
const time = new Date(event.time);
|
|
364
|
+
const peak = isPeak(billing, time);
|
|
365
|
+
const price = peak ? pricing.peak : pricing.offPeak;
|
|
366
|
+
const hit = reported.cacheReadTokens ?? 0;
|
|
367
|
+
const miss = reported.inputTokens + (reported.cacheWriteTokens ?? 0);
|
|
368
|
+
const output = reported.outputTokens;
|
|
369
|
+
const hitCost = hit * price.cacheHitInput / 1e6;
|
|
370
|
+
const missCost = miss * price.cacheMissInput / 1e6;
|
|
371
|
+
const outputCost = output * price.output / 1e6;
|
|
372
|
+
const cost = hitCost + missCost + outputCost;
|
|
373
|
+
return {
|
|
374
|
+
dayKey: beijingDayKey(time),
|
|
375
|
+
model,
|
|
376
|
+
displayName: names.get(model) ?? model,
|
|
377
|
+
cost,
|
|
378
|
+
peakCost: peak ? cost : 0,
|
|
379
|
+
offPeakCost: peak ? 0 : cost,
|
|
380
|
+
cacheHitInputTokens: hit,
|
|
381
|
+
cacheMissInputTokens: miss,
|
|
382
|
+
outputTokens: output,
|
|
383
|
+
cacheHitInputCost: hitCost,
|
|
384
|
+
cacheMissInputCost: missCost,
|
|
385
|
+
outputCost
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
/** A spend with no priced usage. */
|
|
389
|
+
function emptyTodaySpend() {
|
|
390
|
+
return {
|
|
391
|
+
total: 0,
|
|
392
|
+
models: []
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** The today-spend shape of a single priced contribution. */
|
|
396
|
+
function contributionModel(priced) {
|
|
397
|
+
return {
|
|
398
|
+
model: priced.model,
|
|
399
|
+
displayName: priced.displayName,
|
|
400
|
+
cost: priced.cost,
|
|
401
|
+
peakCost: priced.peakCost,
|
|
402
|
+
offPeakCost: priced.offPeakCost,
|
|
403
|
+
cacheHitInputTokens: priced.cacheHitInputTokens,
|
|
404
|
+
cacheMissInputTokens: priced.cacheMissInputTokens,
|
|
405
|
+
outputTokens: priced.outputTokens,
|
|
406
|
+
cacheHitInputCost: priced.cacheHitInputCost,
|
|
407
|
+
cacheMissInputCost: priced.cacheMissInputCost,
|
|
408
|
+
outputCost: priced.outputCost
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Merge one priced event's contribution into an accumulator spend (pure:
|
|
413
|
+
* returns a new spend, never mutates its input).
|
|
414
|
+
* @param spend - the accumulator (per session and day, or across sessions).
|
|
415
|
+
* @param priced - the priced contribution to add.
|
|
416
|
+
* @returns the merged spend.
|
|
417
|
+
*/
|
|
418
|
+
function addEventContribution(spend, priced) {
|
|
419
|
+
const rows = spend.models.map((row) => row.model === priced.model ? {
|
|
420
|
+
...row,
|
|
421
|
+
cost: row.cost + priced.cost,
|
|
422
|
+
peakCost: row.peakCost + priced.peakCost,
|
|
423
|
+
offPeakCost: row.offPeakCost + priced.offPeakCost,
|
|
424
|
+
cacheHitInputTokens: row.cacheHitInputTokens + priced.cacheHitInputTokens,
|
|
425
|
+
cacheMissInputTokens: row.cacheMissInputTokens + priced.cacheMissInputTokens,
|
|
426
|
+
outputTokens: row.outputTokens + priced.outputTokens,
|
|
427
|
+
cacheHitInputCost: row.cacheHitInputCost + priced.cacheHitInputCost,
|
|
428
|
+
cacheMissInputCost: row.cacheMissInputCost + priced.cacheMissInputCost,
|
|
429
|
+
outputCost: row.outputCost + priced.outputCost
|
|
430
|
+
} : row);
|
|
431
|
+
if (!rows.some((row) => row.model === priced.model)) rows.push(contributionModel(priced));
|
|
432
|
+
return {
|
|
433
|
+
total: spend.total + priced.cost,
|
|
434
|
+
models: rows
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Sum two spends (per session and day, or across sessions) into one (pure:
|
|
439
|
+
* returns a new spend, never mutates its inputs).
|
|
440
|
+
* @param target - the accumulator spend.
|
|
441
|
+
* @param source - the spend to add.
|
|
442
|
+
* @returns the summed spend.
|
|
443
|
+
*/
|
|
444
|
+
function mergeTodaySpend(target, source) {
|
|
445
|
+
let merged = target;
|
|
446
|
+
for (const row of source.models) merged = addEventContribution(merged, {
|
|
447
|
+
dayKey: "",
|
|
448
|
+
model: row.model,
|
|
449
|
+
displayName: row.displayName,
|
|
450
|
+
cost: row.cost,
|
|
451
|
+
peakCost: row.peakCost,
|
|
452
|
+
offPeakCost: row.offPeakCost,
|
|
453
|
+
cacheHitInputTokens: row.cacheHitInputTokens,
|
|
454
|
+
cacheMissInputTokens: row.cacheMissInputTokens,
|
|
455
|
+
outputTokens: row.outputTokens,
|
|
456
|
+
cacheHitInputCost: row.cacheHitInputCost,
|
|
457
|
+
cacheMissInputCost: row.cacheMissInputCost,
|
|
458
|
+
outputCost: row.outputCost
|
|
459
|
+
});
|
|
460
|
+
return merged;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
3300
463
|
* Price a set of billed events at the official per-model rates, applying the
|
|
3301
|
-
* peak/off-peak table per event by its Beijing-time hour
|
|
464
|
+
* peak/off-peak table per event by its Beijing-time hour and weekday (peak
|
|
465
|
+
* windows apply Monday–Friday only; weekends are off-peak). Each
|
|
3302
466
|
* `assistant/message` event with usage contributes cache-hit input, cache-miss
|
|
3303
467
|
* input (uncached input plus cache writes), and output (reasoning included)
|
|
3304
468
|
* tokens at the rate of its own timestamp, with the three component costs
|
|
@@ -3311,65 +475,16 @@ function isPeak(billing, now) {
|
|
|
3311
475
|
*/
|
|
3312
476
|
function priceEvents(events, billing, catalog) {
|
|
3313
477
|
const names = new Map(catalog.map((model) => [model.id, model.name]));
|
|
3314
|
-
|
|
478
|
+
let spend = {
|
|
479
|
+
total: 0,
|
|
480
|
+
models: []
|
|
481
|
+
};
|
|
3315
482
|
for (const event of events) {
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
const model = event.data.message.source.model;
|
|
3320
|
-
const pricing = billing.models.get(model);
|
|
3321
|
-
if (pricing === void 0) continue;
|
|
3322
|
-
const peak = isPeak(billing, new Date(event.time));
|
|
3323
|
-
const price = peak ? pricing.peak : pricing.offPeak;
|
|
3324
|
-
const hit = reported.cacheReadTokens ?? 0;
|
|
3325
|
-
const miss = reported.inputTokens + (reported.cacheWriteTokens ?? 0);
|
|
3326
|
-
const output = reported.outputTokens;
|
|
3327
|
-
const hitCost = hit * price.cacheHitInput / 1e6;
|
|
3328
|
-
const missCost = miss * price.cacheMissInput / 1e6;
|
|
3329
|
-
const outputCost = output * price.output / 1e6;
|
|
3330
|
-
const cost = hitCost + missCost + outputCost;
|
|
3331
|
-
let row = rows.get(model);
|
|
3332
|
-
if (row === void 0) {
|
|
3333
|
-
row = {
|
|
3334
|
-
cacheHitInputTokens: 0,
|
|
3335
|
-
cacheMissInputTokens: 0,
|
|
3336
|
-
outputTokens: 0,
|
|
3337
|
-
cost: 0,
|
|
3338
|
-
peakCost: 0,
|
|
3339
|
-
offPeakCost: 0,
|
|
3340
|
-
cacheHitInputCost: 0,
|
|
3341
|
-
cacheMissInputCost: 0,
|
|
3342
|
-
outputCost: 0
|
|
3343
|
-
};
|
|
3344
|
-
rows.set(model, row);
|
|
3345
|
-
}
|
|
3346
|
-
row.cacheHitInputTokens += hit;
|
|
3347
|
-
row.cacheMissInputTokens += miss;
|
|
3348
|
-
row.outputTokens += output;
|
|
3349
|
-
row.cost += cost;
|
|
3350
|
-
row.cacheHitInputCost += hitCost;
|
|
3351
|
-
row.cacheMissInputCost += missCost;
|
|
3352
|
-
row.outputCost += outputCost;
|
|
3353
|
-
if (peak) row.peakCost += cost;
|
|
3354
|
-
else row.offPeakCost += cost;
|
|
483
|
+
const priced = priceEvent(event, billing, names);
|
|
484
|
+
if (priced === void 0) continue;
|
|
485
|
+
spend = addEventContribution(spend, priced);
|
|
3355
486
|
}
|
|
3356
|
-
|
|
3357
|
-
model,
|
|
3358
|
-
displayName: names.get(model) ?? model,
|
|
3359
|
-
cost: row.cost,
|
|
3360
|
-
peakCost: row.peakCost,
|
|
3361
|
-
offPeakCost: row.offPeakCost,
|
|
3362
|
-
cacheHitInputTokens: row.cacheHitInputTokens,
|
|
3363
|
-
cacheMissInputTokens: row.cacheMissInputTokens,
|
|
3364
|
-
outputTokens: row.outputTokens,
|
|
3365
|
-
cacheHitInputCost: row.cacheHitInputCost,
|
|
3366
|
-
cacheMissInputCost: row.cacheMissInputCost,
|
|
3367
|
-
outputCost: row.outputCost
|
|
3368
|
-
}));
|
|
3369
|
-
return {
|
|
3370
|
-
total: models.reduce((sum, model) => sum + model.cost, 0),
|
|
3371
|
-
models
|
|
3372
|
-
};
|
|
487
|
+
return spend;
|
|
3373
488
|
}
|
|
3374
489
|
/**
|
|
3375
490
|
* Price one session's complete event log at the official per-model rates.
|
|
@@ -3393,8 +508,310 @@ function computeSessionSpend(events, billing, catalog) {
|
|
|
3393
508
|
*/
|
|
3394
509
|
function computeTodaySpend(events, billing, catalog, now = /* @__PURE__ */ new Date()) {
|
|
3395
510
|
const day = beijingDayKey(now);
|
|
3396
|
-
|
|
511
|
+
const names = new Map(catalog.map((model) => [model.id, model.name]));
|
|
512
|
+
let spend = emptyTodaySpend();
|
|
513
|
+
for (const event of events) {
|
|
514
|
+
const priced = priceEvent(event, billing, names);
|
|
515
|
+
if (priced === void 0 || priced.dayKey !== day) continue;
|
|
516
|
+
spend = addEventContribution(spend, priced);
|
|
517
|
+
}
|
|
518
|
+
return spend;
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region lib/types/projection.js
|
|
522
|
+
/**
|
|
523
|
+
* `billingTodaySpend` session-projection unit: per-session, per-Beijing-day
|
|
524
|
+
* billed spend, folded eagerly by the DSH projection drive over committed
|
|
525
|
+
* session events and checkpointed by the projection cache. The unit keeps only
|
|
526
|
+
* the spend of the session's LATEST priced day (events are append-only and
|
|
527
|
+
* chronological, so a day strictly older than the state's day never returns);
|
|
528
|
+
* the aggregate "today" read sums the units whose `dayKey` matches the current
|
|
529
|
+
* Beijing day — zero full-log scans once the fold is warm.
|
|
530
|
+
*
|
|
531
|
+
* The unit's fold shares {@link priceEvent} with the events-scan paths
|
|
532
|
+
* (`computeTodaySpend`), so both price with the same table. The unit is
|
|
533
|
+
* client-visible (`wire` = identity) because the persisted-cache read ladder
|
|
534
|
+
* (`sessionProjectionCache.coldSnapshot` / registry `restore`) serves only
|
|
535
|
+
* wired units; the wire value is the state itself.
|
|
536
|
+
* @module @rayadesu/dsh-llm-billing/projection
|
|
537
|
+
*/
|
|
538
|
+
/** The projection key this unit owns. */
|
|
539
|
+
const BILLING_UNIT_KEY = "billingTodaySpend";
|
|
540
|
+
const modelRowSchema = z$1.object({
|
|
541
|
+
model: z$1.string(),
|
|
542
|
+
displayName: z$1.string(),
|
|
543
|
+
cost: z$1.number().nonnegative(),
|
|
544
|
+
peakCost: z$1.number().nonnegative(),
|
|
545
|
+
offPeakCost: z$1.number().nonnegative(),
|
|
546
|
+
cacheHitInputTokens: z$1.number().int().nonnegative(),
|
|
547
|
+
cacheMissInputTokens: z$1.number().int().nonnegative(),
|
|
548
|
+
outputTokens: z$1.number().int().nonnegative(),
|
|
549
|
+
cacheHitInputCost: z$1.number().nonnegative(),
|
|
550
|
+
cacheMissInputCost: z$1.number().nonnegative(),
|
|
551
|
+
outputCost: z$1.number().nonnegative()
|
|
552
|
+
}).strict();
|
|
553
|
+
const todaySpendSchema = z$1.object({
|
|
554
|
+
total: z$1.number().nonnegative(),
|
|
555
|
+
models: z$1.array(modelRowSchema)
|
|
556
|
+
}).strict();
|
|
557
|
+
const billingUnitSchema = z$1.object({
|
|
558
|
+
dayKey: z$1.string(),
|
|
559
|
+
spend: todaySpendSchema
|
|
560
|
+
}).strict();
|
|
561
|
+
/**
|
|
562
|
+
* Build the `billingTodaySpend` unit for one resolved pricing table. The
|
|
563
|
+
* pricing closure is fixed at registration; a pricing-table change therefore
|
|
564
|
+
* prices only events folded after the change (historical spend keeps its
|
|
565
|
+
* historical rates), unlike the events-scan paths which re-price the whole
|
|
566
|
+
* log. Bump {@link ProjectionDefinition.stateVersion} whenever the state
|
|
567
|
+
* shape or fold semantics change, so persisted checkpoint rows are discarded
|
|
568
|
+
* instead of folded forward.
|
|
569
|
+
* @param billing - resolved pricing with peak-hour windows.
|
|
570
|
+
* @param catalog - model display rows, in presentation order.
|
|
571
|
+
* @returns the unit definition to register on `ctx.sessionProjections`.
|
|
572
|
+
*/
|
|
573
|
+
function billingTodaySpendDefinition(billing, catalog) {
|
|
574
|
+
const names = new Map(catalog.map((model) => [model.id, model.name]));
|
|
575
|
+
return {
|
|
576
|
+
key: BILLING_UNIT_KEY,
|
|
577
|
+
stateVersion: 1,
|
|
578
|
+
stateSchema: billingUnitSchema,
|
|
579
|
+
init: () => ({
|
|
580
|
+
dayKey: "",
|
|
581
|
+
spend: emptyTodaySpend()
|
|
582
|
+
}),
|
|
583
|
+
apply: (state, event) => {
|
|
584
|
+
const priced = priceEvent(event, billing, names);
|
|
585
|
+
if (priced === void 0) return state;
|
|
586
|
+
if (state.dayKey === priced.dayKey) return {
|
|
587
|
+
dayKey: state.dayKey,
|
|
588
|
+
spend: addEventContribution(state.spend, priced)
|
|
589
|
+
};
|
|
590
|
+
if (state.dayKey !== "" && priced.dayKey < state.dayKey) return state;
|
|
591
|
+
return {
|
|
592
|
+
dayKey: priced.dayKey,
|
|
593
|
+
spend: addEventContribution(emptyTodaySpend(), priced)
|
|
594
|
+
};
|
|
595
|
+
},
|
|
596
|
+
wire: {
|
|
597
|
+
viewSchema: billingUnitSchema,
|
|
598
|
+
view: (state) => state
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
/** Fold a unit from init over one session's event log (the detached cold recipe). */
|
|
603
|
+
function foldBillingUnit(unit, events) {
|
|
604
|
+
let state = unit.init();
|
|
605
|
+
for (const event of events) state = unit.apply(state, event);
|
|
606
|
+
return state;
|
|
607
|
+
}
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region lib/types/today-spend.js
|
|
610
|
+
/**
|
|
611
|
+
* Today-spend read path: the 60-second Beijing-day cache with in-flight
|
|
612
|
+
* coalescing and a force bypass (plan A1), plus the two scan strategies that
|
|
613
|
+
* compute the aggregate behind a cache miss:
|
|
614
|
+
*
|
|
615
|
+
* - projection path (plan C): live sessions read their eagerly folded
|
|
616
|
+
* `billingTodaySpend` projection cell; cold sessions resolve through the
|
|
617
|
+
* projection-cache ladder (cached row + tail replay + registry restore,
|
|
618
|
+
* with write-back) or, without the cache service, one detached local fold
|
|
619
|
+
* over a full `inspect`. Persisted revisions gate every cold read, so a
|
|
620
|
+
* session whose log did not change since the last resolution costs nothing.
|
|
621
|
+
* - events path (plans A2/A3): collect only today's events (per-event
|
|
622
|
+
* Beijing-day filter during collection) with a hard cap, skipping sessions
|
|
623
|
+
* whose persisted revision is unchanged since the last scan.
|
|
624
|
+
*
|
|
625
|
+
* Both strategies run behind the same {@link TodaySpendCache}, so a miss
|
|
626
|
+
* happens at most once per 60 seconds per process, and a manual refresh
|
|
627
|
+
* (`force`) bypasses the time window but keeps the revision caches — an
|
|
628
|
+
* unchanged log provably cannot change the aggregate.
|
|
629
|
+
* @module @rayadesu/dsh-llm-billing/today-spend
|
|
630
|
+
*/
|
|
631
|
+
/** Bounded parallel fan-out: run `run` over `items` with at most `limit` in flight. */
|
|
632
|
+
async function withConcurrency(items, limit, run) {
|
|
633
|
+
const queue = [...items];
|
|
634
|
+
await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, async () => {
|
|
635
|
+
for (let job = queue.shift(); job !== void 0; job = queue.shift()) await run(job);
|
|
636
|
+
}));
|
|
3397
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* The A1 cache: one Beijing-day key + a 60s window, an in-flight promise that
|
|
640
|
+
* coalesces concurrent misses, and a `force` bypass for the manual refresh
|
|
641
|
+
* path. Cross-day invalidation is automatic (the day key changes); a failed
|
|
642
|
+
* scan leaves the previous value in place and retries on the next call.
|
|
643
|
+
*/
|
|
644
|
+
var TodaySpendCache = class {
|
|
645
|
+
scan;
|
|
646
|
+
ttlMs;
|
|
647
|
+
now;
|
|
648
|
+
cachedDayKey;
|
|
649
|
+
cachedValue;
|
|
650
|
+
cachedAt = 0;
|
|
651
|
+
inFlight;
|
|
652
|
+
/**
|
|
653
|
+
* @param ttlMs - time window in milliseconds (default 60 000).
|
|
654
|
+
* @param now - clock source (injectable for tests).
|
|
655
|
+
* @param scan - the aggregate computation behind a miss.
|
|
656
|
+
*/
|
|
657
|
+
constructor(scan, ttlMs = 6e4, now = () => /* @__PURE__ */ new Date()) {
|
|
658
|
+
this.scan = scan;
|
|
659
|
+
this.ttlMs = ttlMs;
|
|
660
|
+
this.now = now;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Read today's spend, cached per Beijing day within the TTL window.
|
|
664
|
+
* @param force - bypass the time window (manual refresh); the day-key gate
|
|
665
|
+
* and the in-flight coalescing still apply to non-force callers.
|
|
666
|
+
* @returns today's spend.
|
|
667
|
+
*/
|
|
668
|
+
get(force = false) {
|
|
669
|
+
const now = this.now();
|
|
670
|
+
const dayKey = beijingDayKey(now);
|
|
671
|
+
if (!force && this.cachedDayKey === dayKey && this.cachedValue !== void 0 && now.getTime() - this.cachedAt < this.ttlMs) return Promise.resolve(this.cachedValue);
|
|
672
|
+
if (!force && this.inFlight !== void 0) return this.inFlight;
|
|
673
|
+
const run = (async () => {
|
|
674
|
+
try {
|
|
675
|
+
const value = await this.scan(dayKey);
|
|
676
|
+
this.cachedDayKey = dayKey;
|
|
677
|
+
this.cachedValue = value;
|
|
678
|
+
this.cachedAt = now.getTime();
|
|
679
|
+
return value;
|
|
680
|
+
} finally {
|
|
681
|
+
this.inFlight = void 0;
|
|
682
|
+
}
|
|
683
|
+
})();
|
|
684
|
+
if (!force) this.inFlight = run;
|
|
685
|
+
return run;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
/**
|
|
689
|
+
* The aggregate computation behind a cache miss. Chooses the projection path
|
|
690
|
+
* when the projection registry is composed, the events path otherwise; both
|
|
691
|
+
* gate cold reads on persisted revisions so steady-state scans touch only
|
|
692
|
+
* sessions whose logs actually changed.
|
|
693
|
+
*/
|
|
694
|
+
var TodaySpendScanner = class {
|
|
695
|
+
deps;
|
|
696
|
+
/** Cold sessions resolved on the projection path: id → revision + unit state. */
|
|
697
|
+
coldResolved = /* @__PURE__ */ new Map();
|
|
698
|
+
/** Cold sessions resolved on the events path: id → revision (events were collected). */
|
|
699
|
+
lastEventsScan;
|
|
700
|
+
constructor(deps) {
|
|
701
|
+
this.deps = deps;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Compute today's aggregate for one Beijing day.
|
|
705
|
+
* @param dayKey - the Beijing-time calendar-day key to aggregate.
|
|
706
|
+
* @returns today's spend across every session.
|
|
707
|
+
*/
|
|
708
|
+
async scan(dayKey) {
|
|
709
|
+
if (this.deps.projections?.() === void 0) return this.scanEvents(dayKey);
|
|
710
|
+
this.deps.ensureUnit?.();
|
|
711
|
+
return this.scanProjections(dayKey);
|
|
712
|
+
}
|
|
713
|
+
/** Projection path: eager cells for live sessions, revision-gated cold ladder for the rest. */
|
|
714
|
+
async scanProjections(dayKey) {
|
|
715
|
+
const { sessions, persistence, projections, projectionCache, unit, logger } = this.deps;
|
|
716
|
+
let total = emptyTodaySpend();
|
|
717
|
+
const liveIds = /* @__PURE__ */ new Set();
|
|
718
|
+
if (sessions !== void 0) {
|
|
719
|
+
const store = sessions();
|
|
720
|
+
if (store !== void 0) for (const session of store.list()) {
|
|
721
|
+
liveIds.add(session.id);
|
|
722
|
+
const state = projections?.()?.stateOf(session, BILLING_UNIT_KEY);
|
|
723
|
+
if (state !== void 0 && state.dayKey === dayKey) total = mergeTodaySpend(total, state.spend);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
const persistenceService = persistence?.();
|
|
727
|
+
if (persistenceService === void 0) return total;
|
|
728
|
+
const snapshots = await persistenceService.listSnapshots();
|
|
729
|
+
const pending = [];
|
|
730
|
+
for (const { header, revision } of snapshots) {
|
|
731
|
+
if (liveIds.has(header.id)) continue;
|
|
732
|
+
const resolved = this.coldResolved.get(header.id);
|
|
733
|
+
if (resolved !== void 0 && resolved.revision === revision) {
|
|
734
|
+
if (resolved.value.dayKey === dayKey) total = mergeTodaySpend(total, resolved.value.spend);
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
pending.push({
|
|
738
|
+
id: header.id,
|
|
739
|
+
revision
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
await withConcurrency(pending, 8, async ({ id, revision }) => {
|
|
743
|
+
let value;
|
|
744
|
+
const cache = projectionCache?.();
|
|
745
|
+
if (cache !== void 0) try {
|
|
746
|
+
value = (await cache.coldSnapshot(id)).values[BILLING_UNIT_KEY];
|
|
747
|
+
} catch (error) {
|
|
748
|
+
logger.warn(`llm-billing: projection cold read for session ${id} failed: ${String(error)}`);
|
|
749
|
+
}
|
|
750
|
+
if (value === void 0) try {
|
|
751
|
+
const inspection = await persistenceService.inspect(id);
|
|
752
|
+
value = foldBillingUnit(unit, inspection.events);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
logger.warn(`llm-billing: skipping unreadable session ${id}: ${String(error)}`);
|
|
755
|
+
}
|
|
756
|
+
if (value !== void 0) this.coldResolved.set(id, {
|
|
757
|
+
revision,
|
|
758
|
+
value
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
for (const { id } of pending) {
|
|
762
|
+
const resolved = this.coldResolved.get(id);
|
|
763
|
+
if (resolved !== void 0 && resolved.value.dayKey === dayKey) total = mergeTodaySpend(total, resolved.value.spend);
|
|
764
|
+
}
|
|
765
|
+
return total;
|
|
766
|
+
}
|
|
767
|
+
/** Events path: collect only today's events (capped), gated by revisions. */
|
|
768
|
+
async scanEvents(dayKey) {
|
|
769
|
+
const { sessions, persistence, maxEvents, logger, billing, catalog } = this.deps;
|
|
770
|
+
const events = [];
|
|
771
|
+
const liveIds = /* @__PURE__ */ new Set();
|
|
772
|
+
let truncated = false;
|
|
773
|
+
if (sessions !== void 0) {
|
|
774
|
+
const store = sessions();
|
|
775
|
+
if (store !== void 0) for (const session of store.list()) {
|
|
776
|
+
liveIds.add(session.id);
|
|
777
|
+
for (const event of session.events) {
|
|
778
|
+
if (beijingDayKey(new Date(event.time)) !== dayKey) continue;
|
|
779
|
+
events.push(event);
|
|
780
|
+
if (events.length >= maxEvents) {
|
|
781
|
+
truncated = true;
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (truncated) break;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
const persistenceService = persistence?.();
|
|
789
|
+
if (!truncated && persistenceService !== void 0) {
|
|
790
|
+
const snapshots = await persistenceService.listSnapshots();
|
|
791
|
+
for (const { header, revision } of snapshots) {
|
|
792
|
+
if (liveIds.has(header.id)) continue;
|
|
793
|
+
if (this.lastEventsScan?.get(header.id) === revision) continue;
|
|
794
|
+
try {
|
|
795
|
+
const inspection = await persistenceService.inspect(header.id);
|
|
796
|
+
for (const event of inspection.events) {
|
|
797
|
+
if (beijingDayKey(new Date(event.time)) !== dayKey) continue;
|
|
798
|
+
events.push(event);
|
|
799
|
+
if (events.length >= maxEvents) {
|
|
800
|
+
truncated = true;
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
} catch (error) {
|
|
805
|
+
logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
|
|
806
|
+
}
|
|
807
|
+
if (truncated) break;
|
|
808
|
+
}
|
|
809
|
+
if (!truncated) this.lastEventsScan = new Map(snapshots.map((snapshot) => [snapshot.header.id, snapshot.revision]));
|
|
810
|
+
}
|
|
811
|
+
if (truncated) logger.warn(`llm-billing: today's events exceeded ${maxEvents}; result truncated`);
|
|
812
|
+
return computeTodaySpend(events, billing, catalog, /* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`));
|
|
813
|
+
}
|
|
814
|
+
};
|
|
3398
815
|
//#endregion
|
|
3399
816
|
//#region lib/types/index.js
|
|
3400
817
|
/**
|
|
@@ -3403,49 +820,70 @@ function computeTodaySpend(events, billing, catalog, now = /* @__PURE__ */ new D
|
|
|
3403
820
|
* the credential/environment seams, prices each session's billed usage with the
|
|
3404
821
|
* peak/off-peak table, and exposes the `billing` Remote (`getBalance`, the
|
|
3405
822
|
* per-session `getSessionSpend`, and the all-sessions `getTodaySpend`).
|
|
3406
|
-
*
|
|
823
|
+
*
|
|
824
|
+
* Today's spend never scans every session log per request: a 60-second
|
|
825
|
+
* Beijing-day cache with in-flight coalescing serves the message-triggered
|
|
826
|
+
* reads, the manual refresh may bypass the time window (`force`), and the
|
|
827
|
+
* computation behind a miss reads only sessions whose persisted revision
|
|
828
|
+
* changed since the last resolution (see today-spend.ts). When the
|
|
829
|
+
* session-projection registry is composed, the plugin additionally registers
|
|
830
|
+
* the `billingTodaySpend` projection unit, which folds each session's spend
|
|
831
|
+
* eagerly and lets cold reads ride the projection-cache ladder.
|
|
832
|
+
* @module @rayadesu/dsh-llm-billing
|
|
3407
833
|
*/
|
|
3408
834
|
const name = "llm-billing";
|
|
3409
835
|
const DEFAULT_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
3410
836
|
const BASE_URL_ENV = "DEEPSEEK_BASE_URL";
|
|
3411
837
|
/** Public API default; deployments may point elsewhere via $DEEPSEEK_BASE_URL. */
|
|
3412
838
|
const PUBLIC_BASE_URL = "https://api.deepseek.com";
|
|
3413
|
-
const DEFAULT_MODELS = [
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
839
|
+
const DEFAULT_MODELS = [
|
|
840
|
+
{
|
|
841
|
+
id: "deepseek-v4-flash",
|
|
842
|
+
name: "DeepSeek-V4-Flash"
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
id: "deepseek-v4-pro",
|
|
846
|
+
name: "DeepSeek-V4-Pro"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
id: "deepseek-v4-flash-vision-exp",
|
|
850
|
+
name: "DeepSeek-V4-Flash-Vision-Exp"
|
|
851
|
+
}
|
|
852
|
+
];
|
|
853
|
+
const billingModel = z.object({
|
|
854
|
+
id: z.string().required(),
|
|
855
|
+
name: z.string()
|
|
3423
856
|
});
|
|
3424
|
-
const tokenPrice =
|
|
3425
|
-
cacheHitInput:
|
|
3426
|
-
cacheMissInput:
|
|
3427
|
-
output:
|
|
857
|
+
const tokenPrice = z.object({
|
|
858
|
+
cacheHitInput: z.number().min(0),
|
|
859
|
+
cacheMissInput: z.number().min(0),
|
|
860
|
+
output: z.number().min(0)
|
|
3428
861
|
});
|
|
3429
|
-
const billingConfig =
|
|
3430
|
-
peakHours:
|
|
3431
|
-
start:
|
|
3432
|
-
end:
|
|
862
|
+
const billingConfig = z.object({
|
|
863
|
+
peakHours: z.array(z.object({
|
|
864
|
+
start: z.number().step(1).min(0).max(23),
|
|
865
|
+
end: z.number().step(1).min(0).max(24)
|
|
3433
866
|
})).default(DEFAULT_PEAK_HOURS),
|
|
3434
|
-
models:
|
|
3435
|
-
model:
|
|
867
|
+
models: z.array(z.object({
|
|
868
|
+
model: z.string().required(),
|
|
3436
869
|
peak: tokenPrice,
|
|
3437
870
|
offPeak: tokenPrice
|
|
3438
871
|
})).default(DEFAULT_MODEL_PRICING)
|
|
3439
872
|
});
|
|
3440
|
-
const Config =
|
|
3441
|
-
apiKeyEnv:
|
|
3442
|
-
baseURL:
|
|
3443
|
-
models:
|
|
873
|
+
const Config = z.object({
|
|
874
|
+
apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
|
|
875
|
+
baseURL: z.string(),
|
|
876
|
+
models: z.array(billingModel).default(DEFAULT_MODELS),
|
|
3444
877
|
billing: billingConfig
|
|
3445
878
|
});
|
|
879
|
+
/** How often a Beijing-day "today spend" value may be recomputed (60s). */
|
|
880
|
+
const TODAY_SPEND_CACHE_MS = 6e4;
|
|
881
|
+
/** Hard cap on today's events collected by the events scan path. */
|
|
882
|
+
const TODAY_SPEND_MAX_EVENTS = 2e5;
|
|
3446
883
|
/**
|
|
3447
884
|
* Read one session's event log: the live SessionStore first, then the
|
|
3448
|
-
* persistence backend for a flushed session
|
|
885
|
+
* persistence backend for a flushed session (inspected directly by id — no
|
|
886
|
+
* header listing).
|
|
3449
887
|
* @param ctx - plugin context carrying the SessionStore and optional persistence.
|
|
3450
888
|
* @param sessionId - the session to read.
|
|
3451
889
|
* @returns the session's complete event log.
|
|
@@ -3455,42 +893,14 @@ async function sessionEvents(ctx, sessionId) {
|
|
|
3455
893
|
const live = ctx.get("sessions")?.get(sessionId);
|
|
3456
894
|
if (live !== void 0) return live.events;
|
|
3457
895
|
const persistence = ctx.get("sessionPersistence");
|
|
3458
|
-
if (persistence !== void 0)
|
|
3459
|
-
if (header.id !== sessionId) continue;
|
|
896
|
+
if (persistence !== void 0) try {
|
|
3460
897
|
return (await persistence.inspect(sessionId)).events;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
throw new LlmError(`llm-billing: session ${sessionId} not found`, "NOT_FOUND", { cause: error });
|
|
3461
900
|
}
|
|
3462
901
|
throw new LlmError(`llm-billing: session ${sessionId} not found`, "NOT_FOUND");
|
|
3463
902
|
}
|
|
3464
903
|
/**
|
|
3465
|
-
* Read every session's event log, concatenated: each live SessionStore
|
|
3466
|
-
* session first (its log may hold events not yet flushed), then each persisted
|
|
3467
|
-
* session that is not live, so no event is counted twice. Events are appended
|
|
3468
|
-
* one at a time: spreading a very large log into `push(...)` exceeds the
|
|
3469
|
-
* engine's argument limit and throws a stack RangeError.
|
|
3470
|
-
* @param ctx - plugin context carrying the SessionStore and optional persistence.
|
|
3471
|
-
* @returns every session's complete event log, concatenated.
|
|
3472
|
-
*/
|
|
3473
|
-
async function allSessionEvents(ctx) {
|
|
3474
|
-
const events = [];
|
|
3475
|
-
const sessions = ctx.get("sessions");
|
|
3476
|
-
const liveIds = /* @__PURE__ */ new Set();
|
|
3477
|
-
if (sessions !== void 0) for (const session of sessions.list()) {
|
|
3478
|
-
liveIds.add(session.id);
|
|
3479
|
-
for (const event of session.events) events.push(event);
|
|
3480
|
-
}
|
|
3481
|
-
const persistence = ctx.get("sessionPersistence");
|
|
3482
|
-
if (persistence !== void 0) for (const header of await persistence.list()) {
|
|
3483
|
-
if (liveIds.has(header.id)) continue;
|
|
3484
|
-
try {
|
|
3485
|
-
const inspection = await persistence.inspect(header.id);
|
|
3486
|
-
for (const event of inspection.events) events.push(event);
|
|
3487
|
-
} catch (error) {
|
|
3488
|
-
ctx.logger.warn(`llm-billing: skipping unreadable session ${header.id}: ${String(error)}`);
|
|
3489
|
-
}
|
|
3490
|
-
}
|
|
3491
|
-
return events;
|
|
3492
|
-
}
|
|
3493
|
-
/**
|
|
3494
904
|
* Register the `billing` Remote under the `billing` namespace.
|
|
3495
905
|
* @param ctx - owning plugin context.
|
|
3496
906
|
* @param config - validated plugin config.
|
|
@@ -3513,22 +923,37 @@ function apply(ctx, config) {
|
|
|
3513
923
|
const apiKey = await resolveApiKey();
|
|
3514
924
|
return fetchDeepSeekBalance(baseURL(), apiKey);
|
|
3515
925
|
};
|
|
926
|
+
const billing = resolveBilling(config.billing);
|
|
927
|
+
const catalog = (config.models ?? DEFAULT_MODELS).map((model) => ({
|
|
928
|
+
id: model.id,
|
|
929
|
+
name: model.name ?? model.id
|
|
930
|
+
}));
|
|
3516
931
|
const fetchSessionSpend = async (sessionId) => {
|
|
3517
|
-
const billing = resolveBilling(config.billing);
|
|
3518
|
-
const catalog = (config.models ?? DEFAULT_MODELS).map((model) => ({
|
|
3519
|
-
id: model.id,
|
|
3520
|
-
name: model.name ?? model.id
|
|
3521
|
-
}));
|
|
3522
932
|
return computeSessionSpend(await sessionEvents(ctx, sessionId), billing, catalog);
|
|
3523
933
|
};
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
934
|
+
const unit = billingTodaySpendDefinition(billing, catalog);
|
|
935
|
+
let unitRegistered = false;
|
|
936
|
+
const ensureUnit = () => {
|
|
937
|
+
if (unitRegistered) return;
|
|
938
|
+
const registry = ctx.get("sessionProjections");
|
|
939
|
+
if (registry === void 0) return;
|
|
940
|
+
registry.register(unit);
|
|
941
|
+
unitRegistered = true;
|
|
3531
942
|
};
|
|
943
|
+
const scanner = new TodaySpendScanner({
|
|
944
|
+
sessions: () => ctx.get("sessions"),
|
|
945
|
+
persistence: () => ctx.get("sessionPersistence"),
|
|
946
|
+
projections: () => ctx.get("sessionProjections"),
|
|
947
|
+
projectionCache: () => ctx.get("sessionProjectionCache"),
|
|
948
|
+
ensureUnit,
|
|
949
|
+
unit,
|
|
950
|
+
maxEvents: TODAY_SPEND_MAX_EVENTS,
|
|
951
|
+
logger: ctx.logger,
|
|
952
|
+
billing,
|
|
953
|
+
catalog
|
|
954
|
+
});
|
|
955
|
+
const todayCache = new TodaySpendCache((dayKey) => scanner.scan(dayKey), TODAY_SPEND_CACHE_MS);
|
|
956
|
+
const fetchTodaySpend = async (force = false) => todayCache.get(force);
|
|
3532
957
|
new DeepSeekBalanceGateway(ctx, {
|
|
3533
958
|
fetchBalance,
|
|
3534
959
|
fetchSessionSpend,
|
|
@@ -3536,4 +961,4 @@ function apply(ctx, config) {
|
|
|
3536
961
|
});
|
|
3537
962
|
}
|
|
3538
963
|
//#endregion
|
|
3539
|
-
export { Config, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, DeepSeekBalanceGateway, PUBLIC_BASE_URL, apply, computeSessionSpend, computeTodaySpend, fetchDeepSeekBalance, isPeak, name, parseDeepSeekBalance, resolveBilling };
|
|
964
|
+
export { BILLING_UNIT_KEY, Config, DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, DeepSeekBalanceGateway, PUBLIC_BASE_URL, TODAY_SPEND_CACHE_MS, TODAY_SPEND_MAX_EVENTS, TodaySpendCache, TodaySpendScanner, addEventContribution, apply, beijingDayKey, billingTodaySpendDefinition, computeSessionSpend, computeTodaySpend, emptyTodaySpend, fetchDeepSeekBalance, foldBillingUnit, isPeak, mergeTodaySpend, name, parseDeepSeekBalance, priceEvent, resolveBilling };
|