devframe 0.5.4 → 0.6.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/{_shared-CUFqO4kJ.mjs → _shared-bWRzeSa0.mjs} +2 -3
  2. package/dist/adapters/build.d.mts +1 -1
  3. package/dist/adapters/build.mjs +7 -7
  4. package/dist/adapters/cli.d.mts +1 -1
  5. package/dist/adapters/cli.mjs +2 -2
  6. package/dist/adapters/dev.d.mts +8 -2
  7. package/dist/adapters/dev.mjs +1 -1
  8. package/dist/adapters/embedded.d.mts +1 -1
  9. package/dist/adapters/mcp.d.mts +1 -1
  10. package/dist/adapters/mcp.mjs +4 -4
  11. package/dist/client/index.d.mts +148 -6
  12. package/dist/client/index.mjs +257 -44
  13. package/dist/constants.d.mts +17 -1
  14. package/dist/constants.mjs +17 -1
  15. package/dist/context-3x_bgBgZ.mjs +48 -0
  16. package/dist/{context-DTRcO_UH.d.mts → context-BEVByy_Z.d.mts} +1 -1
  17. package/dist/{dev-Cv43GfqM.mjs → dev-BaX8fLH2.mjs} +39 -13
  18. package/dist/{devframe-BuR6n9ZD.d.mts → devframe-D-gr9sBx.d.mts} +365 -15
  19. package/dist/{diagnostics-GitRGKbr.mjs → diagnostics-BXwBQmoN.mjs} +1 -1
  20. package/dist/{dump-9lKIJTLh.mjs → dump-DoXkj4ku.mjs} +1 -1
  21. package/dist/helpers/vite.d.mts +1 -1
  22. package/dist/helpers/vite.mjs +11 -11
  23. package/dist/{host-h3-Dgpgr1Ul.mjs → host-h3-C5SZlk03.mjs} +134 -6
  24. package/dist/{index-C7M1hnvL.d.mts → index-CxaPKDXX.d.mts} +2 -2
  25. package/dist/{index-DH2sBIwd.d.mts → index-DXHWuwJk.d.mts} +1 -1
  26. package/dist/index.d.mts +4 -4
  27. package/dist/index.mjs +1 -1
  28. package/dist/{launch-editor-BbNhtg7b.mjs → launch-editor-DPfltGA4.mjs} +88 -13
  29. package/dist/node/auth.d.mts +43 -23
  30. package/dist/node/auth.mjs +84 -37
  31. package/dist/node/hub-internals.d.mts +2 -2
  32. package/dist/node/hub-internals.mjs +2 -2
  33. package/dist/node/index.d.mts +22 -6
  34. package/dist/node/index.mjs +4 -4
  35. package/dist/recipes/open-helpers.d.mts +1 -1
  36. package/dist/recipes/open-helpers.mjs +3 -3
  37. package/dist/revoke-ENfxWkFK.mjs +79 -0
  38. package/dist/rpc/dump.d.mts +1 -1
  39. package/dist/rpc/dump.mjs +1 -1
  40. package/dist/rpc/index.d.mts +3 -3
  41. package/dist/rpc/index.mjs +4 -4
  42. package/dist/rpc/transports/ws-client.d.mts +1 -1
  43. package/dist/rpc/transports/ws-client.mjs +2 -2
  44. package/dist/rpc/transports/ws-server.d.mts +2 -2
  45. package/dist/rpc/transports/ws-server.mjs +154 -61
  46. package/dist/{serialization-BD_qXGjd.mjs → serialization-DpLXCy13.mjs} +1 -1
  47. package/dist/{server-BBaBJaUL.mjs → server-Dl6TU5LE.mjs} +19 -14
  48. package/dist/server-rX781kz-.d.mts +90 -0
  49. package/dist/{shared-state-u0y123fi.mjs → shared-state-D4PPieA0.mjs} +4 -4
  50. package/dist/{shared-state-BlBNYziY.mjs → storage-l2ezeend.mjs} +128 -6
  51. package/dist/types/index.d.mts +4 -4
  52. package/dist/{types-BkkQ0Txg.d.mts → types-DZEx4ffs.d.mts} +9 -1
  53. package/dist/utils/crypto-token.d.mts +24 -0
  54. package/dist/utils/crypto-token.mjs +45 -0
  55. package/dist/utils/events.d.mts +1 -1
  56. package/dist/utils/hash.mjs +1 -1
  57. package/dist/utils/launch-editor.mjs +1 -1
  58. package/dist/utils/open.mjs +1 -1
  59. package/dist/utils/scope.d.mts +11 -0
  60. package/dist/utils/scope.mjs +15 -0
  61. package/dist/utils/shared-state.d.mts +1 -1
  62. package/dist/utils/shared-state.mjs +1 -1
  63. package/dist/utils/streaming-channel.d.mts +1 -1
  64. package/dist/utils/structured-clone.mjs +1 -1
  65. package/dist/{ws-client-CVYX9niP.d.mts → ws-client-D0z0pOhn.d.mts} +1 -1
  66. package/dist/ws-server-Bc2wBHl-.d.mts +119 -0
  67. package/package.json +12 -7
  68. package/skills/devframe/SKILL.md +123 -23
  69. package/skills/devframe/templates/counter-devframe.ts +14 -4
  70. package/skills/devframe/templates/spa-devframe.ts +13 -3
  71. package/skills/devframe/templates/vite-client.ts +3 -2
  72. package/dist/context-DaKmhhHY.mjs +0 -164
  73. package/dist/revoke-CL0LSAN9.mjs +0 -803
  74. package/dist/server-wHlpcdZ9.d.mts +0 -55
  75. package/dist/utils/human-id.d.mts +0 -8
  76. package/dist/utils/human-id.mjs +0 -769
  77. package/dist/ws-server-C1LjmRnp.d.mts +0 -61
  78. /package/dist/{define-CW9gLnyG.mjs → define-BLWPsH6y.mjs} +0 -0
  79. /package/dist/{diagnostics-reporter-CBBZwoMv.mjs → diagnostics-reporter-CsIG85Q5.mjs} +0 -0
  80. /package/dist/{hash-bwOD8RgU.mjs → hash-DFc5WwhO.mjs} +0 -0
  81. /package/dist/{open-DiQn6zCH.mjs → open-Dusa2Zzd.mjs} +0 -0
  82. /package/dist/{structured-clone-PdCZwt7F.mjs → structured-clone-CeZOHvkd.mjs} +0 -0
  83. /package/dist/{structured-clone-jegjz0hM.mjs → structured-clone-XpHLZ8nr.mjs} +0 -0
  84. /package/dist/{transports-DTFoMUbE.mjs → transports-BmDVn4SF.mjs} +0 -0
