@meekojs/meeko 0.0.2 → 0.0.4
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/README.md +40 -2
- package/dist/autoimports-ts.d.ts +26 -0
- package/dist/autoimports-ts.js +127 -0
- package/dist/autoimports-ts.js.map +1 -0
- package/dist/autoimports.d.ts +21 -0
- package/dist/autoimports.js +285 -0
- package/dist/autoimports.js.map +1 -0
- package/dist/check.js +35 -23
- package/dist/check.js.map +1 -0
- package/dist/cli.js +59 -19
- package/dist/cli.js.map +1 -0
- package/dist/compiler/import-graph.d.ts +8 -0
- package/dist/compiler/import-graph.js +33 -0
- package/dist/compiler/import-graph.js.map +1 -0
- package/dist/compiler/mapping.js +1 -0
- package/dist/compiler/mapping.js.map +1 -0
- package/dist/compiler/project-root.d.ts +2 -0
- package/dist/compiler/project-root.js +18 -0
- package/dist/compiler/project-root.js.map +1 -0
- package/dist/compiler/sfc.js +1 -0
- package/dist/compiler/sfc.js.map +1 -0
- package/dist/compiler/sourcemap.d.ts +3 -0
- package/dist/compiler/sourcemap.js +75 -0
- package/dist/compiler/sourcemap.js.map +1 -0
- package/dist/compiler/template.js +1 -0
- package/dist/compiler/template.js.map +1 -0
- package/dist/compiler.js +22 -11
- package/dist/compiler.js.map +1 -0
- package/dist/devtools-ui/index.js +56 -6
- package/dist/devtools-ui/index.js.map +1 -0
- package/dist/devtools.d.ts +9 -0
- package/dist/devtools.js +16 -6
- package/dist/devtools.js.map +1 -0
- package/dist/dom.d.ts +17 -6
- package/dist/dom.js +86 -19
- package/dist/dom.js.map +1 -0
- package/dist/framework/check-app.d.ts +5 -0
- package/dist/framework/check-app.js +59 -0
- package/dist/framework/check-app.js.map +1 -0
- package/dist/framework/client.d.ts +45 -0
- package/dist/framework/client.js +146 -0
- package/dist/framework/client.js.map +1 -0
- package/dist/framework/clients.d.ts +82 -0
- package/dist/framework/clients.js +182 -0
- package/dist/framework/clients.js.map +1 -0
- package/dist/framework/config-type.d.ts +10 -0
- package/dist/framework/config-type.js +4 -0
- package/dist/framework/config-type.js.map +1 -0
- package/dist/framework/config.d.ts +15 -0
- package/dist/framework/config.js +138 -0
- package/dist/framework/config.js.map +1 -0
- package/dist/framework/contracts-generate.d.ts +8 -0
- package/dist/framework/contracts-generate.js +227 -0
- package/dist/framework/contracts-generate.js.map +1 -0
- package/dist/framework/generate.d.ts +7 -0
- package/dist/framework/generate.js +67 -0
- package/dist/framework/generate.js.map +1 -0
- package/dist/framework/init.d.ts +2 -0
- package/dist/framework/init.js +362 -0
- package/dist/framework/init.js.map +1 -0
- package/dist/framework/modules.d.ts +22 -0
- package/dist/framework/modules.js +29 -0
- package/dist/framework/modules.js.map +1 -0
- package/dist/framework/page.d.ts +60 -0
- package/dist/framework/page.js +42 -0
- package/dist/framework/page.js.map +1 -0
- package/dist/framework/prerender.d.ts +14 -0
- package/dist/framework/prerender.js +104 -0
- package/dist/framework/prerender.js.map +1 -0
- package/dist/framework/router.d.ts +39 -0
- package/dist/framework/router.js +173 -0
- package/dist/framework/router.js.map +1 -0
- package/dist/framework/server.d.ts +51 -0
- package/dist/framework/server.js +192 -0
- package/dist/framework/server.js.map +1 -0
- package/dist/framework/socket-contracts-generate.d.ts +5 -0
- package/dist/framework/socket-contracts-generate.js +159 -0
- package/dist/framework/socket-contracts-generate.js.map +1 -0
- package/dist/framework/socket.d.ts +70 -0
- package/dist/framework/socket.js +150 -0
- package/dist/framework/socket.js.map +1 -0
- package/dist/framework/state.d.ts +15 -0
- package/dist/framework/state.js +72 -0
- package/dist/framework/state.js.map +1 -0
- package/dist/framework-server/auto-clients.d.ts +3 -0
- package/dist/framework-server/auto-clients.js +27 -0
- package/dist/framework-server/auto-clients.js.map +1 -0
- package/dist/framework-server/context.d.ts +21 -0
- package/dist/framework-server/context.js +22 -0
- package/dist/framework-server/context.js.map +1 -0
- package/dist/framework-server/contracts.d.ts +64 -0
- package/dist/framework-server/contracts.js +129 -0
- package/dist/framework-server/contracts.js.map +1 -0
- package/dist/framework-server/discovery.d.ts +40 -0
- package/dist/framework-server/discovery.js +252 -0
- package/dist/framework-server/discovery.js.map +1 -0
- package/dist/framework-server/local.d.ts +19 -0
- package/dist/framework-server/local.js +98 -0
- package/dist/framework-server/local.js.map +1 -0
- package/dist/framework-server/node-upgrade.d.ts +23 -0
- package/dist/framework-server/node-upgrade.js +78 -0
- package/dist/framework-server/node-upgrade.js.map +1 -0
- package/dist/framework-server/node-websocket.d.ts +16 -0
- package/dist/framework-server/node-websocket.js +125 -0
- package/dist/framework-server/node-websocket.js.map +1 -0
- package/dist/framework-server/openapi.d.ts +17 -0
- package/dist/framework-server/openapi.js +118 -0
- package/dist/framework-server/openapi.js.map +1 -0
- package/dist/framework-server/session-stores.d.ts +66 -0
- package/dist/framework-server/session-stores.js +96 -0
- package/dist/framework-server/session-stores.js.map +1 -0
- package/dist/framework-server/sessions.d.ts +64 -0
- package/dist/framework-server/sessions.js +136 -0
- package/dist/framework-server/sessions.js.map +1 -0
- package/dist/framework-server/socket.d.ts +83 -0
- package/dist/framework-server/socket.js +161 -0
- package/dist/framework-server/socket.js.map +1 -0
- package/dist/framework-server.d.ts +21 -0
- package/dist/framework-server.js +12 -0
- package/dist/framework-server.js.map +1 -0
- package/dist/framework.d.ts +29 -0
- package/dist/framework.js +13 -0
- package/dist/framework.js.map +1 -0
- package/dist/hmr.js +1 -0
- package/dist/hmr.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +209 -0
- package/dist/init.js.map +1 -0
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -0
- package/dist/node.d.ts +12 -0
- package/dist/node.js +52 -0
- package/dist/node.js.map +1 -0
- package/dist/reactivity.d.ts +25 -1
- package/dist/reactivity.js +69 -10
- package/dist/reactivity.js.map +1 -0
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -0
- package/dist/types.js +23 -8
- package/dist/types.js.map +1 -0
- package/dist/vite.js +91 -6
- package/dist/vite.js.map +1 -0
- package/package.json +19 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/framework/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdir, stat } from \"node:fs/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { escapeHtml, serializeJson } from \"../server.js\";\nimport type { RequestContext } from \"../framework-server/context.js\";\nimport { withRequestContext } from \"../framework-server/context.js\";\nimport type { ServerFetch } from \"../framework-server/context.js\";\nimport { PageInputError, pageSearchObject, validatePageInputs } from \"./page.js\";\nimport type { PageSchema } from \"./page.js\";\nimport { StateStore, withStateStore } from \"./state.js\";\nimport { createRouteManifest, matchRoute } from \"./router.js\";\nimport type { RouteRecord } from \"./router.js\";\n\nfunction routeId(relativeFile: string): string {\n const parts = relativeFile.slice(0, -\".meeko\".length).split(sep);\n if (parts.at(-1) === \"index\") parts.pop();\n const visible = parts.filter((part) => !/^\\([^/()]+\\)$/.test(part));\n return visible.length ? `/${visible.join(\"/\")}` : \"/\";\n}\n\n/** Discovers pages once; the returned manifest is safe to serialize for the browser. */\nexport async function discoverRoutes(projectRoot: string): Promise<readonly RouteRecord[]> {\n const pagesRoot = resolve(projectRoot, \"app\", \"pages\");\n const files = new Set<string>();\n async function walk(directory: string): Promise<void> {\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n const path = join(directory, entry.name);\n if (entry.isDirectory()) await walk(path);\n else if (entry.isFile()) files.add(path);\n }\n }\n await walk(pagesRoot);\n const appShell = resolve(projectRoot, \"app\", \"app.meeko\");\n let hasAppShell = false;\n try { hasAppShell = (await stat(appShell)).isFile(); }\n catch (error) { if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error; }\n const routes: RouteRecord[] = [];\n for (const file of files) {\n if (!file.endsWith(\".meeko\") || basename(file).startsWith(\"+\")) continue;\n const directory = dirname(file);\n const layouts: string[] = hasAppShell ? [appShell] : [];\n let cursor = pagesRoot;\n const relativeDirectory = relative(pagesRoot, directory);\n if (files.has(join(cursor, \"+layout.meeko\"))) layouts.push(join(cursor, \"+layout.meeko\"));\n if (relativeDirectory) for (const segment of relativeDirectory.split(sep)) {\n cursor = join(cursor, segment);\n if (files.has(join(cursor, \"+layout.meeko\"))) layouts.push(join(cursor, \"+layout.meeko\"));\n }\n let error: string | undefined;\n let errorCursor = directory;\n while (errorCursor.startsWith(pagesRoot)) {\n const candidate = join(errorCursor, \"+error.meeko\");\n if (files.has(candidate)) { error = candidate; break; }\n if (errorCursor === pagesRoot) break;\n errorCursor = dirname(errorCursor);\n }\n const sidecar = file.slice(0, -\".meeko\".length) + \".route.ts\";\n routes.push({\n id: routeId(relative(pagesRoot, file)),\n file,\n layouts,\n ...(error ? { error } : {}),\n ...(files.has(sidecar) ? { sidecar } : {}),\n });\n }\n return createRouteManifest(routes);\n}\n\nexport interface PageModule {\n css?: string;\n renderWithState(props: Record<string, unknown>): { html: string; state: Record<string, unknown> };\n renderToString?(props: Record<string, unknown>): string;\n}\n\nexport interface PageRoute {\n params?: PageSchema;\n search?: PageSchema;\n load?: (context: PageRequestContext) => Promise<unknown> | unknown;\n head?: (context: PageRequestContext & { data: unknown }) => { title?: string };\n render?: \"ssr\" | \"prerender\" | \"client\";\n prerenderEntries?: readonly Readonly<Record<string, string>>[] | (() => Promise<readonly Readonly<Record<string, string>>[]>);\n}\n\nexport interface PageRequestContext {\n readonly request: Request;\n readonly params: Readonly<Record<string, unknown>>;\n readonly search: Readonly<Record<string, unknown>>;\n readonly searchParams: URLSearchParams;\n readonly signal: AbortSignal;\n /** Public state transferred from this request to its browser app instance. */\n readonly state: StateStore;\n /** Present when the host supplies a per-request context. */\n readonly context?: RequestContext;\n}\n\nexport interface FrameworkHandlerOptions {\n readonly routes: readonly RouteRecord[];\n readonly loadPage: (file: string) => Promise<PageModule>;\n readonly loadRoute?: (file: string) => Promise<PageRoute>;\n readonly createContext?: (request: Request) => RequestContext | Promise<RequestContext>;\n /** Optional in-process dispatcher for automatic API/RPC calls during SSR. */\n readonly serverFetch?: ServerFetch;\n /** Optional host layout renderer; default composition uses <component is=\"props.outlet\"/>. */\n readonly renderLayout?: (file: string, children: string, context: PageRequestContext) => Promise<string> | string;\n readonly clientEntry?: string;\n}\n\n/** Portable Fetch handler. Node adapters can pass their Request directly. */\nexport function createFrameworkHandler(options: FrameworkHandlerOptions): (request: Request) => Promise<Response> {\n return async (request) => {\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return new Response(null, { status: 405, headers: { Allow: \"GET, HEAD\" } });\n const url = new URL(request.url);\n const match = matchRoute(options.routes, url.pathname);\n if (!match) return new Response(\"Not Found\", { status: 404 });\n const stateStore = new StateStore();\n let requestContext: RequestContext | undefined;\n const finish = (response: Response): Response => {\n if (!requestContext) return response;\n const headers = new Headers(response.headers);\n headers.set(\"x-request-id\", requestContext.requestId);\n headers.set(\"x-meeko-csrf-token\", requestContext.session.current?.csrfToken ?? \"\");\n if (requestContext.session.cookieHeader) headers.append(\"set-cookie\", requestContext.session.cookieHeader);\n return new Response(response.body, { status: response.status, statusText: response.statusText, headers });\n };\n try {\n requestContext = await options.createContext?.(request);\n if (requestContext && requestContext.request !== request) throw new TypeError(\"Request context belongs to another request\");\n const render = async (): Promise<Response> => {\n const sidecar = match.route.sidecar && options.loadRoute ? await options.loadRoute(match.route.sidecar) : undefined;\n const validated = await validatePageInputs(sidecar, match.params, pageSearchObject(url.searchParams));\n const context: PageRequestContext = {\n request, params: validated.params, search: validated.search, searchParams: url.searchParams, signal: request.signal, state: stateStore,\n ...(requestContext ? { context: requestContext } : {}),\n };\n const data = sidecar?.load ? await sidecar.load(context) : undefined;\n if (data instanceof Response) return finish(data);\n const props: Record<string, unknown> = {\n params: validated.params,\n search: validated.search,\n ...(data === undefined ? {} : { data }),\n };\n let module = await options.loadPage(match.route.file);\n const styles: string[] = [];\n if (module.css) styles.push(module.css);\n let input = props;\n let rendered: ReturnType<PageModule[\"renderWithState\"]>;\n if (options.renderLayout) {\n rendered = withStateStore(stateStore, () => module.renderWithState(input));\n for (const layout of [...match.route.layouts].reverse()) {\n const layoutModule = await options.loadPage(layout);\n if (layoutModule.css) styles.unshift(layoutModule.css);\n rendered = { html: await options.renderLayout(layout, rendered.html, context), state: rendered.state };\n }\n } else {\n for (const layout of [...match.route.layouts].reverse()) {\n const child = module;\n const childInput = input;\n const outlet = {\n renderWithState: () => child.renderWithState(childInput),\n renderToString: () => child.renderToString?.(childInput) ?? child.renderWithState(childInput).html,\n hydrateScope: () => { throw new Error(\"Server outlet cannot hydrate\"); },\n };\n module = await options.loadPage(layout);\n if (module.css) styles.unshift(module.css);\n input = { outlet };\n }\n rendered = withStateStore(stateStore, () => module.renderWithState(input));\n }\n const head = sidecar?.head?.({ ...context, data });\n const title = head?.title ? `<title>${escapeHtml(head.title)}</title>` : \"\";\n const styleTags = [...new Set(styles)].map((css) => `<style>${css.replaceAll(\"<\", \"\\\\3C \")}</style>`).join(\"\");\n const payload = serializeJson({ route: match.route.id, props, state: rendered.state, sharedState: stateStore.snapshot() });\n if (request.headers.get(\"accept\")?.split(\",\").some((value) => value.trim().startsWith(\"application/meeko+json\"))) {\n return finish(new Response(request.method === \"HEAD\" ? null : payload, {\n status: 200, headers: { \"content-type\": \"application/meeko+json; charset=utf-8\", \"cache-control\": \"no-store\" },\n }));\n }\n const script = options.clientEntry ? `<script type=\"module\" src=\"${escapeHtml(options.clientEntry)}\"></script>` : \"\";\n const csrfMeta = requestContext?.session.current ? `<meta name=\"meeko-csrf-token\" content=\"${escapeHtml(requestContext.session.current.csrfToken)}\">` : \"\";\n const document = `<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><link rel=\"icon\" href=\"data:,\">${csrfMeta}${styleTags}${title}</head><body><div id=\"app\">${rendered.html}</div><script id=\"meeko-payload\" type=\"application/json\">${payload}</script>${script}</body></html>`;\n return finish(new Response(request.method === \"HEAD\" ? null : document, {\n status: 200, headers: { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" },\n }));\n };\n return requestContext ? await withRequestContext(requestContext, options.serverFetch, render) : await render();\n } catch (error) {\n if (error instanceof Response) return finish(error);\n if (error instanceof PageInputError) return finish(new Response(error.code, {\n status: 400, headers: { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-store\" },\n }));\n if (match.route.error) {\n try {\n const errorPage = await options.loadPage(match.route.error);\n const errorRendered = withStateStore(stateStore, () => errorPage.renderWithState({\n status: 500, message: \"Internal Server Error\",\n }));\n const document = `<!doctype html><html><head><meta charset=\"utf-8\"><title>Error</title></head><body><div id=\"app\">${errorRendered.html}</div></body></html>`;\n return finish(new Response(request.method === \"HEAD\" ? null : document, {\n status: 500, headers: { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" },\n }));\n } catch {\n // The error route failed; keep private errors out of the response.\n }\n }\n return finish(new Response(request.method === \"HEAD\" ? null : \"Internal Server Error\", { status: 500 }));\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import ts from "@typescript/typescript6";
|
|
4
|
+
import { discoverServerManifest } from "../framework-server/discovery.js";
|
|
5
|
+
function inside(parent, child) {
|
|
6
|
+
const path = relative(parent, child);
|
|
7
|
+
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path));
|
|
8
|
+
}
|
|
9
|
+
function compilerOptions(root) {
|
|
10
|
+
const config = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
|
|
11
|
+
if (!config)
|
|
12
|
+
return { module: ts.ModuleKind.NodeNext, moduleResolution: ts.ModuleResolutionKind.NodeNext };
|
|
13
|
+
const loaded = ts.readConfigFile(config, ts.sys.readFile);
|
|
14
|
+
if (loaded.error)
|
|
15
|
+
throw new Error(ts.flattenDiagnosticMessageText(loaded.error.messageText, "\n"));
|
|
16
|
+
return ts.parseJsonConfigFileContent(loaded.config, ts.sys, dirname(config)).options;
|
|
17
|
+
}
|
|
18
|
+
function resolveImport(specifier, importer, options) {
|
|
19
|
+
const target = ts.resolveModuleName(specifier, importer, options, ts.sys).resolvedModule?.resolvedFileName;
|
|
20
|
+
if (!target)
|
|
21
|
+
throw new Error(`${importer}: unresolved import ${specifier}`);
|
|
22
|
+
return target;
|
|
23
|
+
}
|
|
24
|
+
function validateSharedGraph(file, root, shared, options, seen) {
|
|
25
|
+
if (seen.has(file))
|
|
26
|
+
return;
|
|
27
|
+
seen.add(file);
|
|
28
|
+
if (!inside(shared, file))
|
|
29
|
+
throw new Error(`${file}: public Socket schema must stay inside shared/`);
|
|
30
|
+
const source = ts.sys.readFile(file);
|
|
31
|
+
if (source === undefined)
|
|
32
|
+
throw new Error(`${file}: Socket schema file missing`);
|
|
33
|
+
const ast = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
|
|
34
|
+
const check = (specifier) => {
|
|
35
|
+
const target = resolveImport(specifier, file, options);
|
|
36
|
+
if (inside(root, target) && !relative(root, target).split(sep).includes("node_modules"))
|
|
37
|
+
validateSharedGraph(target, root, shared, options, seen);
|
|
38
|
+
};
|
|
39
|
+
for (const statement of ast.statements) {
|
|
40
|
+
if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement)) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier))
|
|
41
|
+
check(statement.moduleSpecifier.text);
|
|
42
|
+
}
|
|
43
|
+
const visit = (node) => {
|
|
44
|
+
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal))
|
|
45
|
+
check(node.argument.literal.text);
|
|
46
|
+
ts.forEachChild(node, visit);
|
|
47
|
+
};
|
|
48
|
+
visit(ast);
|
|
49
|
+
}
|
|
50
|
+
function property(object, name) {
|
|
51
|
+
const entry = object.properties.find((candidate) => (ts.isPropertyAssignment(candidate) || ts.isShorthandPropertyAssignment(candidate)) && candidate.name && (ts.isIdentifier(candidate.name) || ts.isStringLiteral(candidate.name)) && candidate.name.text === name);
|
|
52
|
+
if (!entry)
|
|
53
|
+
return undefined;
|
|
54
|
+
return ts.isPropertyAssignment(entry) ? entry.initializer : ts.isShorthandPropertyAssignment(entry) ? entry.name : undefined;
|
|
55
|
+
}
|
|
56
|
+
function publicSchemas(file, root, options) {
|
|
57
|
+
const source = ts.sys.readFile(file);
|
|
58
|
+
if (source === undefined)
|
|
59
|
+
throw new Error(`${file}: Socket channel file missing`);
|
|
60
|
+
const ast = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
|
|
61
|
+
const imports = new Map();
|
|
62
|
+
for (const statement of ast.statements) {
|
|
63
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
64
|
+
continue;
|
|
65
|
+
const clause = statement.importClause;
|
|
66
|
+
if (!clause || clause.isTypeOnly)
|
|
67
|
+
continue;
|
|
68
|
+
const target = resolveImport(statement.moduleSpecifier.text, file, options);
|
|
69
|
+
if (clause.name)
|
|
70
|
+
imports.set(clause.name.text, { file: target, exported: "default" });
|
|
71
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
72
|
+
for (const element of clause.namedBindings.elements) {
|
|
73
|
+
if (!element.isTypeOnly)
|
|
74
|
+
imports.set(element.name.text, { file: target, exported: element.propertyName?.text ?? element.name.text });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings))
|
|
78
|
+
imports.set(clause.namedBindings.name.text, { file: target, exported: "*" });
|
|
79
|
+
}
|
|
80
|
+
const assignment = ast.statements.find((statement) => ts.isExportAssignment(statement) && !statement.isExportEquals);
|
|
81
|
+
const call = assignment?.expression;
|
|
82
|
+
if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression) || call.expression.text !== "defineSocketChannel" || !call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) {
|
|
83
|
+
throw new Error(`${file}: public Socket contract requires export default defineSocketChannel({...})`);
|
|
84
|
+
}
|
|
85
|
+
const shared = join(root, "shared");
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const direction = (name) => {
|
|
88
|
+
const object = property(call.arguments[0], name);
|
|
89
|
+
if (!object || !ts.isObjectLiteralExpression(object))
|
|
90
|
+
throw new Error(`${file}: ${name} must be a static object`);
|
|
91
|
+
const result = Object.create(null);
|
|
92
|
+
for (const entry of object.properties) {
|
|
93
|
+
if (!ts.isPropertyAssignment(entry) || !(ts.isIdentifier(entry.name) || ts.isStringLiteral(entry.name)))
|
|
94
|
+
throw new Error(`${file}: ${name} requires named schema properties`);
|
|
95
|
+
const event = entry.name.text;
|
|
96
|
+
const value = entry.initializer;
|
|
97
|
+
let local;
|
|
98
|
+
let member;
|
|
99
|
+
if (ts.isIdentifier(value))
|
|
100
|
+
local = value.text;
|
|
101
|
+
else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) {
|
|
102
|
+
local = value.expression.text;
|
|
103
|
+
member = value.name.text;
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
throw new Error(`${file}: ${name}.${event} must reference an imported shared schema`);
|
|
107
|
+
const imported = imports.get(local);
|
|
108
|
+
if (!imported || !inside(shared, imported.file))
|
|
109
|
+
throw new Error(`${file}: ${name}.${event} must come from shared/`);
|
|
110
|
+
const exported = imported.exported === "*" ? member : imported.exported;
|
|
111
|
+
if (!exported || (member && imported.exported !== "*"))
|
|
112
|
+
throw new Error(`${file}: ${name}.${event} must reference one shared export`);
|
|
113
|
+
validateSharedGraph(imported.file, root, shared, options, seen);
|
|
114
|
+
if (result[event])
|
|
115
|
+
throw new Error(`${file}: duplicate Socket event ${name}.${event}`);
|
|
116
|
+
result[event] = { file: imported.file, exported };
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
return { clientToServer: direction("clientToServer"), serverToClient: direction("serverToClient") };
|
|
121
|
+
}
|
|
122
|
+
/** Emits browser-safe, inferred Socket contracts from server channel definitions. */
|
|
123
|
+
export async function generateSocketContracts(projectRoot) {
|
|
124
|
+
const root = resolve(projectRoot);
|
|
125
|
+
const manifest = await discoverServerManifest(root);
|
|
126
|
+
const output = join(root, ".meeko", "generated", "socket-contracts.ts");
|
|
127
|
+
const options = compilerOptions(root);
|
|
128
|
+
const channels = (manifest.socket ?? []).map((endpoint) => ({ name: endpoint.name, schemas: publicSchemas(endpoint.file, root, options) }));
|
|
129
|
+
const sources = [...new Set(channels.flatMap(({ schemas }) => [...Object.values(schemas.clientToServer), ...Object.values(schemas.serverToClient)].map((entry) => entry.file)))].sort();
|
|
130
|
+
const aliases = new Map(sources.map((file, index) => [file, `__schema${index}`]));
|
|
131
|
+
const lines = sources.map((file) => {
|
|
132
|
+
const target = relative(dirname(output), file).replaceAll("\\", "/").replace(/\.(?:ts|mts|cts)$/, (suffix) => suffix === ".mts" ? ".mjs" : suffix === ".cts" ? ".cjs" : ".js");
|
|
133
|
+
return `import * as ${aliases.get(file)} from ${JSON.stringify(target.startsWith(".") ? target : `./${target}`)};`;
|
|
134
|
+
});
|
|
135
|
+
lines.push("export const socketContracts = {");
|
|
136
|
+
for (const channel of channels) {
|
|
137
|
+
lines.push(` ${JSON.stringify(channel.name)}: {`);
|
|
138
|
+
for (const direction of ["clientToServer", "serverToClient"]) {
|
|
139
|
+
lines.push(` ${direction}: {`);
|
|
140
|
+
for (const [event, schema] of Object.entries(channel.schemas[direction]))
|
|
141
|
+
lines.push(` ${JSON.stringify(event)}: ${aliases.get(schema.file)}[${JSON.stringify(schema.exported)}],`);
|
|
142
|
+
lines.push(" },");
|
|
143
|
+
}
|
|
144
|
+
lines.push(" },");
|
|
145
|
+
}
|
|
146
|
+
lines.push("} as const;", "");
|
|
147
|
+
const code = lines.join("\n");
|
|
148
|
+
let previous;
|
|
149
|
+
try {
|
|
150
|
+
previous = await readFile(output, "utf8");
|
|
151
|
+
}
|
|
152
|
+
catch { /* First generation. */ }
|
|
153
|
+
if (previous !== code) {
|
|
154
|
+
await mkdir(dirname(output), { recursive: true });
|
|
155
|
+
await writeFile(output, code);
|
|
156
|
+
}
|
|
157
|
+
return { file: output, count: channels.length };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=socket-contracts-generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket-contracts-generate.js","sourceRoot":"","sources":["../../src/framework/socket-contracts-generate.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport ts from \"@typescript/typescript6\";\nimport { discoverServerManifest } from \"../framework-server/discovery.js\";\n\ninterface ImportedSymbol { file: string; exported: string; }\ninterface SchemaReference { file: string; exported: string; }\n\nfunction inside(parent: string, child: string): boolean {\n const path = relative(parent, child);\n return path === \"\" || (path !== \"..\" && !path.startsWith(`..${sep}`) && !isAbsolute(path));\n}\n\nfunction compilerOptions(root: string): ts.CompilerOptions {\n const config = ts.findConfigFile(root, ts.sys.fileExists, \"tsconfig.json\");\n if (!config) return { module: ts.ModuleKind.NodeNext, moduleResolution: ts.ModuleResolutionKind.NodeNext };\n const loaded = ts.readConfigFile(config, ts.sys.readFile);\n if (loaded.error) throw new Error(ts.flattenDiagnosticMessageText(loaded.error.messageText, \"\\n\"));\n return ts.parseJsonConfigFileContent(loaded.config, ts.sys, dirname(config)).options;\n}\n\nfunction resolveImport(specifier: string, importer: string, options: ts.CompilerOptions): string {\n const target = ts.resolveModuleName(specifier, importer, options, ts.sys).resolvedModule?.resolvedFileName;\n if (!target) throw new Error(`${importer}: unresolved import ${specifier}`);\n return target;\n}\n\nfunction validateSharedGraph(file: string, root: string, shared: string, options: ts.CompilerOptions, seen: Set<string>): void {\n if (seen.has(file)) return;\n seen.add(file);\n if (!inside(shared, file)) throw new Error(`${file}: public Socket schema must stay inside shared/`);\n const source = ts.sys.readFile(file);\n if (source === undefined) throw new Error(`${file}: Socket schema file missing`);\n const ast = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);\n const check = (specifier: string): void => {\n const target = resolveImport(specifier, file, options);\n if (inside(root, target) && !relative(root, target).split(sep).includes(\"node_modules\")) validateSharedGraph(target, root, shared, options, seen);\n };\n for (const statement of ast.statements) {\n if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement)) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) check(statement.moduleSpecifier.text);\n }\n const visit = (node: ts.Node): void => {\n if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal)) check(node.argument.literal.text);\n ts.forEachChild(node, visit);\n };\n visit(ast);\n}\n\nfunction property(object: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined {\n const entry = object.properties.find((candidate) => (ts.isPropertyAssignment(candidate) || ts.isShorthandPropertyAssignment(candidate)) && candidate.name && (ts.isIdentifier(candidate.name) || ts.isStringLiteral(candidate.name)) && candidate.name.text === name);\n if (!entry) return undefined;\n return ts.isPropertyAssignment(entry) ? entry.initializer : ts.isShorthandPropertyAssignment(entry) ? entry.name : undefined;\n}\n\nfunction publicSchemas(file: string, root: string, options: ts.CompilerOptions): { clientToServer: Record<string, SchemaReference>; serverToClient: Record<string, SchemaReference> } {\n const source = ts.sys.readFile(file);\n if (source === undefined) throw new Error(`${file}: Socket channel file missing`);\n const ast = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);\n const imports = new Map<string, ImportedSymbol>();\n for (const statement of ast.statements) {\n if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue;\n const clause = statement.importClause;\n if (!clause || clause.isTypeOnly) continue;\n const target = resolveImport(statement.moduleSpecifier.text, file, options);\n if (clause.name) imports.set(clause.name.text, { file: target, exported: \"default\" });\n if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {\n for (const element of clause.namedBindings.elements) {\n if (!element.isTypeOnly) imports.set(element.name.text, { file: target, exported: element.propertyName?.text ?? element.name.text });\n }\n }\n if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) imports.set(clause.namedBindings.name.text, { file: target, exported: \"*\" });\n }\n const assignment = ast.statements.find((statement): statement is ts.ExportAssignment => ts.isExportAssignment(statement) && !statement.isExportEquals);\n const call = assignment?.expression;\n if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression) || call.expression.text !== \"defineSocketChannel\" || !call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) {\n throw new Error(`${file}: public Socket contract requires export default defineSocketChannel({...})`);\n }\n const shared = join(root, \"shared\");\n const seen = new Set<string>();\n const direction = (name: \"clientToServer\" | \"serverToClient\"): Record<string, SchemaReference> => {\n const object = property(call.arguments[0] as ts.ObjectLiteralExpression, name);\n if (!object || !ts.isObjectLiteralExpression(object)) throw new Error(`${file}: ${name} must be a static object`);\n const result: Record<string, SchemaReference> = Object.create(null);\n for (const entry of object.properties) {\n if (!ts.isPropertyAssignment(entry) || !(ts.isIdentifier(entry.name) || ts.isStringLiteral(entry.name))) throw new Error(`${file}: ${name} requires named schema properties`);\n const event = entry.name.text;\n const value = entry.initializer;\n let local: string;\n let member: string | undefined;\n if (ts.isIdentifier(value)) local = value.text;\n else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression)) { local = value.expression.text; member = value.name.text; }\n else throw new Error(`${file}: ${name}.${event} must reference an imported shared schema`);\n const imported = imports.get(local);\n if (!imported || !inside(shared, imported.file)) throw new Error(`${file}: ${name}.${event} must come from shared/`);\n const exported = imported.exported === \"*\" ? member : imported.exported;\n if (!exported || (member && imported.exported !== \"*\")) throw new Error(`${file}: ${name}.${event} must reference one shared export`);\n validateSharedGraph(imported.file, root, shared, options, seen);\n if (result[event]) throw new Error(`${file}: duplicate Socket event ${name}.${event}`);\n result[event] = { file: imported.file, exported };\n }\n return result;\n };\n return { clientToServer: direction(\"clientToServer\"), serverToClient: direction(\"serverToClient\") };\n}\n\n/** Emits browser-safe, inferred Socket contracts from server channel definitions. */\nexport async function generateSocketContracts(projectRoot: string): Promise<{ file: string; count: number }> {\n const root = resolve(projectRoot);\n const manifest = await discoverServerManifest(root);\n const output = join(root, \".meeko\", \"generated\", \"socket-contracts.ts\");\n const options = compilerOptions(root);\n const channels = (manifest.socket ?? []).map((endpoint) => ({ name: endpoint.name, schemas: publicSchemas(endpoint.file, root, options) }));\n const sources = [...new Set(channels.flatMap(({ schemas }) => [...Object.values(schemas.clientToServer), ...Object.values(schemas.serverToClient)].map((entry) => entry.file)))].sort();\n const aliases = new Map(sources.map((file, index) => [file, `__schema${index}`]));\n const lines = sources.map((file) => {\n const target = relative(dirname(output), file).replaceAll(\"\\\\\", \"/\").replace(/\\.(?:ts|mts|cts)$/, (suffix) => suffix === \".mts\" ? \".mjs\" : suffix === \".cts\" ? \".cjs\" : \".js\");\n return `import * as ${aliases.get(file)} from ${JSON.stringify(target.startsWith(\".\") ? target : `./${target}`)};`;\n });\n lines.push(\"export const socketContracts = {\");\n for (const channel of channels) {\n lines.push(` ${JSON.stringify(channel.name)}: {`);\n for (const direction of [\"clientToServer\", \"serverToClient\"] as const) {\n lines.push(` ${direction}: {`);\n for (const [event, schema] of Object.entries(channel.schemas[direction])) lines.push(` ${JSON.stringify(event)}: ${aliases.get(schema.file)}[${JSON.stringify(schema.exported)}],`);\n lines.push(\" },\");\n }\n lines.push(\" },\");\n }\n lines.push(\"} as const;\", \"\");\n const code = lines.join(\"\\n\");\n let previous: string | undefined;\n try { previous = await readFile(output, \"utf8\"); } catch { /* First generation. */ }\n if (previous !== code) { await mkdir(dirname(output), { recursive: true }); await writeFile(output, code); }\n return { file: output, count: channels.length };\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ReadonlySignal } from "../reactivity.js";
|
|
2
|
+
export interface SocketSchema<I = unknown, O = I> {
|
|
3
|
+
readonly "~standard": {
|
|
4
|
+
readonly version: 1;
|
|
5
|
+
readonly validate: (value: unknown) => {
|
|
6
|
+
value: O;
|
|
7
|
+
issues?: undefined;
|
|
8
|
+
} | {
|
|
9
|
+
issues: readonly unknown[];
|
|
10
|
+
} | Promise<{
|
|
11
|
+
value: O;
|
|
12
|
+
issues?: undefined;
|
|
13
|
+
} | {
|
|
14
|
+
issues: readonly unknown[];
|
|
15
|
+
}>;
|
|
16
|
+
readonly types?: {
|
|
17
|
+
readonly input: I;
|
|
18
|
+
readonly output: O;
|
|
19
|
+
} | undefined;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
type SchemaInput<T> = T extends {
|
|
23
|
+
readonly "~standard": {
|
|
24
|
+
readonly types?: infer U;
|
|
25
|
+
};
|
|
26
|
+
} ? NonNullable<U> extends {
|
|
27
|
+
readonly input: infer I;
|
|
28
|
+
} ? I : never : T extends SocketSchema<infer I, unknown> ? I : never;
|
|
29
|
+
type SchemaOutput<T> = T extends {
|
|
30
|
+
readonly "~standard": {
|
|
31
|
+
readonly types?: infer U;
|
|
32
|
+
};
|
|
33
|
+
} ? NonNullable<U> extends {
|
|
34
|
+
readonly output: infer O;
|
|
35
|
+
} ? O : never : T extends SocketSchema<unknown, infer O> ? O : never;
|
|
36
|
+
type Events = Record<string, SocketSchema>;
|
|
37
|
+
export interface SocketContract<ClientToServer extends Events = Events, ServerToClient extends Events = Events> {
|
|
38
|
+
readonly clientToServer: ClientToServer;
|
|
39
|
+
readonly serverToClient: ServerToClient;
|
|
40
|
+
}
|
|
41
|
+
export interface SocketClient<C extends SocketContract> {
|
|
42
|
+
readonly state: ReadonlySignal<"connecting" | "open" | "closed">;
|
|
43
|
+
emit<K extends keyof C["clientToServer"] & string>(event: K, data: SchemaInput<C["clientToServer"][K]>): Promise<void>;
|
|
44
|
+
on<K extends keyof C["serverToClient"] & string>(event: K, listener: (data: SchemaOutput<C["serverToClient"][K]>) => void): () => void;
|
|
45
|
+
close(): void;
|
|
46
|
+
}
|
|
47
|
+
export interface SocketClientOptions {
|
|
48
|
+
readonly retryMs?: number;
|
|
49
|
+
readonly maxRetryMs?: number;
|
|
50
|
+
readonly maxMessageBytes?: number;
|
|
51
|
+
readonly webSocketFactory?: (url: string) => WebSocket;
|
|
52
|
+
}
|
|
53
|
+
export interface SocketRegistryOptions extends SocketClientOptions {
|
|
54
|
+
/** HTTP(S) or WS(S) base URL. Defaults to the current browser origin. */
|
|
55
|
+
readonly baseUrl?: string;
|
|
56
|
+
readonly pathnamePrefix?: string;
|
|
57
|
+
readonly roomId?: string;
|
|
58
|
+
}
|
|
59
|
+
export type SocketRegistry<C extends Record<string, SocketContract>> = {
|
|
60
|
+
readonly [K in keyof C]: SocketClient<C[K]>;
|
|
61
|
+
};
|
|
62
|
+
/** Native WebSocket client: no offline queue and no automatic replay. */
|
|
63
|
+
export declare function createSocketClient<C extends SocketContract>(url: string, contract: C, options?: SocketClientOptions): SocketClient<C>;
|
|
64
|
+
/** Binds socket lifetime to a Meeko root; SSR never opens a connection. */
|
|
65
|
+
export declare function useSocket<C extends SocketContract>(url: string, contract: C, options?: SocketClientOptions): SocketClient<C>;
|
|
66
|
+
/** Creates lazily connected, typed channel clients from public shared schemas. */
|
|
67
|
+
export declare function createSocketRegistry<C extends Record<string, SocketContract>>(contracts: C, options?: SocketRegistryOptions): SocketRegistry<C>;
|
|
68
|
+
/** Creates a channel scoped to the current component root. */
|
|
69
|
+
export declare function useSocketChannel<C extends Record<string, SocketContract>, K extends keyof C & string>(contracts: C, name: K, options?: SocketRegistryOptions): SocketClient<C[K]>;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { onCleanup, signal } from "../reactivity.js";
|
|
2
|
+
import { serializeJson } from "../server.js";
|
|
3
|
+
function socketUrl(name, options) {
|
|
4
|
+
if (typeof window === "undefined")
|
|
5
|
+
throw new Error("Socket connections are unavailable during SSR");
|
|
6
|
+
const prefix = options.pathnamePrefix ?? "/_meeko/socket";
|
|
7
|
+
if (!prefix.startsWith("/") || prefix.endsWith("/"))
|
|
8
|
+
throw new TypeError("Invalid socket pathname prefix");
|
|
9
|
+
const url = new URL(`${prefix}/${encodeURIComponent(name)}`, options.baseUrl ?? window.location.href);
|
|
10
|
+
if (url.protocol === "http:")
|
|
11
|
+
url.protocol = "ws:";
|
|
12
|
+
else if (url.protocol === "https:")
|
|
13
|
+
url.protocol = "wss:";
|
|
14
|
+
else if (url.protocol !== "ws:" && url.protocol !== "wss:")
|
|
15
|
+
throw new TypeError("Invalid socket URL protocol");
|
|
16
|
+
if (options.roomId !== undefined)
|
|
17
|
+
url.searchParams.set("room", options.roomId);
|
|
18
|
+
return url.href;
|
|
19
|
+
}
|
|
20
|
+
async function validate(schema, value) {
|
|
21
|
+
const result = await schema["~standard"].validate(value);
|
|
22
|
+
if ("issues" in result && result.issues)
|
|
23
|
+
throw new TypeError("Invalid socket event payload");
|
|
24
|
+
return result.value;
|
|
25
|
+
}
|
|
26
|
+
/** Native WebSocket client: no offline queue and no automatic replay. */
|
|
27
|
+
export function createSocketClient(url, contract, options = {}) {
|
|
28
|
+
const state = signal("connecting");
|
|
29
|
+
const listeners = new Map();
|
|
30
|
+
const factory = options.webSocketFactory ?? ((address) => new WebSocket(address));
|
|
31
|
+
const maxBytes = options.maxMessageBytes ?? 64 * 1024;
|
|
32
|
+
let socket;
|
|
33
|
+
let retry;
|
|
34
|
+
let attempts = 0;
|
|
35
|
+
let closed = false;
|
|
36
|
+
const connect = () => {
|
|
37
|
+
if (closed)
|
|
38
|
+
return;
|
|
39
|
+
state.value = "connecting";
|
|
40
|
+
const peer = factory(url);
|
|
41
|
+
socket = peer;
|
|
42
|
+
peer.addEventListener("open", () => { if (peer === socket) {
|
|
43
|
+
attempts = 0;
|
|
44
|
+
state.value = "open";
|
|
45
|
+
} });
|
|
46
|
+
peer.addEventListener("message", (message) => {
|
|
47
|
+
if (peer !== socket || typeof message.data !== "string" || new TextEncoder().encode(message.data).byteLength > maxBytes) {
|
|
48
|
+
closed = true;
|
|
49
|
+
state.value = "closed";
|
|
50
|
+
peer.close(1009, "Message too large");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
void (async () => {
|
|
54
|
+
const decoded = JSON.parse(message.data);
|
|
55
|
+
if (typeof decoded !== "object" || decoded === null || !("event" in decoded) || !("payload" in decoded) || typeof decoded.event !== "string")
|
|
56
|
+
throw new TypeError("Invalid socket message");
|
|
57
|
+
const schema = contract.serverToClient[decoded.event];
|
|
58
|
+
if (!schema)
|
|
59
|
+
throw new TypeError("Unknown socket event");
|
|
60
|
+
const value = await validate(schema, decoded.payload);
|
|
61
|
+
for (const listener of listeners.get(decoded.event) ?? [])
|
|
62
|
+
listener(value);
|
|
63
|
+
})().catch(() => { closed = true; state.value = "closed"; peer.close(1008, "Invalid event"); });
|
|
64
|
+
});
|
|
65
|
+
peer.addEventListener("close", (event) => {
|
|
66
|
+
if (peer !== socket || closed)
|
|
67
|
+
return;
|
|
68
|
+
state.value = "closed";
|
|
69
|
+
if ([1008, 1009].includes(event.code)) {
|
|
70
|
+
closed = true;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const delay = Math.min((options.retryMs ?? 500) * 2 ** attempts++, options.maxRetryMs ?? 30_000);
|
|
74
|
+
retry = setTimeout(connect, delay);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
connect();
|
|
78
|
+
return {
|
|
79
|
+
state,
|
|
80
|
+
async emit(event, data) {
|
|
81
|
+
if (closed || socket?.readyState !== WebSocket.OPEN)
|
|
82
|
+
throw new Error("Socket is disconnected");
|
|
83
|
+
const schema = contract.clientToServer[event];
|
|
84
|
+
if (!schema)
|
|
85
|
+
throw new TypeError(`Unknown socket event: ${event}`);
|
|
86
|
+
const value = await validate(schema, data);
|
|
87
|
+
if (closed || socket.readyState !== WebSocket.OPEN)
|
|
88
|
+
throw new Error("Socket is disconnected");
|
|
89
|
+
const message = serializeJson({ event, payload: value });
|
|
90
|
+
if (new TextEncoder().encode(message).byteLength > maxBytes)
|
|
91
|
+
throw new RangeError("Socket message too large");
|
|
92
|
+
socket.send(message);
|
|
93
|
+
},
|
|
94
|
+
on(event, listener) {
|
|
95
|
+
const set = listeners.get(event) ?? new Set();
|
|
96
|
+
set.add(listener);
|
|
97
|
+
listeners.set(event, set);
|
|
98
|
+
return () => { set.delete(listener); if (set.size === 0)
|
|
99
|
+
listeners.delete(event); };
|
|
100
|
+
},
|
|
101
|
+
close() {
|
|
102
|
+
if (closed)
|
|
103
|
+
return;
|
|
104
|
+
closed = true;
|
|
105
|
+
if (retry)
|
|
106
|
+
clearTimeout(retry);
|
|
107
|
+
socket?.close(1000, "Client closed");
|
|
108
|
+
listeners.clear();
|
|
109
|
+
state.value = "closed";
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Binds socket lifetime to a Meeko root; SSR never opens a connection. */
|
|
114
|
+
export function useSocket(url, contract, options = {}) {
|
|
115
|
+
if (typeof window === "undefined")
|
|
116
|
+
throw new Error("useSocket() is only available in the browser");
|
|
117
|
+
const client = createSocketClient(url, contract, options);
|
|
118
|
+
try {
|
|
119
|
+
onCleanup(() => client.close());
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
client.close();
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
return client;
|
|
126
|
+
}
|
|
127
|
+
/** Creates lazily connected, typed channel clients from public shared schemas. */
|
|
128
|
+
export function createSocketRegistry(contracts, options = {}) {
|
|
129
|
+
const result = Object.create(null);
|
|
130
|
+
for (const name of Object.keys(contracts)) {
|
|
131
|
+
let client;
|
|
132
|
+
Object.defineProperty(result, name, {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get() {
|
|
135
|
+
if (!client)
|
|
136
|
+
client = createSocketClient(socketUrl(name, options), contracts[name], options);
|
|
137
|
+
return client;
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/** Creates a channel scoped to the current component root. */
|
|
144
|
+
export function useSocketChannel(contracts, name, options = {}) {
|
|
145
|
+
const contract = contracts[name];
|
|
146
|
+
if (!contract)
|
|
147
|
+
throw new TypeError(`Unknown socket channel: ${name}`);
|
|
148
|
+
return useSocket(socketUrl(name, options), contract, options);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=socket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket.js","sourceRoot":"","sources":["../../src/framework/socket.ts"],"names":[],"mappings":"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","sourcesContent":["import { onCleanup, signal } from \"../reactivity.js\";\nimport type { ReadonlySignal } from \"../reactivity.js\";\nimport { serializeJson } from \"../server.js\";\n\nexport interface SocketSchema<I = unknown, O = I> {\n readonly \"~standard\": {\n readonly version: 1;\n readonly validate: (value: unknown) => { value: O; issues?: undefined } | { issues: readonly unknown[] } | Promise<{ value: O; issues?: undefined } | { issues: readonly unknown[] }>;\n readonly types?: { readonly input: I; readonly output: O } | undefined;\n };\n}\n\ntype SchemaInput<T> = T extends { readonly \"~standard\": { readonly types?: infer U } }\n ? NonNullable<U> extends { readonly input: infer I } ? I : never\n : T extends SocketSchema<infer I, unknown> ? I : never;\ntype SchemaOutput<T> = T extends { readonly \"~standard\": { readonly types?: infer U } }\n ? NonNullable<U> extends { readonly output: infer O } ? O : never\n : T extends SocketSchema<unknown, infer O> ? O : never;\ntype Events = Record<string, SocketSchema>;\n\nexport interface SocketContract<ClientToServer extends Events = Events, ServerToClient extends Events = Events> {\n readonly clientToServer: ClientToServer;\n readonly serverToClient: ServerToClient;\n}\n\nexport interface SocketClient<C extends SocketContract> {\n readonly state: ReadonlySignal<\"connecting\" | \"open\" | \"closed\">;\n emit<K extends keyof C[\"clientToServer\"] & string>(event: K, data: SchemaInput<C[\"clientToServer\"][K]>): Promise<void>;\n on<K extends keyof C[\"serverToClient\"] & string>(event: K, listener: (data: SchemaOutput<C[\"serverToClient\"][K]>) => void): () => void;\n close(): void;\n}\n\nexport interface SocketClientOptions {\n readonly retryMs?: number;\n readonly maxRetryMs?: number;\n readonly maxMessageBytes?: number;\n readonly webSocketFactory?: (url: string) => WebSocket;\n}\n\nexport interface SocketRegistryOptions extends SocketClientOptions {\n /** HTTP(S) or WS(S) base URL. Defaults to the current browser origin. */\n readonly baseUrl?: string;\n readonly pathnamePrefix?: string;\n readonly roomId?: string;\n}\n\nexport type SocketRegistry<C extends Record<string, SocketContract>> = {\n readonly [K in keyof C]: SocketClient<C[K]>;\n};\n\nfunction socketUrl(name: string, options: SocketRegistryOptions): string {\n if (typeof window === \"undefined\") throw new Error(\"Socket connections are unavailable during SSR\");\n const prefix = options.pathnamePrefix ?? \"/_meeko/socket\";\n if (!prefix.startsWith(\"/\") || prefix.endsWith(\"/\")) throw new TypeError(\"Invalid socket pathname prefix\");\n const url = new URL(`${prefix}/${encodeURIComponent(name)}`, options.baseUrl ?? window.location.href);\n if (url.protocol === \"http:\") url.protocol = \"ws:\";\n else if (url.protocol === \"https:\") url.protocol = \"wss:\";\n else if (url.protocol !== \"ws:\" && url.protocol !== \"wss:\") throw new TypeError(\"Invalid socket URL protocol\");\n if (options.roomId !== undefined) url.searchParams.set(\"room\", options.roomId);\n return url.href;\n}\n\nasync function validate<T extends SocketSchema>(schema: T, value: unknown): Promise<SchemaOutput<T>> {\n const result = await schema[\"~standard\"].validate(value);\n if (\"issues\" in result && result.issues) throw new TypeError(\"Invalid socket event payload\");\n return (result as { value: SchemaOutput<T> }).value;\n}\n\n/** Native WebSocket client: no offline queue and no automatic replay. */\nexport function createSocketClient<C extends SocketContract>(url: string, contract: C, options: SocketClientOptions = {}): SocketClient<C> {\n const state = signal<\"connecting\" | \"open\" | \"closed\">(\"connecting\");\n const listeners = new Map<string, Set<(value: unknown) => void>>();\n const factory = options.webSocketFactory ?? ((address: string) => new WebSocket(address));\n const maxBytes = options.maxMessageBytes ?? 64 * 1024;\n let socket: WebSocket | undefined;\n let retry: ReturnType<typeof setTimeout> | undefined;\n let attempts = 0;\n let closed = false;\n\n const connect = () => {\n if (closed) return;\n state.value = \"connecting\";\n const peer = factory(url);\n socket = peer;\n peer.addEventListener(\"open\", () => { if (peer === socket) { attempts = 0; state.value = \"open\"; } });\n peer.addEventListener(\"message\", (message) => {\n if (peer !== socket || typeof message.data !== \"string\" || new TextEncoder().encode(message.data).byteLength > maxBytes) {\n closed = true;\n state.value = \"closed\";\n peer.close(1009, \"Message too large\");\n return;\n }\n void (async () => {\n const decoded: unknown = JSON.parse(message.data as string);\n if (typeof decoded !== \"object\" || decoded === null || !(\"event\" in decoded) || !(\"payload\" in decoded) || typeof decoded.event !== \"string\") throw new TypeError(\"Invalid socket message\");\n const schema = contract.serverToClient[decoded.event];\n if (!schema) throw new TypeError(\"Unknown socket event\");\n const value = await validate(schema, decoded.payload);\n for (const listener of listeners.get(decoded.event) ?? []) listener(value);\n })().catch(() => { closed = true; state.value = \"closed\"; peer.close(1008, \"Invalid event\"); });\n });\n peer.addEventListener(\"close\", (event) => {\n if (peer !== socket || closed) return;\n state.value = \"closed\";\n if ([1008, 1009].includes((event as CloseEvent).code)) { closed = true; return; }\n const delay = Math.min((options.retryMs ?? 500) * 2 ** attempts++, options.maxRetryMs ?? 30_000);\n retry = setTimeout(connect, delay);\n });\n };\n connect();\n return {\n state,\n async emit(event, data) {\n if (closed || socket?.readyState !== WebSocket.OPEN) throw new Error(\"Socket is disconnected\");\n const schema = contract.clientToServer[event];\n if (!schema) throw new TypeError(`Unknown socket event: ${event}`);\n const value = await validate(schema, data);\n if (closed || socket.readyState !== WebSocket.OPEN) throw new Error(\"Socket is disconnected\");\n const message = serializeJson({ event, payload: value });\n if (new TextEncoder().encode(message).byteLength > maxBytes) throw new RangeError(\"Socket message too large\");\n socket.send(message);\n },\n on(event, listener) {\n const set = listeners.get(event) ?? new Set();\n set.add(listener as (value: unknown) => void);\n listeners.set(event, set);\n return () => { set.delete(listener as (value: unknown) => void); if (set.size === 0) listeners.delete(event); };\n },\n close() {\n if (closed) return;\n closed = true;\n if (retry) clearTimeout(retry);\n socket?.close(1000, \"Client closed\");\n listeners.clear();\n state.value = \"closed\";\n },\n };\n}\n\n/** Binds socket lifetime to a Meeko root; SSR never opens a connection. */\nexport function useSocket<C extends SocketContract>(url: string, contract: C, options: SocketClientOptions = {}): SocketClient<C> {\n if (typeof window === \"undefined\") throw new Error(\"useSocket() is only available in the browser\");\n const client = createSocketClient(url, contract, options);\n try { onCleanup(() => client.close()); }\n catch (error) { client.close(); throw error; }\n return client;\n}\n\n/** Creates lazily connected, typed channel clients from public shared schemas. */\nexport function createSocketRegistry<C extends Record<string, SocketContract>>(contracts: C, options: SocketRegistryOptions = {}): SocketRegistry<C> {\n const result: Record<string, unknown> = Object.create(null);\n for (const name of Object.keys(contracts)) {\n let client: SocketClient<SocketContract> | undefined;\n Object.defineProperty(result, name, {\n enumerable: true,\n get() {\n if (!client) client = createSocketClient(socketUrl(name, options), contracts[name]!, options);\n return client;\n },\n });\n }\n return result as SocketRegistry<C>;\n}\n\n/** Creates a channel scoped to the current component root. */\nexport function useSocketChannel<C extends Record<string, SocketContract>, K extends keyof C & string>(contracts: C, name: K, options: SocketRegistryOptions = {}): SocketClient<C[K]> {\n const contract = contracts[name];\n if (!contract) throw new TypeError(`Unknown socket channel: ${name}`);\n return useSocket(socketUrl(name, options), contract, options);\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Signal } from "../reactivity.js";
|
|
2
|
+
export declare class StateStore {
|
|
3
|
+
private readonly values;
|
|
4
|
+
constructor(initial?: Readonly<Record<string, unknown>>);
|
|
5
|
+
get<T>(key: string, factory: () => T): Signal<T>;
|
|
6
|
+
/** Seeds values returned by a later server navigation without replacing local state. */
|
|
7
|
+
seed(values: Readonly<Record<string, unknown>>): void;
|
|
8
|
+
snapshot(): Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
/** One keyed signal per application request or browser app instance. */
|
|
11
|
+
export declare function useState<T>(key: string, factory: () => T): Signal<T>;
|
|
12
|
+
/** Runs synchronous SSR rendering with a state provider and releases its root. */
|
|
13
|
+
export declare function withStateStore<T>(store: StateStore, render: () => T): T;
|
|
14
|
+
/** Keeps the provider alive until the hydrated component is released. */
|
|
15
|
+
export declare function hydrateWithStateStore(store: StateStore, hydrate: () => () => void): () => void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createContext, createRoot, inject, provide, signal } from "../reactivity.js";
|
|
2
|
+
import { serializeJson } from "../server.js";
|
|
3
|
+
const stateKey = createContext("Meeko framework state");
|
|
4
|
+
function assertKey(key) {
|
|
5
|
+
if (!key || key === "__proto__" || key === "constructor" || key === "prototype")
|
|
6
|
+
throw new TypeError("Invalid state key");
|
|
7
|
+
}
|
|
8
|
+
export class StateStore {
|
|
9
|
+
values = new Map();
|
|
10
|
+
constructor(initial = {}) {
|
|
11
|
+
serializeJson(initial);
|
|
12
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
13
|
+
assertKey(key);
|
|
14
|
+
this.values.set(key, signal(value));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
get(key, factory) {
|
|
18
|
+
assertKey(key);
|
|
19
|
+
let value = this.values.get(key);
|
|
20
|
+
if (!value) {
|
|
21
|
+
value = signal(factory());
|
|
22
|
+
this.values.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** Seeds values returned by a later server navigation without replacing local state. */
|
|
27
|
+
seed(values) {
|
|
28
|
+
serializeJson(values);
|
|
29
|
+
for (const [key, value] of Object.entries(values)) {
|
|
30
|
+
assertKey(key);
|
|
31
|
+
if (!this.values.has(key))
|
|
32
|
+
this.values.set(key, signal(value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
snapshot() {
|
|
36
|
+
const result = Object.create(null);
|
|
37
|
+
for (const [key, value] of this.values)
|
|
38
|
+
result[key] = value.value;
|
|
39
|
+
serializeJson(result);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** One keyed signal per application request or browser app instance. */
|
|
44
|
+
export function useState(key, factory) {
|
|
45
|
+
return inject(stateKey).get(key, factory);
|
|
46
|
+
}
|
|
47
|
+
/** Runs synchronous SSR rendering with a state provider and releases its root. */
|
|
48
|
+
export function withStateStore(store, render) {
|
|
49
|
+
return createRoot((dispose) => {
|
|
50
|
+
provide(stateKey, store);
|
|
51
|
+
try {
|
|
52
|
+
return render();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
dispose();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Keeps the provider alive until the hydrated component is released. */
|
|
60
|
+
export function hydrateWithStateStore(store, hydrate) {
|
|
61
|
+
return createRoot((dispose) => {
|
|
62
|
+
provide(stateKey, store);
|
|
63
|
+
const release = hydrate();
|
|
64
|
+
return () => { try {
|
|
65
|
+
release();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
dispose();
|
|
69
|
+
} };
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/framework/state.ts"],"names":[],"mappings":"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","sourcesContent":["import { createContext, createRoot, inject, provide, signal } from \"../reactivity.js\";\nimport type { Signal } from \"../reactivity.js\";\nimport { serializeJson } from \"../server.js\";\n\nconst stateKey = createContext<StateStore>(\"Meeko framework state\");\n\nfunction assertKey(key: string): void {\n if (!key || key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") throw new TypeError(\"Invalid state key\");\n}\n\nexport class StateStore {\n private readonly values = new Map<string, Signal<unknown>>();\n\n constructor(initial: Readonly<Record<string, unknown>> = {}) {\n serializeJson(initial);\n for (const [key, value] of Object.entries(initial)) { assertKey(key); this.values.set(key, signal(value)); }\n }\n\n get<T>(key: string, factory: () => T): Signal<T> {\n assertKey(key);\n let value = this.values.get(key);\n if (!value) {\n value = signal(factory());\n this.values.set(key, value);\n }\n return value as Signal<T>;\n }\n\n /** Seeds values returned by a later server navigation without replacing local state. */\n seed(values: Readonly<Record<string, unknown>>): void {\n serializeJson(values);\n for (const [key, value] of Object.entries(values)) {\n assertKey(key);\n if (!this.values.has(key)) this.values.set(key, signal(value));\n }\n }\n\n snapshot(): Record<string, unknown> {\n const result: Record<string, unknown> = Object.create(null);\n for (const [key, value] of this.values) result[key] = value.value;\n serializeJson(result);\n return result;\n }\n}\n\n/** One keyed signal per application request or browser app instance. */\nexport function useState<T>(key: string, factory: () => T): Signal<T> {\n return inject(stateKey).get(key, factory);\n}\n\n/** Runs synchronous SSR rendering with a state provider and releases its root. */\nexport function withStateStore<T>(store: StateStore, render: () => T): T {\n return createRoot((dispose) => {\n provide(stateKey, store);\n try { return render(); }\n finally { dispose(); }\n });\n}\n\n/** Keeps the provider alive until the hydrated component is released. */\nexport function hydrateWithStateStore(store: StateStore, hydrate: () => () => void): () => void {\n return createRoot((dispose) => {\n provide(stateKey, store);\n const release = hydrate();\n return () => { try { release(); } finally { dispose(); } };\n });\n}\n"]}
|