devframe 0.0.0 → 0.1.17

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.
Files changed (75) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +62 -0
  3. package/dist/_shared-Bxa2_kq7.mjs +20 -0
  4. package/dist/adapters/build.d.mts +37 -0
  5. package/dist/adapters/build.mjs +68 -0
  6. package/dist/adapters/cli.d.mts +35 -0
  7. package/dist/adapters/cli.mjs +357 -0
  8. package/dist/adapters/embedded.d.mts +19 -0
  9. package/dist/adapters/embedded.mjs +15 -0
  10. package/dist/adapters/kit.d.mts +23 -0
  11. package/dist/adapters/kit.mjs +16 -0
  12. package/dist/adapters/mcp.d.mts +39 -0
  13. package/dist/adapters/mcp.mjs +278 -0
  14. package/dist/adapters/vite.d.mts +32 -0
  15. package/dist/adapters/vite.mjs +31 -0
  16. package/dist/client/index.d.mts +228 -0
  17. package/dist/client/index.mjs +2 -0
  18. package/dist/client-CsR1_h3o.mjs +1569 -0
  19. package/dist/constants.d.mts +20 -0
  20. package/dist/constants.mjs +34 -0
  21. package/dist/context-xQo1FcxX.mjs +6827 -0
  22. package/dist/define-CW9gLnyG.mjs +11 -0
  23. package/dist/devtool-CHT-4_OU.d.mts +1233 -0
  24. package/dist/helpers/nuxt/index.d.mts +46 -0
  25. package/dist/helpers/nuxt/index.mjs +58 -0
  26. package/dist/helpers/nuxt/runtime/plugin.client.d.mts +8 -0
  27. package/dist/helpers/nuxt/runtime/plugin.client.mjs +12 -0
  28. package/dist/human-id-CHS0s28X.mjs +844 -0
  29. package/dist/immer-DEqg5kOd.mjs +894 -0
  30. package/dist/index-Cei8vVcd.d.mts +140 -0
  31. package/dist/index.d.mts +10 -0
  32. package/dist/index.mjs +2 -0
  33. package/dist/main-BJD9t8dk.mjs +601 -0
  34. package/dist/node/index.d.mts +393 -0
  35. package/dist/node/index.mjs +69 -0
  36. package/dist/open-BMO2_-wC.mjs +533 -0
  37. package/dist/recipes/open-helpers.d.mts +108 -0
  38. package/dist/recipes/open-helpers.mjs +69 -0
  39. package/dist/rpc/client.d.mts +9 -0
  40. package/dist/rpc/client.mjs +13 -0
  41. package/dist/rpc/index.d.mts +3 -0
  42. package/dist/rpc/index.mjs +4 -0
  43. package/dist/rpc/server.d.mts +8 -0
  44. package/dist/rpc/server.mjs +10 -0
  45. package/dist/rpc/transports/ws-client.d.mts +2 -0
  46. package/dist/rpc/transports/ws-client.mjs +58 -0
  47. package/dist/rpc/transports/ws-server.d.mts +2 -0
  48. package/dist/rpc/transports/ws-server.mjs +73 -0
  49. package/dist/rpc-hbRk8qtt.mjs +456 -0
  50. package/dist/serialization-CWcWE7x7.mjs +112 -0
  51. package/dist/server-DkJ2-s0Y.mjs +49 -0
  52. package/dist/src-DIKqQIjp.mjs +85 -0
  53. package/dist/static-dump-DwFfj4U_.mjs +97 -0
  54. package/dist/transports-4bqw6Fqg.mjs +15 -0
  55. package/dist/types/index.d.mts +4 -0
  56. package/dist/types/index.mjs +6 -0
  57. package/dist/types-BkyzI9r4.d.mts +273 -0
  58. package/dist/utils/events.d.mts +9 -0
  59. package/dist/utils/events.mjs +40 -0
  60. package/dist/utils/human-id.d.mts +10 -0
  61. package/dist/utils/human-id.mjs +3 -0
  62. package/dist/utils/nanoid.d.mts +4 -0
  63. package/dist/utils/nanoid.mjs +10 -0
  64. package/dist/utils/promise.d.mts +8 -0
  65. package/dist/utils/promise.mjs +15 -0
  66. package/dist/utils/shared-state.d.mts +2 -0
  67. package/dist/utils/shared-state.mjs +36 -0
  68. package/dist/utils/state.d.mts +49 -0
  69. package/dist/utils/state.mjs +26 -0
  70. package/dist/utils/when.d.mts +2 -0
  71. package/dist/utils/when.mjs +424 -0
  72. package/dist/when-aBBXAEh5.d.mts +401 -0
  73. package/dist/ws-client-CDGmViwt.d.mts +26 -0
  74. package/dist/ws-server-Cu8tmZcF.d.mts +49 -0
  75. package/package.json +116 -8