@@ -836,10 +836,10 @@ function enablePatches() {
836
836
  const parentType = getArchtype(base);
837
837
  let p = path[i];
838
838
  if (typeof p !== "string" && typeof p !== "number") p = "" + p;
839
- if ((parentType === 0 || parentType === 1) && (p === "__proto__" || p === CONSTRUCTOR)) die(errorOffset + 3);
840
- if (isFunction(base) && p === PROTOTYPE) die(errorOffset + 3);
839
+ if ((parentType === 0 || parentType === 1) && (p === "__proto__" || p === CONSTRUCTOR)) die(19);
840
+ if (isFunction(base) && p === PROTOTYPE) die(19);
841
841
  base = get(base, p);
842
- if (!isObjectish(base)) die(errorOffset + 2, path.join("/"));
842
+ if (!isObjectish(base)) die(18, path.join("/"));
843
843
  }
844
844
  const type = getArchtype(base);
845
845
  const value = deepClonePatchValue(patch.value);
@@ -862,7 +862,7 @@ function enablePatches() {
862
862
  case 3: return base.delete(patch.value);
863
863
  default: return delete base[key];
864
864
  }
865
- default: die(errorOffset + 1, op);
865
+ default: die(17, op);
866
866
  }
867
867
  });
868
868
  return draft;
@@ -1,5 +1,8 @@
1
- import { t as devframeReporter } from "./diagnostics-reporter-CBBZwoMv.mjs";
1
+ import { t as devframeReporter } from "./diagnostics-reporter-CsIG85Q5.mjs";
2
2
  import { defineDiagnostics } from "nostics";
3
+ import fs from "node:fs";
4
+ import { dirname } from "pathe";
5
+ import { destr } from "destr";
3
6
  //#region src/node/diagnostics.ts
4
7
  const diagnostics = defineDiagnostics({
5
8
  docsBase: "https://devfra.me/errors",
@@ -39,6 +42,10 @@ const diagnostics = defineDiagnostics({
39
42
  DF0033: {
40
43
  why: (p) => `Failed to start dev RPC bridge for "${p.id}": ${p.reason}`,
41
44
  fix: "Verify the bridge port is free and the devframe setup function does not throw. Pin a port via `cli.port` / `cli.portRange` on the definition, or via `devMiddleware.port` on `viteDevBridge`."
45
+ },
46
+ DF0034: {
47
+ why: (p) => `Scoped RPC registration for namespace "${p.namespace}" received an already-namespaced function name "${p.name}".`,
48
+ fix: "A scoped context auto-namespaces ids. Pass a bare name without a \":\" separator (e.g. `register({ name: \"get-cwd\" })`), or use the unscoped `ctx.base.rpc.register` for a fully-qualified name."
42
49
  }
43
50
  }
44
51
  });
@@ -918,10 +925,10 @@ function enablePatches() {
918
925
  const parentType = getArchtype(base);
919
926
  let p = path[i];
920
927
  if (typeof p !== "string" && typeof p !== "number") p = "" + p;
921
- if ((parentType === 0 || parentType === 1) && (p === "__proto__" || p === CONSTRUCTOR)) die(errorOffset + 3);
922
- if (isFunction(base) && p === PROTOTYPE) die(errorOffset + 3);
928
+ if ((parentType === 0 || parentType === 1) && (p === "__proto__" || p === CONSTRUCTOR)) die(19);
929
+ if (isFunction(base) && p === PROTOTYPE) die(19);
923
930
  base = get(base, p);
924
- if (!isObjectish(base)) die(errorOffset + 2, path.join("/"));
931
+ if (!isObjectish(base)) die(18, path.join("/"));
925
932
  }
926
933
  const type = getArchtype(base);
927
934
  const value = deepClonePatchValue(patch.value);
@@ -944,7 +951,7 @@ function enablePatches() {
944
951
  case 3: return base.delete(patch.value);
945
952
  default: return delete base[key];
946
953
  }
947
- default: die(errorOffset + 1, op);
954
+ default: die(17, op);
948
955
  }
