devframe 0.8.2 → 0.9.0-beta.10

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 (127) hide show
  1. package/dist/_shared-BPmX9fqK.d.mts +41 -0
  2. package/dist/_shared-CbZ_yhbz.mjs +68 -0
  3. package/dist/adapters/build.d.mts +10 -12
  4. package/dist/adapters/build.mjs +26 -29
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +156 -1
  7. package/dist/adapters/dev.d.mts +38 -46
  8. package/dist/adapters/dev.mjs +2 -1
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +224 -0
  11. package/dist/adapters/initiate.mjs +2 -0
  12. package/dist/adapters/mcp.d.mts +24 -7
  13. package/dist/adapters/mcp.mjs +2 -2
  14. package/dist/{agent-tool-name-C3b5vEwJ.mjs → agent-tool-name-EgfoFO8C.mjs} +0 -3
  15. package/dist/cli/main.d.mts +0 -2
  16. package/dist/cli/main.mjs +4 -8
  17. package/dist/client/index.d.mts +71 -7
  18. package/dist/client/index.mjs +720 -1540
  19. package/dist/constants.d.mts +28 -7
  20. package/dist/constants.mjs +28 -7
  21. package/dist/{context-DoSozfvx.d.mts → context-B3YUu_95.d.mts} +19 -1
  22. package/dist/{host-h3-D2hYZvDK.mjs → context-CEe4PBz1.mjs} +25 -557
  23. package/dist/{context-CBg8iRbQ.mjs → context-xdynGSLP.mjs} +10 -1
  24. package/dist/dev--rAPzbvO.mjs +239 -0
  25. package/dist/{devframe-mQ4qZAuo.d.mts → devframe-DJMg0WLe.d.mts} +181 -134
  26. package/dist/{diagnostics-hwjXp_UV.mjs → diagnostics-CD8nlgll.mjs} +1 -3
  27. package/dist/{diagnostics-D7wM2cwM.mjs → diagnostics-DOQPnQmX.mjs} +51 -5
  28. package/dist/dist-eNI9qM_I.mjs +228 -0
  29. package/dist/host-agent-xLCSe65D.mjs +264 -0
  30. package/dist/host-h3-Crkzm42q.mjs +26 -0
  31. package/dist/{fetch-BKaD0Vb3.mjs → http-DYpK0cUi.mjs} +42 -11
  32. package/dist/{index-b64Uuhy2.d.mts → index-CB-fSUyn.d.mts} +2 -2
  33. package/dist/{index-Dbw1p5ch.d.mts → index-tdrk1bzV.d.mts} +3 -32
  34. package/dist/index.d.mts +12 -5
  35. package/dist/index.mjs +8 -1
  36. package/dist/{instance-registry-BnnA2Eoh.mjs → instance-registry-WvQkt42E.mjs} +9 -8
  37. package/dist/instance-shell-CMFyszWJ.mjs +486 -0
  38. package/dist/instance-shell-CljiKq1c.d.mts +255 -0
  39. package/dist/internal/index.d.mts +397 -0
  40. package/dist/internal/index.mjs +9 -0
  41. package/dist/node/auth.d.mts +2 -2
  42. package/dist/node/auth.mjs +1 -1
  43. package/dist/node/hub-internals.d.mts +2 -14
  44. package/dist/node/hub-internals.mjs +2 -2
  45. package/dist/node/index.d.mts +4 -268
  46. package/dist/node/index.mjs +3 -5
  47. package/dist/nostics-CzECRXpE.mjs +59 -0
  48. package/dist/nostics-SPGDgHEP.d.mts +33 -0
  49. package/dist/recipes/common-rpc-functions.d.mts +4 -4
  50. package/dist/recipes/interactive-auth.d.mts +3 -3
  51. package/dist/recipes/interactive-auth.mjs +8 -8
  52. package/dist/remote-assets-Bg4gCUZ_.d.mts +123 -0
  53. package/dist/remote-assets-nTTxY2MU.mjs +636 -0
  54. package/dist/rolldown-runtime-JspESFgx.mjs +35 -0
  55. package/dist/rpc/dump.d.mts +77 -1
  56. package/dist/rpc/dump.mjs +1 -1
  57. package/dist/rpc/index.d.mts +3 -3
  58. package/dist/rpc/index.mjs +4 -18
  59. package/dist/rpc/transports/sse-client.d.mts +2 -0
  60. package/dist/rpc/transports/sse-client.mjs +142 -0
  61. package/dist/rpc/transports/sse-server.d.mts +61 -0
  62. package/dist/rpc/transports/sse-server.mjs +209 -0
  63. package/dist/rpc/transports/ws-bun.d.mts +35 -0
  64. package/dist/rpc/transports/ws-bun.mjs +31 -0
  65. package/dist/rpc/transports/ws-client.d.mts +1 -1
  66. package/dist/rpc/transports/ws-client.mjs +10 -18
  67. package/dist/rpc/transports/ws-server.d.mts +2 -2
  68. package/dist/rpc/transports/ws-server.mjs +2 -235
  69. package/dist/rpc-core-Cc4lucI0.d.mts +48 -0
  70. package/dist/rpc-core-DMTe-hLo.mjs +81 -0
  71. package/dist/{serialization-C8Mnw9hK.mjs → serialization-BGzEwAdr.mjs} +2 -2
  72. package/dist/{shared-state-6kHolYZy.mjs → shared-state-fRRbRUtD.mjs} +1 -1
  73. package/dist/sse-client-CFBOIaUi.d.mts +37 -0
  74. package/dist/state-Cc1IeSmQ.mjs +1291 -0
  75. package/dist/{dump-CgZShDRB.mjs → static-BPZ1_OPA.mjs} +3 -266
  76. package/dist/storage-CYye7P7L.mjs +137 -0
  77. package/dist/types/index.d.mts +6 -4
  78. package/dist/types/index.mjs +1 -6
  79. package/dist/{types-CnJSgRVa.d.mts → types-Djq7CBXh.d.mts} +0 -7
  80. package/dist/utils/agent-tool-name.d.mts +0 -3
  81. package/dist/utils/agent-tool-name.mjs +0 -3
  82. package/dist/utils/events.d.mts +1 -1
  83. package/dist/utils/get-port.d.mts +17 -0
  84. package/dist/utils/get-port.mjs +2 -0
  85. package/dist/utils/launch-editor.mjs +1 -25
  86. package/dist/utils/nostics.d.mts +2 -0
  87. package/dist/utils/nostics.mjs +2 -0
  88. package/dist/utils/open.d.mts +2 -10
  89. package/dist/utils/open.mjs +56 -1
  90. package/dist/utils/remote-assets.d.mts +15 -0
  91. package/dist/utils/remote-assets.mjs +2 -0
  92. package/dist/utils/serve-static.d.mts +13 -5
  93. package/dist/utils/serve-static.mjs +119 -9
  94. package/dist/utils/shared-state.d.mts +1 -1
  95. package/dist/utils/shared-state.mjs +1 -1
  96. package/dist/utils/simple-schema.d.mts +101 -1
  97. package/dist/utils/streaming-channel.d.mts +1 -1
  98. package/dist/validation-CpXFB6Dz.mjs +266 -0
  99. package/dist/wire-codec-0K-o5MYW.mjs +56 -0
  100. package/dist/{ws-client-B6Tnr0ic.d.mts → ws-client-DhjNyQo3.d.mts} +7 -2
  101. package/dist/ws-server-BdSLrhxE.mjs +267 -0
  102. package/dist/{ws-server-uKQcClcJ.d.mts → ws-server-D1d3QM9f.d.mts} +90 -9
  103. package/package.json +13 -12
  104. package/skills/devframe/SKILL.md +91 -93
  105. package/dist/_shared-bWRzeSa0.mjs +0 -19
  106. package/dist/adapters/cli.d.mts +0 -11
  107. package/dist/adapters/cli.mjs +0 -6
  108. package/dist/cac-D4_9ok0z.mjs +0 -157
  109. package/dist/dev-Bc49u3qg.mjs +0 -439
  110. package/dist/diagnostics-reporter-CsIG85Q5.mjs +0 -38
  111. package/dist/helpers/vite.d.mts +0 -100
  112. package/dist/helpers/vite.mjs +0 -105
  113. package/dist/http-C4T68YV6.mjs +0 -38
  114. package/dist/index-vAX6bYuc.d.mts +0 -78
  115. package/dist/open-Deb5xmIT.mjs +0 -547
  116. package/dist/recipes/open-helpers.d.mts +0 -6
  117. package/dist/recipes/open-helpers.mjs +0 -6
  118. package/dist/server-CD8XmBXl.mjs +0 -167
  119. package/dist/server-UHWae43Q.d.mts +0 -149
  120. package/dist/simple-schema-BDzLeJDk.d.mts +0 -102
  121. package/dist/state-BeUHDDjk.mjs +0 -108
  122. package/dist/storage-CTNVbuFB.mjs +0 -1122
  123. package/dist/utils/promise.d.mts +0 -8
  124. package/dist/utils/promise.mjs +0 -15
  125. package/dist/utils/scope.d.mts +0 -11
  126. package/dist/utils/scope.mjs +0 -15
  127. package/skills/devframe/templates/spa-devframe.ts +0 -38
