@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,82 @@
|
|
|
1
|
+
/** Public descriptors intentionally omit handler files and server schemas. */
|
|
2
|
+
export interface PublicApiEndpoint {
|
|
3
|
+
readonly clientKey: string;
|
|
4
|
+
readonly pathname: string;
|
|
5
|
+
readonly method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
6
|
+
readonly contentType?: "json" | "response";
|
|
7
|
+
}
|
|
8
|
+
export interface PublicRpcEndpoint {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly pathname: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PublicServerManifest {
|
|
13
|
+
readonly api: readonly PublicApiEndpoint[];
|
|
14
|
+
readonly rpc: readonly PublicRpcEndpoint[];
|
|
15
|
+
}
|
|
16
|
+
/** Strips private handler paths before serializing a discovered server manifest. */
|
|
17
|
+
export declare function createPublicServerManifest(manifest: PublicServerManifest): PublicServerManifest;
|
|
18
|
+
/** Type-only operation declarations can be inferred from public shared schemas. */
|
|
19
|
+
export interface ApiOperation<Params = undefined, Query = undefined, Body = undefined, Output = unknown> {
|
|
20
|
+
readonly params: Params;
|
|
21
|
+
readonly query: Query;
|
|
22
|
+
readonly body: Body;
|
|
23
|
+
readonly output: Output;
|
|
24
|
+
}
|
|
25
|
+
export interface RpcOperation<Input = unknown, Output = unknown> {
|
|
26
|
+
readonly input: Input;
|
|
27
|
+
readonly output: Output;
|
|
28
|
+
}
|
|
29
|
+
type AnyApiOperation = ApiOperation<unknown, unknown, unknown, unknown>;
|
|
30
|
+
type RequestOptions = {
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
};
|
|
33
|
+
type ParamOption<T> = T extends undefined ? {
|
|
34
|
+
readonly params?: never;
|
|
35
|
+
} : {
|
|
36
|
+
readonly params: T;
|
|
37
|
+
};
|
|
38
|
+
type QueryOption<T> = T extends undefined ? {
|
|
39
|
+
readonly query?: never;
|
|
40
|
+
} : {
|
|
41
|
+
readonly query: T;
|
|
42
|
+
};
|
|
43
|
+
type BodyOption<T> = T extends undefined ? {
|
|
44
|
+
readonly body?: never;
|
|
45
|
+
} : {
|
|
46
|
+
readonly body: T;
|
|
47
|
+
};
|
|
48
|
+
type ApiArguments<O extends AnyApiOperation> = O["params"] extends undefined ? O["query"] extends undefined ? O["body"] extends undefined ? [options?: ParamOption<O["params"]> & QueryOption<O["query"]> & BodyOption<O["body"]> & RequestOptions] : [options: ParamOption<O["params"]> & QueryOption<O["query"]> & BodyOption<O["body"]> & RequestOptions] : [options: ParamOption<O["params"]> & QueryOption<O["query"]> & BodyOption<O["body"]> & RequestOptions] : [options: ParamOption<O["params"]> & QueryOption<O["query"]> & BodyOption<O["body"]> & RequestOptions];
|
|
49
|
+
type Nest<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? {
|
|
50
|
+
[K in Head]: Nest<Tail, Value>;
|
|
51
|
+
} : {
|
|
52
|
+
[K in Path]: Value;
|
|
53
|
+
};
|
|
54
|
+
type Intersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Result) => void ? Result : never;
|
|
55
|
+
type ApiMethods<Operations> = {
|
|
56
|
+
[Method in keyof Operations]: Operations[Method] extends AnyApiOperation ? (...args: ApiArguments<Operations[Method]>) => Promise<Operations[Method]["output"]> : never;
|
|
57
|
+
};
|
|
58
|
+
export type ApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>> = Intersection<{
|
|
59
|
+
[Key in keyof Contract & string]: Nest<Key, ApiMethods<Contract[Key]>>;
|
|
60
|
+
}[keyof Contract & string]>;
|
|
61
|
+
export type RpcClient<Contract extends Record<string, RpcOperation>> = Intersection<{
|
|
62
|
+
[Key in keyof Contract & string]: Nest<Key, (input: Contract[Key]["input"], options?: RequestOptions) => Promise<Contract[Key]["output"]>>;
|
|
63
|
+
}[keyof Contract & string]>;
|
|
64
|
+
export declare class ClientError extends Error {
|
|
65
|
+
readonly code: string;
|
|
66
|
+
readonly status: number;
|
|
67
|
+
readonly requestId?: string | undefined;
|
|
68
|
+
constructor(code: string, status: number, requestId?: string | undefined);
|
|
69
|
+
}
|
|
70
|
+
export interface ClientOptions {
|
|
71
|
+
readonly baseUrl?: string | (() => string);
|
|
72
|
+
readonly fetch?: typeof fetch;
|
|
73
|
+
readonly csrfToken?: string | (() => string | undefined);
|
|
74
|
+
readonly headers?: HeadersInit | (() => HeadersInit);
|
|
75
|
+
}
|
|
76
|
+
/** Reads the current document token at call time, including after client navigation. */
|
|
77
|
+
export declare function createBrowserClientOptions(): ClientOptions;
|
|
78
|
+
/** Creates browser API methods from public endpoint paths, without server imports. */
|
|
79
|
+
export declare function createApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>>(manifest: Pick<PublicServerManifest, "api">, options?: ClientOptions): ApiClient<Contract>;
|
|
80
|
+
/** Creates browser RPC procedures from public endpoint paths. */
|
|
81
|
+
export declare function createRpcClient<Contract extends Record<string, RpcOperation>>(manifest: Pick<PublicServerManifest, "rpc">, options?: ClientOptions): RpcClient<Contract>;
|
|
82
|
+
export {};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/** Strips private handler paths before serializing a discovered server manifest. */
|
|
2
|
+
export function createPublicServerManifest(manifest) {
|
|
3
|
+
return {
|
|
4
|
+
api: manifest.api.map(({ clientKey, pathname, method, contentType }) => ({
|
|
5
|
+
clientKey, pathname, method, ...(contentType ? { contentType } : {}),
|
|
6
|
+
})),
|
|
7
|
+
rpc: manifest.rpc.map(({ id, pathname }) => ({ id, pathname })),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export class ClientError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
status;
|
|
13
|
+
requestId;
|
|
14
|
+
constructor(code, status, requestId) {
|
|
15
|
+
super(code);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.requestId = requestId;
|
|
19
|
+
this.name = "ClientError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Reads the current document token at call time, including after client navigation. */
|
|
23
|
+
export function createBrowserClientOptions() {
|
|
24
|
+
return {
|
|
25
|
+
csrfToken: () => typeof document === "undefined"
|
|
26
|
+
? undefined
|
|
27
|
+
: document.querySelector('meta[name="meeko-csrf-token"]')?.getAttribute("content") ?? undefined,
|
|
28
|
+
fetch: async (input, init) => {
|
|
29
|
+
const response = await fetch(input, init);
|
|
30
|
+
const token = response.headers.get("x-meeko-csrf-token");
|
|
31
|
+
if (token && typeof document !== "undefined") {
|
|
32
|
+
let meta = document.querySelector('meta[name="meeko-csrf-token"]');
|
|
33
|
+
if (!meta) {
|
|
34
|
+
meta = document.createElement("meta");
|
|
35
|
+
meta.setAttribute("name", "meeko-csrf-token");
|
|
36
|
+
document.head.append(meta);
|
|
37
|
+
}
|
|
38
|
+
meta.setAttribute("content", token);
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function pathWithParams(pathname, params = {}) {
|
|
45
|
+
const used = new Set();
|
|
46
|
+
const path = pathname.replace(/\[(\.\.\.)?([A-Za-z_$][\w$]*)\]/g, (_match, rest, name) => {
|
|
47
|
+
const value = params[name];
|
|
48
|
+
if (!value)
|
|
49
|
+
throw new TypeError(`Missing route parameter: ${name}`);
|
|
50
|
+
used.add(name);
|
|
51
|
+
const parts = rest ? value.split("/") : [value];
|
|
52
|
+
if (parts.some((part) => !part || part === "." || part === ".." || part.includes("/") || part.includes("\\")))
|
|
53
|
+
throw new TypeError(`Invalid route parameter: ${name}`);
|
|
54
|
+
return parts.map(encodeURIComponent).join("/");
|
|
55
|
+
});
|
|
56
|
+
for (const key of Object.keys(params))
|
|
57
|
+
if (!used.has(key))
|
|
58
|
+
throw new TypeError(`Unknown route parameter: ${key}`);
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
function queryString(query) {
|
|
62
|
+
if (!query)
|
|
63
|
+
return "";
|
|
64
|
+
const search = new URLSearchParams();
|
|
65
|
+
for (const [key, raw] of Object.entries(query)) {
|
|
66
|
+
for (const value of Array.isArray(raw) ? raw : [raw]) {
|
|
67
|
+
if (value !== null && value !== undefined)
|
|
68
|
+
search.append(key, String(value));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const result = search.toString();
|
|
72
|
+
return result ? `?${result}` : "";
|
|
73
|
+
}
|
|
74
|
+
async function responseValue(response, contentType = "json") {
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
let payload;
|
|
77
|
+
try {
|
|
78
|
+
payload = await response.json();
|
|
79
|
+
}
|
|
80
|
+
catch { /* Bad error bodies still produce a normalized error. */ }
|
|
81
|
+
const record = payload && typeof payload === "object" ? payload : {};
|
|
82
|
+
const code = typeof record.code === "string" ? record.code : "HTTP_ERROR";
|
|
83
|
+
const requestId = typeof record.requestId === "string" ? record.requestId : response.headers.get("x-request-id") ?? undefined;
|
|
84
|
+
throw new ClientError(code, response.status, requestId);
|
|
85
|
+
}
|
|
86
|
+
if (contentType === "response")
|
|
87
|
+
return response;
|
|
88
|
+
if (response.status === 204)
|
|
89
|
+
return undefined;
|
|
90
|
+
try {
|
|
91
|
+
return await response.json();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new ClientError("INVALID_RESPONSE", response.status, response.headers.get("x-request-id") ?? undefined);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function endpointUrl(pathname, options) {
|
|
98
|
+
const baseUrl = typeof options.baseUrl === "function" ? options.baseUrl() : options.baseUrl;
|
|
99
|
+
return baseUrl ? new URL(pathname, baseUrl).toString() : pathname;
|
|
100
|
+
}
|
|
101
|
+
function headersFor(options, method, json) {
|
|
102
|
+
const headers = new Headers(typeof options.headers === "function" ? options.headers() : options.headers);
|
|
103
|
+
if (json)
|
|
104
|
+
headers.set("content-type", "application/json");
|
|
105
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
106
|
+
const csrf = typeof options.csrfToken === "function" ? options.csrfToken() : options.csrfToken;
|
|
107
|
+
if (csrf)
|
|
108
|
+
headers.set("x-meeko-csrf", csrf);
|
|
109
|
+
}
|
|
110
|
+
return headers;
|
|
111
|
+
}
|
|
112
|
+
function insert(root, key, value) {
|
|
113
|
+
const parts = key.split(".");
|
|
114
|
+
if (parts.some((part) => !/^[A-Za-z_$][\w$]*$/.test(part)))
|
|
115
|
+
throw new TypeError(`Invalid client key: ${key}`);
|
|
116
|
+
let node = root;
|
|
117
|
+
for (const part of parts.slice(0, -1)) {
|
|
118
|
+
const existing = node[part];
|
|
119
|
+
if (existing === undefined)
|
|
120
|
+
node[part] = Object.create(null);
|
|
121
|
+
else if (!existing || typeof existing !== "object")
|
|
122
|
+
throw new TypeError(`Client key collision: ${key}`);
|
|
123
|
+
node = node[part];
|
|
124
|
+
}
|
|
125
|
+
const last = parts.at(-1);
|
|
126
|
+
if (node[last] !== undefined)
|
|
127
|
+
throw new TypeError(`Client key collision: ${key}`);
|
|
128
|
+
node[last] = value;
|
|
129
|
+
}
|
|
130
|
+
function ensureObject(root, key) {
|
|
131
|
+
const parts = key.split(".");
|
|
132
|
+
if (parts.some((part) => !/^[A-Za-z_$][\w$]*$/.test(part)))
|
|
133
|
+
throw new TypeError(`Invalid client key: ${key}`);
|
|
134
|
+
let node = root;
|
|
135
|
+
for (const part of parts) {
|
|
136
|
+
const existing = node[part];
|
|
137
|
+
if (existing === undefined)
|
|
138
|
+
node[part] = Object.create(null);
|
|
139
|
+
else if (!existing || typeof existing !== "object")
|
|
140
|
+
throw new TypeError(`Client key collision: ${key}`);
|
|
141
|
+
node = node[part];
|
|
142
|
+
}
|
|
143
|
+
return node;
|
|
144
|
+
}
|
|
145
|
+
/** Creates browser API methods from public endpoint paths, without server imports. */
|
|
146
|
+
export function createApiClient(manifest, options = {}) {
|
|
147
|
+
const root = Object.create(null);
|
|
148
|
+
for (const endpoint of manifest.api) {
|
|
149
|
+
const methods = ensureObject(root, endpoint.clientKey);
|
|
150
|
+
const method = endpoint.method.toLowerCase();
|
|
151
|
+
if (methods[method])
|
|
152
|
+
throw new TypeError(`Duplicate API method: ${endpoint.clientKey}.${method}`);
|
|
153
|
+
methods[method] = async (input = {}) => {
|
|
154
|
+
const path = pathWithParams(endpoint.pathname, input.params) + queryString(input.query);
|
|
155
|
+
const url = endpointUrl(path, options);
|
|
156
|
+
const fetcher = options.fetch ?? fetch;
|
|
157
|
+
const response = await fetcher(url, {
|
|
158
|
+
method: endpoint.method, headers: headersFor(options, endpoint.method, input.body !== undefined),
|
|
159
|
+
credentials: "same-origin", ...(input.body === undefined ? {} : { body: JSON.stringify(input.body) }),
|
|
160
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
161
|
+
});
|
|
162
|
+
return responseValue(response, endpoint.contentType);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return root;
|
|
166
|
+
}
|
|
167
|
+
/** Creates browser RPC procedures from public endpoint paths. */
|
|
168
|
+
export function createRpcClient(manifest, options = {}) {
|
|
169
|
+
const root = Object.create(null);
|
|
170
|
+
for (const endpoint of manifest.rpc) {
|
|
171
|
+
insert(root, endpoint.id, async (input, request = {}) => {
|
|
172
|
+
const fetcher = options.fetch ?? fetch;
|
|
173
|
+
const response = await fetcher(endpointUrl(endpoint.pathname, options), {
|
|
174
|
+
method: "POST", headers: headersFor(options, "POST", true), credentials: "same-origin",
|
|
175
|
+
body: JSON.stringify(input), ...(request.signal ? { signal: request.signal } : {}),
|
|
176
|
+
});
|
|
177
|
+
return responseValue(response);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return root;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=clients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients.js","sourceRoot":"","sources":["../../src/framework/clients.ts"],"names":[],"mappings":"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","sourcesContent":["/** Public descriptors intentionally omit handler files and server schemas. */\nexport interface PublicApiEndpoint {\n readonly clientKey: string;\n readonly pathname: string;\n readonly method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n readonly contentType?: \"json\" | \"response\";\n}\n\nexport interface PublicRpcEndpoint {\n readonly id: string;\n readonly pathname: string;\n}\n\nexport interface PublicServerManifest {\n readonly api: readonly PublicApiEndpoint[];\n readonly rpc: readonly PublicRpcEndpoint[];\n}\n\n/** Strips private handler paths before serializing a discovered server manifest. */\nexport function createPublicServerManifest(manifest: PublicServerManifest): PublicServerManifest {\n return {\n api: manifest.api.map(({ clientKey, pathname, method, contentType }) => ({\n clientKey, pathname, method, ...(contentType ? { contentType } : {}),\n })),\n rpc: manifest.rpc.map(({ id, pathname }) => ({ id, pathname })),\n };\n}\n\n/** Type-only operation declarations can be inferred from public shared schemas. */\nexport interface ApiOperation<Params = undefined, Query = undefined, Body = undefined, Output = unknown> {\n readonly params: Params;\n readonly query: Query;\n readonly body: Body;\n readonly output: Output;\n}\n\nexport interface RpcOperation<Input = unknown, Output = unknown> {\n readonly input: Input;\n readonly output: Output;\n}\n\ntype AnyApiOperation = ApiOperation<unknown, unknown, unknown, unknown>;\n\ntype SearchValue = string | number | boolean | null | undefined;\ntype QueryValue = SearchValue | readonly SearchValue[];\ntype RequestOptions = { readonly signal?: AbortSignal };\ntype ParamOption<T> = T extends undefined ? { readonly params?: never } : { readonly params: T };\ntype QueryOption<T> = T extends undefined ? { readonly query?: never } : { readonly query: T };\ntype BodyOption<T> = T extends undefined ? { readonly body?: never } : { readonly body: T };\ntype ApiArguments<O extends AnyApiOperation> = O[\"params\"] extends undefined\n ? O[\"query\"] extends undefined\n ? O[\"body\"] extends undefined ? [options?: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions] : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions]\n : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions]\n : [options: ParamOption<O[\"params\"]> & QueryOption<O[\"query\"]> & BodyOption<O[\"body\"]> & RequestOptions];\ntype Nest<Path extends string, Value> = Path extends `${infer Head}.${infer Tail}` ? { [K in Head]: Nest<Tail, Value> } : { [K in Path]: Value };\ntype Intersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (value: infer Result) => void ? Result : never;\ntype ApiMethods<Operations> = { [Method in keyof Operations]: Operations[Method] extends AnyApiOperation\n ? (...args: ApiArguments<Operations[Method]>) => Promise<Operations[Method][\"output\"]>\n : never };\nexport type ApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>> = Intersection<{\n [Key in keyof Contract & string]: Nest<Key, ApiMethods<Contract[Key]>>\n}[keyof Contract & string]>;\nexport type RpcClient<Contract extends Record<string, RpcOperation>> = Intersection<{\n [Key in keyof Contract & string]: Nest<Key, (input: Contract[Key][\"input\"], options?: RequestOptions) => Promise<Contract[Key][\"output\"]>>\n}[keyof Contract & string]>;\n\nexport class ClientError extends Error {\n constructor(public readonly code: string, public readonly status: number, public readonly requestId?: string) {\n super(code);\n this.name = \"ClientError\";\n }\n}\n\nexport interface ClientOptions {\n readonly baseUrl?: string | (() => string);\n readonly fetch?: typeof fetch;\n readonly csrfToken?: string | (() => string | undefined);\n readonly headers?: HeadersInit | (() => HeadersInit);\n}\n\n/** Reads the current document token at call time, including after client navigation. */\nexport function createBrowserClientOptions(): ClientOptions {\n return {\n csrfToken: () => typeof document === \"undefined\"\n ? undefined\n : document.querySelector('meta[name=\"meeko-csrf-token\"]')?.getAttribute(\"content\") ?? undefined,\n fetch: async (input, init) => {\n const response = await fetch(input, init);\n const token = response.headers.get(\"x-meeko-csrf-token\");\n if (token && typeof document !== \"undefined\") {\n let meta = document.querySelector('meta[name=\"meeko-csrf-token\"]');\n if (!meta) {\n meta = document.createElement(\"meta\");\n meta.setAttribute(\"name\", \"meeko-csrf-token\");\n document.head.append(meta);\n }\n meta.setAttribute(\"content\", token);\n }\n return response;\n },\n };\n}\n\nfunction pathWithParams(pathname: string, params: Readonly<Record<string, string>> = {}): string {\n const used = new Set<string>();\n const path = pathname.replace(/\\[(\\.\\.\\.)?([A-Za-z_$][\\w$]*)\\]/g, (_match, rest: string | undefined, name: string) => {\n const value = params[name];\n if (!value) throw new TypeError(`Missing route parameter: ${name}`);\n used.add(name);\n const parts = rest ? value.split(\"/\") : [value];\n if (parts.some((part) => !part || part === \".\" || part === \"..\" || part.includes(\"/\") || part.includes(\"\\\\\"))) throw new TypeError(`Invalid route parameter: ${name}`);\n return parts.map(encodeURIComponent).join(\"/\");\n });\n for (const key of Object.keys(params)) if (!used.has(key)) throw new TypeError(`Unknown route parameter: ${key}`);\n return path;\n}\n\nfunction queryString(query: Readonly<Record<string, QueryValue>> | undefined): string {\n if (!query) return \"\";\n const search = new URLSearchParams();\n for (const [key, raw] of Object.entries(query)) {\n for (const value of Array.isArray(raw) ? raw : [raw]) {\n if (value !== null && value !== undefined) search.append(key, String(value));\n }\n }\n const result = search.toString();\n return result ? `?${result}` : \"\";\n}\n\nasync function responseValue(response: Response, contentType: \"json\" | \"response\" = \"json\"): Promise<unknown> {\n if (!response.ok) {\n let payload: unknown;\n try { payload = await response.json(); } catch { /* Bad error bodies still produce a normalized error. */ }\n const record = payload && typeof payload === \"object\" ? payload as Record<string, unknown> : {};\n const code = typeof record.code === \"string\" ? record.code : \"HTTP_ERROR\";\n const requestId = typeof record.requestId === \"string\" ? record.requestId : response.headers.get(\"x-request-id\") ?? undefined;\n throw new ClientError(code, response.status, requestId);\n }\n if (contentType === \"response\") return response;\n if (response.status === 204) return undefined;\n try { return await response.json(); }\n catch { throw new ClientError(\"INVALID_RESPONSE\", response.status, response.headers.get(\"x-request-id\") ?? undefined); }\n}\n\nfunction endpointUrl(pathname: string, options: ClientOptions): string {\n const baseUrl = typeof options.baseUrl === \"function\" ? options.baseUrl() : options.baseUrl;\n return baseUrl ? new URL(pathname, baseUrl).toString() : pathname;\n}\n\nfunction headersFor(options: ClientOptions, method: string, json: boolean): Headers {\n const headers = new Headers(typeof options.headers === \"function\" ? options.headers() : options.headers);\n if (json) headers.set(\"content-type\", \"application/json\");\n if (method !== \"GET\" && method !== \"HEAD\") {\n const csrf = typeof options.csrfToken === \"function\" ? options.csrfToken() : options.csrfToken;\n if (csrf) headers.set(\"x-meeko-csrf\", csrf);\n }\n return headers;\n}\n\nfunction insert(root: Record<string, unknown>, key: string, value: unknown): void {\n const parts = key.split(\".\");\n if (parts.some((part) => !/^[A-Za-z_$][\\w$]*$/.test(part))) throw new TypeError(`Invalid client key: ${key}`);\n let node = root;\n for (const part of parts.slice(0, -1)) {\n const existing = node[part];\n if (existing === undefined) node[part] = Object.create(null) as Record<string, unknown>;\n else if (!existing || typeof existing !== \"object\") throw new TypeError(`Client key collision: ${key}`);\n node = node[part] as Record<string, unknown>;\n }\n const last = parts.at(-1)!;\n if (node[last] !== undefined) throw new TypeError(`Client key collision: ${key}`);\n node[last] = value;\n}\n\nfunction ensureObject(root: Record<string, unknown>, key: string): Record<string, unknown> {\n const parts = key.split(\".\");\n if (parts.some((part) => !/^[A-Za-z_$][\\w$]*$/.test(part))) throw new TypeError(`Invalid client key: ${key}`);\n let node = root;\n for (const part of parts) {\n const existing = node[part];\n if (existing === undefined) node[part] = Object.create(null) as Record<string, unknown>;\n else if (!existing || typeof existing !== \"object\") throw new TypeError(`Client key collision: ${key}`);\n node = node[part] as Record<string, unknown>;\n }\n return node;\n}\n\n/** Creates browser API methods from public endpoint paths, without server imports. */\nexport function createApiClient<Contract extends Record<string, Record<string, AnyApiOperation>>>(manifest: Pick<PublicServerManifest, \"api\">, options: ClientOptions = {}): ApiClient<Contract> {\n const root: Record<string, unknown> = Object.create(null);\n for (const endpoint of manifest.api) {\n const methods = ensureObject(root, endpoint.clientKey);\n const method = endpoint.method.toLowerCase();\n if (methods[method]) throw new TypeError(`Duplicate API method: ${endpoint.clientKey}.${method}`);\n methods[method] = async (input: { params?: Record<string, string>; query?: Record<string, QueryValue>; body?: unknown; signal?: AbortSignal } = {}) => {\n const path = pathWithParams(endpoint.pathname, input.params) + queryString(input.query);\n const url = endpointUrl(path, options);\n const fetcher = options.fetch ?? fetch;\n const response = await fetcher(url, {\n method: endpoint.method, headers: headersFor(options, endpoint.method, input.body !== undefined),\n credentials: \"same-origin\", ...(input.body === undefined ? {} : { body: JSON.stringify(input.body) }),\n ...(input.signal ? { signal: input.signal } : {}),\n });\n return responseValue(response, endpoint.contentType);\n };\n }\n return root as ApiClient<Contract>;\n}\n\n/** Creates browser RPC procedures from public endpoint paths. */\nexport function createRpcClient<Contract extends Record<string, RpcOperation>>(manifest: Pick<PublicServerManifest, \"rpc\">, options: ClientOptions = {}): RpcClient<Contract> {\n const root: Record<string, unknown> = Object.create(null);\n for (const endpoint of manifest.rpc) {\n insert(root, endpoint.id, async (input: unknown, request: RequestOptions = {}) => {\n const fetcher = options.fetch ?? fetch;\n const response = await fetcher(endpointUrl(endpoint.pathname, options), {\n method: \"POST\", headers: headersFor(options, \"POST\", true), credentials: \"same-origin\",\n body: JSON.stringify(input), ...(request.signal ? { signal: request.signal } : {}),\n });\n return responseValue(response);\n });\n }\n return root as RpcClient<Contract>;\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface MeekoConfig {
|
|
2
|
+
/** Layers are applied after the application, in declaration order. Collisions fail. */
|
|
3
|
+
readonly extends?: readonly string[];
|
|
4
|
+
/** Modules use the same app/pages convention as layers. */
|
|
5
|
+
readonly modules?: readonly string[];
|
|
6
|
+
readonly imports?: {
|
|
7
|
+
readonly serverDirs?: readonly string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function defineMeekoConfig<const T extends MeekoConfig>(config: T): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-type.js","sourceRoot":"","sources":["../../src/framework/config-type.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,iBAAiB,CAA8B,MAAS;IACtE,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["export interface MeekoConfig {\n /** Layers are applied after the application, in declaration order. Collisions fail. */\n readonly extends?: readonly string[];\n /** Modules use the same app/pages convention as layers. */\n readonly modules?: readonly string[];\n readonly imports?: { readonly serverDirs?: readonly string[] };\n}\n\nexport function defineMeekoConfig<const T extends MeekoConfig>(config: T): T {\n return config;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RouteRecord } from "./router.js";
|
|
2
|
+
import type { MeekoConfig } from "./config-type.js";
|
|
3
|
+
export type { MeekoConfig } from "./config-type.js";
|
|
4
|
+
export interface ConfigSource {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly root: string;
|
|
7
|
+
readonly kind: "app" | "layer" | "module";
|
|
8
|
+
}
|
|
9
|
+
/** Loads the optional project configuration using Node 24's TypeScript support. */
|
|
10
|
+
export declare function loadMeekoConfig(projectRoot: string): Promise<MeekoConfig>;
|
|
11
|
+
/** Resolves sources and rejects accidental escape or duplicate names before discovery. */
|
|
12
|
+
/** Reads static server import directories for synchronous editor discovery. */
|
|
13
|
+
export declare function readMeekoConfigSync(projectRoot: string): MeekoConfig;
|
|
14
|
+
export declare function resolveConfigSources(projectRoot: string, config?: MeekoConfig): Promise<readonly ConfigSource[]>;
|
|
15
|
+
export declare function discoverConfiguredRoutes(projectRoot: string): Promise<readonly RouteRecord[]>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { basename, extname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import ts from "@typescript/typescript6";
|
|
6
|
+
import { composeFrameworkRoutes } from "./modules.js";
|
|
7
|
+
function inside(root, target) {
|
|
8
|
+
const path = relative(root, target);
|
|
9
|
+
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path));
|
|
10
|
+
}
|
|
11
|
+
async function configFile(root) {
|
|
12
|
+
const files = ["meeko.config.ts", "meeko.config.mts", "meeko.config.js", "meeko.config.mjs"];
|
|
13
|
+
const found = [];
|
|
14
|
+
for (const name of files) {
|
|
15
|
+
const file = resolve(root, name);
|
|
16
|
+
try {
|
|
17
|
+
if ((await stat(file)).isFile())
|
|
18
|
+
found.push(file);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code !== "ENOENT")
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (found.length > 1)
|
|
26
|
+
throw new Error(`Multiple Meeko configuration files: ${found.join(", ")}`);
|
|
27
|
+
return found[0];
|
|
28
|
+
}
|
|
29
|
+
/** Loads the optional project configuration using Node 24's TypeScript support. */
|
|
30
|
+
export async function loadMeekoConfig(projectRoot) {
|
|
31
|
+
const root = resolve(projectRoot);
|
|
32
|
+
const file = await configFile(root);
|
|
33
|
+
if (!file)
|
|
34
|
+
return {};
|
|
35
|
+
const url = pathToFileURL(file);
|
|
36
|
+
url.searchParams.set("mtime", String((await stat(file)).mtimeMs));
|
|
37
|
+
let loaded;
|
|
38
|
+
try {
|
|
39
|
+
loaded = (await import(url.href)).default;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`Cannot load ${file}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
43
|
+
}
|
|
44
|
+
if (!loaded || typeof loaded !== "object" || Array.isArray(loaded))
|
|
45
|
+
throw new TypeError(`${file} must export a Meeko configuration object`);
|
|
46
|
+
const config = loaded;
|
|
47
|
+
for (const [key, entries] of [["extends", config.extends], ["modules", config.modules], ["imports.serverDirs", config.imports?.serverDirs]]) {
|
|
48
|
+
if (entries !== undefined && (!Array.isArray(entries) || entries.some((entry) => typeof entry !== "string" || !entry))) {
|
|
49
|
+
throw new TypeError(`${file}: ${key} must be an array of nonempty paths`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return config;
|
|
53
|
+
}
|
|
54
|
+
/** Resolves sources and rejects accidental escape or duplicate names before discovery. */
|
|
55
|
+
/** Reads static server import directories for synchronous editor discovery. */
|
|
56
|
+
export function readMeekoConfigSync(projectRoot) {
|
|
57
|
+
const file = ["meeko.config.ts", "meeko.config.mts", "meeko.config.js", "meeko.config.mjs"]
|
|
58
|
+
.map((name) => resolve(projectRoot, name)).find(existsSync);
|
|
59
|
+
if (!file)
|
|
60
|
+
return {};
|
|
61
|
+
const source = ts.createSourceFile(file, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true);
|
|
62
|
+
const exported = source.statements.find(ts.isExportAssignment);
|
|
63
|
+
const expression = exported?.expression;
|
|
64
|
+
const config = expression && ts.isCallExpression(expression) ? expression.arguments[0] : expression;
|
|
65
|
+
if (!config || !ts.isObjectLiteralExpression(config))
|
|
66
|
+
return {};
|
|
67
|
+
const property = (node, name) => {
|
|
68
|
+
const item = node.properties.find((entry) => ts.isPropertyAssignment(entry) &&
|
|
69
|
+
((ts.isIdentifier(entry.name) && entry.name.text === name) || (ts.isStringLiteral(entry.name) && entry.name.text === name)));
|
|
70
|
+
return item && ts.isPropertyAssignment(item) ? item.initializer : undefined;
|
|
71
|
+
};
|
|
72
|
+
const strings = (node, name) => {
|
|
73
|
+
const value = property(node, name);
|
|
74
|
+
if (!value)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (!ts.isArrayLiteralExpression(value) || value.elements.some((entry) => !ts.isStringLiteral(entry))) {
|
|
77
|
+
throw new TypeError(`${file}: ${name} must be a static string array for editor discovery`);
|
|
78
|
+
}
|
|
79
|
+
return value.elements.map((entry) => entry.text);
|
|
80
|
+
};
|
|
81
|
+
const imports = property(config, "imports");
|
|
82
|
+
const serverDirs = imports && ts.isObjectLiteralExpression(imports) ? strings(imports, "serverDirs") : undefined;
|
|
83
|
+
const extensions = strings(config, "extends");
|
|
84
|
+
const modules = strings(config, "modules");
|
|
85
|
+
return {
|
|
86
|
+
...(extensions ? { extends: extensions } : {}),
|
|
87
|
+
...(modules ? { modules } : {}),
|
|
88
|
+
...(serverDirs ? { imports: { serverDirs } } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export async function resolveConfigSources(projectRoot, config) {
|
|
92
|
+
config ??= await loadMeekoConfig(projectRoot);
|
|
93
|
+
const root = resolve(projectRoot);
|
|
94
|
+
const actualRoot = await realpath(root);
|
|
95
|
+
const result = [{ name: "app", root, kind: "app" }];
|
|
96
|
+
const names = new Set(["app"]);
|
|
97
|
+
const roots = new Set([actualRoot.toLowerCase()]);
|
|
98
|
+
for (const [kind, entries] of [["layer", config.extends], ["module", config.modules]]) {
|
|
99
|
+
for (const entry of entries ?? []) {
|
|
100
|
+
const target = resolve(root, entry);
|
|
101
|
+
if (!inside(root, target))
|
|
102
|
+
throw new Error(`${kind} ${entry} escapes project root ${root}`);
|
|
103
|
+
let actualTarget;
|
|
104
|
+
try {
|
|
105
|
+
actualTarget = await realpath(target);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new Error(`${kind} ${entry} does not exist: ${target}`);
|
|
109
|
+
}
|
|
110
|
+
if (!inside(actualRoot, actualTarget))
|
|
111
|
+
throw new Error(`${kind} ${entry} escapes project root ${root}`);
|
|
112
|
+
if (roots.has(actualTarget.toLowerCase()))
|
|
113
|
+
throw new Error(`Duplicate Meeko source root: ${target}`);
|
|
114
|
+
const name = basename(target, extname(target));
|
|
115
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name) || names.has(name))
|
|
116
|
+
throw new Error(`Invalid or duplicate Meeko source name ${name}: ${target}`);
|
|
117
|
+
try {
|
|
118
|
+
if (!(await stat(resolve(target, "app", "pages"))).isDirectory())
|
|
119
|
+
throw new Error("not a directory");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
throw new Error(`${kind} ${entry} has no app/pages directory: ${target}`);
|
|
123
|
+
}
|
|
124
|
+
roots.add(actualTarget.toLowerCase());
|
|
125
|
+
names.add(name);
|
|
126
|
+
result.push({ name, root: target, kind });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
export async function discoverConfiguredRoutes(projectRoot) {
|
|
132
|
+
const sources = await resolveConfigSources(projectRoot);
|
|
133
|
+
return composeFrameworkRoutes({
|
|
134
|
+
projectRoot,
|
|
135
|
+
layers: sources.slice(1).map(({ name, root }) => ({ name, root })),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/framework/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath, stat } from \"node:fs/promises\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { basename, extname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport ts from \"@typescript/typescript6\";\nimport { composeFrameworkRoutes } from \"./modules.js\";\nimport type { RouteRecord } from \"./router.js\";\nimport type { MeekoConfig } from \"./config-type.js\";\n\nexport type { MeekoConfig } from \"./config-type.js\";\n\nexport interface ConfigSource {\n readonly name: string;\n readonly root: string;\n readonly kind: \"app\" | \"layer\" | \"module\";\n}\n\nfunction inside(root: string, target: string): boolean {\n const path = relative(root, target);\n return path === \"\" || (path !== \"..\" && !path.startsWith(`..${sep}`) && !isAbsolute(path));\n}\n\nasync function configFile(root: string): Promise<string | undefined> {\n const files = [\"meeko.config.ts\", \"meeko.config.mts\", \"meeko.config.js\", \"meeko.config.mjs\"];\n const found: string[] = [];\n for (const name of files) {\n const file = resolve(root, name);\n try { if ((await stat(file)).isFile()) found.push(file); }\n catch (error) { if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error; }\n }\n if (found.length > 1) throw new Error(`Multiple Meeko configuration files: ${found.join(\", \")}`);\n return found[0];\n}\n\n/** Loads the optional project configuration using Node 24's TypeScript support. */\nexport async function loadMeekoConfig(projectRoot: string): Promise<MeekoConfig> {\n const root = resolve(projectRoot);\n const file = await configFile(root);\n if (!file) return {};\n const url = pathToFileURL(file);\n url.searchParams.set(\"mtime\", String((await stat(file)).mtimeMs));\n let loaded: unknown;\n try { loaded = (await import(url.href) as { default?: unknown }).default; }\n catch (error) { throw new Error(`Cannot load ${file}: ${error instanceof Error ? error.message : String(error)}`, { cause: error }); }\n if (!loaded || typeof loaded !== \"object\" || Array.isArray(loaded)) throw new TypeError(`${file} must export a Meeko configuration object`);\n const config = loaded as MeekoConfig;\n for (const [key, entries] of [[\"extends\", config.extends], [\"modules\", config.modules], [\"imports.serverDirs\", config.imports?.serverDirs]] as const) {\n if (entries !== undefined && (!Array.isArray(entries) || entries.some((entry) => typeof entry !== \"string\" || !entry))) {\n throw new TypeError(`${file}: ${key} must be an array of nonempty paths`);\n }\n }\n return config;\n}\n\n/** Resolves sources and rejects accidental escape or duplicate names before discovery. */\n/** Reads static server import directories for synchronous editor discovery. */\nexport function readMeekoConfigSync(projectRoot: string): MeekoConfig {\n const file = [\"meeko.config.ts\", \"meeko.config.mts\", \"meeko.config.js\", \"meeko.config.mjs\"]\n .map((name) => resolve(projectRoot, name)).find(existsSync);\n if (!file) return {};\n const source = ts.createSourceFile(file, readFileSync(file, \"utf8\"), ts.ScriptTarget.Latest, true);\n const exported = source.statements.find(ts.isExportAssignment);\n const expression = exported?.expression;\n const config = expression && ts.isCallExpression(expression) ? expression.arguments[0] : expression;\n if (!config || !ts.isObjectLiteralExpression(config)) return {};\n const property = (node: ts.ObjectLiteralExpression, name: string): ts.Expression | undefined => {\n const item = node.properties.find((entry) => ts.isPropertyAssignment(entry) &&\n ((ts.isIdentifier(entry.name) && entry.name.text === name) || (ts.isStringLiteral(entry.name) && entry.name.text === name)));\n return item && ts.isPropertyAssignment(item) ? item.initializer : undefined;\n };\n const strings = (node: ts.ObjectLiteralExpression, name: string): readonly string[] | undefined => {\n const value = property(node, name);\n if (!value) return undefined;\n if (!ts.isArrayLiteralExpression(value) || value.elements.some((entry) => !ts.isStringLiteral(entry))) {\n throw new TypeError(`${file}: ${name} must be a static string array for editor discovery`);\n }\n return value.elements.map((entry) => (entry as ts.StringLiteral).text);\n };\n const imports = property(config, \"imports\");\n const serverDirs = imports && ts.isObjectLiteralExpression(imports) ? strings(imports, \"serverDirs\") : undefined;\n const extensions = strings(config, \"extends\");\n const modules = strings(config, \"modules\");\n return {\n ...(extensions ? { extends: extensions } : {}),\n ...(modules ? { modules } : {}),\n ...(serverDirs ? { imports: { serverDirs } } : {}),\n };\n}\n\nexport async function resolveConfigSources(projectRoot: string, config?: MeekoConfig): Promise<readonly ConfigSource[]> {\n config ??= await loadMeekoConfig(projectRoot);\n const root = resolve(projectRoot);\n const actualRoot = await realpath(root);\n const result: ConfigSource[] = [{ name: \"app\", root, kind: \"app\" }];\n const names = new Set([\"app\"]);\n const roots = new Set([actualRoot.toLowerCase()]);\n for (const [kind, entries] of [[\"layer\", config.extends], [\"module\", config.modules]] as const) {\n for (const entry of entries ?? []) {\n const target = resolve(root, entry);\n if (!inside(root, target)) throw new Error(`${kind} ${entry} escapes project root ${root}`);\n let actualTarget: string;\n try { actualTarget = await realpath(target); }\n catch { throw new Error(`${kind} ${entry} does not exist: ${target}`); }\n if (!inside(actualRoot, actualTarget)) throw new Error(`${kind} ${entry} escapes project root ${root}`);\n if (roots.has(actualTarget.toLowerCase())) throw new Error(`Duplicate Meeko source root: ${target}`);\n const name = basename(target, extname(target));\n if (!/^[a-z][a-z0-9-]*$/.test(name) || names.has(name)) throw new Error(`Invalid or duplicate Meeko source name ${name}: ${target}`);\n try { if (!(await stat(resolve(target, \"app\", \"pages\"))).isDirectory()) throw new Error(\"not a directory\"); }\n catch { throw new Error(`${kind} ${entry} has no app/pages directory: ${target}`); }\n roots.add(actualTarget.toLowerCase()); names.add(name);\n result.push({ name, root: target, kind });\n }\n }\n return result;\n}\n\nexport async function discoverConfiguredRoutes(projectRoot: string): Promise<readonly RouteRecord[]> {\n const sources = await resolveConfigSources(projectRoot);\n return composeFrameworkRoutes({\n projectRoot,\n layers: sources.slice(1).map(({ name, root }) => ({ name, root })),\n });\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface GeneratedServerContracts {
|
|
2
|
+
readonly types: string;
|
|
3
|
+
readonly manifest: string;
|
|
4
|
+
readonly apiCount: number;
|
|
5
|
+
readonly rpcCount: number;
|
|
6
|
+
}
|
|
7
|
+
/** Generate public API/RPC declarations without importing handler modules. */
|
|
8
|
+
export declare function generateServerContracts(projectRoot: string): Promise<GeneratedServerContracts>;
|