949
956
  });
950
957
  return draft;
@@ -1016,4 +1023,119 @@ function createSharedState(options) {
1016
1023
  };
1017
1024
  }
1018
1025
  //#endregion
1019
- export { diagnostics as i, nanoid as n, createEventEmitter as r, createSharedState as t };
1026
+ //#region ../../node_modules/.pnpm/perfect-debounce@2.1.0/node_modules/perfect-debounce/dist/index.mjs
1027
+ const DEBOUNCE_DEFAULTS = { trailing: true };
1028
+ /**
1029
+ Debounce functions
1030
+ @param fn - Promise-returning/async function to debounce.
1031
+ @param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
1032
+ @returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
1033
+ @example
1034
+ ```
1035
+ import { debounce } from 'perfect-debounce';
1036
+ const expensiveCall = async input => input;
1037
+ const debouncedFn = debounce(expensiveCall, 200);
1038
+ for (const number of [1, 2, 3]) {
1039
+ console.log(await debouncedFn(number));
1040
+ }
1041
+ //=> 1
1042
+ //=> 2
1043
+ //=> 3
1044
+ ```
1045
+ */
1046
+ function debounce(fn, wait = 25, options = {}) {
1047
+ options = {
1048
+ ...DEBOUNCE_DEFAULTS,
1049
+ ...options
1050
+ };
1051
+ if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
1052
+ let leadingValue;
1053
+ let timeout;
1054
+ let resolveList = [];
1055
+ let currentPromise;
1056
+ let trailingArgs;
1057
+ const applyFn = (_this, args) => {
1058
+ currentPromise = _applyPromised(fn, _this, args);
1059
+ currentPromise.finally(() => {
1060
+ currentPromise = null;
1061
+ if (options.trailing && trailingArgs && !timeout) {
1062
+ const promise = applyFn(_this, trailingArgs);
1063
+ trailingArgs = null;
1064
+ return promise;
1065
+ }
1066
+ });
1067
+ return currentPromise;
1068
+ };
1069
+ const debounced = function(...args) {
1070
+ if (options.trailing) trailingArgs = args;
1071
+ if (currentPromise) return currentPromise;
1072
+ return new Promise((resolve) => {
1073
+ const shouldCallNow = !timeout && options.leading;
1074
+ clearTimeout(timeout);
1075
+ timeout = setTimeout(() => {
1076
+ timeout = null;
1077
+ const promise = options.leading ? leadingValue : applyFn(this, args);
1078
+ trailingArgs = null;
1079
+ for (const _resolve of resolveList) _resolve(promise);
1080
+ resolveList = [];
1081
+ }, wait);
1082
+ if (shouldCallNow) {
1083
+ leadingValue = applyFn(this, args);
1084
+ resolve(leadingValue);
1085
+ } else resolveList.push(resolve);
1086
+ });
1087
+ };
1088
+ const _clearTimeout = (timer) => {
1089
+ if (timer) {
1090
+ clearTimeout(timer);
1091
+ timeout = null;
1092
+ }
1093
+ };
1094
+ debounced.isPending = () => !!timeout;
1095
+ debounced.cancel = () => {
1096
+ _clearTimeout(timeout);
1097
+ resolveList = [];
1098
+ trailingArgs = null;
1099
+ };
1100
+ debounced.flush = () => {
1101
+ _clearTimeout(timeout);
1102
+ if (!trailingArgs || currentPromise) return;
1103
+ const args = trailingArgs;
1104
+ trailingArgs = null;
1105
+ return applyFn(this, args);
1106
+ };
1107
+ return debounced;
1108
+ }
1109
+ async function _applyPromised(fn, _this, args) {
1110
+ return await fn.apply(_this, args);
1111
+ }
1112
+ //#endregion
1113
+ //#region src/node/storage.ts
1114
+ function createStorage(options) {
1115
+ const { mergeInitialValue = (initialValue, savedValue) => ({
1116
+ ...initialValue,
1117
+ ...savedValue
1118
+ }), debounce: debounceTime = 100 } = options;
1119
+ let initialValue = options.initialValue;
1120
+ if (fs.existsSync(options.filepath)) try {
1121
+ const savedValue = destr(fs.readFileSync(options.filepath, "utf-8"), { strict: true });
1122
+ initialValue = mergeInitialValue ? mergeInitialValue(options.initialValue, savedValue) : savedValue;
1123
+ } catch (error) {
1124
+ diagnostics.DF0012({
1125
+ filepath: options.filepath,
1126
+ cause: error
1127
+ }, { method: "warn" });
1128
+ initialValue = options.initialValue;
1129
+ }
1130
+ const state = createSharedState({
1131
+ initialValue,
1132
+ enablePatches: false
1133
+ });
1134
+ state.on("updated", debounce((newState) => {
1135
+ fs.mkdirSync(dirname(options.filepath), { recursive: true });
1136
+ fs.writeFileSync(options.filepath, `${JSON.stringify(newState, null, 2)}\n`);
1137
+ }, debounceTime));
1138
+ return state;
1139
+ }
1140
+ //#endregion
1141
+ export { diagnostics as a, createEventEmitter as i, createSharedState as n, nanoid as r, createStorage as t };
@@ -1,4 +1,4 @@
1
- import { $ as AgentResource, B as EntriesToObject, G as DevframeRpcSharedStates, H as Thenable, J as DevframeDiagnosticsDefinition, K as DevframeHost, Q as AgentManifest, U as DevframeRpcClientFunctions, V as PartialWithoutId, W as DevframeRpcServerFunctions, X as DevframeDiagnosticsLogger, Y as DevframeDiagnosticsHost, Z as AgentHandle, _ as RpcStreamingChannel, a as DevframeRuntime, at as DevframeAgentHostEvents, c as defineDevframe, ct as EventsMap, d as DevframeNodeContext, et as AgentResourceContent, f as DevframeNodeRpcSession, g as RpcSharedStateHost, h as RpcSharedStateGetOptions, i as DevframeDeploymentKind, it as DevframeAgentHost, l as ConnectionMeta, m as RpcFunctionsHost, n as DevframeCliOptions, nt as AgentTool, o as DevframeSetupInfo, ot as EventEmitter, p as RpcBroadcastOptions, q as DevframeDefineDiagnosticsOptions, r as DevframeDefinition, rt as AgentToolInput, s as DevframeSpaOptions, st as EventUnsubscribe, t as DevframeBrowserContext, tt as AgentResourceInput, u as DevframeCapabilities, v as RpcStreamingChannelOptions, y as RpcStreamingHost, z as DevframeViewHost } from "../devframe-BuR6n9ZD.mjs";
2
- import { h as RpcFunctionAgentOptions } from "../types-BkkQ0Txg.mjs";
3
- import { t as DevframeNodeRpcSessionMeta } from "../ws-server-C1LjmRnp.mjs";
4
- export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, ConnectionMeta, DevframeAgentHost, DevframeAgentHostEvents, DevframeBrowserContext, DevframeCapabilities, DevframeCliOptions, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsDefinition, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates, DevframeRuntime, DevframeSetupInfo, DevframeSpaOptions, DevframeViewHost, EntriesToObject, EventEmitter, EventUnsubscribe, EventsMap, PartialWithoutId, RpcBroadcastOptions, RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, Thenable, defineDevframe };
1
+ import { $ as ScopedServerFunctions, G as DevframeScopedNodeContext, H as EntriesToObject, J as DevframeSettings, K as DevframeScopedNodeRpc, Q as ScopedClientFunctions, S as RpcStreamingHost, St as EventsMap, U as PartialWithoutId, W as Thenable, X as DevframeSettingsStore, Y as DevframeSettingsRegistry, Z as ScopedBroadcastOptions, _ as RpcFunctionsHost, _t as AgentToolInput, a as DevframeDuplicationStrategy, at as DevframeRpcSharedStates, b as RpcStreamingChannel, bt as EventEmitter, c as DevframeSpaOptions, ct as DevframeDiagnosticsDefinition, d as ConnectionMeta, dt as AgentHandle, et as ScopedSharedStates, f as ConnectionMetaWebsocket, ft as AgentManifest, g as RpcBroadcastOptions, gt as AgentTool, h as DevframeNodeRpcSession, ht as AgentResourceInput, i as DevframeDeploymentKind, it as DevframeRpcServerFunctions, l as DevframeWsOptions, lt as DevframeDiagnosticsHost, m as DevframeNodeContext, mt as AgentResourceContent, n as DevframeCliOptions, nt as DevframeViewHost, o as DevframeRuntime, ot as DevframeHost, p as DevframeCapabilities, pt as AgentResource, q as DevframeScopedStreamingHost, r as DevframeDefinition, rt as DevframeRpcClientFunctions, s as DevframeSetupInfo, st as DevframeDefineDiagnosticsOptions, t as DevframeBrowserContext, tt as SettingsForNamespace, u as defineDevframe, ut as DevframeDiagnosticsLogger, v as RpcSharedStateGetOptions, vt as DevframeAgentHost, x as RpcStreamingChannelOptions, xt as EventUnsubscribe, y as RpcSharedStateHost, yt as DevframeAgentHostEvents } from "../devframe-D-gr9sBx.mjs";
2
+ import { g as RpcFunctionAgentOptions } from "../types-DZEx4ffs.mjs";
3
+ import { t as DevframeNodeRpcSessionMeta } from "../ws-server-Bc2wBHl-.mjs";
4
+ export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, ConnectionMeta, ConnectionMetaWebsocket, DevframeAgentHost, DevframeAgentHostEvents, DevframeBrowserContext, DevframeCapabilities, DevframeCliOptions, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsDefinition, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeDuplicationStrategy, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates, DevframeRuntime, DevframeScopedNodeContext, DevframeScopedNodeRpc, DevframeScopedStreamingHost, DevframeSettings, DevframeSettingsRegistry, DevframeSettingsStore, DevframeSetupInfo, DevframeSpaOptions, DevframeViewHost, DevframeWsOptions, EntriesToObject, EventEmitter, EventUnsubscribe, EventsMap, PartialWithoutId, RpcBroadcastOptions, RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, ScopedBroadcastOptions, ScopedClientFunctions, ScopedServerFunctions, ScopedSharedStates, SettingsForNamespace, Thenable, defineDevframe };
@@ -282,8 +282,16 @@ type RpcFunctionDefinitionToFunction<T extends RpcFunctionDefinitionAny> = T ext
282
282
  type RpcFunctionDefinitionAny = RpcFunctionDefinition<string, any, any, any, any, any, any>;