@@ -1,167 +0,0 @@
1
- import { createRpcServer } from "./rpc/server.mjs";
2
- import { attachWsRpcTransport } from "./rpc/transports/ws-server.mjs";
3
- import { t as diagnostics } from "./diagnostics-D7wM2cwM.mjs";
4
- import { t as getInternalContext } from "./context-CBg8iRbQ.mjs";
5
- import { createServer } from "node:http";
6
- import { AsyncLocalStorage } from "node:async_hooks";
7
- import { H3, toNodeHandler } from "h3";
8
- import { isIP } from "node:net";
9
- //#region src/node/utils.ts
10
- function isObject(value) {
11
- return Object.prototype.toString.call(value) === "[object Object]";
12
- }
13
- const NON_DIALABLE_HOSTS = /* @__PURE__ */ new Set([
14
- "0.0.0.0",
15
- "127.0.0.1",
16
- "::",
17
- "0000:0000:0000:0000:0000:0000:0000:0000",
18
- ""
19
- ]);
20
- /** Map a bind host to a host a client can actually connect to. */
21
- function toDialableHost(host) {
22
- return NON_DIALABLE_HOSTS.has(host) ? "localhost" : host;
23
- }
24
- /** Format a bind host for use in a URL authority (dialable, IPv6-bracketed). */
25
- function formatHostForUrl(host) {
26
- const dialable = toDialableHost(host);
27
- return isIP(dialable) === 6 ? `[${dialable}]` : dialable;
28
- }
29
- function normalizeHttpServerUrl(host, port) {
30
- return `http://${formatHostForUrl(host)}:${port}`;
31
- }
32
- //#endregion
33
- //#region src/node/server.ts
34
- /**
35
- * Compose an h3 + WebSocket server for a devframe context. The RPC
36
- * group is bound to `context.rpc.functions`; the WS endpoint lives on
37
- * the same port as the HTTP server.
38
- */
39
- async function startHttpAndWs(options) {
40
- const { context, port } = options;
41
- const bindHost = options.host ?? "localhost";
42
- const app = options.app ?? new H3();
43
- const ownsHttpServer = !options.server;
44
- const httpServer = options.server ?? createServer(toNodeHandler(app));
45
- const rpcHost = context.rpc;
46
- const asyncStorage = new AsyncLocalStorage();
47
- const authHandler = typeof options.auth === "object" ? options.auth : void 0;
48
- const effectiveAuthorize = options.authorize ?? authHandler?.authorize;
49
- if (authHandler) {
50
- for (const fn of authHandler.rpcFunctions) if (!rpcHost.definitions.has(fn.name)) rpcHost.register(fn);
51
- }
52
- const rpcGroup = createRpcServer(rpcHost.functions, { rpcOptions: {
53
- onFunctionError: options.rpcOptions?.onFunctionError,
54
- onGeneralError: options.rpcOptions?.onGeneralError,
55
- resolver(name, fn) {
56
- const rpc = this;
57
- if (!fn) return void 0;
58
- return async function(...args) {
59
- const meta = rpc.$meta;
60
- if (effectiveAuthorize && !effectiveAuthorize(name, {
61
- meta,
62
- rpc
63
- })) throw diagnostics.DF0036({ name });
64
- return await asyncStorage.run({
65
- rpc,
66
- meta
67
- }, async () => {
68
- return (await fn).apply(this, args);
69
- });
70
- };
71
- }
72
- } });
73
- const separateWsPort = ownsHttpServer && options.wsPort != null && options.wsPort !== port ? options.wsPort : void 0;
74
- const { ws, close: closeWs } = attachWsRpcTransport(rpcGroup, {
75
- ...separateWsPort != null ? {
76
- port: separateWsPort,
77
- host: bindHost
78
- } : { server: httpServer },
79
- path: options.path,
80
- destroyUnmatched: ownsHttpServer,
81
- allowedOrigins: options.allowedOrigins,
82
- onConnected: authHandler || options.onPeerConnect ? (peer, meta) => {
83
- const session = {
84
- meta,
85
- rpc: rpcGroup.clients.find((client) => client.$meta === meta)
86
- };
87
- authHandler?.onConnect(peer, session);
88
- options.onPeerConnect?.(peer, session);
89
- } : void 0,
90
- onDisconnected: (peer, meta) => {
91
- options.onPeerDisconnect?.(peer, meta);
92
- rpcHost._emitSessionDisconnected(meta);
93
- }
94
- });
95
- rpcHost._rpcGroup = rpcGroup;
96
- rpcHost._asyncStorage = asyncStorage;
97
- rpcHost._authDisabled = options.auth === false;
98
- if (options.auth === false && !rpcHost.definitions.has("anonymous:devframe:auth")) rpcHost.register({
99
- name: "anonymous:devframe:auth",
100
- type: "action",
101
- handler: () => {
102
- const session = rpcHost.getCurrentRpcSession();
103
- if (session) session.meta.isTrusted = true;
104
- return { isTrusted: true };
105
- }
106
- });
107
- if (ownsHttpServer) {
108
- try {
109
- await new Promise((resolve, reject) => {
110
- const onError = (error) => reject(error);
111
- httpServer.once("error", onError);
112
- httpServer.listen(port, bindHost, () => {
113
- httpServer.removeListener("error", onError);
114
- resolve();
115
- });
116
- });
117
- } catch (error) {
118
- await closeWs().catch(() => {});
119
- throw diagnostics.DF0052({
120
- host: bindHost,
121
- port,
122
- reason: error instanceof Error ? error.message : String(error),
123
- cause: error
124
- });
125
- }
126
- if (options.onServerError) httpServer.on("error", options.onServerError);
127
- }
128
- const address = httpServer.address();
129
- const resolvedPort = typeof address === "object" && address ? address.port : port;
130
- const origin = normalizeHttpServerUrl(bindHost, resolvedPort);
131
- const internal = getInternalContext(context);
132
- const wsPortForUrl = separateWsPort ?? resolvedPort;
133
- const wsUrl = `ws://${formatHostForUrl(bindHost)}:${wsPortForUrl}${options.path ?? ""}`;
134
- internal.wsEndpoint = { url: wsUrl };
135
- if (options.onReady) await options.onReady({
136
- origin,
137
- port: resolvedPort,
138
- app
139
- });
140
- function connectionMeta() {
141
- const jsonSerializableMethods = [];
142
- for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
143
- return {
144
- backend: "websocket",
145
- websocket: separateWsPort != null ? {
146
- port: separateWsPort,
147
- path: options.path
148
- } : { path: options.path },
149
- jsonSerializableMethods
150
- };
151
- }
152
- return {
153
- origin,
154
- port: resolvedPort,
155
- app,
156
- ws,
157
- rpcGroup,
158
- connectionMeta,
159
- async close() {
160
- await closeWs();
161
- if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));
162
- if (getInternalContext(context).wsEndpoint?.url === wsUrl) getInternalContext(context).wsEndpoint = void 0;
163
- }
164
- };
165
- }
166
- //#endregion
167
- export { toDialableHost as a, normalizeHttpServerUrl as i, formatHostForUrl as n, isObject as r, startHttpAndWs as t };
@@ -1,149 +0,0 @@
1
- import { $ as DevframeRpcServerFunctions, Q as DevframeRpcClientFunctions, W as DevframeNodeRpcSession, _ as ConnectionMeta, b as DevframeNodeContext, p as DevframeAuthHandler } from "./devframe-mQ4qZAuo.mjs";
2
- import { n as DevframeNodeRpcSessionMeta, r as WsOriginRegistry } from "./ws-server-uKQcClcJ.mjs";
3
- import "./index-b64Uuhy2.mjs";
4
- import { Peer } from "crossws";
5
- import { BirpcGroup, EventOptions } from "birpc";
6
- import { NodeAdapter } from "crossws/adapters/node";
7
- import { Server } from "node:http";
8
- import { H3 } from "h3";
9
- //#region src/node/server.d.ts
10
- interface StartHttpAndWsOptions {
11
- context: DevframeNodeContext;
12
- host?: string;
13
- port: number;
14
- /**
15
- * Optional h3 app to mount on. When omitted a fresh one is created;
16
- * when provided, callers can add their own routes (static handlers,
17
- * auth middleware, etc.) first.
18
- */
19
- app?: H3;
20
- /**
21
- * Bind the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`) instead of
22
- * claiming every upgrade on the port. This lets the socket share a server
23
- * with other upgrade handlers (Vite HMR, a host framework's own sockets)
24
- * and is what the SPA's `__connection.json` points at. When omitted, the WS
25
- * server handles every upgrade on the port (legacy behaviour).
26
- */
27
- path?: string;
28
- /**
29
- * Bind the WS endpoint on its own port instead of sharing the HTTP server's.
30
- * The HTTP/SPA server still listens on `port`; the socket gets a dedicated
31
- * `ws` server on `wsPort` (same `host`). Use this for the "different port"
32
- * connection scenario. Ignored when a `server` is supplied.
33
- */
34
- wsPort?: number;
35
- /**
36
- * Mount the WS endpoint onto an existing HTTP server, sharing its port,
37
- * rather than creating and listening on a fresh one. Use this to embed
38
- * devframe's RPC socket inside a host server (e.g. a Vite dev server) — pair
39
- * it with `path` so it coexists with the host's routes. The caller owns the
40
- * server's lifecycle: {@link StartedServer.close} detaches devframe's upgrade
41
- * listener but leaves the host server running. When set, `host`/`port` are
42
- * only used to report the resolved origin.
43
- */
44
- server?: Server;
45
- /**
46
- * Authentication for the server:
47
- *
48
- * - `true` (default) — no gate; every registered method is callable
49
- * regardless of trust (today's behavior, unchanged).
50
- * - `false` — the RPC server is started without a trust handshake.
51
- * Intended for single-user localhost tools where an auth round-trip
52
- * would only get in the way. A noop `anonymous:devframe:auth` handler
53
- * is registered so the browser client's unconditional handshake call
54
- * succeeds and auto-trusts.
55
- * - A {@link DevframeAuthHandler} (e.g. from
56
- * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`) —
57
- * registers its `rpcFunctions`, wires its `authorize` as the resolver
58
- * gate, and wires its `onConnect` on every new peer. This is the
59
- * fully-authenticated server: an untrusted caller can only reach
60
- * `anonymous:`-prefixed methods (see `isAnonymousRpcMethod`).
61
- */
62
- auth?: boolean | DevframeAuthHandler;
63
- /**
64
- * Lower-level escape hatch: gate individual RPC calls by method name and
65
- * session without a full {@link DevframeAuthHandler}. Ignored when `auth`
66
- * is a handler object (its own `authorize` is used); combine with `auth:
67
- * true` to layer a custom policy on top of an otherwise ungated server.
68
- */
69
- authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean;
70
- /**
71
- * Called once per new WS connection, right after its session is created
72
- * (before any RPC call is dispatched). Runs after the auth handler's own
73
- * `onConnect` (when `auth` is a {@link DevframeAuthHandler}), so it can
74
- * observe — but not override — the connect-time trust decision.
75
- */
76
- onPeerConnect?: (peer: Peer, session: DevframeNodeRpcSession) => void;
77
- /**
78
- * Called once per closed WS connection, right after the transport's own
79
- * disconnect bookkeeping runs. Unlike {@link onPeerConnect} this receives
80
- * the raw session meta, not a wrapped session — by the time a peer
81
- * disconnects there is no live RPC client left to attach.
82
- */
83
- onPeerDisconnect?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
84
- /**
85
- * Forwarded verbatim to the internal `createRpcServer`'s birpc
86
- * `rpcOptions`, alongside the resolver `startHttpAndWs` installs for
87
- * auth/session wiring. Use this so a host that owns its own structured
88
- * diagnostics (e.g. a coded error reporter) keeps seeing RPC failures
89
- * instead of them being silently absorbed by delegating to
90
- * `startHttpAndWs`. Returning `true` from either callback suppresses
91
- * birpc's own error response to the caller — see birpc's
92
- * `EventOptions` for the full contract.
93
- */
94
- rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>;
95
- /**
96
- * Extra origins to accept on the WS upgrade beyond the loopback default
97
- * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less request from a
98
- * native client). Add your LAN/tunnel origin here when reaching the tool
99
- * from another host. Pass `false` to disable origin checking entirely
100
- * (not recommended). Default: loopback-only.
101
- */
102
- allowedOrigins?: readonly string[] | WsOriginRegistry | false;
103
- /**
104
- * Called once the WS server is bound so callers can mount static
105
- * handlers whose origin depends on the resolved port, or print their
106
- * own startup banner. Devframe does not print one itself.
107
- */
108
- onReady?: (info: {
109
- origin: string;
110
- port: number;
111
- app: H3;
112
- }) => void | Promise<void>;
113
- /**
114
- * Called for any error the HTTP server devframe owns emits after it starts
115
- * listening — e.g. a transient `EMFILE` while accepting a connection.
116
- * Without it such an error has no listener and crashes the process.
117
- *
118
- * Applies only to a server devframe created itself. When `server` is
119
- * supplied the caller owns that object and attaches to it directly, so
120
- * devframe leaves its error handling alone.
121
- */
122
- onServerError?: (error: Error) => void;
123
- }
124
- interface StartedServer {
125
- /** Listening origin, e.g. `http://localhost:9999`. */
126
- origin: string;
127
- port: number;
128
- app: H3;
129
- /** The crossws node adapter driving the RPC socket (connected peers, pub/sub). */
130
- ws: NodeAdapter;
131
- rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
132
- /**
133
- * The {@link ConnectionMeta} descriptor for this server — the same shape
134
- * a `__connection.json` route should serve so a devframe client's
135
- * `resolveWsUrl` can dial back in. Reflects the `path` / `wsPort` this
136
- * server was started with and the `jsonSerializable` methods currently
137
- * registered on `context.rpc`.
138
- */
139
- connectionMeta: () => ConnectionMeta;
140
- close: () => Promise<void>;
141
- }
142
- /**
143
- * Compose an h3 + WebSocket server for a devframe context. The RPC
144
- * group is bound to `context.rpc.functions`; the WS endpoint lives on
145
- * the same port as the HTTP server.
146
- */
147
- declare function startHttpAndWs(options: StartHttpAndWsOptions): Promise<StartedServer>;
148
- //#endregion
149
- export { StartedServer as n, startHttpAndWs as r, StartHttpAndWsOptions as t };
@@ -1,102 +0,0 @@
1
- import { StandardSchemaV1 } from "@standard-schema/spec";
2
- //#region src/utils/simple-schema.d.ts
3
- /**
4
- * A tiny, zero-dependency [Standard Schema](https://standardschema.dev/)
5
- * builder.
6
- *
7
- * ⚠️ **Discouraged for app code.** This is a deliberately minimal,
8
- * best-effort validator that exists only so devframe's own first-party
9
- * packages (recipes, built-in plugins) can declare `args`/`returns`/flag
10
- * schemas without taking on a validator dependency. It implements a small
11
- * subset of primitives and approximates refinements — it is not a
12
- * general-purpose validator.
13
- *
14
- * For your own code, prefer a real Standard Schema validator — **valibot**,
15
- * **zod**, or **arktype**. Devframe's RPC and CLI-flag layers accept any of
16
- * them; install the one you like and use it directly:
17
- *
18
- * ```ts
19
- * import * as v from 'valibot' // npm i valibot
20
- *
21
- * defineRpcFunction({
22
- * name: 'greet',
23
- * args: [v.object({ name: v.string() })],
24
- * returns: v.string(),
25
- * handler: ({ name }) => `hi ${name}`,
26
- * })
27
- * ```
28
- */
29
- /**
30
- * A Standard Schema produced by the {@link s} builder. It carries a
31
- * duck-typed `type` marker (and `wrapped` for wrappers) alongside the
32
- * standard `~standard` prop so the CLI-flags adapter can introspect the
33
- * schema kind without importing any validator.
34
- */
35
- interface SimpleSchema<Input, Output = Input> extends StandardSchemaV1<Input, Output> {
36
- /** Schema kind marker, e.g. `'string'`, `'boolean'`, `'optional'`. */
37
- readonly type: string;
38
- /** Inner schema for wrapper kinds (`optional` / `nullable`). */
39
- readonly wrapped?: StandardSchemaV1;
40
- /** Optional human description (surfaced as CLI option help). */
41
- readonly description?: string;
42
- }
43
- /** Any string. */
44
- declare function string(): SimpleSchema<string>;
45
- /** A finite number (rejects `NaN`). */
46
- declare function number(): SimpleSchema<number>;
47
- /** A boolean. */
48
- declare function boolean(): SimpleSchema<boolean>;
49
- /** `undefined` — mirrors valibot's `void`. */
50
- declare function voidType(): SimpleSchema<void>;
51
- /** `null`. */
52
- declare function nullType(): SimpleSchema<null>;
53
- /** One of a fixed set of literal values. */
54
- declare function picklist<const T extends readonly (string | number | boolean)[]>(values: T): SimpleSchema<T[number]>;
55
- /** A single literal value (string / number / boolean). */
56
- declare function literal<const T extends string | number | boolean>(value: T): SimpleSchema<T>;
57
- /** A value matching any one of the given schemas. */
58
- declare function union<const T extends readonly StandardSchemaV1[]>(options: T): SimpleSchema<StandardSchemaV1.InferInput<T[number]>, StandardSchemaV1.InferOutput<T[number]>>;
59
- /** A record with string keys whose values each satisfy the value schema. */
60
- declare function record<V extends StandardSchemaV1>(_key: StandardSchemaV1, value: V): SimpleSchema<Record<string, StandardSchemaV1.InferInput<V>>, Record<string, StandardSchemaV1.InferOutput<V>>>;
61
- /** An array whose every element satisfies the item schema. */
62
- declare function array<T extends StandardSchemaV1>(item: T): SimpleSchema<StandardSchemaV1.InferInput<T>[], StandardSchemaV1.InferOutput<T>[]>;
63
- /** Flatten an intersection into a single object literal for readable types. */
64
- type Prettify<T> = { [K in keyof T]: T[K]; } & {};
65
- /**
66
- * Map a shape to its object type, turning fields whose type includes
67
- * `undefined` (i.e. `optional()`) into optional keys — mirroring how
68
- * valibot/zod render `optional` object entries.
69
- */
70
- type InferField<T extends StandardSchemaV1, Mode extends 'input' | 'output'> = Mode extends 'input' ? StandardSchemaV1.InferInput<T> : StandardSchemaV1.InferOutput<T>;
71
- type InferObject<T extends Record<string, StandardSchemaV1>, Mode extends 'input' | 'output'> = Prettify<{ [K in keyof T as undefined extends InferField<T[K], Mode> ? never : K]: InferField<T[K], Mode>; } & { [K in keyof T as undefined extends InferField<T[K], Mode> ? K : never]?: InferField<T[K], Mode>; }>;
72
- /** An object whose known keys each satisfy their schema (extra keys are kept). */
73
- declare function object<T extends Record<string, StandardSchemaV1>>(shape: T): SimpleSchema<InferObject<T, 'input'>, InferObject<T, 'output'>>;
74
- /** Allow `undefined` in addition to the inner schema. */
75
- declare function optional<T extends StandardSchemaV1>(inner: T): SimpleSchema<StandardSchemaV1.InferInput<T> | undefined, StandardSchemaV1.InferOutput<T> | undefined>;
76
- /** Allow `null` in addition to the inner schema. */
77
- declare function nullable<T extends StandardSchemaV1>(inner: T): SimpleSchema<StandardSchemaV1.InferInput<T> | null, StandardSchemaV1.InferOutput<T> | null>;
78
- /** Attach a human-readable description (used for CLI option help). */
79
- declare function describe<T extends SimpleSchema<any, any>>(schema: T, description: string): T;
80
- /**
81
- * Grouped access to every builder — `s.string()`, `s.object({ ... })`,
82
- * `s.void()`, etc. Handy for a valibot-like `import { s } from
83
- * 'devframe/utils/simple-schema'` call site.
84
- */
85
- declare const s: {
86
- readonly string: typeof string;
87
- readonly number: typeof number;
88
- readonly boolean: typeof boolean;
89
- readonly void: typeof voidType;
90
- readonly null: typeof nullType;
91
- readonly literal: typeof literal;
92
- readonly picklist: typeof picklist;
93
- readonly union: typeof union;
94
- readonly record: typeof record;
95
- readonly array: typeof array;
96
- readonly object: typeof object;
97
- readonly optional: typeof optional;
98
- readonly nullable: typeof nullable;
99
- readonly describe: typeof describe;
100
- };
101
- //#endregion
102
- export { literal as a, number as c, picklist as d, record as f, voidType as g, union as h, describe as i, object as l, string as m, array as n, nullType as o, s as p, boolean as r, nullable as s, SimpleSchema as t, optional as u };
@@ -1,108 +0,0 @@
1
- import { DEVFRAME_OTP_URL_PARAM } from "./constants.mjs";
2
- import { n as randomToken, r as timingSafeEqual, t as randomDigits } from "./crypto-token-XCqTSMg9.mjs";
3
- //#region src/node/auth/state.ts
4
- /** Number of decimal digits in a human-typed one-time authentication code. */
5
- const TEMP_AUTH_CODE_LENGTH = 6;
6
- /**
7
- * How long an authentication code stays valid after it is (re)generated. A
8
- * 6-digit code only has ~20 bits of entropy, so a short lifetime plus the
9
- * attempt cap below are what keep it brute-force resistant.
10
- */
11
- const TEMP_AUTH_CODE_TTL = 5 * 6e4;
12
- /** Failed attempts allowed against a single code before it is rotated. */
13
- const TEMP_AUTH_MAX_ATTEMPTS = 5;
14
- let tempAuthCode = generateTempCode();
15
- let tempAuthCodeExpiresAt = Date.now() + TEMP_AUTH_CODE_TTL;
16
- let tempAuthFailedAttempts = 0;
17
- function generateTempCode() {
18
- return randomDigits(TEMP_AUTH_CODE_LENGTH);
19
- }
20
- /**
21
- * The current one-time authentication code. Display this to the user (e.g. in
22
- * the dev-server terminal) so they can type it into the browser to authenticate.
23
- */
24
- function getTempAuthCode() {
25
- return tempAuthCode;
26
- }
27
- /**
28
- * Rotate the authentication code, resetting its expiry window and failed-attempt
29
- * counter. Call this when a new authentication flow begins (e.g. when an
30
- * untrusted client starts authenticating) so the displayed code is freshly
31
- * valid for its full TTL.
32
- */
33
- function refreshTempAuthCode() {
34
- tempAuthCode = generateTempCode();
35
- tempAuthCodeExpiresAt = Date.now() + TEMP_AUTH_CODE_TTL;
36
- tempAuthFailedAttempts = 0;
37
- return tempAuthCode;
38
- }
39
- /**
40
- * Build a "magic link" authentication URL that embeds a one-time code (OTP) in
41
- * the URL **fragment**. Opening it authenticates the client without typing —
42
- * print it on startup (devframe stays headless, so the host prints its own
43
- * banner). Defaults to the current code; the link is subject to the same TTL.
44
- *
45
- * The code rides the fragment (`#devframe_otp=…`), not the query string, so it
46
- * is never sent to the server, written to an access log, or leaked in a
47
- * `Referer` header — the browser client reads it locally (see
48
- * `consumeOtpFromUrl`). Any existing fragment parameters are preserved.
49
- */
50
- function buildOtpAuthUrl(baseUrl, code = tempAuthCode) {
51
- const url = new URL(baseUrl);
52
- const fragment = new URLSearchParams(url.hash.replace(/^#/, ""));
53
- fragment.set(DEVFRAME_OTP_URL_PARAM, code);
54
- url.hash = fragment.toString();
55
- return url.href;
56
- }
57
- /**
58
- * Re-authenticate a connection that presents a previously-issued bearer token.
59
- * Returns `true` and marks the session trusted when the token is known.
60
- *
61
- * Used by the `anonymous:devframe:auth` handler so a client that already
62
- * authenticated (token persisted in the browser) is trusted on reconnect
63
- * without entering the code again.
64
- */
65
- function verifyAuthToken(token, session, storage) {
66
- if (!token || !storage.value().trusted[token]) return false;
67
- session.meta.clientAuthToken = token;
68
- session.meta.isTrusted = true;
69
- return true;
70
- }
71
- /**
72
- * Exchange a one-time authentication code for a fresh, node-issued bearer token.
73
- *
74
- * On success this mints a high-entropy token, records it in the trusted store,
75
- * marks the calling session trusted, rotates the code, and returns the token
76
- * for the client to persist. Returns `null` on any failure.
77
- *
78
- * Because the code is short and human-typed, verification is hardened against
79
- * brute force: it enforces a time-to-live, compares in constant time, and
80
- * rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an
81
- * attacker cannot keep guessing against the same code.
82
- */
83
- function exchangeTempAuthCode(code, session, info, storage) {
84
- if (Date.now() > tempAuthCodeExpiresAt) {
85
- refreshTempAuthCode();
86
- return null;
87
- }
88
- if (!timingSafeEqual(code, tempAuthCode)) {
89
- tempAuthFailedAttempts += 1;
90
- if (tempAuthFailedAttempts >= TEMP_AUTH_MAX_ATTEMPTS) refreshTempAuthCode();
91
- return null;
92
- }
93
- const authToken = randomToken();
94
- storage.mutate((state) => {
95
- state.trusted[authToken] = {
96
- authToken,
97
- ua: info.ua,
98
- origin: info.origin,
99
- timestamp: Date.now()
100
- };
101
- });
102
- session.meta.clientAuthToken = authToken;
103
- session.meta.isTrusted = true;
104
- refreshTempAuthCode();
105
- return authToken;
106
- }
107
- //#endregion
108
- export { verifyAuthToken as a, refreshTempAuthCode as i, exchangeTempAuthCode as n, getTempAuthCode as r, buildOtpAuthUrl as t };