@astrohacker/factory 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +23 -2
- package/dist/ahfact.js +2127 -0
- package/package.json +28 -4
package/dist/ahfact.js
ADDED
|
@@ -0,0 +1,2127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// cli/ahfact.ts
|
|
4
|
+
import { chmod, mkdir, readdir, readFile as readFile2, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { homedir, hostname } from "node:os";
|
|
6
|
+
import { join as join2 } from "node:path";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
|
|
9
|
+
// node_modules/@orpc/shared/dist/index.mjs
|
|
10
|
+
function resolveMaybeOptionalOptions(rest) {
|
|
11
|
+
return rest[0] ?? {};
|
|
12
|
+
}
|
|
13
|
+
function toArray(value) {
|
|
14
|
+
return Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
|
|
15
|
+
}
|
|
16
|
+
var DEFAULT_MAX_BIGINT_DIGITS = 4300;
|
|
17
|
+
var BIGINT_PATTERN = /^-?(?:0|[1-9]\d*)$/;
|
|
18
|
+
function parseBigIntString(value, maxDigits) {
|
|
19
|
+
const digits = value.startsWith("-") ? value.length - 1 : value.length;
|
|
20
|
+
if (digits > maxDigits || !BIGINT_PATTERN.test(value)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
return BigInt(value);
|
|
24
|
+
}
|
|
25
|
+
var ORPC_NAME = "orpc";
|
|
26
|
+
var ORPC_SHARED_PACKAGE_NAME = "@orpc/shared";
|
|
27
|
+
var ORPC_SHARED_PACKAGE_VERSION = "1.16.2";
|
|
28
|
+
|
|
29
|
+
class AbortError extends Error {
|
|
30
|
+
constructor(...rest) {
|
|
31
|
+
super(...rest);
|
|
32
|
+
this.name = "AbortError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function once(fn) {
|
|
36
|
+
let cached;
|
|
37
|
+
return () => {
|
|
38
|
+
if (cached) {
|
|
39
|
+
return cached.result;
|
|
40
|
+
}
|
|
41
|
+
const result = fn();
|
|
42
|
+
cached = { result };
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function sequential(fn) {
|
|
47
|
+
let lastOperationPromise = Promise.resolve();
|
|
48
|
+
return (...args) => {
|
|
49
|
+
return lastOperationPromise = lastOperationPromise.catch(() => {}).then(() => {
|
|
50
|
+
return fn(...args);
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
var SPAN_ERROR_STATUS = 2;
|
|
55
|
+
var GLOBAL_OTEL_CONFIG_KEY = `__${ORPC_SHARED_PACKAGE_NAME}@${ORPC_SHARED_PACKAGE_VERSION}/otel/config__`;
|
|
56
|
+
function getGlobalOtelConfig() {
|
|
57
|
+
return globalThis[GLOBAL_OTEL_CONFIG_KEY];
|
|
58
|
+
}
|
|
59
|
+
function startSpan(name, options = {}, context) {
|
|
60
|
+
const tracer = getGlobalOtelConfig()?.tracer;
|
|
61
|
+
return tracer?.startSpan(name, options, context);
|
|
62
|
+
}
|
|
63
|
+
function setSpanError(span, error, options = {}) {
|
|
64
|
+
if (!span) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const exception = toOtelException(error);
|
|
68
|
+
span.recordException(exception);
|
|
69
|
+
if (!options.signal?.aborted || options.signal.reason !== error) {
|
|
70
|
+
span.setStatus({
|
|
71
|
+
code: SPAN_ERROR_STATUS,
|
|
72
|
+
message: exception.message
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function toOtelException(error) {
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
const exception = {
|
|
79
|
+
message: error.message,
|
|
80
|
+
name: error.name,
|
|
81
|
+
stack: error.stack
|
|
82
|
+
};
|
|
83
|
+
if ("code" in error && (typeof error.code === "string" || typeof error.code === "number")) {
|
|
84
|
+
exception.code = error.code;
|
|
85
|
+
}
|
|
86
|
+
return exception;
|
|
87
|
+
}
|
|
88
|
+
return { message: String(error) };
|
|
89
|
+
}
|
|
90
|
+
async function runWithSpan({ name, context, ...options }, fn) {
|
|
91
|
+
const tracer = getGlobalOtelConfig()?.tracer;
|
|
92
|
+
if (!tracer) {
|
|
93
|
+
return fn();
|
|
94
|
+
}
|
|
95
|
+
const callback = async (span) => {
|
|
96
|
+
try {
|
|
97
|
+
return await fn(span);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
setSpanError(span, e, options);
|
|
100
|
+
throw e;
|
|
101
|
+
} finally {
|
|
102
|
+
span.end();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
if (context) {
|
|
106
|
+
return tracer.startActiveSpan(name, options, context, callback);
|
|
107
|
+
} else {
|
|
108
|
+
return tracer.startActiveSpan(name, options, callback);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function runInSpanContext(span, fn) {
|
|
112
|
+
const otelConfig = getGlobalOtelConfig();
|
|
113
|
+
if (!span || !otelConfig) {
|
|
114
|
+
return fn();
|
|
115
|
+
}
|
|
116
|
+
const ctx = otelConfig.trace.setSpan(otelConfig.context.active(), span);
|
|
117
|
+
return otelConfig.context.with(ctx, fn);
|
|
118
|
+
}
|
|
119
|
+
function isAsyncIteratorObject(maybe) {
|
|
120
|
+
if (!maybe || typeof maybe !== "object") {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return "next" in maybe && typeof maybe.next === "function" && Symbol.asyncIterator in maybe && typeof maybe[Symbol.asyncIterator] === "function";
|
|
124
|
+
}
|
|
125
|
+
var fallbackAsyncDisposeSymbol = Symbol.for("asyncDispose");
|
|
126
|
+
var asyncDisposeSymbol = Symbol.asyncDispose ?? fallbackAsyncDisposeSymbol;
|
|
127
|
+
|
|
128
|
+
class AsyncIteratorClass {
|
|
129
|
+
#isDone = false;
|
|
130
|
+
#isExecuteComplete = false;
|
|
131
|
+
#cleanup;
|
|
132
|
+
#next;
|
|
133
|
+
constructor(next, cleanup) {
|
|
134
|
+
this.#cleanup = cleanup;
|
|
135
|
+
this.#next = sequential(async () => {
|
|
136
|
+
if (this.#isDone) {
|
|
137
|
+
return { done: true, value: undefined };
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const result = await next();
|
|
141
|
+
if (result.done) {
|
|
142
|
+
this.#isDone = true;
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
this.#isDone = true;
|
|
147
|
+
throw err;
|
|
148
|
+
} finally {
|
|
149
|
+
if (this.#isDone && !this.#isExecuteComplete) {
|
|
150
|
+
this.#isExecuteComplete = true;
|
|
151
|
+
await this.#cleanup("next");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
next() {
|
|
157
|
+
return this.#next();
|
|
158
|
+
}
|
|
159
|
+
async return(value) {
|
|
160
|
+
this.#isDone = true;
|
|
161
|
+
if (!this.#isExecuteComplete) {
|
|
162
|
+
this.#isExecuteComplete = true;
|
|
163
|
+
await this.#cleanup("return");
|
|
164
|
+
}
|
|
165
|
+
return { done: true, value };
|
|
166
|
+
}
|
|
167
|
+
async throw(err) {
|
|
168
|
+
this.#isDone = true;
|
|
169
|
+
if (!this.#isExecuteComplete) {
|
|
170
|
+
this.#isExecuteComplete = true;
|
|
171
|
+
await this.#cleanup("throw");
|
|
172
|
+
}
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
async[asyncDisposeSymbol]() {
|
|
176
|
+
this.#isDone = true;
|
|
177
|
+
if (!this.#isExecuteComplete) {
|
|
178
|
+
this.#isExecuteComplete = true;
|
|
179
|
+
await this.#cleanup("dispose");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
[Symbol.asyncIterator]() {
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function asyncIteratorWithSpan({ name, ...options }, iterator) {
|
|
187
|
+
let span;
|
|
188
|
+
return new AsyncIteratorClass(async () => {
|
|
189
|
+
span ??= startSpan(name);
|
|
190
|
+
try {
|
|
191
|
+
const result = await runInSpanContext(span, () => iterator.next());
|
|
192
|
+
span?.addEvent(result.done ? "completed" : "yielded");
|
|
193
|
+
return result;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
setSpanError(span, err, options);
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
}, async (reason) => {
|
|
199
|
+
try {
|
|
200
|
+
if (reason !== "next") {
|
|
201
|
+
await runInSpanContext(span, () => iterator.return?.());
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
setSpanError(span, err, options);
|
|
205
|
+
throw err;
|
|
206
|
+
} finally {
|
|
207
|
+
span?.end();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function intercept(interceptors, options, main) {
|
|
212
|
+
const next = (options2, index) => {
|
|
213
|
+
const interceptor = interceptors[index];
|
|
214
|
+
if (!interceptor) {
|
|
215
|
+
return main(options2);
|
|
216
|
+
}
|
|
217
|
+
return interceptor({
|
|
218
|
+
...options2,
|
|
219
|
+
next: (newOptions = options2) => next(newOptions, index + 1)
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
return next(options, 0);
|
|
223
|
+
}
|
|
224
|
+
function parseEmptyableJSON(text) {
|
|
225
|
+
if (!text) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
return JSON.parse(text);
|
|
229
|
+
}
|
|
230
|
+
function stringifyJSON(value) {
|
|
231
|
+
return JSON.stringify(value);
|
|
232
|
+
}
|
|
233
|
+
function getConstructor(value) {
|
|
234
|
+
if (!isTypescriptObject(value)) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return Object.getPrototypeOf(value)?.constructor;
|
|
238
|
+
}
|
|
239
|
+
function isObject(value) {
|
|
240
|
+
if (!value || typeof value !== "object") {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const proto = Object.getPrototypeOf(value);
|
|
244
|
+
return proto === Object.prototype || !proto || !proto.constructor;
|
|
245
|
+
}
|
|
246
|
+
function isTypescriptObject(value) {
|
|
247
|
+
return !!value && (typeof value === "object" || typeof value === "function");
|
|
248
|
+
}
|
|
249
|
+
function setOwn(object, key, value) {
|
|
250
|
+
if (key === "__proto__") {
|
|
251
|
+
Object.defineProperty(object, key, {
|
|
252
|
+
value,
|
|
253
|
+
writable: true,
|
|
254
|
+
enumerable: true,
|
|
255
|
+
configurable: true
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
object[key] = value;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function copyOnWrite(parent, key, original) {
|
|
262
|
+
const value = parent[key];
|
|
263
|
+
if (value !== original) {
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
let copy;
|
|
267
|
+
if (Array.isArray(value)) {
|
|
268
|
+
copy = value.slice();
|
|
269
|
+
} else if (isObject(value)) {
|
|
270
|
+
copy = { ...value };
|
|
271
|
+
} else {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
setOwn(parent, key, copy);
|
|
275
|
+
return copy;
|
|
276
|
+
}
|
|
277
|
+
function value(value2, ...args) {
|
|
278
|
+
if (typeof value2 === "function") {
|
|
279
|
+
return value2(...args);
|
|
280
|
+
}
|
|
281
|
+
return value2;
|
|
282
|
+
}
|
|
283
|
+
function preventNativeAwait(target) {
|
|
284
|
+
return new Proxy(target, {
|
|
285
|
+
get(target2, prop, receiver) {
|
|
286
|
+
const value2 = Reflect.get(target2, prop, receiver);
|
|
287
|
+
if (prop !== "then" || typeof value2 !== "function") {
|
|
288
|
+
return value2;
|
|
289
|
+
}
|
|
290
|
+
return new Proxy(value2, {
|
|
291
|
+
apply(targetFn, thisArg, args) {
|
|
292
|
+
if (args.length !== 2 || args.some((arg) => !isNativeFunction(arg))) {
|
|
293
|
+
return Reflect.apply(targetFn, thisArg, args);
|
|
294
|
+
}
|
|
295
|
+
let shouldOmit = true;
|
|
296
|
+
args[0].call(thisArg, preventNativeAwait(new Proxy(target2, {
|
|
297
|
+
get: (target3, prop2, receiver2) => {
|
|
298
|
+
if (shouldOmit && prop2 === "then") {
|
|
299
|
+
shouldOmit = false;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
return Reflect.get(target3, prop2, receiver2);
|
|
303
|
+
}
|
|
304
|
+
})));
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
var NATIVE_FUNCTION_REGEX = /^\s*function\s*\(\)\s*\{\s*\[native code\]\s*\}\s*$/;
|
|
311
|
+
function isNativeFunction(fn) {
|
|
312
|
+
return typeof fn === "function" && NATIVE_FUNCTION_REGEX.test(fn.toString());
|
|
313
|
+
}
|
|
314
|
+
var REGEXP_FLAGS = /^[dgimsuvy]*$/;
|
|
315
|
+
function isValidRegExpFlags(flags) {
|
|
316
|
+
return REGEXP_FLAGS.test(flags) && new Set(flags).size === flags.length && !(flags.includes("u") && flags.includes("v"));
|
|
317
|
+
}
|
|
318
|
+
function createLazyRegExp(pattern, flags) {
|
|
319
|
+
let compiled;
|
|
320
|
+
let lastIndex = 0;
|
|
321
|
+
const compile = () => {
|
|
322
|
+
if (compiled === undefined) {
|
|
323
|
+
compiled = new RegExp(pattern, flags);
|
|
324
|
+
compiled.lastIndex = lastIndex;
|
|
325
|
+
}
|
|
326
|
+
return compiled;
|
|
327
|
+
};
|
|
328
|
+
return new Proxy(Object.create(RegExp.prototype), {
|
|
329
|
+
get(_, prop) {
|
|
330
|
+
if (prop === Symbol.toStringTag) {
|
|
331
|
+
return "RegExp";
|
|
332
|
+
}
|
|
333
|
+
if (compiled === undefined) {
|
|
334
|
+
if (prop === "lastIndex") {
|
|
335
|
+
return lastIndex;
|
|
336
|
+
}
|
|
337
|
+
if (!(prop in RegExp.prototype)) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const target = compile();
|
|
342
|
+
const value = Reflect.get(target, prop, target);
|
|
343
|
+
return typeof value === "function" && prop !== "constructor" ? value.bind(target) : value;
|
|
344
|
+
},
|
|
345
|
+
set(_, prop, value) {
|
|
346
|
+
if (compiled === undefined && prop === "lastIndex") {
|
|
347
|
+
lastIndex = value;
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
const target = compile();
|
|
351
|
+
return Reflect.set(target, prop, value, target);
|
|
352
|
+
},
|
|
353
|
+
has(_, prop) {
|
|
354
|
+
if (compiled === undefined) {
|
|
355
|
+
return prop === "lastIndex" || prop in RegExp.prototype;
|
|
356
|
+
}
|
|
357
|
+
return Reflect.has(compiled, prop);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function tryDecodeURIComponent(value) {
|
|
362
|
+
try {
|
|
363
|
+
return decodeURIComponent(value);
|
|
364
|
+
} catch {
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// node_modules/@orpc/client/dist/shared/client.DeR0HTYb.mjs
|
|
370
|
+
var ORPC_CLIENT_PACKAGE_NAME = "@orpc/client";
|
|
371
|
+
var ORPC_CLIENT_PACKAGE_VERSION = "1.16.2";
|
|
372
|
+
var RECURSIVE_CLIENT_UNWRAP_KEYS = /* @__PURE__ */ new Set([
|
|
373
|
+
"bind",
|
|
374
|
+
"valueOf",
|
|
375
|
+
"toString",
|
|
376
|
+
"toJSON"
|
|
377
|
+
]);
|
|
378
|
+
var COMMON_ORPC_ERROR_DEFS = {
|
|
379
|
+
BAD_REQUEST: {
|
|
380
|
+
status: 400,
|
|
381
|
+
message: "Bad Request"
|
|
382
|
+
},
|
|
383
|
+
UNAUTHORIZED: {
|
|
384
|
+
status: 401,
|
|
385
|
+
message: "Unauthorized"
|
|
386
|
+
},
|
|
387
|
+
FORBIDDEN: {
|
|
388
|
+
status: 403,
|
|
389
|
+
message: "Forbidden"
|
|
390
|
+
},
|
|
391
|
+
NOT_FOUND: {
|
|
392
|
+
status: 404,
|
|
393
|
+
message: "Not Found"
|
|
394
|
+
},
|
|
395
|
+
METHOD_NOT_SUPPORTED: {
|
|
396
|
+
status: 405,
|
|
397
|
+
message: "Method Not Supported"
|
|
398
|
+
},
|
|
399
|
+
NOT_ACCEPTABLE: {
|
|
400
|
+
status: 406,
|
|
401
|
+
message: "Not Acceptable"
|
|
402
|
+
},
|
|
403
|
+
TIMEOUT: {
|
|
404
|
+
status: 408,
|
|
405
|
+
message: "Request Timeout"
|
|
406
|
+
},
|
|
407
|
+
CONFLICT: {
|
|
408
|
+
status: 409,
|
|
409
|
+
message: "Conflict"
|
|
410
|
+
},
|
|
411
|
+
PRECONDITION_FAILED: {
|
|
412
|
+
status: 412,
|
|
413
|
+
message: "Precondition Failed"
|
|
414
|
+
},
|
|
415
|
+
PAYLOAD_TOO_LARGE: {
|
|
416
|
+
status: 413,
|
|
417
|
+
message: "Payload Too Large"
|
|
418
|
+
},
|
|
419
|
+
UNSUPPORTED_MEDIA_TYPE: {
|
|
420
|
+
status: 415,
|
|
421
|
+
message: "Unsupported Media Type"
|
|
422
|
+
},
|
|
423
|
+
UNPROCESSABLE_CONTENT: {
|
|
424
|
+
status: 422,
|
|
425
|
+
message: "Unprocessable Content"
|
|
426
|
+
},
|
|
427
|
+
TOO_MANY_REQUESTS: {
|
|
428
|
+
status: 429,
|
|
429
|
+
message: "Too Many Requests"
|
|
430
|
+
},
|
|
431
|
+
CLIENT_CLOSED_REQUEST: {
|
|
432
|
+
status: 499,
|
|
433
|
+
message: "Client Closed Request"
|
|
434
|
+
},
|
|
435
|
+
INTERNAL_SERVER_ERROR: {
|
|
436
|
+
status: 500,
|
|
437
|
+
message: "Internal Server Error"
|
|
438
|
+
},
|
|
439
|
+
NOT_IMPLEMENTED: {
|
|
440
|
+
status: 501,
|
|
441
|
+
message: "Not Implemented"
|
|
442
|
+
},
|
|
443
|
+
BAD_GATEWAY: {
|
|
444
|
+
status: 502,
|
|
445
|
+
message: "Bad Gateway"
|
|
446
|
+
},
|
|
447
|
+
SERVICE_UNAVAILABLE: {
|
|
448
|
+
status: 503,
|
|
449
|
+
message: "Service Unavailable"
|
|
450
|
+
},
|
|
451
|
+
GATEWAY_TIMEOUT: {
|
|
452
|
+
status: 504,
|
|
453
|
+
message: "Gateway Timeout"
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
function fallbackORPCErrorStatus(code, status) {
|
|
457
|
+
return status ?? COMMON_ORPC_ERROR_DEFS[code]?.status ?? 500;
|
|
458
|
+
}
|
|
459
|
+
function fallbackORPCErrorMessage(code, message) {
|
|
460
|
+
return message || COMMON_ORPC_ERROR_DEFS[code]?.message || code;
|
|
461
|
+
}
|
|
462
|
+
var globalORPCErrorConstructors;
|
|
463
|
+
|
|
464
|
+
class ORPCError extends Error {
|
|
465
|
+
defined;
|
|
466
|
+
code;
|
|
467
|
+
status;
|
|
468
|
+
data;
|
|
469
|
+
static {
|
|
470
|
+
const GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL = Symbol.for(`__${ORPC_CLIENT_PACKAGE_NAME}@${ORPC_CLIENT_PACKAGE_VERSION}/error/ORPC_ERROR_CONSTRUCTORS__`);
|
|
471
|
+
globalThis[GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL] ??= /* @__PURE__ */ new WeakSet;
|
|
472
|
+
globalORPCErrorConstructors = globalThis[GLOBAL_ORPC_ERROR_CONSTRUCTORS_SYMBOL];
|
|
473
|
+
globalORPCErrorConstructors.add(ORPCError);
|
|
474
|
+
}
|
|
475
|
+
constructor(code, ...rest) {
|
|
476
|
+
const options = resolveMaybeOptionalOptions(rest);
|
|
477
|
+
if (options.status !== undefined && !isORPCErrorStatus(options.status)) {
|
|
478
|
+
throw new Error("[ORPCError] Invalid error status code.");
|
|
479
|
+
}
|
|
480
|
+
const message = fallbackORPCErrorMessage(code, options.message);
|
|
481
|
+
super(message, options);
|
|
482
|
+
this.code = code;
|
|
483
|
+
this.status = fallbackORPCErrorStatus(code, options.status);
|
|
484
|
+
this.defined = options.defined ?? false;
|
|
485
|
+
this.data = options.data;
|
|
486
|
+
}
|
|
487
|
+
toJSON() {
|
|
488
|
+
return {
|
|
489
|
+
defined: this.defined,
|
|
490
|
+
code: this.code,
|
|
491
|
+
status: this.status,
|
|
492
|
+
message: this.message,
|
|
493
|
+
data: this.data
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
static [Symbol.hasInstance](instance) {
|
|
497
|
+
if (globalORPCErrorConstructors.has(this)) {
|
|
498
|
+
const constructor = getConstructor(instance);
|
|
499
|
+
if (constructor && globalORPCErrorConstructors.has(constructor)) {
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return super[Symbol.hasInstance](instance);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function toORPCError(error) {
|
|
507
|
+
return error instanceof ORPCError ? error : new ORPCError("INTERNAL_SERVER_ERROR", {
|
|
508
|
+
message: "Internal server error",
|
|
509
|
+
cause: error
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function isORPCErrorStatus(status) {
|
|
513
|
+
return status < 200 || status >= 400;
|
|
514
|
+
}
|
|
515
|
+
function isORPCErrorJson(json) {
|
|
516
|
+
if (!isObject(json)) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
const validKeys = ["defined", "code", "status", "message", "data"];
|
|
520
|
+
if (Object.keys(json).some((k) => !validKeys.includes(k))) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
return "defined" in json && typeof json.defined === "boolean" && "code" in json && typeof json.code === "string" && "status" in json && typeof json.status === "number" && isORPCErrorStatus(json.status) && "message" in json && typeof json.message === "string";
|
|
524
|
+
}
|
|
525
|
+
function createORPCErrorFromJson(json, options = {}) {
|
|
526
|
+
return new ORPCError(json.code, {
|
|
527
|
+
...options,
|
|
528
|
+
...json
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
// node_modules/@orpc/standard-server/dist/index.mjs
|
|
532
|
+
class EventEncoderError extends TypeError {
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
class EventDecoderError extends TypeError {
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
class ErrorEvent extends Error {
|
|
539
|
+
data;
|
|
540
|
+
constructor(options) {
|
|
541
|
+
super(options?.message ?? "An error event was received", options);
|
|
542
|
+
this.data = options?.data;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
var LINE_ENDING_REGEX$1 = /\r\n|\r(?!\n)|\n/;
|
|
546
|
+
var MESSAGE_DELIMITER_REGEX = /(?:\r\n|\r(?!\n)|\n){2}/;
|
|
547
|
+
var MESSAGE_DELIMITER_GLOBAL_REGEX = /(?:\r\n|\r(?!\n)|\n){2}/g;
|
|
548
|
+
var CR = 13;
|
|
549
|
+
var LF = 10;
|
|
550
|
+
var SPACE = 32;
|
|
551
|
+
function decodeEventMessage(encoded) {
|
|
552
|
+
const message = {
|
|
553
|
+
data: undefined,
|
|
554
|
+
event: undefined,
|
|
555
|
+
id: undefined,
|
|
556
|
+
retry: undefined,
|
|
557
|
+
comments: []
|
|
558
|
+
};
|
|
559
|
+
for (const line of encoded.split(LINE_ENDING_REGEX$1)) {
|
|
560
|
+
if (line === "") {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const index = line.indexOf(":");
|
|
564
|
+
const value = index === -1 ? "" : line.slice(line.charCodeAt(index + 1) === SPACE ? index + 2 : index + 1);
|
|
565
|
+
if (index === 0) {
|
|
566
|
+
message.comments.push(value);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
switch (index === -1 ? line : line.slice(0, index)) {
|
|
570
|
+
case "data":
|
|
571
|
+
message.data = message.data === undefined ? value : `${message.data}
|
|
572
|
+
${value}`;
|
|
573
|
+
break;
|
|
574
|
+
case "event":
|
|
575
|
+
message.event = value;
|
|
576
|
+
break;
|
|
577
|
+
case "id":
|
|
578
|
+
message.id = value;
|
|
579
|
+
break;
|
|
580
|
+
case "retry": {
|
|
581
|
+
const maybeInteger = Number.parseInt(value, 10);
|
|
582
|
+
if (maybeInteger >= 0 && maybeInteger.toString() === value) {
|
|
583
|
+
message.retry = maybeInteger;
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return message;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
class EventDecoder {
|
|
593
|
+
constructor(options = {}) {
|
|
594
|
+
this.options = options;
|
|
595
|
+
}
|
|
596
|
+
pending = [];
|
|
597
|
+
tail = "";
|
|
598
|
+
discardLeadingLF = false;
|
|
599
|
+
feed(chunk) {
|
|
600
|
+
if (chunk === "") {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (this.discardLeadingLF) {
|
|
604
|
+
this.discardLeadingLF = false;
|
|
605
|
+
if (chunk.charCodeAt(0) === LF) {
|
|
606
|
+
chunk = chunk.slice(1);
|
|
607
|
+
if (chunk === "") {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const scan = this.tail + chunk;
|
|
613
|
+
if (!MESSAGE_DELIMITER_REGEX.test(scan)) {
|
|
614
|
+
this.pending.push(chunk);
|
|
615
|
+
this.tail = scan.slice(-3);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.pending.push(chunk);
|
|
619
|
+
const buffered = this.pending.length === 1 ? chunk : this.pending.join("");
|
|
620
|
+
const offset = buffered.length - scan.length;
|
|
621
|
+
const parts = [];
|
|
622
|
+
let start = 0;
|
|
623
|
+
for (const match of scan.matchAll(MESSAGE_DELIMITER_GLOBAL_REGEX)) {
|
|
624
|
+
parts.push(buffered.slice(start, offset + match.index));
|
|
625
|
+
start = offset + match.index + match[0].length;
|
|
626
|
+
}
|
|
627
|
+
const incomplete = buffered.slice(start);
|
|
628
|
+
this.pending.length = 0;
|
|
629
|
+
this.tail = incomplete.slice(-3);
|
|
630
|
+
if (incomplete === "") {
|
|
631
|
+
this.discardLeadingLF = chunk.charCodeAt(chunk.length - 1) === CR;
|
|
632
|
+
} else {
|
|
633
|
+
this.pending.push(incomplete);
|
|
634
|
+
}
|
|
635
|
+
for (const encoded of parts) {
|
|
636
|
+
const message = decodeEventMessage(encoded);
|
|
637
|
+
if (this.options.onEvent) {
|
|
638
|
+
this.options.onEvent(message);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
end() {
|
|
643
|
+
if (this.pending.length !== 0) {
|
|
644
|
+
throw new EventDecoderError("Event Iterator ended before complete");
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
class EventDecoderStream extends TransformStream {
|
|
650
|
+
constructor() {
|
|
651
|
+
let decoder;
|
|
652
|
+
super({
|
|
653
|
+
start(controller) {
|
|
654
|
+
decoder = new EventDecoder({
|
|
655
|
+
onEvent: (event) => {
|
|
656
|
+
controller.enqueue(event);
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
},
|
|
660
|
+
transform(chunk) {
|
|
661
|
+
decoder.feed(chunk);
|
|
662
|
+
},
|
|
663
|
+
flush() {
|
|
664
|
+
decoder.end();
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
var LINE_ENDING_REGEX = /\r\n|[\n\r]/;
|
|
670
|
+
var LINE_ENDING_GLOBAL_REGEX = /\r\n|[\n\r]/g;
|
|
671
|
+
function containsLineBreak(value) {
|
|
672
|
+
return LINE_ENDING_REGEX.test(value);
|
|
673
|
+
}
|
|
674
|
+
function assertEventId(id) {
|
|
675
|
+
if (containsLineBreak(id)) {
|
|
676
|
+
throw new EventEncoderError("Event's id must not contain a carriage return or newline character");
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function assertEventName(event) {
|
|
680
|
+
if (containsLineBreak(event)) {
|
|
681
|
+
throw new EventEncoderError("Event's event must not contain a carriage return or newline character");
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function assertEventRetry(retry) {
|
|
685
|
+
if (!Number.isInteger(retry) || retry < 0) {
|
|
686
|
+
throw new EventEncoderError("Event's retry must be a integer and >= 0");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function assertEventComment(comment) {
|
|
690
|
+
if (containsLineBreak(comment)) {
|
|
691
|
+
throw new EventEncoderError("Event's comment must not contain a carriage return or newline character");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function encodeEventData(data) {
|
|
695
|
+
if (data === undefined) {
|
|
696
|
+
return "";
|
|
697
|
+
}
|
|
698
|
+
return `data: ${data.replace(LINE_ENDING_GLOBAL_REGEX, `
|
|
699
|
+
data: `)}
|
|
700
|
+
`;
|
|
701
|
+
}
|
|
702
|
+
function encodeEventComments(comments) {
|
|
703
|
+
let output = "";
|
|
704
|
+
for (const comment of comments ?? []) {
|
|
705
|
+
assertEventComment(comment);
|
|
706
|
+
output += `: ${comment}
|
|
707
|
+
`;
|
|
708
|
+
}
|
|
709
|
+
return output;
|
|
710
|
+
}
|
|
711
|
+
function encodeEventMessage(message) {
|
|
712
|
+
let output = "";
|
|
713
|
+
output += encodeEventComments(message.comments);
|
|
714
|
+
if (message.event !== undefined) {
|
|
715
|
+
assertEventName(message.event);
|
|
716
|
+
output += `event: ${message.event}
|
|
717
|
+
`;
|
|
718
|
+
}
|
|
719
|
+
if (message.retry !== undefined) {
|
|
720
|
+
assertEventRetry(message.retry);
|
|
721
|
+
output += `retry: ${message.retry}
|
|
722
|
+
`;
|
|
723
|
+
}
|
|
724
|
+
if (message.id !== undefined) {
|
|
725
|
+
assertEventId(message.id);
|
|
726
|
+
output += `id: ${message.id}
|
|
727
|
+
`;
|
|
728
|
+
}
|
|
729
|
+
output += encodeEventData(message.data);
|
|
730
|
+
output += `
|
|
731
|
+
`;
|
|
732
|
+
return output;
|
|
733
|
+
}
|
|
734
|
+
var EVENT_SOURCE_META_SYMBOL = Symbol("ORPC_EVENT_SOURCE_META");
|
|
735
|
+
function withEventMeta(container, meta) {
|
|
736
|
+
if (meta.id === undefined && meta.retry === undefined && !meta.comments?.length) {
|
|
737
|
+
return container;
|
|
738
|
+
}
|
|
739
|
+
if (meta.id !== undefined) {
|
|
740
|
+
assertEventId(meta.id);
|
|
741
|
+
}
|
|
742
|
+
if (meta.retry !== undefined) {
|
|
743
|
+
assertEventRetry(meta.retry);
|
|
744
|
+
}
|
|
745
|
+
if (meta.comments !== undefined) {
|
|
746
|
+
for (const comment of meta.comments) {
|
|
747
|
+
assertEventComment(comment);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return new Proxy(container, {
|
|
751
|
+
get(target, prop, receiver) {
|
|
752
|
+
if (prop === EVENT_SOURCE_META_SYMBOL) {
|
|
753
|
+
return meta;
|
|
754
|
+
}
|
|
755
|
+
return Reflect.get(target, prop, receiver);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function getEventMeta(container) {
|
|
760
|
+
return isTypescriptObject(container) ? Reflect.get(container, EVENT_SOURCE_META_SYMBOL) : undefined;
|
|
761
|
+
}
|
|
762
|
+
function generateContentDisposition(filename, disposition = "inline") {
|
|
763
|
+
const encodedFileName = filename.replace(/[^\x20-\x7E]/g, "_").replace(/"/g, "\\\"");
|
|
764
|
+
const encodedFilenameStar = encodeURIComponent(filename).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%(7C|60|5E)/g, (str, hex) => String.fromCharCode(Number.parseInt(hex, 16)));
|
|
765
|
+
return `${disposition}; filename="${encodedFileName}"; filename*=utf-8''${encodedFilenameStar}`;
|
|
766
|
+
}
|
|
767
|
+
function getFilenameFromContentDisposition(contentDisposition) {
|
|
768
|
+
const encodedFilenameStarMatch = contentDisposition.match(/filename\*=(UTF-8'')?([^;]*)/i);
|
|
769
|
+
if (encodedFilenameStarMatch && typeof encodedFilenameStarMatch[2] === "string") {
|
|
770
|
+
return tryDecodeURIComponent(encodedFilenameStarMatch[2]);
|
|
771
|
+
}
|
|
772
|
+
const encodedFilenameMatch = contentDisposition.match(/filename="((?:\\"|[^"])*)"/i);
|
|
773
|
+
if (encodedFilenameMatch && typeof encodedFilenameMatch[1] === "string") {
|
|
774
|
+
return encodedFilenameMatch[1].replace(/\\"/g, '"');
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function mergeStandardHeaders(a, b) {
|
|
778
|
+
const merged = { ...a };
|
|
779
|
+
for (const key in b) {
|
|
780
|
+
if (Array.isArray(b[key])) {
|
|
781
|
+
merged[key] = [...toArray(merged[key]), ...b[key]];
|
|
782
|
+
} else if (b[key] !== undefined) {
|
|
783
|
+
if (Array.isArray(merged[key])) {
|
|
784
|
+
merged[key] = [...merged[key], b[key]];
|
|
785
|
+
} else if (merged[key] !== undefined) {
|
|
786
|
+
merged[key] = [merged[key], b[key]];
|
|
787
|
+
} else {
|
|
788
|
+
merged[key] = b[key];
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return merged;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// node_modules/@orpc/client/dist/shared/client.BLtwTQUg.mjs
|
|
796
|
+
function mapEventIterator(iterator, maps) {
|
|
797
|
+
const mapError = async (error) => {
|
|
798
|
+
let mappedError = await maps.error(error);
|
|
799
|
+
if (mappedError !== error) {
|
|
800
|
+
const meta = getEventMeta(error);
|
|
801
|
+
if (meta && isTypescriptObject(mappedError)) {
|
|
802
|
+
mappedError = withEventMeta(mappedError, meta);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return mappedError;
|
|
806
|
+
};
|
|
807
|
+
return new AsyncIteratorClass(async () => {
|
|
808
|
+
const { done, value } = await (async () => {
|
|
809
|
+
try {
|
|
810
|
+
return await iterator.next();
|
|
811
|
+
} catch (error) {
|
|
812
|
+
throw await mapError(error);
|
|
813
|
+
}
|
|
814
|
+
})();
|
|
815
|
+
let mappedValue = await maps.value(value, done);
|
|
816
|
+
if (mappedValue !== value) {
|
|
817
|
+
const meta = getEventMeta(value);
|
|
818
|
+
if (meta && isTypescriptObject(mappedValue)) {
|
|
819
|
+
mappedValue = withEventMeta(mappedValue, meta);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return { done, value: mappedValue };
|
|
823
|
+
}, async () => {
|
|
824
|
+
try {
|
|
825
|
+
await iterator.return?.();
|
|
826
|
+
} catch (error) {
|
|
827
|
+
throw await mapError(error);
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// node_modules/@orpc/client/dist/index.mjs
|
|
833
|
+
function resolveFriendlyClientOptions(options) {
|
|
834
|
+
return {
|
|
835
|
+
...options,
|
|
836
|
+
context: options.context ?? {}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function createORPCClient(link, options = {}) {
|
|
840
|
+
const path = options.path ?? [];
|
|
841
|
+
const procedureClient = async (...[input, options2 = {}]) => {
|
|
842
|
+
return await link.call(path, input, resolveFriendlyClientOptions(options2));
|
|
843
|
+
};
|
|
844
|
+
const recursive = new Proxy(procedureClient, {
|
|
845
|
+
get(target, key) {
|
|
846
|
+
if (typeof key !== "string" || RECURSIVE_CLIENT_UNWRAP_KEYS.has(key)) {
|
|
847
|
+
return Reflect.get(target, key);
|
|
848
|
+
}
|
|
849
|
+
return createORPCClient(link, {
|
|
850
|
+
...options,
|
|
851
|
+
path: [...path, key]
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
return preventNativeAwait(recursive);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// node_modules/@orpc/standard-server-fetch/dist/index.mjs
|
|
859
|
+
function toEventIterator(stream, options = {}) {
|
|
860
|
+
const eventStream = stream?.pipeThrough(new TextDecoderStream).pipeThrough(new EventDecoderStream);
|
|
861
|
+
const reader = eventStream?.getReader();
|
|
862
|
+
let span;
|
|
863
|
+
let isCancelled = false;
|
|
864
|
+
return new AsyncIteratorClass(async () => {
|
|
865
|
+
span ??= startSpan("consume_event_iterator_stream");
|
|
866
|
+
try {
|
|
867
|
+
while (true) {
|
|
868
|
+
if (reader === undefined) {
|
|
869
|
+
return { done: true, value: undefined };
|
|
870
|
+
}
|
|
871
|
+
const { done, value } = await runInSpanContext(span, () => reader.read());
|
|
872
|
+
if (done) {
|
|
873
|
+
if (isCancelled) {
|
|
874
|
+
throw new AbortError("Stream was cancelled");
|
|
875
|
+
}
|
|
876
|
+
return { done: true, value: undefined };
|
|
877
|
+
}
|
|
878
|
+
const event = value.event ?? (value.data !== undefined ? "message" : undefined);
|
|
879
|
+
switch (event) {
|
|
880
|
+
case "message": {
|
|
881
|
+
let message = parseEmptyableJSON(value.data);
|
|
882
|
+
if (isTypescriptObject(message)) {
|
|
883
|
+
message = withEventMeta(message, value);
|
|
884
|
+
}
|
|
885
|
+
span?.addEvent("message");
|
|
886
|
+
return { done: false, value: message };
|
|
887
|
+
}
|
|
888
|
+
case "error": {
|
|
889
|
+
let error = new ErrorEvent({
|
|
890
|
+
data: parseEmptyableJSON(value.data)
|
|
891
|
+
});
|
|
892
|
+
error = withEventMeta(error, value);
|
|
893
|
+
span?.addEvent("error");
|
|
894
|
+
throw error;
|
|
895
|
+
}
|
|
896
|
+
case "done":
|
|
897
|
+
case "close": {
|
|
898
|
+
let done2 = parseEmptyableJSON(value.data);
|
|
899
|
+
if (isTypescriptObject(done2)) {
|
|
900
|
+
done2 = withEventMeta(done2, value);
|
|
901
|
+
}
|
|
902
|
+
span?.addEvent("done");
|
|
903
|
+
return { done: true, value: done2 };
|
|
904
|
+
}
|
|
905
|
+
default: {
|
|
906
|
+
span?.addEvent("maybe_keepalive");
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
} catch (e) {
|
|
911
|
+
if (!(e instanceof ErrorEvent)) {
|
|
912
|
+
setSpanError(span, e, options);
|
|
913
|
+
}
|
|
914
|
+
throw e;
|
|
915
|
+
}
|
|
916
|
+
}, async (reason) => {
|
|
917
|
+
try {
|
|
918
|
+
if (reason !== "next") {
|
|
919
|
+
isCancelled = true;
|
|
920
|
+
span?.addEvent("cancelled");
|
|
921
|
+
}
|
|
922
|
+
await runInSpanContext(span, () => reader?.cancel());
|
|
923
|
+
} catch (e) {
|
|
924
|
+
setSpanError(span, e, options);
|
|
925
|
+
throw e;
|
|
926
|
+
} finally {
|
|
927
|
+
span?.end();
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
function toEventStream(iterator, options = {}) {
|
|
932
|
+
const keepAliveEnabled = options.eventIteratorKeepAliveEnabled ?? true;
|
|
933
|
+
const keepAliveInterval = options.eventIteratorKeepAliveInterval ?? 5000;
|
|
934
|
+
const keepAliveComment = options.eventIteratorKeepAliveComment ?? "";
|
|
935
|
+
const initialCommentEnabled = options.eventIteratorInitialCommentEnabled ?? true;
|
|
936
|
+
const initialComment = options.eventIteratorInitialComment ?? "";
|
|
937
|
+
let cancelled = false;
|
|
938
|
+
let timeout;
|
|
939
|
+
let span;
|
|
940
|
+
const stream = new ReadableStream({
|
|
941
|
+
start(controller) {
|
|
942
|
+
span = startSpan("stream_event_iterator");
|
|
943
|
+
if (initialCommentEnabled) {
|
|
944
|
+
controller.enqueue(encodeEventMessage({
|
|
945
|
+
comments: [initialComment]
|
|
946
|
+
}));
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
async pull(controller) {
|
|
950
|
+
try {
|
|
951
|
+
if (keepAliveEnabled) {
|
|
952
|
+
timeout = setInterval(() => {
|
|
953
|
+
controller.enqueue(encodeEventMessage({
|
|
954
|
+
comments: [keepAliveComment]
|
|
955
|
+
}));
|
|
956
|
+
span?.addEvent("keepalive");
|
|
957
|
+
}, keepAliveInterval);
|
|
958
|
+
}
|
|
959
|
+
const value = await runInSpanContext(span, () => iterator.next());
|
|
960
|
+
clearInterval(timeout);
|
|
961
|
+
if (cancelled) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const meta = getEventMeta(value.value);
|
|
965
|
+
if (!value.done || value.value !== undefined || meta !== undefined) {
|
|
966
|
+
const event = value.done ? "done" : "message";
|
|
967
|
+
controller.enqueue(encodeEventMessage({
|
|
968
|
+
...meta,
|
|
969
|
+
event,
|
|
970
|
+
data: stringifyJSON(value.value)
|
|
971
|
+
}));
|
|
972
|
+
span?.addEvent(event);
|
|
973
|
+
}
|
|
974
|
+
if (value.done) {
|
|
975
|
+
controller.close();
|
|
976
|
+
span?.end();
|
|
977
|
+
}
|
|
978
|
+
} catch (err) {
|
|
979
|
+
clearInterval(timeout);
|
|
980
|
+
if (cancelled) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (err instanceof ErrorEvent) {
|
|
984
|
+
controller.enqueue(encodeEventMessage({
|
|
985
|
+
...getEventMeta(err),
|
|
986
|
+
event: "error",
|
|
987
|
+
data: stringifyJSON(err.data)
|
|
988
|
+
}));
|
|
989
|
+
span?.addEvent("error");
|
|
990
|
+
controller.close();
|
|
991
|
+
} else {
|
|
992
|
+
setSpanError(span, err);
|
|
993
|
+
controller.error(err);
|
|
994
|
+
}
|
|
995
|
+
span?.end();
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
async cancel() {
|
|
999
|
+
try {
|
|
1000
|
+
cancelled = true;
|
|
1001
|
+
clearInterval(timeout);
|
|
1002
|
+
span?.addEvent("cancelled");
|
|
1003
|
+
await runInSpanContext(span, () => iterator.return?.());
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
setSpanError(span, e);
|
|
1006
|
+
throw e;
|
|
1007
|
+
} finally {
|
|
1008
|
+
span?.end();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}).pipeThrough(new TextEncoderStream);
|
|
1012
|
+
return stream;
|
|
1013
|
+
}
|
|
1014
|
+
function toStandardBody(re, options = {}) {
|
|
1015
|
+
return runWithSpan({ name: "parse_standard_body", signal: options.signal }, async () => {
|
|
1016
|
+
const contentDisposition = re.headers.get("content-disposition");
|
|
1017
|
+
if (typeof contentDisposition === "string") {
|
|
1018
|
+
const fileName = getFilenameFromContentDisposition(contentDisposition) ?? "blob";
|
|
1019
|
+
const blob2 = await re.blob();
|
|
1020
|
+
return new File([blob2], fileName, {
|
|
1021
|
+
type: blob2.type
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
const contentType = re.headers.get("content-type");
|
|
1025
|
+
if (!contentType || contentType.startsWith("application/json")) {
|
|
1026
|
+
const text = await re.text();
|
|
1027
|
+
return parseEmptyableJSON(text);
|
|
1028
|
+
}
|
|
1029
|
+
if (contentType.startsWith("multipart/form-data")) {
|
|
1030
|
+
return await re.formData();
|
|
1031
|
+
}
|
|
1032
|
+
if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
|
1033
|
+
const text = await re.text();
|
|
1034
|
+
return new URLSearchParams(text);
|
|
1035
|
+
}
|
|
1036
|
+
if (contentType.startsWith("text/event-stream")) {
|
|
1037
|
+
return toEventIterator(re.body, options);
|
|
1038
|
+
}
|
|
1039
|
+
if (contentType.startsWith("text/plain")) {
|
|
1040
|
+
return await re.text();
|
|
1041
|
+
}
|
|
1042
|
+
const blob = await re.blob();
|
|
1043
|
+
return new File([blob], "blob", {
|
|
1044
|
+
type: blob.type
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
function toFetchBody(body, headers, options = {}) {
|
|
1049
|
+
if (body instanceof ReadableStream) {
|
|
1050
|
+
return body;
|
|
1051
|
+
}
|
|
1052
|
+
const currentContentDisposition = headers.get("content-disposition");
|
|
1053
|
+
headers.delete("content-type");
|
|
1054
|
+
headers.delete("content-disposition");
|
|
1055
|
+
if (body === undefined) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
if (body instanceof Blob) {
|
|
1059
|
+
headers.set("content-type", body.type);
|
|
1060
|
+
headers.set("content-length", body.size.toString());
|
|
1061
|
+
headers.set("content-disposition", currentContentDisposition ?? generateContentDisposition(body instanceof File ? body.name : "blob"));
|
|
1062
|
+
return body;
|
|
1063
|
+
}
|
|
1064
|
+
if (body instanceof FormData) {
|
|
1065
|
+
return body;
|
|
1066
|
+
}
|
|
1067
|
+
if (body instanceof URLSearchParams) {
|
|
1068
|
+
return body;
|
|
1069
|
+
}
|
|
1070
|
+
if (isAsyncIteratorObject(body)) {
|
|
1071
|
+
headers.set("content-type", "text/event-stream");
|
|
1072
|
+
return toEventStream(body, options);
|
|
1073
|
+
}
|
|
1074
|
+
headers.set("content-type", "application/json");
|
|
1075
|
+
return stringifyJSON(body);
|
|
1076
|
+
}
|
|
1077
|
+
function toStandardHeaders(headers, standardHeaders = {}) {
|
|
1078
|
+
headers.forEach((value, key) => {
|
|
1079
|
+
if (Array.isArray(standardHeaders[key])) {
|
|
1080
|
+
standardHeaders[key].push(value);
|
|
1081
|
+
} else if (standardHeaders[key] !== undefined) {
|
|
1082
|
+
standardHeaders[key] = [standardHeaders[key], value];
|
|
1083
|
+
} else {
|
|
1084
|
+
standardHeaders[key] = value;
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
return standardHeaders;
|
|
1088
|
+
}
|
|
1089
|
+
function toFetchHeaders(headers, fetchHeaders = new Headers) {
|
|
1090
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1091
|
+
if (Array.isArray(value)) {
|
|
1092
|
+
for (const v of value) {
|
|
1093
|
+
fetchHeaders.append(key, v);
|
|
1094
|
+
}
|
|
1095
|
+
} else if (value !== undefined) {
|
|
1096
|
+
fetchHeaders.append(key, value);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return fetchHeaders;
|
|
1100
|
+
}
|
|
1101
|
+
function toFetchRequest(request, options = {}) {
|
|
1102
|
+
const headers = toFetchHeaders(request.headers);
|
|
1103
|
+
const body = toFetchBody(request.body, headers, options);
|
|
1104
|
+
return new Request(request.url, {
|
|
1105
|
+
signal: request.signal,
|
|
1106
|
+
method: request.method,
|
|
1107
|
+
headers,
|
|
1108
|
+
body
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function toStandardLazyResponse(response, options = {}) {
|
|
1112
|
+
return {
|
|
1113
|
+
body: once(() => toStandardBody(response, options)),
|
|
1114
|
+
status: response.status,
|
|
1115
|
+
get headers() {
|
|
1116
|
+
const headers = toStandardHeaders(response.headers);
|
|
1117
|
+
Object.defineProperty(this, "headers", { value: headers, writable: true });
|
|
1118
|
+
return headers;
|
|
1119
|
+
},
|
|
1120
|
+
set headers(value) {
|
|
1121
|
+
Object.defineProperty(this, "headers", { value, writable: true });
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// node_modules/@orpc/client/dist/shared/client.BmCiDGqJ.mjs
|
|
1127
|
+
class CompositeStandardLinkPlugin {
|
|
1128
|
+
plugins;
|
|
1129
|
+
constructor(plugins = []) {
|
|
1130
|
+
this.plugins = [...plugins].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
1131
|
+
}
|
|
1132
|
+
init(options) {
|
|
1133
|
+
for (const plugin of this.plugins) {
|
|
1134
|
+
plugin.init?.(options);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
class StandardLink {
|
|
1140
|
+
constructor(codec, sender, options = {}) {
|
|
1141
|
+
this.codec = codec;
|
|
1142
|
+
this.sender = sender;
|
|
1143
|
+
const plugin = new CompositeStandardLinkPlugin(options.plugins);
|
|
1144
|
+
plugin.init(options);
|
|
1145
|
+
this.interceptors = toArray(options.interceptors);
|
|
1146
|
+
this.clientInterceptors = toArray(options.clientInterceptors);
|
|
1147
|
+
}
|
|
1148
|
+
interceptors;
|
|
1149
|
+
clientInterceptors;
|
|
1150
|
+
call(path, input, options) {
|
|
1151
|
+
return runWithSpan({ name: `${ORPC_NAME}.${path.join("/")}`, signal: options.signal }, (span) => {
|
|
1152
|
+
span?.setAttribute("rpc.system", ORPC_NAME);
|
|
1153
|
+
span?.setAttribute("rpc.method", path.join("."));
|
|
1154
|
+
if (isAsyncIteratorObject(input)) {
|
|
1155
|
+
input = asyncIteratorWithSpan({ name: "consume_event_iterator_input", signal: options.signal }, input);
|
|
1156
|
+
}
|
|
1157
|
+
return intercept(this.interceptors, { ...options, path, input }, async ({ path: path2, input: input2, ...options2 }) => {
|
|
1158
|
+
const otelConfig = getGlobalOtelConfig();
|
|
1159
|
+
let otelContext;
|
|
1160
|
+
const currentSpan = otelConfig?.trace.getActiveSpan() ?? span;
|
|
1161
|
+
if (currentSpan && otelConfig) {
|
|
1162
|
+
otelContext = otelConfig?.trace.setSpan(otelConfig.context.active(), currentSpan);
|
|
1163
|
+
}
|
|
1164
|
+
const request = await runWithSpan({ name: "encode_request", context: otelContext }, () => this.codec.encode(path2, input2, options2));
|
|
1165
|
+
const response = await intercept(this.clientInterceptors, { ...options2, input: input2, path: path2, request }, ({ input: input3, path: path3, request: request2, ...options3 }) => {
|
|
1166
|
+
return runWithSpan({ name: "send_request", signal: options3.signal, context: otelContext }, () => this.sender.call(request2, options3, path3, input3));
|
|
1167
|
+
});
|
|
1168
|
+
const output = await runWithSpan({ name: "decode_response", context: otelContext }, () => this.codec.decode(response, options2, path2, input2));
|
|
1169
|
+
if (isAsyncIteratorObject(output)) {
|
|
1170
|
+
return asyncIteratorWithSpan({ name: "consume_event_iterator_output", signal: options2.signal }, output);
|
|
1171
|
+
}
|
|
1172
|
+
return output;
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
var STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES = {
|
|
1178
|
+
BIGINT: 0,
|
|
1179
|
+
DATE: 1,
|
|
1180
|
+
NAN: 2,
|
|
1181
|
+
UNDEFINED: 3,
|
|
1182
|
+
URL: 4,
|
|
1183
|
+
REGEXP: 5,
|
|
1184
|
+
SET: 6,
|
|
1185
|
+
MAP: 7
|
|
1186
|
+
};
|
|
1187
|
+
var BUILT_IN_TYPES = new Set(Object.values(STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES));
|
|
1188
|
+
var SERIALIZED_REGEXP_FORMAT = /^\/(.*)\/([a-z]*)$/;
|
|
1189
|
+
function invalidSerializedData(detail) {
|
|
1190
|
+
return new TypeError(`Invalid RPC serialized data: ${detail}`);
|
|
1191
|
+
}
|
|
1192
|
+
function invalidSerializedType(type, name, expected) {
|
|
1193
|
+
return invalidSerializedData(`type ${type} (${name}) expects ${expected}.`);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
class StandardRPCJsonSerializer {
|
|
1197
|
+
customSerializers;
|
|
1198
|
+
constructor(options = {}) {
|
|
1199
|
+
this.customSerializers = options.customJsonSerializers ?? [];
|
|
1200
|
+
if (this.customSerializers.length !== new Set(this.customSerializers.map((custom) => custom.type)).size) {
|
|
1201
|
+
throw new Error("Custom serializer type must be unique.");
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
serialize(data, segments = [], meta = [], maps = [], blobs = []) {
|
|
1205
|
+
for (const custom of this.customSerializers) {
|
|
1206
|
+
if (custom.condition(data)) {
|
|
1207
|
+
const result = this.serialize(custom.serialize(data), segments, meta, maps, blobs);
|
|
1208
|
+
meta.push([custom.type, ...segments]);
|
|
1209
|
+
return result;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (data instanceof Blob) {
|
|
1213
|
+
maps.push(segments);
|
|
1214
|
+
blobs.push(data);
|
|
1215
|
+
return [data, meta, maps, blobs];
|
|
1216
|
+
}
|
|
1217
|
+
if (typeof data === "bigint") {
|
|
1218
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.BIGINT, ...segments]);
|
|
1219
|
+
return [data.toString(), meta, maps, blobs];
|
|
1220
|
+
}
|
|
1221
|
+
if (data instanceof Date) {
|
|
1222
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.DATE, ...segments]);
|
|
1223
|
+
if (Number.isNaN(data.getTime())) {
|
|
1224
|
+
return [null, meta, maps, blobs];
|
|
1225
|
+
}
|
|
1226
|
+
return [data.toISOString(), meta, maps, blobs];
|
|
1227
|
+
}
|
|
1228
|
+
if (Number.isNaN(data)) {
|
|
1229
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.NAN, ...segments]);
|
|
1230
|
+
return [null, meta, maps, blobs];
|
|
1231
|
+
}
|
|
1232
|
+
if (data instanceof URL) {
|
|
1233
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.URL, ...segments]);
|
|
1234
|
+
return [data.toString(), meta, maps, blobs];
|
|
1235
|
+
}
|
|
1236
|
+
if (data instanceof RegExp) {
|
|
1237
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.REGEXP, ...segments]);
|
|
1238
|
+
return [data.toString(), meta, maps, blobs];
|
|
1239
|
+
}
|
|
1240
|
+
if (data instanceof Set) {
|
|
1241
|
+
const result = this.serialize(Array.from(data), segments, meta, maps, blobs);
|
|
1242
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.SET, ...segments]);
|
|
1243
|
+
return result;
|
|
1244
|
+
}
|
|
1245
|
+
if (data instanceof Map) {
|
|
1246
|
+
const result = this.serialize(Array.from(data.entries()), segments, meta, maps, blobs);
|
|
1247
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.MAP, ...segments]);
|
|
1248
|
+
return result;
|
|
1249
|
+
}
|
|
1250
|
+
if (Array.isArray(data)) {
|
|
1251
|
+
const json = data.map((v, i) => {
|
|
1252
|
+
if (v === undefined) {
|
|
1253
|
+
meta.push([STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.UNDEFINED, ...segments, i]);
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
return this.serialize(v, [...segments, i], meta, maps, blobs)[0];
|
|
1257
|
+
});
|
|
1258
|
+
return [json, meta, maps, blobs];
|
|
1259
|
+
}
|
|
1260
|
+
if (isObject(data)) {
|
|
1261
|
+
const json = {};
|
|
1262
|
+
for (const k in data) {
|
|
1263
|
+
if (k === "toJSON" && typeof data[k] === "function") {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
json[k] = this.serialize(data[k], [...segments, k], meta, maps, blobs)[0];
|
|
1267
|
+
}
|
|
1268
|
+
return [json, meta, maps, blobs];
|
|
1269
|
+
}
|
|
1270
|
+
return [data, meta, maps, blobs];
|
|
1271
|
+
}
|
|
1272
|
+
deserialize(json, meta, maps, getBlob) {
|
|
1273
|
+
const ref = { data: json };
|
|
1274
|
+
const input = { data: json };
|
|
1275
|
+
if (maps && getBlob) {
|
|
1276
|
+
maps.forEach((segments, i) => {
|
|
1277
|
+
const blob = getBlob(i);
|
|
1278
|
+
if (!(blob instanceof Blob)) {
|
|
1279
|
+
throw invalidSerializedData(`blob ${i} is not a Blob.`);
|
|
1280
|
+
}
|
|
1281
|
+
let original = input;
|
|
1282
|
+
let currentRef = ref;
|
|
1283
|
+
let preSegment = "data";
|
|
1284
|
+
segments.forEach((segment) => {
|
|
1285
|
+
original = original[preSegment];
|
|
1286
|
+
currentRef = copyOnWrite(currentRef, preSegment, original);
|
|
1287
|
+
preSegment = segment;
|
|
1288
|
+
if (!Object.hasOwn(currentRef, preSegment)) {
|
|
1289
|
+
throw invalidSerializedData(`segment "${preSegment}" does not exist.`);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
currentRef[preSegment] = blob;
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
for (const item of meta) {
|
|
1296
|
+
const type = item[0];
|
|
1297
|
+
const custom = this.customSerializers.find((custom2) => custom2.type === type);
|
|
1298
|
+
if (custom === undefined && !BUILT_IN_TYPES.has(type)) {
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
let original = input;
|
|
1302
|
+
let currentRef = ref;
|
|
1303
|
+
let preSegment = "data";
|
|
1304
|
+
for (let i = 1;i < item.length; i++) {
|
|
1305
|
+
original = original[preSegment];
|
|
1306
|
+
currentRef = copyOnWrite(currentRef, preSegment, original);
|
|
1307
|
+
preSegment = item[i];
|
|
1308
|
+
if (!Object.hasOwn(currentRef, preSegment)) {
|
|
1309
|
+
throw invalidSerializedData(`segment "${preSegment}" does not exist.`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (custom !== undefined) {
|
|
1313
|
+
currentRef[preSegment] = custom.deserialize(currentRef[preSegment]);
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
const serialized = currentRef[preSegment];
|
|
1317
|
+
switch (type) {
|
|
1318
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.BIGINT: {
|
|
1319
|
+
if (typeof serialized !== "string") {
|
|
1320
|
+
throw invalidSerializedType(type, "bigint", "a string");
|
|
1321
|
+
}
|
|
1322
|
+
const value = parseBigIntString(serialized, DEFAULT_MAX_BIGINT_DIGITS);
|
|
1323
|
+
if (value === undefined) {
|
|
1324
|
+
throw invalidSerializedType(type, "bigint", `a decimal integer string of at most ${DEFAULT_MAX_BIGINT_DIGITS} digits`);
|
|
1325
|
+
}
|
|
1326
|
+
currentRef[preSegment] = value;
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.DATE:
|
|
1330
|
+
if (typeof serialized !== "string" && serialized !== null) {
|
|
1331
|
+
throw invalidSerializedType(type, "date", "a string or null");
|
|
1332
|
+
}
|
|
1333
|
+
currentRef[preSegment] = new Date(serialized ?? Number.NaN);
|
|
1334
|
+
break;
|
|
1335
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.NAN:
|
|
1336
|
+
if (serialized !== null) {
|
|
1337
|
+
throw invalidSerializedType(type, "nan", "null");
|
|
1338
|
+
}
|
|
1339
|
+
currentRef[preSegment] = Number.NaN;
|
|
1340
|
+
break;
|
|
1341
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.UNDEFINED:
|
|
1342
|
+
if (serialized !== null) {
|
|
1343
|
+
throw invalidSerializedType(type, "undefined", "null");
|
|
1344
|
+
}
|
|
1345
|
+
currentRef[preSegment] = undefined;
|
|
1346
|
+
break;
|
|
1347
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.URL:
|
|
1348
|
+
if (typeof serialized !== "string") {
|
|
1349
|
+
throw invalidSerializedType(type, "url", "a string");
|
|
1350
|
+
}
|
|
1351
|
+
currentRef[preSegment] = new URL(serialized);
|
|
1352
|
+
break;
|
|
1353
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.REGEXP: {
|
|
1354
|
+
if (typeof serialized !== "string") {
|
|
1355
|
+
throw invalidSerializedType(type, "regexp", "a string");
|
|
1356
|
+
}
|
|
1357
|
+
const match = serialized.match(SERIALIZED_REGEXP_FORMAT);
|
|
1358
|
+
if (match === null || !isValidRegExpFlags(match[2])) {
|
|
1359
|
+
throw invalidSerializedData(`type ${type} (regexp) expects a "/pattern/flags" string.`);
|
|
1360
|
+
}
|
|
1361
|
+
currentRef[preSegment] = createLazyRegExp(match[1], match[2]);
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.SET:
|
|
1365
|
+
if (!Array.isArray(serialized)) {
|
|
1366
|
+
throw invalidSerializedType(type, "set", "an array");
|
|
1367
|
+
}
|
|
1368
|
+
currentRef[preSegment] = new Set(serialized);
|
|
1369
|
+
break;
|
|
1370
|
+
case STANDARD_RPC_JSON_SERIALIZER_BUILT_IN_TYPES.MAP:
|
|
1371
|
+
if (!Array.isArray(serialized)) {
|
|
1372
|
+
throw invalidSerializedType(type, "map", "an array");
|
|
1373
|
+
}
|
|
1374
|
+
currentRef[preSegment] = new Map(serialized);
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return ref.data;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
function toHttpPath(path) {
|
|
1382
|
+
return `/${path.map(encodeURIComponent).join("/")}`;
|
|
1383
|
+
}
|
|
1384
|
+
function toStandardHeaders2(headers) {
|
|
1385
|
+
if (typeof headers.forEach === "function") {
|
|
1386
|
+
return toStandardHeaders(headers);
|
|
1387
|
+
}
|
|
1388
|
+
return headers;
|
|
1389
|
+
}
|
|
1390
|
+
function getMalformedResponseErrorCode(status) {
|
|
1391
|
+
return Object.entries(COMMON_ORPC_ERROR_DEFS).find(([, def]) => def.status === status)?.[0] ?? "MALFORMED_ORPC_ERROR_RESPONSE";
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
class StandardRPCLinkCodec {
|
|
1395
|
+
constructor(serializer, options) {
|
|
1396
|
+
this.serializer = serializer;
|
|
1397
|
+
this.baseUrl = options.url;
|
|
1398
|
+
this.maxUrlLength = options.maxUrlLength ?? 2083;
|
|
1399
|
+
this.fallbackMethod = options.fallbackMethod ?? "POST";
|
|
1400
|
+
this.expectedMethod = options.method ?? this.fallbackMethod;
|
|
1401
|
+
this.headers = options.headers ?? {};
|
|
1402
|
+
}
|
|
1403
|
+
baseUrl;
|
|
1404
|
+
maxUrlLength;
|
|
1405
|
+
fallbackMethod;
|
|
1406
|
+
expectedMethod;
|
|
1407
|
+
headers;
|
|
1408
|
+
async encode(path, input, options) {
|
|
1409
|
+
let headers = toStandardHeaders2(await value(this.headers, options, path, input));
|
|
1410
|
+
if (options.lastEventId !== undefined) {
|
|
1411
|
+
headers = mergeStandardHeaders(headers, { "last-event-id": options.lastEventId });
|
|
1412
|
+
}
|
|
1413
|
+
const expectedMethod = await value(this.expectedMethod, options, path, input);
|
|
1414
|
+
const baseUrl = await value(this.baseUrl, options, path, input);
|
|
1415
|
+
const url = new URL(baseUrl);
|
|
1416
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}${toHttpPath(path)}`;
|
|
1417
|
+
const serialized = this.serializer.serialize(input);
|
|
1418
|
+
if (expectedMethod === "GET" && !(serialized instanceof FormData) && !isAsyncIteratorObject(serialized)) {
|
|
1419
|
+
const maxUrlLength = await value(this.maxUrlLength, options, path, input);
|
|
1420
|
+
const getUrl = new URL(url);
|
|
1421
|
+
getUrl.searchParams.append("data", stringifyJSON(serialized));
|
|
1422
|
+
if (getUrl.toString().length <= maxUrlLength) {
|
|
1423
|
+
return {
|
|
1424
|
+
body: undefined,
|
|
1425
|
+
method: expectedMethod,
|
|
1426
|
+
headers,
|
|
1427
|
+
url: getUrl,
|
|
1428
|
+
signal: options.signal
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
url,
|
|
1434
|
+
method: expectedMethod === "GET" ? this.fallbackMethod : expectedMethod,
|
|
1435
|
+
headers,
|
|
1436
|
+
body: serialized,
|
|
1437
|
+
signal: options.signal
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
async decode(response) {
|
|
1441
|
+
if (response.status >= 300 && response.status < 400) {
|
|
1442
|
+
throw new Error(`Unexpected redirect response (status ${response.status}): RPCLink does not follow redirects.`);
|
|
1443
|
+
}
|
|
1444
|
+
const isOk = !isORPCErrorStatus(response.status);
|
|
1445
|
+
const deserialized = await (async () => {
|
|
1446
|
+
let isBodyOk = false;
|
|
1447
|
+
try {
|
|
1448
|
+
const body = await response.body();
|
|
1449
|
+
isBodyOk = true;
|
|
1450
|
+
return this.serializer.deserialize(body);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
if (!isBodyOk) {
|
|
1453
|
+
throw new Error("Cannot parse response body, please check the response body and content-type.", {
|
|
1454
|
+
cause: error
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
throw new Error("Invalid RPC response format.", {
|
|
1458
|
+
cause: error
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
})();
|
|
1462
|
+
if (!isOk) {
|
|
1463
|
+
if (isORPCErrorJson(deserialized)) {
|
|
1464
|
+
throw createORPCErrorFromJson(deserialized);
|
|
1465
|
+
}
|
|
1466
|
+
throw new ORPCError(getMalformedResponseErrorCode(response.status), {
|
|
1467
|
+
status: response.status,
|
|
1468
|
+
data: { ...response, body: deserialized }
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
return deserialized;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
class StandardRPCSerializer {
|
|
1476
|
+
constructor(jsonSerializer) {
|
|
1477
|
+
this.jsonSerializer = jsonSerializer;
|
|
1478
|
+
}
|
|
1479
|
+
serialize(data) {
|
|
1480
|
+
if (isAsyncIteratorObject(data)) {
|
|
1481
|
+
return mapEventIterator(data, {
|
|
1482
|
+
value: async (value) => this.#serialize(value, false),
|
|
1483
|
+
error: async (e) => {
|
|
1484
|
+
return new ErrorEvent({
|
|
1485
|
+
data: this.#serialize(toORPCError(e).toJSON(), false),
|
|
1486
|
+
cause: e
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
return this.#serialize(data, true);
|
|
1492
|
+
}
|
|
1493
|
+
#serialize(data, enableFormData) {
|
|
1494
|
+
const [json, meta_, maps, blobs] = this.jsonSerializer.serialize(data);
|
|
1495
|
+
const meta = meta_.length === 0 ? undefined : meta_;
|
|
1496
|
+
if (!enableFormData || blobs.length === 0) {
|
|
1497
|
+
return {
|
|
1498
|
+
json,
|
|
1499
|
+
meta
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
const form = new FormData;
|
|
1503
|
+
form.set("data", stringifyJSON({ json, meta, maps }));
|
|
1504
|
+
blobs.forEach((blob, i) => {
|
|
1505
|
+
form.set(i.toString(), blob);
|
|
1506
|
+
});
|
|
1507
|
+
return form;
|
|
1508
|
+
}
|
|
1509
|
+
deserialize(data) {
|
|
1510
|
+
if (isAsyncIteratorObject(data)) {
|
|
1511
|
+
return mapEventIterator(data, {
|
|
1512
|
+
value: async (value) => this.#deserialize(value),
|
|
1513
|
+
error: async (e) => {
|
|
1514
|
+
if (!(e instanceof ErrorEvent)) {
|
|
1515
|
+
return e;
|
|
1516
|
+
}
|
|
1517
|
+
const deserialized = this.#deserialize(e.data);
|
|
1518
|
+
if (isORPCErrorJson(deserialized)) {
|
|
1519
|
+
return createORPCErrorFromJson(deserialized, { cause: e });
|
|
1520
|
+
}
|
|
1521
|
+
return new ErrorEvent({
|
|
1522
|
+
data: deserialized,
|
|
1523
|
+
cause: e
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
return this.#deserialize(data);
|
|
1529
|
+
}
|
|
1530
|
+
#deserialize(data) {
|
|
1531
|
+
if (data === undefined) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (!(data instanceof FormData)) {
|
|
1535
|
+
return this.jsonSerializer.deserialize(data.json, data.meta ?? []);
|
|
1536
|
+
}
|
|
1537
|
+
const serialized = JSON.parse(data.get("data"));
|
|
1538
|
+
return this.jsonSerializer.deserialize(serialized.json, serialized.meta ?? [], serialized.maps, (i) => data.get(i.toString()));
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
class StandardRPCLink extends StandardLink {
|
|
1543
|
+
constructor(linkClient, options) {
|
|
1544
|
+
const jsonSerializer = new StandardRPCJsonSerializer(options);
|
|
1545
|
+
const serializer = new StandardRPCSerializer(jsonSerializer);
|
|
1546
|
+
const linkCodec = new StandardRPCLinkCodec(serializer, options);
|
|
1547
|
+
super(linkCodec, linkClient, options);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
// node_modules/@orpc/client/dist/adapters/fetch/index.mjs
|
|
1552
|
+
class CompositeLinkFetchPlugin extends CompositeStandardLinkPlugin {
|
|
1553
|
+
initRuntimeAdapter(options) {
|
|
1554
|
+
for (const plugin of this.plugins) {
|
|
1555
|
+
plugin.initRuntimeAdapter?.(options);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
class LinkFetchClient {
|
|
1561
|
+
fetch;
|
|
1562
|
+
toFetchRequestOptions;
|
|
1563
|
+
adapterInterceptors;
|
|
1564
|
+
constructor(options) {
|
|
1565
|
+
const plugin = new CompositeLinkFetchPlugin(options.plugins);
|
|
1566
|
+
plugin.initRuntimeAdapter(options);
|
|
1567
|
+
this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1568
|
+
this.toFetchRequestOptions = options;
|
|
1569
|
+
this.adapterInterceptors = toArray(options.adapterInterceptors);
|
|
1570
|
+
}
|
|
1571
|
+
async call(standardRequest, options, path, input) {
|
|
1572
|
+
const request = toFetchRequest(standardRequest, this.toFetchRequestOptions);
|
|
1573
|
+
const fetchResponse = await intercept(this.adapterInterceptors, { ...options, request, path, input, init: { redirect: "manual" } }, ({ request: request2, path: path2, input: input2, init, ...options2 }) => this.fetch(request2, init, options2, path2, input2));
|
|
1574
|
+
const lazyResponse = toStandardLazyResponse(fetchResponse, { signal: request.signal });
|
|
1575
|
+
return lazyResponse;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
class RPCLink extends StandardRPCLink {
|
|
1580
|
+
constructor(options) {
|
|
1581
|
+
const linkClient = new LinkFetchClient(options);
|
|
1582
|
+
super(linkClient, options);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// app/lib/github.ts
|
|
1587
|
+
var NAME = /^[A-Za-z0-9_.-]+$/;
|
|
1588
|
+
function parseGitHubRemote(remote) {
|
|
1589
|
+
const text = remote.trim();
|
|
1590
|
+
let path = null;
|
|
1591
|
+
const scp = /^(?:[A-Za-z0-9_.-]+@)?github\.com:(.+)$/i.exec(text);
|
|
1592
|
+
if (scp) {
|
|
1593
|
+
path = scp[1];
|
|
1594
|
+
} else {
|
|
1595
|
+
try {
|
|
1596
|
+
const url = new URL(text);
|
|
1597
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol))
|
|
1598
|
+
return null;
|
|
1599
|
+
if (url.hostname.toLowerCase() !== "github.com" && url.hostname.toLowerCase() !== "www.github.com")
|
|
1600
|
+
return null;
|
|
1601
|
+
path = url.pathname;
|
|
1602
|
+
} catch {
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
const parts = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "").split("/");
|
|
1607
|
+
if (parts.length !== 2)
|
|
1608
|
+
return null;
|
|
1609
|
+
const [owner, name] = parts;
|
|
1610
|
+
if (!NAME.test(owner) || !NAME.test(name) || name === "." || name === "..")
|
|
1611
|
+
return null;
|
|
1612
|
+
return `${owner}/${name}`;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// cli/github-read.ts
|
|
1616
|
+
import { execFile } from "node:child_process";
|
|
1617
|
+
var ISSUE_LIMIT = 300;
|
|
1618
|
+
var PR_LIMIT = 200;
|
|
1619
|
+
var ISSUE_FIELDS = "number,title,state,labels,url";
|
|
1620
|
+
var PR_FIELDS = "number,title,state,isDraft,headRefOid,headRefName,baseRefName,labels,closingIssuesReferences,author,url,mergeCommit";
|
|
1621
|
+
function mapIssue(i) {
|
|
1622
|
+
return { number: i.number, title: i.title, state: i.state === "OPEN" ? "open" : "closed", labels: i.labels.map((l) => l.name), url: i.url };
|
|
1623
|
+
}
|
|
1624
|
+
function mapPullRequest(p, repo) {
|
|
1625
|
+
const sameRepo = (url) => url === undefined || url.toLowerCase().startsWith(`https://github.com/${repo.toLowerCase()}/`);
|
|
1626
|
+
return {
|
|
1627
|
+
number: p.number,
|
|
1628
|
+
title: p.title,
|
|
1629
|
+
state: p.state === "MERGED" ? "merged" : p.state === "OPEN" ? "open" : "closed",
|
|
1630
|
+
draft: p.isDraft,
|
|
1631
|
+
headSha: p.headRefOid,
|
|
1632
|
+
headRef: p.headRefName,
|
|
1633
|
+
baseRef: p.baseRefName,
|
|
1634
|
+
labels: p.labels.map((l) => l.name),
|
|
1635
|
+
closingIssues: p.closingIssuesReferences.filter((r) => sameRepo(r.url)).map((r) => r.number),
|
|
1636
|
+
author: p.author?.login ?? "",
|
|
1637
|
+
url: p.url,
|
|
1638
|
+
mergeCommit: p.mergeCommit?.oid ?? null
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
function gh(args) {
|
|
1642
|
+
return new Promise((resolve, reject) => {
|
|
1643
|
+
execFile("gh", args, { maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
1644
|
+
if (error)
|
|
1645
|
+
reject(new Error(`gh ${args.slice(0, 2).join(" ")} failed: ${(stderr || error.message).trim()}`));
|
|
1646
|
+
else
|
|
1647
|
+
resolve(stdout);
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
async function readGitHub(repo) {
|
|
1652
|
+
const [issues, prs] = await Promise.all([
|
|
1653
|
+
gh(["issue", "list", "--repo", repo, "--state", "all", "--limit", String(ISSUE_LIMIT), "--json", ISSUE_FIELDS]),
|
|
1654
|
+
gh(["pr", "list", "--repo", repo, "--state", "all", "--limit", String(PR_LIMIT), "--json", PR_FIELDS])
|
|
1655
|
+
]);
|
|
1656
|
+
return {
|
|
1657
|
+
issues: JSON.parse(issues).map(mapIssue),
|
|
1658
|
+
pullRequests: JSON.parse(prs).map((p) => mapPullRequest(p, repo))
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// cli/repo.ts
|
|
1663
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
1664
|
+
import { readFile } from "node:fs/promises";
|
|
1665
|
+
import { join } from "node:path";
|
|
1666
|
+
var HOOKS = ["setup", "check", "deploy"];
|
|
1667
|
+
|
|
1668
|
+
class ConfigError extends Error {
|
|
1669
|
+
}
|
|
1670
|
+
function repoRoot(cwd = process.cwd()) {
|
|
1671
|
+
try {
|
|
1672
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1673
|
+
} catch {
|
|
1674
|
+
return null;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function checkoutRepo(cwd = process.cwd()) {
|
|
1678
|
+
try {
|
|
1679
|
+
const url = execFileSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
1680
|
+
return parseGitHubRemote(url);
|
|
1681
|
+
} catch {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
function parseRepoConfig(text, file = ".ahfact.json") {
|
|
1686
|
+
let raw;
|
|
1687
|
+
try {
|
|
1688
|
+
raw = JSON.parse(text);
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
throw new ConfigError(`${file} is not valid JSON: ${error.message}`);
|
|
1691
|
+
}
|
|
1692
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
1693
|
+
throw new ConfigError(`${file} must be a JSON object.`);
|
|
1694
|
+
const obj = raw;
|
|
1695
|
+
const config = { hooks: {} };
|
|
1696
|
+
if (obj["project"] !== undefined) {
|
|
1697
|
+
if (typeof obj["project"] !== "string")
|
|
1698
|
+
throw new ConfigError(`${file}: "project" must be a string.`);
|
|
1699
|
+
config.project = obj["project"];
|
|
1700
|
+
}
|
|
1701
|
+
const hooks = obj["hooks"] ?? {};
|
|
1702
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks))
|
|
1703
|
+
throw new ConfigError(`${file}: "hooks" must be an object.`);
|
|
1704
|
+
for (const [name, command] of Object.entries(hooks)) {
|
|
1705
|
+
if (!HOOKS.includes(name)) {
|
|
1706
|
+
throw new ConfigError(`${file}: unknown hook "${name}". Known hooks: ${HOOKS.join(", ")}.`);
|
|
1707
|
+
}
|
|
1708
|
+
if (typeof command !== "string" || command.trim() === "")
|
|
1709
|
+
throw new ConfigError(`${file}: hook "${name}" must be a command string.`);
|
|
1710
|
+
config.hooks[name] = command;
|
|
1711
|
+
}
|
|
1712
|
+
return config;
|
|
1713
|
+
}
|
|
1714
|
+
async function loadRepoConfig(root) {
|
|
1715
|
+
const file = join(root, ".ahfact.json");
|
|
1716
|
+
let text;
|
|
1717
|
+
try {
|
|
1718
|
+
text = await readFile(file, "utf8");
|
|
1719
|
+
} catch {
|
|
1720
|
+
return { hooks: {} };
|
|
1721
|
+
}
|
|
1722
|
+
return parseRepoConfig(text, file);
|
|
1723
|
+
}
|
|
1724
|
+
var TAIL_LINES = 40;
|
|
1725
|
+
function runHook(root, hook, command, env = {}) {
|
|
1726
|
+
const started = Date.now();
|
|
1727
|
+
return new Promise((resolve) => {
|
|
1728
|
+
const child = spawn("sh", ["-c", command], { cwd: root, env: { ...process.env, ...env }, stdio: ["ignore", "pipe", "pipe"] });
|
|
1729
|
+
let output = "";
|
|
1730
|
+
const collect = (chunk) => {
|
|
1731
|
+
process.stderr.write(chunk);
|
|
1732
|
+
output = (output + chunk.toString("utf8")).slice(-64000);
|
|
1733
|
+
};
|
|
1734
|
+
child.stdout.on("data", collect);
|
|
1735
|
+
child.stderr.on("data", collect);
|
|
1736
|
+
child.on("close", (code) => {
|
|
1737
|
+
const exitCode = code ?? 1;
|
|
1738
|
+
const tail = output.trimEnd().split(`
|
|
1739
|
+
`).slice(-TAIL_LINES).join(`
|
|
1740
|
+
`);
|
|
1741
|
+
resolve({ hook, command, ok: exitCode === 0, exitCode, durationMs: Date.now() - started, tail });
|
|
1742
|
+
});
|
|
1743
|
+
child.on("error", (error) => {
|
|
1744
|
+
resolve({ hook, command, ok: false, exitCode: 127, durationMs: Date.now() - started, tail: error.message });
|
|
1745
|
+
});
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// cli/ahfact.ts
|
|
1750
|
+
var DEFAULT_SERVER = "https://ahfact.com";
|
|
1751
|
+
var sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
1752
|
+
var HELP = `ahfact: work with the Astrohacker Factory from the command line.
|
|
1753
|
+
|
|
1754
|
+
You, once per computer:
|
|
1755
|
+
ahfact login [--machine M] sign the CLI in; approve the code in the browser
|
|
1756
|
+
ahfact logout revoke this computer's CLI token
|
|
1757
|
+
ahfact whoami who the CLI acts as, and this checkout's project and worker
|
|
1758
|
+
|
|
1759
|
+
Projects (a project is a GitHub repository):
|
|
1760
|
+
ahfact project add [--slug S] [--name N] [--repo OWNER/NAME]
|
|
1761
|
+
register this checkout's origin (or --repo)
|
|
1762
|
+
ahfact project show this checkout's project
|
|
1763
|
+
ahfact project list
|
|
1764
|
+
|
|
1765
|
+
Workers (one per project, harness and computer):
|
|
1766
|
+
ahfact worker start --harness H [--machine M]
|
|
1767
|
+
create or reuse this checkout's worker and save its token
|
|
1768
|
+
ahfact worker whoami the worker this checkout's commands act as
|
|
1769
|
+
ahfact sync [--force] read this project's GitHub issues and PRs with gh and report them;
|
|
1770
|
+
ahfact.com turns them into jobs (one worker at a time, every 60s)
|
|
1771
|
+
ahfact wait [--timeout SECONDS] [--no-sync]
|
|
1772
|
+
sync when due, then lease the next job; repeat until one comes
|
|
1773
|
+
ahfact next [--wait SECONDS] one attempt, waiting at most 55 seconds; prints null if none
|
|
1774
|
+
ahfact leases the worker's live leases, to resume after a restart
|
|
1775
|
+
ahfact heartbeat say "still here"
|
|
1776
|
+
|
|
1777
|
+
This checkout (hooks from .ahfact.json):
|
|
1778
|
+
ahfact setup run the setup hook, such as installing dependencies
|
|
1779
|
+
ahfact check run the check hook; prints {ok, exitCode, durationMs, tail}
|
|
1780
|
+
|
|
1781
|
+
Jobs:
|
|
1782
|
+
ahfact job create --kind K --subject S --title T [--harness H] [--machine M] [--exclude AGENT]...
|
|
1783
|
+
|
|
1784
|
+
Subagents, for the lease they hold:
|
|
1785
|
+
ahfact log LEASE MESSAGE report progress; renews the lease
|
|
1786
|
+
ahfact renew LEASE renew without a message
|
|
1787
|
+
ahfact ref LEASE AGENT_ID record the harness's id for this subagent
|
|
1788
|
+
ahfact done LEASE [MESSAGE] the job is finished
|
|
1789
|
+
ahfact release LEASE [MESSAGE] hand the job back to the queue
|
|
1790
|
+
|
|
1791
|
+
Options and environment:
|
|
1792
|
+
--server URL / AHFACT_URL the factory (default ${DEFAULT_SERVER})
|
|
1793
|
+
--worker NAME / AHFACT_WORKER act as this worker
|
|
1794
|
+
--harness H / AHFACT_HARNESS pick this checkout's worker by harness
|
|
1795
|
+
--machine M / AHFACT_MACHINE this computer's short name (default: the one given at login)
|
|
1796
|
+
Credentials live in ~/.config/ahfact/, mode 0600.`;
|
|
1797
|
+
|
|
1798
|
+
class UsageError extends Error {
|
|
1799
|
+
}
|
|
1800
|
+
function configDir() {
|
|
1801
|
+
return join2(process.env["XDG_CONFIG_HOME"] ?? join2(homedir(), ".config"), "ahfact");
|
|
1802
|
+
}
|
|
1803
|
+
function hostOf(server) {
|
|
1804
|
+
return new URL(server).host.replace(/[^A-Za-z0-9.-]/g, "_");
|
|
1805
|
+
}
|
|
1806
|
+
async function writePrivate(file, value) {
|
|
1807
|
+
await mkdir(join2(file, ".."), { recursive: true, mode: 448 });
|
|
1808
|
+
await writeFile(file, JSON.stringify(value, null, 2) + `
|
|
1809
|
+
`, { mode: 384 });
|
|
1810
|
+
await chmod(file, 384);
|
|
1811
|
+
}
|
|
1812
|
+
async function readJson(file) {
|
|
1813
|
+
try {
|
|
1814
|
+
return JSON.parse(await readFile2(file, "utf8"));
|
|
1815
|
+
} catch {
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
var personFile = (server) => join2(configDir(), "people", `${hostOf(server)}.json`);
|
|
1820
|
+
var workerFile = (worker) => join2(configDir(), "workers", `${worker}.json`);
|
|
1821
|
+
async function loadPerson(server) {
|
|
1822
|
+
const cred = await readJson(personFile(server));
|
|
1823
|
+
if (cred === null || cred.server !== server)
|
|
1824
|
+
throw new UsageError(`Not signed in to ${server}. Run: ahfact login`);
|
|
1825
|
+
return cred;
|
|
1826
|
+
}
|
|
1827
|
+
async function allWorkers(server) {
|
|
1828
|
+
let files = [];
|
|
1829
|
+
try {
|
|
1830
|
+
files = (await readdir(join2(configDir(), "workers"))).filter((f) => f.endsWith(".json"));
|
|
1831
|
+
} catch {}
|
|
1832
|
+
const creds = [];
|
|
1833
|
+
for (const f of files) {
|
|
1834
|
+
const cred = await readJson(join2(configDir(), "workers", f));
|
|
1835
|
+
if (cred !== null && cred.server === server)
|
|
1836
|
+
creds.push(cred);
|
|
1837
|
+
}
|
|
1838
|
+
return creds;
|
|
1839
|
+
}
|
|
1840
|
+
function requireRepo(explicit) {
|
|
1841
|
+
if (explicit) {
|
|
1842
|
+
const repo = parseGitHubRemote(explicit) ?? (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(explicit) ? explicit : null);
|
|
1843
|
+
if (repo === null)
|
|
1844
|
+
throw new UsageError(`Not a GitHub repository: ${explicit}`);
|
|
1845
|
+
return repo;
|
|
1846
|
+
}
|
|
1847
|
+
const repo = checkoutRepo();
|
|
1848
|
+
if (repo === null)
|
|
1849
|
+
throw new UsageError("This directory has no GitHub origin. Run in a checkout, or pass --repo OWNER/NAME.");
|
|
1850
|
+
return repo;
|
|
1851
|
+
}
|
|
1852
|
+
async function loadWorker(server, opts) {
|
|
1853
|
+
const all = await allWorkers(server);
|
|
1854
|
+
const named = opts.worker ?? process.env["AHFACT_WORKER"];
|
|
1855
|
+
if (named) {
|
|
1856
|
+
const found = all.find((c) => c.worker === named);
|
|
1857
|
+
if (!found)
|
|
1858
|
+
throw new UsageError(`Worker ${named} has no saved token for ${server}. Run: ahfact worker start --harness H`);
|
|
1859
|
+
return found;
|
|
1860
|
+
}
|
|
1861
|
+
const repo = checkoutRepo();
|
|
1862
|
+
const harness = opts.harness ?? process.env["AHFACT_HARNESS"];
|
|
1863
|
+
let candidates = repo === null ? all : all.filter((c) => c.repo.toLowerCase() === repo.toLowerCase());
|
|
1864
|
+
if (harness)
|
|
1865
|
+
candidates = candidates.filter((c) => c.harness === harness);
|
|
1866
|
+
if (candidates.length === 1)
|
|
1867
|
+
return candidates[0];
|
|
1868
|
+
if (candidates.length === 0) {
|
|
1869
|
+
throw new UsageError(`No worker for ${repo ?? "this directory"}${harness ? ` on ${harness}` : ""}. Run: ahfact worker start --harness H`);
|
|
1870
|
+
}
|
|
1871
|
+
throw new UsageError(`Several workers match (${candidates.map((c) => c.worker).join(", ")}). Pick one with --worker, AHFACT_WORKER or AHFACT_HARNESS.`);
|
|
1872
|
+
}
|
|
1873
|
+
function client(server, token) {
|
|
1874
|
+
const link = new RPCLink({
|
|
1875
|
+
url: `${server}/rpc`,
|
|
1876
|
+
headers: () => token ? { authorization: `Bearer ${token}` } : {}
|
|
1877
|
+
});
|
|
1878
|
+
return createORPCClient(link);
|
|
1879
|
+
}
|
|
1880
|
+
function print(value) {
|
|
1881
|
+
process.stdout.write(JSON.stringify(value, null, 2) + `
|
|
1882
|
+
`);
|
|
1883
|
+
}
|
|
1884
|
+
function hint(text) {
|
|
1885
|
+
process.stderr.write(text + `
|
|
1886
|
+
`);
|
|
1887
|
+
}
|
|
1888
|
+
function defaultMachine() {
|
|
1889
|
+
return (process.env["AHFACT_MACHINE"] ?? hostname().split(".")[0]).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
1890
|
+
}
|
|
1891
|
+
async function login(server, machineFlag) {
|
|
1892
|
+
const machine = machineFlag ?? defaultMachine();
|
|
1893
|
+
const api = client(server);
|
|
1894
|
+
const started = await api.device.start({ machine });
|
|
1895
|
+
hint(`To sign in the ahfact CLI on ${machine}, open:
|
|
1896
|
+
|
|
1897
|
+
${started.verificationUrl}
|
|
1898
|
+
|
|
1899
|
+
and check the code is ${started.userCode}. Waiting for approval…`);
|
|
1900
|
+
for (;; ) {
|
|
1901
|
+
if (Date.now() > new Date(started.expiresAt).getTime())
|
|
1902
|
+
throw new UsageError("The sign-in request expired. Run login again.");
|
|
1903
|
+
await sleep(started.interval * 1000);
|
|
1904
|
+
const polled = await api.device.poll({ deviceCode: started.deviceCode });
|
|
1905
|
+
if (polled.status === "pending")
|
|
1906
|
+
continue;
|
|
1907
|
+
if (polled.status !== "approved")
|
|
1908
|
+
throw new UsageError(`Sign-in ${polled.status}.`);
|
|
1909
|
+
const file = personFile(server);
|
|
1910
|
+
await writePrivate(file, { server, email: polled.email, machine, token: polled.token });
|
|
1911
|
+
hint(`Signed in as ${polled.email}. Token saved to ${file}.`);
|
|
1912
|
+
print({ email: polled.email, machine, server });
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
var str = (values, key) => {
|
|
1917
|
+
const v = values[key];
|
|
1918
|
+
return typeof v === "string" ? v : undefined;
|
|
1919
|
+
};
|
|
1920
|
+
async function main(argv) {
|
|
1921
|
+
const { values, positionals } = parseArgs({
|
|
1922
|
+
args: argv,
|
|
1923
|
+
allowPositionals: true,
|
|
1924
|
+
options: {
|
|
1925
|
+
server: { type: "string" },
|
|
1926
|
+
worker: { type: "string" },
|
|
1927
|
+
repo: { type: "string" },
|
|
1928
|
+
slug: { type: "string" },
|
|
1929
|
+
name: { type: "string" },
|
|
1930
|
+
harness: { type: "string" },
|
|
1931
|
+
machine: { type: "string" },
|
|
1932
|
+
wait: { type: "string" },
|
|
1933
|
+
timeout: { type: "string" },
|
|
1934
|
+
kind: { type: "string" },
|
|
1935
|
+
subject: { type: "string" },
|
|
1936
|
+
title: { type: "string" },
|
|
1937
|
+
exclude: { type: "string", multiple: true },
|
|
1938
|
+
force: { type: "boolean" },
|
|
1939
|
+
"no-sync": { type: "boolean" },
|
|
1940
|
+
help: { type: "boolean", short: "h" }
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
const [command, sub, ...rest] = positionals;
|
|
1944
|
+
if (values.help || command === undefined || command === "help") {
|
|
1945
|
+
hint(HELP);
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
const server = (str(values, "server") ?? process.env["AHFACT_URL"] ?? DEFAULT_SERVER).replace(/\/+$/, "");
|
|
1949
|
+
const asPerson = async () => client(server, (await loadPerson(server)).token);
|
|
1950
|
+
const workerCred = () => loadWorker(server, { worker: values.worker, harness: values.harness });
|
|
1951
|
+
const asWorker = async () => client(server, (await workerCred()).token);
|
|
1952
|
+
const syncOnce = async (cred, force) => {
|
|
1953
|
+
const api = client(server, cred.token);
|
|
1954
|
+
const turn = await api.worker.syncBegin({ force });
|
|
1955
|
+
if (!turn.due)
|
|
1956
|
+
return { synced: false, ...turn };
|
|
1957
|
+
const seen = await readGitHub(cred.repo);
|
|
1958
|
+
const result = await api.worker.syncReport(seen);
|
|
1959
|
+
return { synced: true, ...result };
|
|
1960
|
+
};
|
|
1961
|
+
const leaseArgs = [sub, ...rest];
|
|
1962
|
+
const leaseId = () => {
|
|
1963
|
+
const id = leaseArgs[0];
|
|
1964
|
+
if (!id)
|
|
1965
|
+
throw new UsageError(`${command} needs a lease id.`);
|
|
1966
|
+
return id;
|
|
1967
|
+
};
|
|
1968
|
+
const message = (from) => leaseArgs.slice(from).join(" ");
|
|
1969
|
+
switch (command) {
|
|
1970
|
+
case "login":
|
|
1971
|
+
return login(server, values.machine);
|
|
1972
|
+
case "logout": {
|
|
1973
|
+
const cred = await readJson(personFile(server));
|
|
1974
|
+
if (cred !== null) {
|
|
1975
|
+
try {
|
|
1976
|
+
await client(server, cred.token).auth.logout();
|
|
1977
|
+
} catch {}
|
|
1978
|
+
await rm(personFile(server), { force: true });
|
|
1979
|
+
}
|
|
1980
|
+
return print({ signedOut: cred !== null, server });
|
|
1981
|
+
}
|
|
1982
|
+
case "setup":
|
|
1983
|
+
case "check": {
|
|
1984
|
+
const root = repoRoot();
|
|
1985
|
+
if (root === null)
|
|
1986
|
+
throw new UsageError(`${command} runs in a Git checkout.`);
|
|
1987
|
+
const config = await loadRepoConfig(root);
|
|
1988
|
+
const hook = command;
|
|
1989
|
+
const script = config.hooks[hook];
|
|
1990
|
+
if (!script)
|
|
1991
|
+
throw new UsageError(`${root}/.ahfact.json has no "${hook}" hook.`);
|
|
1992
|
+
hint(`ahfact ${hook}: ${script}`);
|
|
1993
|
+
const result = await runHook(root, hook, script);
|
|
1994
|
+
print(result);
|
|
1995
|
+
if (!result.ok)
|
|
1996
|
+
process.exit(1);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
case "whoami": {
|
|
2000
|
+
const cred = await readJson(personFile(server));
|
|
2001
|
+
const person = cred === null ? null : await client(server, cred.token).auth.me().catch(() => null);
|
|
2002
|
+
const repo = checkoutRepo();
|
|
2003
|
+
const workers = (await allWorkers(server)).filter((c) => repo !== null && c.repo.toLowerCase() === repo.toLowerCase());
|
|
2004
|
+
return print({ server, person: person && { email: person.email, machine: cred.machine }, repo, workers: workers.map((w) => w.worker) });
|
|
2005
|
+
}
|
|
2006
|
+
case "project": {
|
|
2007
|
+
const api = await asPerson();
|
|
2008
|
+
if (sub === "add") {
|
|
2009
|
+
return print(await api.projects.add({ repo: requireRepo(values.repo), slug: values.slug, name: values.name }));
|
|
2010
|
+
}
|
|
2011
|
+
if (sub === "show")
|
|
2012
|
+
return print(await api.projects.get({ repo: requireRepo(values.repo) }));
|
|
2013
|
+
if (sub === "list")
|
|
2014
|
+
return print(await api.projects.list());
|
|
2015
|
+
throw new UsageError("Usage: ahfact project add|show|list");
|
|
2016
|
+
}
|
|
2017
|
+
case "worker": {
|
|
2018
|
+
if (sub === "whoami")
|
|
2019
|
+
return print(await (await asWorker()).worker.whoami());
|
|
2020
|
+
if (sub !== "start")
|
|
2021
|
+
throw new UsageError("Usage: ahfact worker start --harness H | ahfact worker whoami");
|
|
2022
|
+
const cred = await loadPerson(server);
|
|
2023
|
+
const harness = values.harness ?? process.env["AHFACT_HARNESS"];
|
|
2024
|
+
if (!harness)
|
|
2025
|
+
throw new UsageError("worker start needs --harness (claude, codex, grok, …) or AHFACT_HARNESS.");
|
|
2026
|
+
const machine = values.machine ?? process.env["AHFACT_MACHINE"] ?? cred.machine;
|
|
2027
|
+
const repo = requireRepo(values.repo);
|
|
2028
|
+
const started = await client(server, cred.token).workers.start({ project: { repo }, harness, machine });
|
|
2029
|
+
const file = workerFile(started.worker);
|
|
2030
|
+
await writePrivate(file, { server, worker: started.worker, token: started.token, repo, harness, machine });
|
|
2031
|
+
hint(`Worker ${started.worker} is ready. Token saved to ${file}. Next: ahfact wait`);
|
|
2032
|
+
return print({ worker: started.worker, repo, harness, machine });
|
|
2033
|
+
}
|
|
2034
|
+
case "heartbeat":
|
|
2035
|
+
return print(await (await asWorker()).worker.heartbeat());
|
|
2036
|
+
case "leases":
|
|
2037
|
+
return print(await (await asWorker()).worker.leases());
|
|
2038
|
+
case "next":
|
|
2039
|
+
return print(await (await asWorker()).worker.next({ wait: Number(values.wait ?? 0) }));
|
|
2040
|
+
case "sync": {
|
|
2041
|
+
const cred = await workerCred();
|
|
2042
|
+
return print(await syncOnce(cred, values.force === true));
|
|
2043
|
+
}
|
|
2044
|
+
case "wait": {
|
|
2045
|
+
const cred = await workerCred();
|
|
2046
|
+
const api = client(server, cred.token);
|
|
2047
|
+
const timeout = values.timeout === undefined ? Infinity : Number(values.timeout) * 1000;
|
|
2048
|
+
const deadline = Date.now() + timeout;
|
|
2049
|
+
for (;; ) {
|
|
2050
|
+
if (!values["no-sync"]) {
|
|
2051
|
+
try {
|
|
2052
|
+
const result = await syncOnce(cred, false);
|
|
2053
|
+
if (result.synced && result.applied.length > 0) {
|
|
2054
|
+
hint(`ahfact: synced GitHub: ${result.applied.map((a) => `${a.type} ${a.subject}`).join(", ")}`);
|
|
2055
|
+
}
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
hint(`ahfact: sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
const left = Math.ceil((deadline - Date.now()) / 1000);
|
|
2061
|
+
const claim = await api.worker.next({ wait: Math.max(0, Math.min(55, left)) });
|
|
2062
|
+
if (claim !== null)
|
|
2063
|
+
return print(claim);
|
|
2064
|
+
if (Date.now() >= deadline)
|
|
2065
|
+
return print(null);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
case "job": {
|
|
2069
|
+
if (sub !== "create")
|
|
2070
|
+
throw new UsageError("Usage: ahfact job create --kind K --subject S --title T");
|
|
2071
|
+
if (!values.kind || !values.subject || !values.title)
|
|
2072
|
+
throw new UsageError("job create needs --kind, --subject and --title.");
|
|
2073
|
+
const job = {
|
|
2074
|
+
kind: values.kind,
|
|
2075
|
+
subject: values.subject,
|
|
2076
|
+
title: values.title,
|
|
2077
|
+
requirements: { harness: values.harness, machine: values.machine },
|
|
2078
|
+
excludedAgents: values.exclude ?? []
|
|
2079
|
+
};
|
|
2080
|
+
if (values.worker ?? process.env["AHFACT_WORKER"])
|
|
2081
|
+
return print(await (await asWorker()).worker.createJob(job));
|
|
2082
|
+
return print(await (await asPerson()).jobs.create({ ...job, project: { repo: requireRepo(values.repo) } }));
|
|
2083
|
+
}
|
|
2084
|
+
case "log": {
|
|
2085
|
+
const text = message(1);
|
|
2086
|
+
if (text === "")
|
|
2087
|
+
throw new UsageError("log needs a message.");
|
|
2088
|
+
return print(await (await asWorker()).lease.log({ leaseId: leaseId(), message: text }));
|
|
2089
|
+
}
|
|
2090
|
+
case "renew":
|
|
2091
|
+
return print(await (await asWorker()).lease.renew({ leaseId: leaseId() }));
|
|
2092
|
+
case "ref": {
|
|
2093
|
+
const ref = leaseArgs[1];
|
|
2094
|
+
if (!ref)
|
|
2095
|
+
throw new UsageError("ref needs the subagent's id.");
|
|
2096
|
+
return print(await (await asWorker()).lease.setAgentRef({ leaseId: leaseId(), ref }));
|
|
2097
|
+
}
|
|
2098
|
+
case "done":
|
|
2099
|
+
return print(await (await asWorker()).lease.done({ leaseId: leaseId(), message: message(1) }));
|
|
2100
|
+
case "release":
|
|
2101
|
+
return print(await (await asWorker()).lease.release({ leaseId: leaseId(), message: message(1) }));
|
|
2102
|
+
default:
|
|
2103
|
+
throw new UsageError(`Unknown command ${command}. Run: ahfact help`);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
try {
|
|
2107
|
+
await main(process.argv.slice(2));
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
if (error instanceof UsageError || error instanceof ConfigError) {
|
|
2110
|
+
hint(`ahfact: ${error.message}`);
|
|
2111
|
+
process.exit(2);
|
|
2112
|
+
}
|
|
2113
|
+
if (error instanceof ORPCError) {
|
|
2114
|
+
process.stderr.write(JSON.stringify({ error: error.code, message: error.message }) + `
|
|
2115
|
+
`);
|
|
2116
|
+
process.exit(1);
|
|
2117
|
+
}
|
|
2118
|
+
if (error instanceof Error && /fetch|ECONNREFUSED|Unable to connect/i.test(error.message)) {
|
|
2119
|
+
hint(`ahfact: cannot reach the factory: ${error.message}`);
|
|
2120
|
+
process.exit(3);
|
|
2121
|
+
}
|
|
2122
|
+
if (error instanceof Error && error.message.startsWith("gh ")) {
|
|
2123
|
+
hint(`ahfact: ${error.message}`);
|
|
2124
|
+
process.exit(4);
|
|
2125
|
+
}
|
|
2126
|
+
throw error;
|
|
2127
|
+
}
|