283
283
  type RpcFunctionDefinitionAnyWithContext<CONTEXT = undefined> = RpcFunctionDefinition<string, any, any, any, any, any, CONTEXT>;
284
284
  type RpcDefinitionsToFunctions<T extends readonly RpcFunctionDefinitionAny[]> = EntriesToObject<{ [K in keyof T]: [T[K]['name'], RpcFunctionDefinitionToFunction<T[K]>] }>;
285
+ /**
286
+ * Like {@link RpcDefinitionsToFunctions}, but prefixes every (bare)
287
+ * definition name with `<NS>:`. Use this when functions are defined with
288
+ * bare names and registered through a scoped context
289
+ * (`ctx.scope(NS).rpc.register(...)`), so the augmented registry keys
290
+ * match the namespaced ids stored at runtime.
291
+ */
292
+ type RpcDefinitionsToFunctionsWithNamespace<NS extends string, T extends readonly RpcFunctionDefinitionAny[]> = EntriesToObject<{ [K in keyof T]: [`${NS}:${T[K]['name'] & string}`, RpcFunctionDefinitionToFunction<T[K]>] }>;
285
293
  type RpcDefinitionsFilter<T extends readonly RpcFunctionDefinitionAny[], Type extends RpcFunctionType> = { [K in keyof T]: T[K] extends {
286
294
  type: Type;
287
295
  } ? T[K] : never };
