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.
- package/LICENSE.md +21 -0
- package/README.md +62 -0
- package/dist/_shared-Bxa2_kq7.mjs +20 -0
- package/dist/adapters/build.d.mts +37 -0
- package/dist/adapters/build.mjs +68 -0
- package/dist/adapters/cli.d.mts +35 -0
- package/dist/adapters/cli.mjs +357 -0
- package/dist/adapters/embedded.d.mts +19 -0
- package/dist/adapters/embedded.mjs +15 -0
- package/dist/adapters/kit.d.mts +23 -0
- package/dist/adapters/kit.mjs +16 -0
- package/dist/adapters/mcp.d.mts +39 -0
- package/dist/adapters/mcp.mjs +278 -0
- package/dist/adapters/vite.d.mts +32 -0
- package/dist/adapters/vite.mjs +31 -0
- package/dist/client/index.d.mts +228 -0
- package/dist/client/index.mjs +2 -0
- package/dist/client-CsR1_h3o.mjs +1569 -0
- package/dist/constants.d.mts +20 -0
- package/dist/constants.mjs +34 -0
- package/dist/context-xQo1FcxX.mjs +6827 -0
- package/dist/define-CW9gLnyG.mjs +11 -0
- package/dist/devtool-CHT-4_OU.d.mts +1233 -0
- package/dist/helpers/nuxt/index.d.mts +46 -0
- package/dist/helpers/nuxt/index.mjs +58 -0
- package/dist/helpers/nuxt/runtime/plugin.client.d.mts +8 -0
- package/dist/helpers/nuxt/runtime/plugin.client.mjs +12 -0
- package/dist/human-id-CHS0s28X.mjs +844 -0
- package/dist/immer-DEqg5kOd.mjs +894 -0
- package/dist/index-Cei8vVcd.d.mts +140 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +2 -0
- package/dist/main-BJD9t8dk.mjs +601 -0
- package/dist/node/index.d.mts +393 -0
- package/dist/node/index.mjs +69 -0
- package/dist/open-BMO2_-wC.mjs +533 -0
- package/dist/recipes/open-helpers.d.mts +108 -0
- package/dist/recipes/open-helpers.mjs +69 -0
- package/dist/rpc/client.d.mts +9 -0
- package/dist/rpc/client.mjs +13 -0
- package/dist/rpc/index.d.mts +3 -0
- package/dist/rpc/index.mjs +4 -0
- package/dist/rpc/server.d.mts +8 -0
- package/dist/rpc/server.mjs +10 -0
- package/dist/rpc/transports/ws-client.d.mts +2 -0
- package/dist/rpc/transports/ws-client.mjs +58 -0
- package/dist/rpc/transports/ws-server.d.mts +2 -0
- package/dist/rpc/transports/ws-server.mjs +73 -0
- package/dist/rpc-hbRk8qtt.mjs +456 -0
- package/dist/serialization-CWcWE7x7.mjs +112 -0
- package/dist/server-DkJ2-s0Y.mjs +49 -0
- package/dist/src-DIKqQIjp.mjs +85 -0
- package/dist/static-dump-DwFfj4U_.mjs +97 -0
- package/dist/transports-4bqw6Fqg.mjs +15 -0
- package/dist/types/index.d.mts +4 -0
- package/dist/types/index.mjs +6 -0
- package/dist/types-BkyzI9r4.d.mts +273 -0
- package/dist/utils/events.d.mts +9 -0
- package/dist/utils/events.mjs +40 -0
- package/dist/utils/human-id.d.mts +10 -0
- package/dist/utils/human-id.mjs +3 -0
- package/dist/utils/nanoid.d.mts +4 -0
- package/dist/utils/nanoid.mjs +10 -0
- package/dist/utils/promise.d.mts +8 -0
- package/dist/utils/promise.mjs +15 -0
- package/dist/utils/shared-state.d.mts +2 -0
- package/dist/utils/shared-state.mjs +36 -0
- package/dist/utils/state.d.mts +49 -0
- package/dist/utils/state.mjs +26 -0
- package/dist/utils/when.d.mts +2 -0
- package/dist/utils/when.mjs +424 -0
- package/dist/when-aBBXAEh5.d.mts +401 -0
- package/dist/ws-client-CDGmViwt.d.mts +26 -0
- package/dist/ws-server-Cu8tmZcF.d.mts +49 -0
- 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 };
|