@@ -0,0 +1,456 @@
1
+ import { c as logger } from "./serialization-CWcWE7x7.mjs";
2
+ import { hash } from "ohash";
3
+ //#region src/rpc/cache.ts
4
+ /**
5
+ * @experimental API is expected to change.
6
+ */
7
+ var RpcCacheManager = class {
8
+ cacheMap = /* @__PURE__ */ new Map();
9
+ options;
10
+ keySerializer;
11
+ constructor(options) {
12
+ this.options = options;
13
+ this.keySerializer = options.keySerializer || ((args) => hash(args));
14
+ }
15
+ updateOptions(options) {
16
+ this.options = {
17
+ ...this.options,
18
+ ...options
19
+ };
20
+ }
21
+ cached(m, a) {
22
+ const methodCache = this.cacheMap.get(m);
23
+ if (methodCache) return methodCache.get(this.keySerializer(a));
24
+ }
25
+ apply(req, res) {
26
+ const methodCache = this.cacheMap.get(req.m) || /* @__PURE__ */ new Map();
27
+ methodCache.set(this.keySerializer(req.a), res);
28
+ this.cacheMap.set(req.m, methodCache);
29
+ }
30
+ validate(m) {
31
+ return this.options.functions.includes(m);
32
+ }
33
+ clear(fn) {
34
+ if (fn) this.cacheMap.delete(fn);
35
+ else this.cacheMap.clear();
36
+ }
37
+ };
38
+ //#endregion
39
+ //#region src/rpc/handler.ts
40
+ async function getRpcResolvedSetupResult(definition, context) {
41
+ if (definition.__resolved) return definition.__resolved;
42
+ if (!definition.setup) return {};
43
+ definition.__promise ??= Promise.resolve(definition.setup(context)).then((r) => {
44
+ definition.__resolved = r;
45
+ definition.__promise = void 0;
46
+ return r;
47
+ });
48
+ return definition.__resolved ??= await definition.__promise;
49
+ }
50
+ async function getRpcHandler(definition, context) {
51
+ if (definition.handler) return definition.handler;
52
+ const result = await getRpcResolvedSetupResult(definition, context);
53
+ if (!result.handler) throw logger.DF0024({ name: definition.name }).throw();
54
+ return result.handler;
55
+ }
56
+ //#endregion
57
+ //#region src/rpc/collector.ts
58
+ var RpcFunctionsCollectorBase = class {
59
+ definitions = /* @__PURE__ */ new Map();
60
+ functions;
61
+ _onChanged = [];
62
+ constructor(context) {
63
+ this.context = context;
64
+ const definitions = this.definitions;
65
+ const self = this;
66
+ this.functions = new Proxy({}, {
67
+ get(_, prop) {
68
+ const definition = definitions.get(prop);
69
+ if (!definition) return void 0;
70
+ return getRpcHandler(definition, self.context);
71
+ },
72
+ has(_, prop) {
73
+ return definitions.has(prop);
74
+ },
75
+ getOwnPropertyDescriptor(_, prop) {
76
+ return {
77
+ value: definitions.get(prop)?.handler,
78
+ configurable: true,
79
+ enumerable: true
80
+ };
81
+ },
82
+ ownKeys() {
83
+ return Array.from(definitions.keys());
84
+ }
85
+ });
86
+ }
87
+ register(fn, force = false) {
88
+ if (this.definitions.has(fn.name) && !force) throw logger.DF0021({ name: fn.name }).throw();
89
+ assertAgentJsonSerializable(fn);
90
+ this.definitions.set(fn.name, fn);
91
+ this._onChanged.forEach((cb) => cb(fn.name));
92
+ }
93
+ update(fn, force = false) {
94
+ if (!this.definitions.has(fn.name) && !force) throw logger.DF0022({ name: fn.name }).throw();
95
+ assertAgentJsonSerializable(fn);
96
+ this.definitions.set(fn.name, fn);
97
+ this._onChanged.forEach((cb) => cb(fn.name));
98
+ }
99
+ onChanged(fn) {
100
+ this._onChanged.push(fn);
101
+ return () => {
102
+ const index = this._onChanged.indexOf(fn);
103
+ if (index !== -1) this._onChanged.splice(index, 1);
104
+ };
105
+ }
106
+ async getHandler(name) {
107
+ return await getRpcHandler(this.definitions.get(name), this.context);
108
+ }
109
+ getSchema(name) {
110
+ const definition = this.definitions.get(name);
111
+ if (!definition) throw logger.DF0023({ name: String(name) }).throw();
112
+ return {
113
+ args: definition.args,
114
+ returns: definition.returns
115
+ };
116
+ }
117
+ has(name) {
118
+ return this.definitions.has(name);
119
+ }
120
+ get(name) {
121
+ return this.definitions.get(name);
122
+ }
123
+ list() {
124
+ return Array.from(this.definitions.keys());
125
+ }
126
+ };
127
+ function assertAgentJsonSerializable(fn) {
128
+ if (fn.agent && fn.jsonSerializable !== true) throw logger.DF0019({ name: fn.name }).throw();
129
+ }
130
+ //#endregion
131
+ //#region ../../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js
132
+ var Node = class {
133
+ value;
134
+ next;
135
+ constructor(value) {
136
+ this.value = value;
137
+ }
138
+ };
139
+ var Queue = class {
140
+ #head;
141
+ #tail;
142
+ #size;
143
+ constructor() {
144
+ this.clear();
145
+ }
146
+ enqueue(value) {
147
+ const node = new Node(value);
148
+ if (this.#head) {
149
+ this.#tail.next = node;
150
+ this.#tail = node;
151
+ } else {
152
+ this.#head = node;
153
+ this.#tail = node;
154
+ }
155
+ this.#size++;
156
+ }
157
+ dequeue() {
158
+ const current = this.#head;
159
+ if (!current) return;
160
+ this.#head = this.#head.next;
161
+ this.#size--;
162
+ if (!this.#head) this.#tail = void 0;
163
+ return current.value;
164
+ }
165
+ peek() {
166
+ if (!this.#head) return;
167
+ return this.#head.value;
168
+ }
169
+ clear() {
170
+ this.#head = void 0;
171
+ this.#tail = void 0;
172
+ this.#size = 0;
173
+ }
174
+ get size() {
175
+ return this.#size;
176
+ }
177
+ *[Symbol.iterator]() {
178
+ let current = this.#head;
179
+ while (current) {
180
+ yield current.value;
181
+ current = current.next;
182
+ }
183
+ }
184
+ *drain() {
185
+ while (this.#head) yield this.dequeue();
186
+ }
187
+ };
188
+ //#endregion
189
+ //#region ../../../node_modules/.pnpm/p-limit@7.3.0/node_modules/p-limit/index.js
190
+ function pLimit(concurrency) {
191
+ let rejectOnClear = false;
192
+ if (typeof concurrency === "object") ({concurrency, rejectOnClear = false} = concurrency);
193
+ validateConcurrency(concurrency);
194
+ if (typeof rejectOnClear !== "boolean") throw new TypeError("Expected `rejectOnClear` to be a boolean");
195
+ const queue = new Queue();
196
+ let activeCount = 0;
197
+ const resumeNext = () => {
198
+ if (activeCount < concurrency && queue.size > 0) {
199
+ activeCount++;
200
+ queue.dequeue().run();
201
+ }
202
+ };
203
+ const next = () => {
204
+ activeCount--;
205
+ resumeNext();
206
+ };
207
+ const run = async (function_, resolve, arguments_) => {
208
+ const result = (async () => function_(...arguments_))();
209
+ resolve(result);
210
+ try {
211
+ await result;
212
+ } catch {}
213
+ next();
214
+ };
215
+ const enqueue = (function_, resolve, reject, arguments_) => {
216
+ const queueItem = { reject };
217
+ new Promise((internalResolve) => {
218
+ queueItem.run = internalResolve;
219
+ queue.enqueue(queueItem);
220
+ }).then(run.bind(void 0, function_, resolve, arguments_));
221
+ if (activeCount < concurrency) resumeNext();
222
+ };
223
+ const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
224
+ enqueue(function_, resolve, reject, arguments_);
225
+ });
226
+ Object.defineProperties(generator, {
227
+ activeCount: { get: () => activeCount },
228
+ pendingCount: { get: () => queue.size },
229
+ clearQueue: { value() {
230
+ if (!rejectOnClear) {
231
+ queue.clear();
232
+ return;
233
+ }
234
+ const abortError = AbortSignal.abort().reason;
235
+ while (queue.size > 0) queue.dequeue().reject(abortError);
236
+ } },
237
+ concurrency: {
238
+ get: () => concurrency,
239
+ set(newConcurrency) {
240
+ validateConcurrency(newConcurrency);
241
+ concurrency = newConcurrency;
242
+ queueMicrotask(() => {
243
+ while (activeCount < concurrency && queue.size > 0) resumeNext();
244
+ });
245
+ }
246
+ },
247
+ map: { async value(iterable, function_) {
248
+ const promises = Array.from(iterable, (value, index) => this(function_, value, index));
249
+ return Promise.all(promises);
250
+ } }
251
+ });
252
+ return generator;
253
+ }
254
+ function validateConcurrency(concurrency) {
255
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) throw new TypeError("Expected `concurrency` to be a number from 1 and up");
256
+ }
257
+ //#endregion
258
+ //#region src/rpc/validation.ts
259
+ /**
260
+ * Validates RPC function definitions.
261
+ * Action and event functions cannot have dumps (side effects should not be cached).
262
+ *
263
+ * @throws {Error} If an action or event function has a dump configuration
264
+ */
265
+ function validateDefinitions(definitions) {
266
+ for (const definition of definitions) {
267
+ const type = definition.type || "query";
268
+ if ((type === "action" || type === "event") && definition.dump) throw logger.DF0027({
269
+ name: definition.name,
270
+ type
271
+ }).throw();
272
+ if (definition.snapshot && type !== "query") throw logger.DF0028({
273
+ name: definition.name,
274
+ type
275
+ }).throw();
276
+ }
277
+ }
278
+ /**
279
+ * Validates a single RPC function definition.
280
+ *
281
+ * @throws {Error} If an action or event function has a dump configuration
282
+ */
283
+ function validateDefinition(definition) {
284
+ validateDefinitions([definition]);
285
+ }
286
+ //#endregion
287
+ //#region src/rpc/dumps.ts
288
+ function getDumpRecordKey(functionName, args) {
289
+ return `${functionName}---${hash(args)}`;
290
+ }
291
+ function getDumpFallbackKey(functionName) {
292
+ return `${functionName}---fallback`;
293
+ }
294
+ async function resolveGetter(valueOrGetter) {
295
+ return typeof valueOrGetter === "function" ? await valueOrGetter() : valueOrGetter;
296
+ }
297
+ /**
298
+ * Collects pre-computed dumps by executing functions with their defined input combinations.
299
+ * Static functions without dump config automatically get `{ inputs: [[]] }`.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * const store = await dumpFunctions([greet], context, { concurrency: 10 })
304
+ * ```
305
+ */
306
+ async function dumpFunctions(definitions, context, options = {}) {
307
+ validateDefinitions(definitions);
308
+ const concurrency = options.concurrency === true ? 5 : options.concurrency === false || options.concurrency == null ? 1 : options.concurrency;
309
+ const store = {
310
+ definitions: {},
311
+ records: {}
312
+ };
313
+ const tasksResolutions = definitions.map((definition) => async () => {
314
+ if (definition.type === "event" || definition.type === "action") return;
315
+ const setupResult = definition.setup ? await Promise.resolve(definition.setup(context)) : {};
316
+ const handler = setupResult.handler || definition.handler;
317
+ if (!handler) throw logger.DF0024({ name: definition.name }).throw();
318
+ let dump = setupResult.dump ?? definition.dump;
319
+ if (!dump && definition.type === "static") dump = { inputs: [[]] };
320
+ if (!dump && definition.snapshot) dump = async (_ctx, h) => {
321
+ const output = await Promise.resolve(h(...[]));
322
+ return {
323
+ records: [{
324
+ inputs: [],
325
+ output
326
+ }],
327
+ fallback: output
328
+ };
329
+ };
330
+ if (!dump) return;
331
+ if (typeof dump === "function") dump = await Promise.resolve(dump(context, handler));
332
+ store.definitions[definition.name] = {
333
+ name: definition.name,
334
+ type: definition.type
335
+ };
336
+ return {
337
+ handler,
338
+ dump,
339
+ definition
340
+ };
341
+ });
342
+ let functionsToDump = [];
343
+ if (concurrency <= 1) for (const task of tasksResolutions) {
344
+ const resolution = await task();
345
+ if (resolution) functionsToDump.push(resolution);
346
+ }
347
+ else {
348
+ const limit = pLimit(concurrency);
349
+ functionsToDump = (await Promise.all(tasksResolutions.map((task) => limit(task)))).filter((x) => !!x);
350
+ }
351
+ const dumpTasks = [];
352
+ for (const { definition, handler, dump } of functionsToDump) {
353
+ const { inputs, records, fallback } = dump;
354
+ if (records) for (const record of records) {
355
+ const recordKey = getDumpRecordKey(definition.name, record.inputs);
356
+ store.records[recordKey] = record;
357
+ }
358
+ if ("fallback" in dump) {
359
+ const fallbackKey = getDumpFallbackKey(definition.name);
360
+ store.records[fallbackKey] = {
361
+ inputs: [],
362
+ output: fallback
363
+ };
364
+ }
365
+ if (inputs) for (const input of inputs) dumpTasks.push(async () => {
366
+ const recordKey = getDumpRecordKey(definition.name, input);
367
+ try {
368
+ const output = await Promise.resolve(handler(...input));
369
+ store.records[recordKey] = {
370
+ inputs: input,
371
+ output
372
+ };
373
+ } catch (error) {
374
+ store.records[recordKey] = {
375
+ inputs: input,
376
+ error: {
377
+ message: error.message,
378
+ name: error.name
379
+ }
380
+ };
381
+ }
382
+ });
383
+ }
384
+ if (concurrency <= 1) for (const task of dumpTasks) await task();
385
+ else {
386
+ const limit = pLimit(concurrency);
387
+ await Promise.all(dumpTasks.map((task) => limit(task)));
388
+ }
389
+ return store;
390
+ }
391
+ /**
392
+ * Creates a client that serves pre-computed results from a dump store.
393
+ * Uses argument hashing to match calls to stored records.
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * const client = createClientFromDump(store)
398
+ * await client.greet('Alice')
399
+ * ```
400
+ */
401
+ function createClientFromDump(store, options = {}) {
402
+ const { onMiss } = options;
403
+ return new Proxy({}, {
404
+ get(_, functionName) {
405
+ if (!(functionName in store.definitions)) throw logger.DF0025({ name: functionName }).throw();
406
+ return async (...args) => {
407
+ const recordKey = getDumpRecordKey(functionName, args);
408
+ const recordOrGetter = store.records[recordKey];
409
+ if (recordOrGetter) {
410
+ const record = await resolveGetter(recordOrGetter);
411
+ if (record.error) {
412
+ const error = new Error(record.error.message);
413
+ error.name = record.error.name;
414
+ throw error;
415
+ }
416
+ if (typeof record.output === "function") return await record.output();
417
+ return record.output;
418
+ }
419
+ onMiss?.(functionName, args);
420
+ const fallbackKey = getDumpFallbackKey(functionName);
421
+ if (fallbackKey in store.records) {
422
+ const fallbackOrGetter = store.records[fallbackKey];
423
+ const fallbackRecord = await resolveGetter(fallbackOrGetter);
424
+ if (fallbackRecord && typeof fallbackRecord.output === "function") return await fallbackRecord.output();
425
+ if (fallbackRecord) return fallbackRecord.output;
426
+ }
427
+ throw logger.DF0026({
428
+ name: functionName,
429
+ args: JSON.stringify(args)
430
+ }).throw();
431
+ };
432
+ },
433
+ has(_, functionName) {
434
+ return functionName in store.definitions;
435
+ },
436
+ ownKeys() {
437
+ return Object.keys(store.definitions);
438
+ },
439
+ getOwnPropertyDescriptor(_, functionName) {
440
+ return functionName in store.definitions ? {
441
+ configurable: true,
442
+ enumerable: true,
443
+ value: void 0
444
+ } : void 0;
445
+ }
446
+ });
447
+ }
448
+ /**
449
+ * Filters function definitions to only those with dump definitions.
450
+ * Note: Only checks the definition itself, not setup results.
451
+ */
452
+ function getDefinitionsWithDumps(definitions) {
453
+ return definitions.filter((def) => def.dump !== void 0);
454
+ }
455
+ //#endregion
456
+ export { validateDefinitions as a, getRpcResolvedSetupResult as c, validateDefinition as i, RpcCacheManager as l, dumpFunctions as n, RpcFunctionsCollectorBase as o, getDefinitionsWithDumps as r, getRpcHandler as s, createClientFromDump as t };
@@ -0,0 +1,112 @@
1
+ import { consoleReporter, createLogger, defineDiagnostics, plainFormatter } from "logs-sdk";
2
+ import { deserialize as structuredCloneDeserialize, parse as structuredCloneParse, serialize as structuredCloneSerialize, stringify as structuredCloneStringify } from "structured-clone-es";
3
+ //#region src/rpc/diagnostics.ts
4
+ const diagnostics = defineDiagnostics({
5
+ docsBase: "https://devtools.vite.dev/devframe/errors",
6
+ codes: {
7
+ DF0019: {
8
+ message: (p) => `RPC function "${p.name}" has \`agent\` set but \`jsonSerializable\` is not \`true\` — MCP requires JSON-serializable data.`,
9
+ hint: "Set `jsonSerializable: true` if the payload is JSON-safe, or remove `agent` to keep it RPC-only."
10
+ },
11
+ DF0020: {
12
+ message: (p) => `RPC function "${p.name}" declares \`jsonSerializable: true\` but the value at "${p.path}" is a ${p.type}.`,
13
+ hint: "Either drop `jsonSerializable: true` (falls back to structured-clone) or change the value to a JSON-safe shape."
14
+ },
15
+ DF0021: {
16
+ message: (p) => `RPC function "${p.name}" is already registered`,
17
+ hint: "Use the `force` parameter to overwrite an existing registration."
18
+ },
19
+ DF0022: { message: (p) => `RPC function "${p.name}" is not registered. Use register() to add new functions.` },
20
+ DF0023: { message: (p) => `RPC function "${p.name}" is not registered` },
21
+ DF0024: { message: (p) => `Either handler or setup function must be provided for RPC function "${p.name}"` },
22
+ DF0025: { message: (p) => `Function "${p.name}" not found in dump store` },
23
+ DF0026: { message: (p) => `No dump match for "${p.name}" with args: ${p.args}` },
24
+ DF0027: { message: (p) => `Function "${p.name}" with type "${p.type}" cannot have dump configuration. Only "static" and "query" types support dumps.` },
25
+ DF0028: {
26
+ message: (p) => `Function "${p.name}" with type "${p.type}" cannot use \`snapshot: true\`. Only "query" functions support this sugar; "static" functions have equivalent default behavior already.`,
27
+ hint: "Remove `snapshot: true`, or change the function type to `query`."
28
+ }
29
+ }
30
+ });
31
+ const logger = createLogger({
32
+ diagnostics: [diagnostics],
33
+ formatter: plainFormatter,
34
+ reporters: consoleReporter
35
+ });
36
+ //#endregion
37
+ //#region src/rpc/serialization.ts
38
+ /**
39
+ * Wire format used by the WS RPC transport.
40
+ *
41
+ * - **JSON (default, unprefixed):** payload is plain JSON text. Used when
42
+ * the dispatched method is declared `jsonSerializable: true`. Encoded
43
+ * via {@link strictJsonStringify} (rejects non-JSON values), decoded
44
+ * via `JSON.parse`.
45
+ * - **Structured-clone (`s:` prefix):** payload is `s:` followed by
46
+ * `structured-clone-es` text. Used when the method is declared
47
+ * `jsonSerializable: false` (or omitted, the default). Round-trips
48
+ * `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances.
49
+ *
50
+ * birpc envelopes always start with `{`, so a leading byte that is not
51
+ * `s` is unambiguously JSON. Each direction independently chooses its
52
+ * encoding from local definitions — request and response are not
53
+ * coupled by a mirror rule.
54
+ */
55
+ const STRUCTURED_CLONE_PREFIX = "s:";
56
+ /**
57
+ * `JSON.stringify` with a single-pass strict replacer.
58
+ *
59
+ * Throws `DF0020` synchronously when the value contains a type JSON
60
+ * cannot round-trip losslessly: `Map`, `Set`, `Date`, `BigInt`, class
61
+ * instances, or `undefined` inside an array (silently becomes `null`).
62
+ *
63
+ * Native pass-throughs (no extra work needed):
64
+ * - circular references — `JSON.stringify` raises `TypeError`.
65
+ * - `BigInt` at top level — caught here for a friendlier error path.
66
+ *
67
+ * Lenient cases (allowed without throwing):
68
+ * - `undefined` as an object property — legitimate optional field;
69
+ * JSON.stringify just omits it.
70
+ * - `undefined` at the root — legitimate "action returned nothing".
71
+ * - `Symbol` / `Function` values — semantically "drop me" in JSON.
72
+ *
73
+ * `fnName` is used only for the diagnostic message — pass the RPC
74
+ * function name when calling from a wire serializer / dump writer so
75
+ * the error points at the offending function.
76
+ */
77
+ function strictJsonStringify(value, fnName = "") {
78
+ return JSON.stringify(value, function strictReplacer(key, val) {
79
+ const holder = this;
80
+ const original = holder != null ? holder[key] : val;
81
+ if (original === void 0) {
82
+ if (Array.isArray(holder)) throw nonJsonAt(fnName, "undefined", holder, key);
83
+ return val;
84
+ }
85
+ if (original === null) return val;
86
+ if (typeof original === "bigint") throw nonJsonAt(fnName, "BigInt", holder, key);
87
+ if (typeof original === "object") {
88
+ if (original instanceof Map) throw nonJsonAt(fnName, "Map", holder, key);
89
+ if (original instanceof Set) throw nonJsonAt(fnName, "Set", holder, key);
90
+ if (original instanceof Date) throw nonJsonAt(fnName, "Date", holder, key);
91
+ if (Array.isArray(original)) return val;
92
+ const proto = Object.getPrototypeOf(original);
93
+ if (proto !== null && proto !== Object.prototype) throw nonJsonAt(fnName, original.constructor?.name ?? "class instance", holder, key);
94
+ }
95
+ return val;
96
+ });
97
+ }
98
+ function nonJsonAt(fnName, type, parent, key) {
99
+ const path = formatPath(parent, key);
100
+ return logger.DF0020({
101
+ name: fnName || "<anonymous>",
102
+ type,
103
+ path
104
+ }).throw();
105
+ }
106
+ function formatPath(parent, key) {
107
+ if (Array.isArray(parent)) return `[${key}]`;
108
+ if (key === "") return "<root>";
109
+ return key;
110
+ }
111
+ //#endregion
112
+ export { structuredCloneSerialize as a, logger as c, structuredCloneParse as i, strictJsonStringify as n, structuredCloneStringify as o, structuredCloneDeserialize as r, diagnostics as s, STRUCTURED_CLONE_PREFIX as t };
@@ -0,0 +1,49 @@
1
+ import { createRpcServer } from "./rpc/server.mjs";
2
+ import { attachWsRpcTransport } from "./rpc/transports/ws-server.mjs";
3
+ import { WebSocketServer } from "ws";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ import { createServer } from "node:http";
6
+ import { createApp, toNodeListener } from "h3";
7
+ //#region src/node/server.ts
8
+ /**
9
+ * Compose an h3 + WebSocket server for a devtool context. The RPC
10
+ * group is bound to `context.rpc.functions`; the WS endpoint lives on
11
+ * the same port as the HTTP server.
12
+ */
13
+ async function startHttpAndWs(options) {
14
+ const { context, port } = options;
15
+ const bindHost = options.host ?? "localhost";
16
+ const app = options.app ?? createApp();
17
+ const httpServer = createServer(toNodeListener(app));
18
+ const wss = new WebSocketServer({ server: httpServer });
19
+ const rpcHost = context.rpc;
20
+ const asyncStorage = new AsyncLocalStorage();
21
+ const rpcGroup = createRpcServer(rpcHost.functions);
22
+ attachWsRpcTransport(rpcGroup, { wss });
23
+ rpcHost._rpcGroup = rpcGroup;
24
+ rpcHost._asyncStorage = asyncStorage;
25
+ rpcHost._authDisabled = options.auth === false;
26
+ await new Promise((resolveListen) => {
27
+ httpServer.listen(port, bindHost, () => resolveListen());
28
+ });
29
+ const origin = `http://${bindHost}:${port}`;
30
+ if (options.onReady) await options.onReady({
31
+ origin,
32
+ port,
33
+ app
34
+ });
35
+ return {
36
+ origin,
37
+ port,
38
+ app,
39
+ wss,
40
+ rpcGroup,
41
+ async close() {
42
+ for (const ws of wss.clients) ws.terminate();
43
+ await new Promise((r) => wss.close(() => r()));
44
+ await new Promise((r) => httpServer.close(() => r()));
45
+ }
46
+ };
47
+ }
48
+ //#endregion
49
+ export { startHttpAndWs as t };
@@ -0,0 +1,85 @@
1
+ import { t as createDefineWrapperWithContext } from "./define-CW9gLnyG.mjs";
2
+ import { safeParse } from "valibot";
3
+ //#region src/adapters/flags.ts
4
+ /**
5
+ * Identity helper that preserves the literal schema-map type — use this
6
+ * so `InferCliFlags<typeof myFlags>` resolves to the right object shape.
7
+ *
8
+ * ```ts
9
+ * const appFlags = defineCliFlags({
10
+ * depth: v.pipe(v.number(), v.integer()),
11
+ * config: v.optional(v.string()),
12
+ * })
13
+ *
14
+ * defineDevtool({
15
+ * cli: { flags: appFlags },
16
+ * setup(ctx, info) {
17
+ * const flags = info.flags as InferCliFlags<typeof appFlags>
18
+ * flags.depth // number
19
+ * flags.config // string | undefined
20
+ * },
21
+ * })
22
+ * ```
23
+ */
24
+ function defineCliFlags(flags) {
25
+ return flags;
26
+ }
27
+ /**
28
+ * Best-effort probe of a valibot schema to decide whether the
29
+ * corresponding CAC option takes a value. Unwraps `optional` / `nullable`
30
+ * / `nullish` / `default` / `pipe` wrappers then matches on the inner
31
+ * type's kind.
32
+ */
33
+ function getSchemaKind(schema) {
34
+ let current = schema;
35
+ while (current) {
36
+ const kind = current.type;
37
+ if (kind === "optional" || kind === "nullable" || kind === "nullish" || kind === "undefined") {
38
+ current = current.wrapped ?? current.inner;
39
+ continue;
40
+ }
41
+ if (kind === "pipe" && Array.isArray(current.pipe) && current.pipe.length > 0) {
42
+ current = current.pipe[0];
43
+ continue;
44
+ }
45
+ return kind;
46
+ }
47
+ return "unknown";
48
+ }
49
+ /** Whether the CAC option for this schema should be a boolean flag. */
50
+ function isBooleanFlag(schema) {
51
+ return getSchemaKind(schema) === "boolean";
52
+ }
53
+ /** Validate and coerce the raw cac-parsed bag against a {@link CliFlagsSchema}. */
54
+ function parseCliFlags(schema, raw) {
55
+ const flags = {};
56
+ const issues = [];
57
+ for (const [key, fieldSchema] of Object.entries(schema)) {
58
+ const result = safeParse(fieldSchema, raw[key]);
59
+ if (result.success) flags[key] = result.output;
60
+ else issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`);
61
+ }
62
+ for (const [key, value] of Object.entries(raw)) if (!(key in schema) && !(key in flags)) flags[key] = value;
63
+ return issues.length ? {
64
+ flags,
65
+ issues
66
+ } : { flags };
67
+ }
68
+ function toKebab(camel) {
69
+ return camel.replaceAll(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
70
+ }
71
+ /** Kebab-case a schema key for CAC option registration. */
72
+ function flagKeyToOption(camel) {
73
+ return toKebab(camel);
74
+ }
75
+ //#endregion
76
+ //#region src/define.ts
77
+ const defineRpcFunction = createDefineWrapperWithContext();
78
+ function defineCommand(command) {
79
+ return command;
80
+ }
81
+ function defineJsonRenderSpec(spec) {
82
+ return spec;
83
+ }
84
+ //#endregion
85
+ export { flagKeyToOption as a, defineCliFlags as i, defineJsonRenderSpec as n, isBooleanFlag as o, defineRpcFunction as r, parseCliFlags as s, defineCommand as t };