288
296
  //#endregion
289
- export { RpcFunctionsCollector as C, RpcFunctionType as S, Thenable as T, RpcFunctionDefinitionAny as _, RpcDefinitionsFilter as a, RpcFunctionDefinitionToFunction as b, RpcDumpClientOptions as c, RpcDumpGetter as d, RpcDumpRecord as f, RpcFunctionDefinition as g, RpcFunctionAgentOptions as h, RpcArgsSchema as i, RpcDumpCollectionOptions as l, RpcDumpStore as m, BirpcReturn$1 as n, RpcDefinitionsToFunctions as o, RpcDumpRecordError as p, EntriesToObject as r, RpcDump as s, BirpcFn as t, RpcDumpDefinition as u, RpcFunctionDefinitionAnyWithContext as v, RpcReturnSchema as w, RpcFunctionSetupResult as x, RpcFunctionDefinitionBase as y };
297
+ export { RpcFunctionType as C, Thenable as E, RpcFunctionSetupResult as S, RpcReturnSchema as T, RpcFunctionDefinition as _, RpcDefinitionsFilter as a, RpcFunctionDefinitionBase as b, RpcDump as c, RpcDumpDefinition as d, RpcDumpGetter as f, RpcFunctionAgentOptions as g, RpcDumpStore as h, RpcArgsSchema as i, RpcDumpClientOptions as l, RpcDumpRecordError as m, BirpcReturn$1 as n, RpcDefinitionsToFunctions as o, RpcDumpRecord as p, EntriesToObject as r, RpcDefinitionsToFunctionsWithNamespace as s, BirpcFn as t, RpcDumpCollectionOptions as u, RpcFunctionDefinitionAny as v, RpcFunctionsCollector as w, RpcFunctionDefinitionToFunction as x, RpcFunctionDefinitionAnyWithContext as y };
@@ -0,0 +1,24 @@
1
+ //#region src/utils/crypto-token.d.ts
2
+ /**
3
+ * Generate a high-entropy, URL-safe (hex) random token suitable for use as a
4
+ * bearer credential — e.g. the persistent client auth token or an ephemeral
5
+ * remote-dock token. Defaults to 16 bytes (128 bits) of entropy.
6
+ */
7
+ declare function randomToken(byteLength?: number): string;
8
+ /**
9
+ * Generate a uniformly-distributed string of decimal digits using rejection
10
+ * sampling to avoid modulo bias. Intended for short, human-typed one-time
11
+ * codes (e.g. a 6-digit authentication code). Leading zeros are preserved.
12
+ */
13
+ declare function randomDigits(length: number): string;
14
+ /**
15
+ * Constant-time string equality. Compares every character so the comparison
16
+ * time does not depend on the position of the first mismatch, mitigating
17
+ * timing side-channels when verifying secrets.
18
+ *
19
+ * Length is treated as public (it short-circuits on differing lengths), which
20
+ * is appropriate for fixed-length codes and tokens.
21
+ */
22
+ declare function timingSafeEqual(a: string, b: string): boolean;
23
+ //#endregion
24
+ export { randomDigits, randomToken, timingSafeEqual };
@@ -0,0 +1,45 @@
1
+ //#region src/utils/crypto-token.ts
2
+ const HEX = "0123456789abcdef";
3
+ /**
4
+ * Generate a high-entropy, URL-safe (hex) random token suitable for use as a
5
+ * bearer credential — e.g. the persistent client auth token or an ephemeral
6
+ * remote-dock token. Defaults to 16 bytes (128 bits) of entropy.
7
+ */
8
+ function randomToken(byteLength = 16) {
9
+ const bytes = new Uint8Array(byteLength);
10
+ globalThis.crypto.getRandomValues(bytes);
11
+ let out = "";
12
+ for (let i = 0; i < bytes.length; i++) out += HEX[bytes[i] >> 4] + HEX[bytes[i] & 15];
13
+ return out;
14
+ }
15
+ /**
16
+ * Generate a uniformly-distributed string of decimal digits using rejection
17
+ * sampling to avoid modulo bias. Intended for short, human-typed one-time
18
+ * codes (e.g. a 6-digit authentication code). Leading zeros are preserved.
19
+ */
20
+ function randomDigits(length) {
21
+ const limit = 250;
22
+ const buf = new Uint8Array(1);
23
+ let out = "";
24
+ while (out.length < length) {
25
+ globalThis.crypto.getRandomValues(buf);
26
+ if (buf[0] < limit) out += String(buf[0] % 10);
27
+ }
28
+ return out;
29
+ }
30
+ /**
31
+ * Constant-time string equality. Compares every character so the comparison
32
+ * time does not depend on the position of the first mismatch, mitigating
33
+ * timing side-channels when verifying secrets.
34
+ *
35
+ * Length is treated as public (it short-circuits on differing lengths), which
36
+ * is appropriate for fixed-length codes and tokens.
37
+ */
38
+ function timingSafeEqual(a, b) {
39
+ if (a.length !== b.length) return false;
40
+ let mismatch = 0;
41
+ for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
42
+ return mismatch === 0;
43
+ }
44
+ //#endregion
45
+ export { randomDigits, randomToken, timingSafeEqual };
@@ -1,4 +1,4 @@
1
- import { ct as EventsMap, ot as EventEmitter } from "../devframe-BuR6n9ZD.mjs";
1
+ import { St as EventsMap, bt as EventEmitter } from "../devframe-D-gr9sBx.mjs";
2
2
 
3
3
  //#region src/utils/events.d.ts
4
4
  /**
@@ -1,2 +1,2 @@
1
- import { t as hash } from "../hash-bwOD8RgU.mjs";
1
+ import { t as hash } from "../hash-DFc5WwhO.mjs";
2
2
  export { hash };
@@ -1,2 +1,2 @@
1
- import { t as launchEditor } from "../launch-editor-BbNhtg7b.mjs";
1
+ import { t as launchEditor } from "../launch-editor-DPfltGA4.mjs";
2
2
  export { launchEditor };
@@ -1,2 +1,2 @@
1
- import { t as open } from "../open-DiQn6zCH.mjs";
1
+ import { t as open } from "../open-Dusa2Zzd.mjs";
2
2
  export { open };
@@ -0,0 +1,11 @@
1
+ //#region src/utils/scope.d.ts
2
+ /** Whether a name is already namespaced (contains a `:` separator). */
3
+ declare function isQualifiedName(name: string): boolean;
4
+ /**
5
+ * Prefix a bare name with `<namespace>:`. Names that already contain a
6
+ * `:` are returned unchanged, so callers can reference another scope's
7
+ * ids explicitly (e.g. `ctx.rpc.call('other-plugin:fn')`).
8
+ */
9
+ declare function qualifyName(namespace: string, name: string): string;
10
+ //#endregion
11
+ export { isQualifiedName, qualifyName };
@@ -0,0 +1,15 @@
1
+ //#region src/utils/scope.ts
2
+ /** Whether a name is already namespaced (contains a `:` separator). */
3
+ function isQualifiedName(name) {
4
+ return name.includes(":");
5
+ }
6
+ /**
7
+ * Prefix a bare name with `<namespace>:`. Names that already contain a
8
+ * `:` are returned unchanged, so callers can reference another scope's
9
+ * ids explicitly (e.g. `ctx.rpc.call('other-plugin:fn')`).
10
+ */
11
+ function qualifyName(namespace, name) {
12
+ return isQualifiedName(name) ? name : `${namespace}:${name}`;
13
+ }
14
+ //#endregion
15
+ export { isQualifiedName, qualifyName };
@@ -1,2 +1,2 @@
1
- import { A as ImmutableArray, F as SharedStateEvents, I as SharedStateOptions, L as SharedStatePatch, M as ImmutableObject, N as ImmutableSet, P as SharedState, R as createSharedState, j as ImmutableMap, k as Immutable } from "../devframe-BuR6n9ZD.mjs";
1
+ import { B as SharedStatePatch, F as ImmutableObject, I as ImmutableSet, L as SharedState, M as Immutable, N as ImmutableArray, P as ImmutableMap, R as SharedStateEvents, V as createSharedState, z as SharedStateOptions } from "../devframe-D-gr9sBx.mjs";
2
2
  export { Immutable, ImmutableArray, ImmutableMap, ImmutableObject, ImmutableSet, SharedState, SharedStateEvents, SharedStateOptions, SharedStatePatch, createSharedState };
@@ -1,2 +1,2 @@
1
- import { t as createSharedState } from "../shared-state-u0y123fi.mjs";
1
+ import { t as createSharedState } from "../shared-state-D4PPieA0.mjs";
2
2
  export { createSharedState };
@@ -1,2 +1,2 @@
1
- import { C as StreamErrorPayload, D as createStreamReader, E as StreamSinkEvents, O as createStreamSink, S as CreateStreamSinkOptions, T as StreamSink, b as BufferedChunk, w as StreamReader, x as CreateStreamReaderOptions } from "../devframe-BuR6n9ZD.mjs";
1
+ import { A as createStreamReader, C as BufferedChunk, D as StreamReader, E as StreamErrorPayload, O as StreamSink, T as CreateStreamSinkOptions, j as createStreamSink, k as StreamSinkEvents, w as CreateStreamReaderOptions } from "../devframe-D-gr9sBx.mjs";
2
2
  export { BufferedChunk, CreateStreamReaderOptions, CreateStreamSinkOptions, StreamErrorPayload, StreamReader, StreamSink, StreamSinkEvents, createStreamReader, createStreamSink };
@@ -1,2 +1,2 @@
1
- import { i as structuredCloneStringify, n as structuredCloneParse, r as structuredCloneSerialize, t as structuredCloneDeserialize } from "../structured-clone-PdCZwt7F.mjs";
1
+ import { i as structuredCloneStringify, n as structuredCloneParse, r as structuredCloneSerialize, t as structuredCloneDeserialize } from "../structured-clone-CeZOHvkd.mjs";
2
2
  export { structuredCloneDeserialize, structuredCloneParse, structuredCloneSerialize, structuredCloneStringify };
@@ -1,4 +1,4 @@
1
- import { _ as RpcFunctionDefinitionAny } from "./types-BkkQ0Txg.mjs";
1
+ import { v as RpcFunctionDefinitionAny } from "./types-DZEx4ffs.mjs";
2
2
  import { ChannelOptions } from "birpc";
3
3
 
4
4
  //#region src/rpc/transports/ws-client.d.ts
@@ -0,0 +1,119 @@
1
+ import { v as RpcFunctionDefinitionAny } from "./types-DZEx4ffs.mjs";
2
+ import { BirpcGroup, ChannelOptions } from "birpc";
3
+ import { Peer } from "crossws";
4
+ import { NodeAdapter } from "crossws/adapters/node";
5
+ import { Server } from "node:http";
6
+ import { Server as Server$1, ServerOptions } from "node:https";
7
+
8
+ //#region src/rpc/transports/ws-server.d.ts
9
+ interface DevframeNodeRpcSessionMeta {
10
+ id: number;
11
+ /** The crossws peer backing this session's socket. */
12
+ peer?: Peer;
13
+ clientAuthToken?: string;
14
+ isTrusted?: boolean;
15
+ subscribedStates: Set<string>;
16
+ /**
17
+ * Streams this session has subscribed to via
18
+ * `rpc.streaming.subscribe(channel, id)`. Tracked here for O(1) cleanup
19
+ * on disconnect; the wire format is `${channel}\x1F${id}`.
20
+ */
21
+ subscribedStreams?: Set<string>;
22
+ /**
23
+ * Inbound streams this session is currently uploading to (via
24
+ * `rpc.streaming.upload(channel, id)`). Tracked for cleanup on
25
+ * disconnect; same wire format as `subscribedStreams`.
26
+ */
27
+ uploadingStreams?: Set<string>;
28
+ }
29
+ interface WsRpcTransportOptions {
30
+ /**
31
+ * Attach to an existing HTTP(S) server, sharing its port. Combine with
32
+ * `path` to bind the WS endpoint to a single route so it coexists with
33
+ * other upgrade handlers on the same server (e.g. a Vite dev server's HMR
34
+ * socket). The shared server's lifecycle is owned by the caller — closing
35
+ * this transport detaches the upgrade listener without closing the server.
36
+ */
37
+ server?: Server | Server$1;
38
+ /** Port for a newly-created standalone WS server. */
39
+ port?: number;
40
+ /** Host for a newly-created standalone WS server. Defaults to `localhost`. */
41
+ host?: string;
42
+ /**
43
+ * Restrict the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`). When
44
+ * sharing a `server`, non-matching upgrade requests are left untouched for
45
+ * other listeners to handle, so devframe's socket can sit alongside
46
+ * framework sockets (Vite HMR, etc.).
47
+ */
48
+ path?: string;
49
+ /**
50
+ * Destroy upgrade requests that don't match `path` instead of leaving them
51
+ * for other listeners. Enable this when devframe owns the shared server
52
+ * outright (nothing else handles its upgrades), so an off-route client is
53
+ * rejected promptly rather than left hanging. Default: `false`
54
+ * (coexist-friendly); servers this transport creates itself always
55
+ * destroy unmatched upgrades.
56
+ */
57
+ destroyUnmatched?: boolean;
58
+ /** When set, a new https.Server is created and the WS endpoint is attached to it. */
59
+ https?: ServerOptions;
60
+ /**
61
+ * Extra origins to accept on the WS upgrade beyond the loopback default.
62
+ * Add your LAN/tunnel origin here when reaching the tool from another host.
63
+ * Pass `false` to disable origin checking entirely (not recommended).
64
+ * Default: loopback-only.
65
+ */
66
+ allowedOrigins?: readonly string[] | false;
67
+ /**
68
+ * RPC function definitions, used by the per-call wire serializer to
69
+ * dispatch between strict-JSON and structured-clone encoding based
70
+ * on each function's `jsonSerializable` flag.
71
+ *
72
+ * When omitted, all messages fall back to structured-clone — safe but
73
+ * loses dev-time validation for `jsonSerializable: true` declarations.
74
+ */
75
+ definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
76
+ onConnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
77
+ onDisconnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
78
+ /** Override the default per-call serializer. Most callers should leave this unset. */
79
+ serialize?: ChannelOptions['serialize'];
80
+ /** Override the default per-call deserializer. Most callers should leave this unset. */
81
+ deserialize?: ChannelOptions['deserialize'];
82
+ }
83
+ interface WsRpcTransport {
84
+ /**
85
+ * The crossws node adapter driving the socket — exposes the connected
86
+ * `peers` and pub/sub. See https://crossws.h3.dev.
87
+ */
88
+ ws: NodeAdapter;
89
+ /** Remove the upgrade listener from a shared `server` (a no-op otherwise). */
90
+ detach: () => void;
91
+ /**
92
+ * Tear the transport down deterministically: detach from a shared server,
93
+ * force-terminate every connected peer, and close any server this
94
+ * transport created itself (`port` / `https` modes).
95
+ */
96
+ close: () => Promise<void>;
97
+ }
98
+ declare function isLoopbackHostname(hostname: string): boolean;
99
+ /**
100
+ * Default origin policy for a localhost dev tool: allow requests with no
101
+ * `Origin` header (native, non-browser clients), allow any loopback origin
102
+ * (so cross-port localhost dev setups keep working), and allow explicitly
103
+ * configured origins. Everything else — a real remote page in the dev's
104
+ * browser — is rejected.
105
+ */
106
+ declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: readonly string[]): boolean;
107
+ /**
108
+ * Attach a WebSocket transport to an existing RPC group, powered by
109
+ * [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
110
+ * `server` (sharing its port, optionally scoped to a `path`), or let this
111
+ * helper create a standalone server from `port` / `host` / `https`.
112
+ *
113
+ * Returns the crossws node adapter plus `detach` (remove the upgrade
114
+ * listener from a shared `server`) and `close` (full deterministic
115
+ * teardown).
116
+ */
117
+ declare function attachWsRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: WsRpcTransportOptions): WsRpcTransport;
118
+ //#endregion
119
+ export { isAllowedOrigin as a, attachWsRpcTransport as i, WsRpcTransport as n, isLoopbackHostname as o, WsRpcTransportOptions as r, DevframeNodeRpcSessionMeta as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "devframe",
3
3
  "type": "module",
4
- "version": "0.5.4",
4
+ "version": "0.6.0-beta.2",
5
5
  "description": "Framework for building generic devframes",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -40,13 +40,14 @@
40
40
  "./rpc/transports/ws-server": "./dist/rpc/transports/ws-server.mjs",
41
41
  "./types": "./dist/types/index.mjs",
42
42
  "./utils/colors": "./dist/utils/colors.mjs",
43
+ "./utils/crypto-token": "./dist/utils/crypto-token.mjs",
43
44
  "./utils/events": "./dist/utils/events.mjs",
44
45
  "./utils/hash": "./dist/utils/hash.mjs",
45
- "./utils/human-id": "./dist/utils/human-id.mjs",
46
46
  "./utils/launch-editor": "./dist/utils/launch-editor.mjs",
47
47
  "./utils/nanoid": "./dist/utils/nanoid.mjs",
48
48
  "./utils/open": "./dist/utils/open.mjs",
49
49
  "./utils/promise": "./dist/utils/promise.mjs",
50
+ "./utils/scope": "./dist/utils/scope.mjs",
50
51
  "./utils/serve-static": "./dist/utils/serve-static.mjs",
51
52
  "./utils/shared-state": "./dist/utils/shared-state.mjs",
52
53
  "./utils/streaming-channel": "./dist/utils/streaming-channel.mjs",
@@ -71,12 +72,14 @@
71
72
  "@valibot/to-json-schema": "^1.7.0",
72
73
  "birpc": "^4.0.0",
73
74
  "cac": "^7.0.0",
75
+ "crossws": "^0.4.8",
76
+ "destr": "^2.0.5",
74
77
  "h3": "2.0.1-rc.22",
75
78
  "mrmime": "^2.0.1",
76
- "nostics": "^0.2.0",
79
+ "nostics": "^1.1.4",
77
80
  "pathe": "^2.0.3",
78
- "valibot": "^1.4.1",
79
- "ws": "^8.21.0"
81
+ "ufo": "^1.6.4",
82
+ "valibot": "^1.4.1"
80
83
  },
81
84
  "devDependencies": {
82
85
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -93,7 +96,8 @@
93
96
  "tinyglobby": "^0.2.16",
94
97
  "tsdown": "^0.22.0",
95
98
  "ua-parser-modern": "^0.1.1",
96
- "whenexpr": "^0.1.2"
99
+ "whenexpr": "^0.1.2",
100
+ "ws": "^8.21.0"
97
101
  },
98
102
  "inlinedDependencies": {
99
103
  "bundle-name": "4.1.0",
@@ -124,6 +128,7 @@
124
128
  },
125
129
  "scripts": {
126
130
  "build": "tsdown",
127
- "watch": "tsdown --watch"
131
+ "watch": "tsdown --watch",
132
+ "typecheck": "tsc --noEmit"
128
133
  }
129
134
  }