setti 0.0.0-stage → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1406 -2
- package/bin/nixo.mjs +8 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3060 -0
- package/dist/client/index.d.ts +180 -0
- package/dist/client/index.js +958 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +816 -0
- package/dist/not-found-fz-2RDV6.d.ts +340 -0
- package/dist/prefetch-Bp9dMSTK.d.ts +766 -0
- package/dist/server.d.ts +635 -0
- package/dist/server.js +2813 -0
- package/package.json +85 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3060 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs5, { promises } from 'fs';
|
|
3
|
+
import path10 from 'path';
|
|
4
|
+
import { stringify } from 'devalue';
|
|
5
|
+
import { createJiti } from 'jiti';
|
|
6
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
7
|
+
import { createElement } from 'react';
|
|
8
|
+
import { renderToString, renderToPipeableStream } from 'react-dom/server';
|
|
9
|
+
import { Transform } from 'stream';
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
import Fastify from 'fastify';
|
|
13
|
+
import fastifyStatic from '@fastify/static';
|
|
14
|
+
|
|
15
|
+
var __defProp = Object.defineProperty;
|
|
16
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
17
|
+
var __esm = (fn, res) => function __init() {
|
|
18
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/errors.ts
|
|
26
|
+
var NixoError;
|
|
27
|
+
var init_errors = __esm({
|
|
28
|
+
"src/errors.ts"() {
|
|
29
|
+
NixoError = class extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "NixoError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
async function fileExists(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
const stat = await promises.stat(filePath);
|
|
40
|
+
return stat.isFile();
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function walkFiles(dir, onFile) {
|
|
46
|
+
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
49
|
+
const full = path10.join(dir, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
await walkFiles(full, onFile);
|
|
52
|
+
} else if (entry.isFile()) {
|
|
53
|
+
await onFile(full);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var init_fs = __esm({
|
|
58
|
+
"src/utils/fs.ts"() {
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// src/url.ts
|
|
63
|
+
function escapeRegex(s) {
|
|
64
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
65
|
+
}
|
|
66
|
+
function idSegments(route, byId) {
|
|
67
|
+
const segments = [];
|
|
68
|
+
let current = route;
|
|
69
|
+
while (current) {
|
|
70
|
+
if (!current.index) segments.unshift(current.id);
|
|
71
|
+
current = current.parentId ? byId.get(current.parentId) : void 0;
|
|
72
|
+
}
|
|
73
|
+
return segments;
|
|
74
|
+
}
|
|
75
|
+
function computePath(route, byId) {
|
|
76
|
+
if (route.path) return route.path;
|
|
77
|
+
const segments = idSegments(route, byId);
|
|
78
|
+
if (route.slug) {
|
|
79
|
+
let ri = 0;
|
|
80
|
+
for (const s of route.slug) {
|
|
81
|
+
if (s.startsWith("/regex:")) {
|
|
82
|
+
segments.push(ri === 0 ? ":slug" : `:slug${ri}`);
|
|
83
|
+
ri++;
|
|
84
|
+
} else {
|
|
85
|
+
segments.push(s);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return "/" + segments.join("/");
|
|
90
|
+
}
|
|
91
|
+
function compilePath(pattern) {
|
|
92
|
+
if (!pattern.startsWith("/")) {
|
|
93
|
+
throw new Error(`Path must start with "/": "${pattern}"`);
|
|
94
|
+
}
|
|
95
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
96
|
+
const paramNames = [];
|
|
97
|
+
const parts = segments.map((seg) => {
|
|
98
|
+
if (seg.startsWith(":")) {
|
|
99
|
+
const name = seg.slice(1);
|
|
100
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
101
|
+
throw new Error(`Invalid param name "${name}" in pattern "${pattern}"`);
|
|
102
|
+
}
|
|
103
|
+
paramNames.push(name);
|
|
104
|
+
return "([^/]+)";
|
|
105
|
+
}
|
|
106
|
+
return escapeRegex(seg);
|
|
107
|
+
});
|
|
108
|
+
return { regexSource: "^/" + parts.join("/") + "$", paramNames };
|
|
109
|
+
}
|
|
110
|
+
function compileRoute(route, byId) {
|
|
111
|
+
if (route.path) return compilePath(route.path);
|
|
112
|
+
const parts = [];
|
|
113
|
+
const paramNames = [];
|
|
114
|
+
for (const seg of idSegments(route, byId)) {
|
|
115
|
+
parts.push(escapeRegex(seg));
|
|
116
|
+
}
|
|
117
|
+
if (route.slug) {
|
|
118
|
+
let ri = 0;
|
|
119
|
+
for (const s of route.slug) {
|
|
120
|
+
if (s.startsWith("/regex:")) {
|
|
121
|
+
const body = s.slice("/regex:".length);
|
|
122
|
+
if (!body) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Empty regex in slug for route "${route.id}": "${s}"`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
parts.push(`(${body})`);
|
|
128
|
+
paramNames.push(ri === 0 ? "slug" : `slug${ri}`);
|
|
129
|
+
ri++;
|
|
130
|
+
} else if (s.startsWith(":")) {
|
|
131
|
+
const name = s.slice(1);
|
|
132
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Invalid param name "${name}" in slug for route "${route.id}"`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
parts.push("([^/]+)");
|
|
138
|
+
paramNames.push(name);
|
|
139
|
+
} else {
|
|
140
|
+
parts.push(escapeRegex(s));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { regexSource: "^/" + parts.join("/") + "$", paramNames };
|
|
145
|
+
}
|
|
146
|
+
function matchRoute(url, compiled) {
|
|
147
|
+
for (const r of compiled) {
|
|
148
|
+
const m = new RegExp(r.regexSource).exec(url);
|
|
149
|
+
if (m) {
|
|
150
|
+
const params = {};
|
|
151
|
+
for (let i = 0; i < r.paramNames.length; i++) {
|
|
152
|
+
params[r.paramNames[i]] = decodeURIComponent(m[i + 1]);
|
|
153
|
+
}
|
|
154
|
+
return { routeId: r.id, params };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
var init_url = __esm({
|
|
160
|
+
"src/url.ts"() {
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// src/shared/prefetch.ts
|
|
165
|
+
async function runGlobalPrefetch(ctx, opts = {}) {
|
|
166
|
+
const { route, globalRegistry, loadModule, registry, router } = ctx;
|
|
167
|
+
const states = /* @__PURE__ */ new Map();
|
|
168
|
+
const slices = /* @__PURE__ */ new Map();
|
|
169
|
+
const needed = new Set(
|
|
170
|
+
route.contentConfig.prefetchConfig.dependencies ?? []
|
|
171
|
+
);
|
|
172
|
+
const queue = [...needed];
|
|
173
|
+
while (queue.length > 0) {
|
|
174
|
+
const name = queue.shift();
|
|
175
|
+
const def = globalRegistry[name];
|
|
176
|
+
if (!def) continue;
|
|
177
|
+
for (const dep of def.dependencies ?? []) {
|
|
178
|
+
if (!needed.has(dep)) {
|
|
179
|
+
needed.add(dep);
|
|
180
|
+
queue.push(dep);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (opts.skipExisting) {
|
|
185
|
+
for (const name of [...needed]) {
|
|
186
|
+
if (registry.get(name)) needed.delete(name);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (needed.size === 0) return states;
|
|
190
|
+
const sorted = topoSort(needed, (name) => {
|
|
191
|
+
const def = globalRegistry[name];
|
|
192
|
+
if (!def) {
|
|
193
|
+
throw new NixoError(
|
|
194
|
+
`Global dependency "${name}" not found in any *.loader.ts`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return def.dependencies ?? [];
|
|
198
|
+
});
|
|
199
|
+
for (const name of sorted) {
|
|
200
|
+
const def = globalRegistry[name];
|
|
201
|
+
const implRaw = await loadModule(def.dataFactoryName);
|
|
202
|
+
const impl = implRaw;
|
|
203
|
+
if (!impl || typeof impl.loadData !== "function" || typeof impl.slice !== "function") {
|
|
204
|
+
throw new NixoError(
|
|
205
|
+
`Invalid global loader "${name}": expected loadData + slice class`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const args = { ...def.dataFactoryArguments ?? {} };
|
|
209
|
+
if (typeof impl.getDataFactoryArguments === "function") {
|
|
210
|
+
Object.assign(args, await impl.getDataFactoryArguments());
|
|
211
|
+
}
|
|
212
|
+
const deps = {};
|
|
213
|
+
for (const dep of def.dependencies ?? []) deps[dep] = states.get(dep);
|
|
214
|
+
const data = await impl.loadData(args, deps, { router });
|
|
215
|
+
states.set(name, data);
|
|
216
|
+
slices.set(name, {
|
|
217
|
+
dataFactoryName: def.dataFactoryName,
|
|
218
|
+
state: data,
|
|
219
|
+
scope: "global"
|
|
220
|
+
});
|
|
221
|
+
const SliceClass = impl.slice;
|
|
222
|
+
const slice = new SliceClass();
|
|
223
|
+
slice.hydrate(data);
|
|
224
|
+
registry.setGlobal(name, slice);
|
|
225
|
+
}
|
|
226
|
+
return states;
|
|
227
|
+
}
|
|
228
|
+
async function runPagePrefetch(ctx, globalStates) {
|
|
229
|
+
const { route, globalRegistry, loadModule, registry, router } = ctx;
|
|
230
|
+
const prefetchCfg = route.contentConfig.prefetchConfig;
|
|
231
|
+
const states = new Map(globalStates);
|
|
232
|
+
const slices = /* @__PURE__ */ new Map();
|
|
233
|
+
const getConfigRaw = await loadModule(prefetchCfg.configLoader);
|
|
234
|
+
if (typeof getConfigRaw !== "function") {
|
|
235
|
+
throw new NixoError(
|
|
236
|
+
`configLoader "${prefetchCfg.configLoader}" must export a function`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const factoryMap = await getConfigRaw(
|
|
240
|
+
prefetchCfg.configParams ?? {},
|
|
241
|
+
router
|
|
242
|
+
);
|
|
243
|
+
if (!factoryMap || typeof factoryMap !== "object") {
|
|
244
|
+
throw new NixoError(
|
|
245
|
+
`getConfig from "${prefetchCfg.configLoader}" must return an object`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
for (const name of Object.keys(factoryMap)) {
|
|
249
|
+
if (globalRegistry[name]) {
|
|
250
|
+
throw new NixoError(
|
|
251
|
+
`A3 violation: "${name}" declared both in page's getConfig and global registry`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const needed = new Set(Object.keys(factoryMap));
|
|
256
|
+
const queue = [...needed];
|
|
257
|
+
while (queue.length > 0) {
|
|
258
|
+
const name = queue.shift();
|
|
259
|
+
const def = factoryMap[name];
|
|
260
|
+
if (!def) continue;
|
|
261
|
+
for (const dep of def.dependencies ?? []) {
|
|
262
|
+
if (!needed.has(dep) && !globalStates.has(dep)) {
|
|
263
|
+
needed.add(dep);
|
|
264
|
+
queue.push(dep);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const sorted = topoSort(needed, (name) => {
|
|
269
|
+
const def = factoryMap[name];
|
|
270
|
+
if (!def) {
|
|
271
|
+
if (globalStates.has(name)) return [];
|
|
272
|
+
throw new NixoError(
|
|
273
|
+
`Unknown dependency "${name}" \u2014 not in getConfig, not a loaded global`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return def.dependencies ?? [];
|
|
277
|
+
});
|
|
278
|
+
for (const name of sorted) {
|
|
279
|
+
const def = factoryMap[name];
|
|
280
|
+
const implRaw = await loadModule(def.dataFactoryName);
|
|
281
|
+
const impl = implRaw;
|
|
282
|
+
if (!impl || typeof impl.loadData !== "function" || typeof impl.slice !== "function") {
|
|
283
|
+
throw new NixoError(`Invalid factory "${name}": expected loadData + slice`);
|
|
284
|
+
}
|
|
285
|
+
const args = { ...def.dataFactoryArguments ?? {} };
|
|
286
|
+
if (typeof impl.getDataFactoryArguments === "function") {
|
|
287
|
+
Object.assign(args, await impl.getDataFactoryArguments());
|
|
288
|
+
}
|
|
289
|
+
const deps = {};
|
|
290
|
+
for (const dep of def.dependencies ?? []) deps[dep] = states.get(dep);
|
|
291
|
+
const data = await impl.loadData(args, deps, { router });
|
|
292
|
+
states.set(name, data);
|
|
293
|
+
slices.set(name, {
|
|
294
|
+
dataFactoryName: def.dataFactoryName,
|
|
295
|
+
state: data,
|
|
296
|
+
scope: "page"
|
|
297
|
+
});
|
|
298
|
+
const SliceClass = impl.slice;
|
|
299
|
+
const slice = new SliceClass();
|
|
300
|
+
slice.hydrate(data);
|
|
301
|
+
registry.setPage(name, slice);
|
|
302
|
+
}
|
|
303
|
+
for (const [name, data] of globalStates) {
|
|
304
|
+
const def = globalRegistry[name];
|
|
305
|
+
if (def) {
|
|
306
|
+
slices.set(name, {
|
|
307
|
+
dataFactoryName: def.dataFactoryName,
|
|
308
|
+
state: data,
|
|
309
|
+
scope: "global"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return { states, slices };
|
|
314
|
+
}
|
|
315
|
+
function topoSort(nodes, depsOf) {
|
|
316
|
+
const result = [];
|
|
317
|
+
const state = /* @__PURE__ */ new Map();
|
|
318
|
+
const visit = (name, pathStack) => {
|
|
319
|
+
const s = state.get(name);
|
|
320
|
+
if (s === "done") return;
|
|
321
|
+
if (s === "visiting") {
|
|
322
|
+
throw new NixoError(
|
|
323
|
+
`Cycle detected: ${[...pathStack, name].join(" \u2192 ")}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
state.set(name, "visiting");
|
|
327
|
+
for (const dep of depsOf(name)) visit(dep, [...pathStack, name]);
|
|
328
|
+
state.set(name, "done");
|
|
329
|
+
result.push(name);
|
|
330
|
+
};
|
|
331
|
+
for (const name of nodes) visit(name, []);
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
var init_prefetch = __esm({
|
|
335
|
+
"src/shared/prefetch.ts"() {
|
|
336
|
+
init_errors();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// src/server/prefetch.ts
|
|
341
|
+
var init_prefetch2 = __esm({
|
|
342
|
+
"src/server/prefetch.ts"() {
|
|
343
|
+
init_prefetch();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
async function fileExists2(filePath) {
|
|
347
|
+
try {
|
|
348
|
+
const stat = await promises.stat(filePath);
|
|
349
|
+
return stat.isFile();
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function resolveModulePath(projectDir, clientDir, spec) {
|
|
355
|
+
const [file, exportName = "default"] = spec.split(":");
|
|
356
|
+
const absBase = path10.resolve(clientDir, file);
|
|
357
|
+
for (const ext of [".tsx", ".ts", ".jsx", ".js", ""]) {
|
|
358
|
+
const abs = absBase + ext;
|
|
359
|
+
if (await fileExists2(abs)) {
|
|
360
|
+
const fromRoot = "/" + path10.relative(projectDir, abs).replace(/\\/g, "/");
|
|
361
|
+
return { modulePath: fromRoot, exportName };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
throw new Error(`Module not found: ${spec} (resolved from ${absBase})`);
|
|
365
|
+
}
|
|
366
|
+
async function serializeState(slices, projectDir, clientDir) {
|
|
367
|
+
const out = { version: 1, slices: {} };
|
|
368
|
+
for (const [name, info] of slices) {
|
|
369
|
+
const { modulePath, exportName } = await resolveModulePath(
|
|
370
|
+
projectDir,
|
|
371
|
+
clientDir,
|
|
372
|
+
info.dataFactoryName
|
|
373
|
+
);
|
|
374
|
+
out.slices[name] = {
|
|
375
|
+
modulePath,
|
|
376
|
+
exportName,
|
|
377
|
+
state: info.state,
|
|
378
|
+
scope: info.scope
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
function stringifyState(state) {
|
|
384
|
+
try {
|
|
385
|
+
return stringify(state).replace(/</g, "\\u003c");
|
|
386
|
+
} catch (err) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Failed to serialize __NIXO_STATE__ (devalue): ${err.message}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
var init_serialize = __esm({
|
|
393
|
+
"src/server/serialize.ts"() {
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
async function loadMiddleware(def, projectDir, side) {
|
|
397
|
+
const [file, exportName] = def.handler.split(":");
|
|
398
|
+
const base = path10.resolve(projectDir, file);
|
|
399
|
+
let abs = null;
|
|
400
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ""]) {
|
|
401
|
+
if (await fileExists(base + ext)) {
|
|
402
|
+
abs = base + ext;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!abs) {
|
|
407
|
+
throw new NixoError(
|
|
408
|
+
`Middleware "${def.id}" not found: ${def.handler} (resolved from ${base})`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true, jsx: true });
|
|
412
|
+
const mod = await jiti.import(abs);
|
|
413
|
+
let fn = void 0;
|
|
414
|
+
if (exportName) {
|
|
415
|
+
fn = mod[exportName];
|
|
416
|
+
} else if (def.side === "both" && typeof mod[side] === "function") {
|
|
417
|
+
fn = mod[side];
|
|
418
|
+
} else {
|
|
419
|
+
fn = mod.default;
|
|
420
|
+
}
|
|
421
|
+
if (typeof fn !== "function") {
|
|
422
|
+
throw new NixoError(
|
|
423
|
+
`Middleware "${def.id}" at ${abs}: expected function${def.side === "both" ? ` export "${side}" or default` : " (default or named)"}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return fn;
|
|
427
|
+
}
|
|
428
|
+
var init_middleware_loader = __esm({
|
|
429
|
+
"src/server/middleware-loader.ts"() {
|
|
430
|
+
init_fs();
|
|
431
|
+
init_errors();
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// src/server/run-server-middleware.ts
|
|
436
|
+
async function runServerMiddleware(globalMiddleware, route, ctx, projectDir) {
|
|
437
|
+
const chain = [...globalMiddleware, ...route.middleware ?? []].filter(
|
|
438
|
+
(def) => {
|
|
439
|
+
if (def.side === "client") return false;
|
|
440
|
+
if (def.side === "server" || def.side === "both") return true;
|
|
441
|
+
if (def.handler.startsWith("client/")) return false;
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
for (const def of chain) {
|
|
446
|
+
const fn = await loadMiddleware(def, projectDir, "server");
|
|
447
|
+
const result = await fn(def.params ?? {}, ctx);
|
|
448
|
+
if (result && typeof result === "object" && "redirect" in result) {
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
var init_run_server_middleware = __esm({
|
|
455
|
+
"src/server/run-server-middleware.ts"() {
|
|
456
|
+
init_middleware_loader();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
function runWithRpcContext(ctx, fn) {
|
|
460
|
+
return als.run(ctx, fn);
|
|
461
|
+
}
|
|
462
|
+
function getRpcContext() {
|
|
463
|
+
return als.getStore();
|
|
464
|
+
}
|
|
465
|
+
function parseCookieHeader(cookie) {
|
|
466
|
+
if (!cookie) return {};
|
|
467
|
+
const result = {};
|
|
468
|
+
for (const pair of cookie.split(";")) {
|
|
469
|
+
const idx = pair.indexOf("=");
|
|
470
|
+
if (idx === -1) continue;
|
|
471
|
+
const key = pair.slice(0, idx).trim();
|
|
472
|
+
const value = pair.slice(idx + 1).trim();
|
|
473
|
+
if (key) result[key] = decodeURIComponent(value);
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
var als;
|
|
478
|
+
var init_als = __esm({
|
|
479
|
+
"src/server/rpc/als.ts"() {
|
|
480
|
+
als = new AsyncLocalStorage();
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
function createSSRHandler(options) {
|
|
484
|
+
const {
|
|
485
|
+
config,
|
|
486
|
+
projectDir,
|
|
487
|
+
clientDir,
|
|
488
|
+
routeTree,
|
|
489
|
+
manifest: _manifest,
|
|
490
|
+
mode: _mode,
|
|
491
|
+
globalRegistry,
|
|
492
|
+
NixoProvider,
|
|
493
|
+
SliceRegistry,
|
|
494
|
+
loadModule,
|
|
495
|
+
isDev,
|
|
496
|
+
entryScriptSrc,
|
|
497
|
+
clientManifest,
|
|
498
|
+
clientOutPublicPrefix = "/",
|
|
499
|
+
globalMiddleware,
|
|
500
|
+
rpcExecutor,
|
|
501
|
+
renderLayoutChain,
|
|
502
|
+
NixoErrorBoundary,
|
|
503
|
+
NixoLoading: _NixoLoading,
|
|
504
|
+
NixoNotFound
|
|
505
|
+
} = options;
|
|
506
|
+
return async function handler(req, reply) {
|
|
507
|
+
reply.hijack();
|
|
508
|
+
const runHandler = async () => {
|
|
509
|
+
const url = req.url.split("?")[0];
|
|
510
|
+
const query = queryToObject(req.url);
|
|
511
|
+
const matched = matchRoute(url, routeTree.compiled);
|
|
512
|
+
const route = matched ? routeTree.byId.get(matched.routeId) ?? null : null;
|
|
513
|
+
const params = matched?.params ?? {};
|
|
514
|
+
const viteClientScript = isDev ? `<script type="module" src="/@vite/client"></script>
|
|
515
|
+
<script type="module">
|
|
516
|
+
import RefreshRuntime from "/@react-refresh"
|
|
517
|
+
RefreshRuntime.injectIntoGlobalHook(window)
|
|
518
|
+
window.$RefreshReg$ = () => {}
|
|
519
|
+
window.$RefreshSig$ = () => (type) => type
|
|
520
|
+
window.__vite_plugin_react_preamble_installed__ = true
|
|
521
|
+
</script>` : "";
|
|
522
|
+
const entryScript = `<script type="module" src="${entryScriptSrc}"></script>`;
|
|
523
|
+
const clientMiddleware = (config.middleware ?? []).filter(
|
|
524
|
+
(m) => !m.side || m.side === "client" || m.side === "both"
|
|
525
|
+
);
|
|
526
|
+
const middlewareJson = JSON.stringify(clientMiddleware).replace(
|
|
527
|
+
/</g,
|
|
528
|
+
"\\u003c"
|
|
529
|
+
);
|
|
530
|
+
const middlewareScript = `<script>window.__NIXO_MIDDLEWARE__=${middlewareJson}</script>`;
|
|
531
|
+
if (!route) {
|
|
532
|
+
const accept = req.headers.accept ?? "";
|
|
533
|
+
const wantsJson = accept.includes("application/json") && !accept.includes("text/html");
|
|
534
|
+
if (wantsJson) {
|
|
535
|
+
reply.raw.statusCode = 404;
|
|
536
|
+
reply.raw.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
537
|
+
reply.raw.end(JSON.stringify({ error: "Not Found", path: url }));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const notFoundCss = buildCssLinks({
|
|
541
|
+
isDev,
|
|
542
|
+
clientManifest,
|
|
543
|
+
clientOutPublicPrefix,
|
|
544
|
+
mode: "spa",
|
|
545
|
+
projectDir,
|
|
546
|
+
clientDir,
|
|
547
|
+
route: null,
|
|
548
|
+
config
|
|
549
|
+
});
|
|
550
|
+
const nfRegistry = new SliceRegistry();
|
|
551
|
+
const nfRouter = {
|
|
552
|
+
id: "__not-found__",
|
|
553
|
+
path: url,
|
|
554
|
+
params: {},
|
|
555
|
+
query,
|
|
556
|
+
request: req
|
|
557
|
+
};
|
|
558
|
+
let notFoundHtml;
|
|
559
|
+
if (config.components.notFound) {
|
|
560
|
+
try {
|
|
561
|
+
const NotFound = await loadModule(
|
|
562
|
+
config.components.notFound
|
|
563
|
+
);
|
|
564
|
+
notFoundHtml = renderToString(
|
|
565
|
+
createElement(
|
|
566
|
+
NixoProvider,
|
|
567
|
+
{ sliceRegistry: nfRegistry, router: nfRouter },
|
|
568
|
+
createElement(NotFound)
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
console.warn(
|
|
573
|
+
"[nixo] failed to render config.components.notFound, fallback to default:",
|
|
574
|
+
err
|
|
575
|
+
);
|
|
576
|
+
notFoundHtml = renderToString(
|
|
577
|
+
createElement(
|
|
578
|
+
NixoProvider,
|
|
579
|
+
{ sliceRegistry: nfRegistry, router: nfRouter },
|
|
580
|
+
createElement(NixoNotFound)
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
notFoundHtml = renderToString(
|
|
586
|
+
createElement(
|
|
587
|
+
NixoProvider,
|
|
588
|
+
{ sliceRegistry: nfRegistry, router: nfRouter },
|
|
589
|
+
createElement(NixoNotFound)
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
reply.raw.statusCode = 404;
|
|
594
|
+
reply.raw.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
595
|
+
reply.raw.end(
|
|
596
|
+
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>404</title>${notFoundCss}${viteClientScript}</head><body><div id="root">${notFoundHtml}</div><script>window.__NIXO_SSR__=true</script>` + middlewareScript + `<script>window.__NIXO_STATE_STR__=""</script>${entryScript}</body></html>`
|
|
597
|
+
);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const globalSSR = config.ssr !== false;
|
|
601
|
+
const routeSSR = globalSSR ? route.ssr !== false : false;
|
|
602
|
+
const shouldSSR = routeSSR && query.noSSR !== "true";
|
|
603
|
+
const ssrFlagScript = `<script>window.__NIXO_SSR__=${shouldSSR}</script>`;
|
|
604
|
+
const cssLinksHtml = buildCssLinks({
|
|
605
|
+
isDev,
|
|
606
|
+
clientManifest,
|
|
607
|
+
clientOutPublicPrefix,
|
|
608
|
+
mode: shouldSSR ? "ssr" : "spa",
|
|
609
|
+
projectDir,
|
|
610
|
+
clientDir,
|
|
611
|
+
route,
|
|
612
|
+
config
|
|
613
|
+
});
|
|
614
|
+
if (!shouldSSR) {
|
|
615
|
+
reply.raw.statusCode = 200;
|
|
616
|
+
reply.raw.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
617
|
+
reply.raw.end(
|
|
618
|
+
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>${escapeHtml(route.title)}</title>${cssLinksHtml}${viteClientScript}</head><body><div id="root"></div>` + ssrFlagScript + middlewareScript + `<script>window.__NIXO_STATE_STR__=""</script>${entryScript}</body></html>`
|
|
619
|
+
);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
let Template;
|
|
623
|
+
const Layouts = [];
|
|
624
|
+
try {
|
|
625
|
+
const templateSpec = route.contentConfig.workspaceConfig.template;
|
|
626
|
+
const templateValue = await loadModule(templateSpec);
|
|
627
|
+
if (typeof templateValue !== "function") {
|
|
628
|
+
throw new Error(`Template is not a component: ${templateSpec}`);
|
|
629
|
+
}
|
|
630
|
+
Template = templateValue;
|
|
631
|
+
const layoutTypes = routeTree.layoutChains.get(route.id) ?? [];
|
|
632
|
+
for (const type of layoutTypes) {
|
|
633
|
+
const layoutRel = config.layouts[type];
|
|
634
|
+
if (!layoutRel) continue;
|
|
635
|
+
const layoutValue = await loadModule(layoutRel.replace(/^\.\//, ""));
|
|
636
|
+
if (typeof layoutValue === "function") {
|
|
637
|
+
Layouts.push(layoutValue);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
} catch (err) {
|
|
641
|
+
reply.raw.statusCode = 500;
|
|
642
|
+
reply.raw.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
643
|
+
reply.raw.end(
|
|
644
|
+
`<!DOCTYPE html><html><body><pre>SSR error: ${escapeHtml(
|
|
645
|
+
err.message
|
|
646
|
+
)}</pre></body></html>`
|
|
647
|
+
);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const sliceRegistry = new SliceRegistry();
|
|
651
|
+
const router = {
|
|
652
|
+
id: route.id,
|
|
653
|
+
path: url,
|
|
654
|
+
params,
|
|
655
|
+
query,
|
|
656
|
+
request: req
|
|
657
|
+
};
|
|
658
|
+
let serializedStateJson = "";
|
|
659
|
+
try {
|
|
660
|
+
const globalStates = await runGlobalPrefetch({
|
|
661
|
+
route,
|
|
662
|
+
globalRegistry,
|
|
663
|
+
loadModule,
|
|
664
|
+
registry: sliceRegistry,
|
|
665
|
+
router
|
|
666
|
+
});
|
|
667
|
+
const mwResult = await runServerMiddleware(
|
|
668
|
+
globalMiddleware,
|
|
669
|
+
route,
|
|
670
|
+
{ router, route, globalSlices: sliceRegistry },
|
|
671
|
+
projectDir
|
|
672
|
+
);
|
|
673
|
+
if (mwResult && "redirect" in mwResult) {
|
|
674
|
+
reply.raw.statusCode = 302;
|
|
675
|
+
reply.raw.setHeader("Location", mwResult.redirect);
|
|
676
|
+
reply.raw.end();
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const prefetchResult = await runPagePrefetch(
|
|
680
|
+
{
|
|
681
|
+
route,
|
|
682
|
+
globalRegistry,
|
|
683
|
+
loadModule,
|
|
684
|
+
registry: sliceRegistry,
|
|
685
|
+
router
|
|
686
|
+
},
|
|
687
|
+
globalStates
|
|
688
|
+
);
|
|
689
|
+
const serialized = await serializeState(
|
|
690
|
+
prefetchResult.slices,
|
|
691
|
+
projectDir,
|
|
692
|
+
clientDir
|
|
693
|
+
);
|
|
694
|
+
serializedStateJson = stringifyState(serialized);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
reply.raw.statusCode = 500;
|
|
697
|
+
reply.raw.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
698
|
+
reply.raw.end(
|
|
699
|
+
`<!DOCTYPE html><html><body><pre>Prefetch error: ${escapeHtml(
|
|
700
|
+
err.message
|
|
701
|
+
)}</pre></body></html>`
|
|
702
|
+
);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (process.env.NIXO_DEBUG) {
|
|
706
|
+
console.log(
|
|
707
|
+
`[nixo] __NIXO_STATE_STR__ length=${serializedStateJson.length}`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
let ErrorComponent;
|
|
711
|
+
if (config.components.error) {
|
|
712
|
+
try {
|
|
713
|
+
ErrorComponent = await loadModule(
|
|
714
|
+
config.components.error
|
|
715
|
+
);
|
|
716
|
+
} catch (err) {
|
|
717
|
+
console.warn(
|
|
718
|
+
"[nixo] failed to load config.components.error, fallback to default:",
|
|
719
|
+
err
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const child = createElement(Template);
|
|
724
|
+
const tree = createElement(
|
|
725
|
+
NixoErrorBoundary,
|
|
726
|
+
{ fallback: ErrorComponent },
|
|
727
|
+
renderLayoutChain(child, Layouts)
|
|
728
|
+
);
|
|
729
|
+
const prefix = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${escapeHtml(route.title)}</title>${cssLinksHtml}${viteClientScript}</head><body><div id="root">`;
|
|
730
|
+
const suffix = `</div>` + ssrFlagScript + middlewareScript + `<script>window.__NIXO_STATE_STR__=${JSON.stringify(serializedStateJson || "")}</script>${entryScript}</body></html>`;
|
|
731
|
+
const initialRouter = {
|
|
732
|
+
path: url,
|
|
733
|
+
params,
|
|
734
|
+
query,
|
|
735
|
+
routeId: route.id
|
|
736
|
+
};
|
|
737
|
+
let didError = false;
|
|
738
|
+
let shellSent = false;
|
|
739
|
+
const stream = renderToPipeableStream(
|
|
740
|
+
createElement(
|
|
741
|
+
NixoProvider,
|
|
742
|
+
{ sliceRegistry, router: initialRouter },
|
|
743
|
+
tree
|
|
744
|
+
),
|
|
745
|
+
{
|
|
746
|
+
onShellReady() {
|
|
747
|
+
if (shellSent) return;
|
|
748
|
+
shellSent = true;
|
|
749
|
+
reply.raw.statusCode = didError ? 500 : 200;
|
|
750
|
+
reply.raw.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
751
|
+
const inject = new Transform({
|
|
752
|
+
transform(chunk, _enc, cb) {
|
|
753
|
+
cb(null, chunk);
|
|
754
|
+
},
|
|
755
|
+
flush(cb) {
|
|
756
|
+
this.push(suffix);
|
|
757
|
+
cb();
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
reply.raw.write(prefix);
|
|
761
|
+
stream.pipe(inject).pipe(reply.raw);
|
|
762
|
+
},
|
|
763
|
+
onShellError(err) {
|
|
764
|
+
didError = true;
|
|
765
|
+
if (reply.raw.headersSent) {
|
|
766
|
+
reply.raw.end();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
reply.raw.statusCode = 500;
|
|
770
|
+
reply.raw.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
771
|
+
reply.raw.end(
|
|
772
|
+
`<!DOCTYPE html><html><body><pre>Shell error: ${escapeHtml(
|
|
773
|
+
err.message
|
|
774
|
+
)}</pre></body></html>`
|
|
775
|
+
);
|
|
776
|
+
},
|
|
777
|
+
onError(err) {
|
|
778
|
+
didError = true;
|
|
779
|
+
console.error("[nixo] SSR error:", err);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
};
|
|
784
|
+
if (rpcExecutor) {
|
|
785
|
+
const headers = {};
|
|
786
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
787
|
+
if (typeof v === "string") headers[k] = v;
|
|
788
|
+
else if (Array.isArray(v)) headers[k] = v.join(", ");
|
|
789
|
+
}
|
|
790
|
+
const cookies = parseCookieHeader(headers.cookie);
|
|
791
|
+
await runWithRpcContext(
|
|
792
|
+
{
|
|
793
|
+
headers,
|
|
794
|
+
cookies,
|
|
795
|
+
executor: rpcExecutor,
|
|
796
|
+
cache: /* @__PURE__ */ new Map()
|
|
797
|
+
// per-request кэш NixoService
|
|
798
|
+
},
|
|
799
|
+
runHandler
|
|
800
|
+
);
|
|
801
|
+
} else {
|
|
802
|
+
await runHandler();
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function buildCssLinks(ctx) {
|
|
807
|
+
if (ctx.isDev) {
|
|
808
|
+
const hrefs2 = [];
|
|
809
|
+
if (ctx.config.css.main) {
|
|
810
|
+
hrefs2.push(
|
|
811
|
+
resolveVitePath(
|
|
812
|
+
ctx.projectDir,
|
|
813
|
+
path10.resolve(ctx.clientDir, ctx.config.css.main)
|
|
814
|
+
)
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
if (ctx.route) {
|
|
818
|
+
const directFiles = [];
|
|
819
|
+
const templateRel = ctx.route.contentConfig.workspaceConfig.template.split(":")[0];
|
|
820
|
+
directFiles.push(path10.resolve(ctx.clientDir, templateRel));
|
|
821
|
+
const layoutTypes = ctx.config.layouts ? ctx.route.type ? [ctx.route.type] : [] : [];
|
|
822
|
+
for (const t of layoutTypes) {
|
|
823
|
+
const layoutRel = ctx.config.layouts[t];
|
|
824
|
+
if (layoutRel) {
|
|
825
|
+
directFiles.push(
|
|
826
|
+
path10.resolve(ctx.clientDir, layoutRel.replace(/^\.\//, ""))
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
for (const cssPath of collectDirectCssImports(directFiles, ctx.projectDir)) {
|
|
831
|
+
hrefs2.push(cssPath);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return hrefs2.map((h) => `<link rel="stylesheet" href="${h}">`).join("");
|
|
835
|
+
}
|
|
836
|
+
if (!ctx.clientManifest) return "";
|
|
837
|
+
const mf = ctx.clientManifest;
|
|
838
|
+
const hrefs = [];
|
|
839
|
+
const add = (files) => {
|
|
840
|
+
if (!files) return;
|
|
841
|
+
for (const f of files) {
|
|
842
|
+
const href = ctx.clientOutPublicPrefix + f;
|
|
843
|
+
if (!hrefs.includes(href)) hrefs.push(href);
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
for (const entry of Object.values(mf)) {
|
|
847
|
+
if (entry.isEntry) add(entry.css);
|
|
848
|
+
}
|
|
849
|
+
if (ctx.mode === "spa") {
|
|
850
|
+
for (const entry of Object.values(mf)) add(entry.css);
|
|
851
|
+
} else if (ctx.route) {
|
|
852
|
+
const templateRel = ctx.route.contentConfig.workspaceConfig.template.split(":")[0];
|
|
853
|
+
const templateAbs = path10.resolve(ctx.clientDir, templateRel);
|
|
854
|
+
const templateVitePath = resolveVitePath(ctx.projectDir, templateAbs);
|
|
855
|
+
for (const entry of Object.values(mf)) {
|
|
856
|
+
if (entry.src && entry.src.startsWith(templateVitePath)) add(entry.css);
|
|
857
|
+
}
|
|
858
|
+
const layoutRel = ctx.config.layouts[ctx.route.type];
|
|
859
|
+
if (layoutRel) {
|
|
860
|
+
const layoutAbs = path10.resolve(
|
|
861
|
+
ctx.clientDir,
|
|
862
|
+
layoutRel.replace(/^\.\//, "")
|
|
863
|
+
);
|
|
864
|
+
const layoutVitePath = resolveVitePath(ctx.projectDir, layoutAbs);
|
|
865
|
+
for (const entry of Object.values(mf)) {
|
|
866
|
+
if (entry.src && entry.src.startsWith(layoutVitePath)) add(entry.css);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return hrefs.map((href) => `<link rel="stylesheet" href="${href}">`).join("");
|
|
871
|
+
}
|
|
872
|
+
function resolveVitePath(projectDir, absPath) {
|
|
873
|
+
return "/" + path10.relative(projectDir, absPath).replace(/\\/g, "/");
|
|
874
|
+
}
|
|
875
|
+
function collectDirectCssImports(sourceFiles, projectDir) {
|
|
876
|
+
const result = /* @__PURE__ */ new Set();
|
|
877
|
+
const visited = /* @__PURE__ */ new Set();
|
|
878
|
+
const MAX_DEPTH = 30;
|
|
879
|
+
const visit = (base, depth) => {
|
|
880
|
+
if (depth > MAX_DEPTH) return;
|
|
881
|
+
let file = base;
|
|
882
|
+
for (const ext of [".tsx", ".ts", ".jsx", ".js", ""]) {
|
|
883
|
+
if (fs5.existsSync(base + ext)) {
|
|
884
|
+
file = base + ext;
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (visited.has(file)) return;
|
|
889
|
+
visited.add(file);
|
|
890
|
+
let src;
|
|
891
|
+
try {
|
|
892
|
+
src = fs5.readFileSync(file, "utf8");
|
|
893
|
+
} catch {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const cssRegex = /import\s+['"](\.{1,2}\/[^'"]+\.css)['"]/g;
|
|
897
|
+
let m;
|
|
898
|
+
while ((m = cssRegex.exec(src)) !== null) {
|
|
899
|
+
const absCss = path10.resolve(path10.dirname(file), m[1]);
|
|
900
|
+
result.add(resolveVitePath(projectDir, absCss));
|
|
901
|
+
}
|
|
902
|
+
const jsRegex = /import\s+(?:[^'"]*?\s+from\s+)?['"](\.{1,2}\/[^'"]+)['"]/g;
|
|
903
|
+
while ((m = jsRegex.exec(src)) !== null) {
|
|
904
|
+
const rel = m[1];
|
|
905
|
+
if (rel.endsWith(".css")) continue;
|
|
906
|
+
const childBase = path10.resolve(path10.dirname(file), rel);
|
|
907
|
+
visit(childBase, depth + 1);
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
for (const base of sourceFiles) {
|
|
911
|
+
visit(base, 0);
|
|
912
|
+
}
|
|
913
|
+
return [...result];
|
|
914
|
+
}
|
|
915
|
+
function queryToObject(url) {
|
|
916
|
+
const idx = url.indexOf("?");
|
|
917
|
+
if (idx === -1) return {};
|
|
918
|
+
const qs = url.slice(idx + 1);
|
|
919
|
+
const result = {};
|
|
920
|
+
for (const pair of qs.split("&")) {
|
|
921
|
+
if (!pair) continue;
|
|
922
|
+
const [k, v = ""] = pair.split("=");
|
|
923
|
+
result[decodeURIComponent(k)] = decodeURIComponent(v);
|
|
924
|
+
}
|
|
925
|
+
return result;
|
|
926
|
+
}
|
|
927
|
+
function escapeHtml(s) {
|
|
928
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
929
|
+
}
|
|
930
|
+
var init_ssr_handler = __esm({
|
|
931
|
+
"src/server/ssr-handler.ts"() {
|
|
932
|
+
init_prefetch2();
|
|
933
|
+
init_serialize();
|
|
934
|
+
init_run_server_middleware();
|
|
935
|
+
init_als();
|
|
936
|
+
init_url();
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
function createDevModuleLoader(clientDir, vite) {
|
|
940
|
+
return async (spec) => {
|
|
941
|
+
const [file, exportName] = spec.split(":");
|
|
942
|
+
const abs = path10.resolve(clientDir, file);
|
|
943
|
+
const mod = await vite.ssrLoadModule(abs);
|
|
944
|
+
const value = exportName ? mod[exportName] : mod.default;
|
|
945
|
+
if (value === void 0) {
|
|
946
|
+
throw new NixoError(
|
|
947
|
+
`Module "${spec}" resolved to undefined. ` + (exportName ? `Export "${exportName}" not found in "${abs}"` : `No default export in "${abs}"`)
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return value;
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function createProdModuleLoader(serverEntryPath) {
|
|
954
|
+
let cached = null;
|
|
955
|
+
const ensureLoaded = () => {
|
|
956
|
+
if (!cached) {
|
|
957
|
+
cached = import(pathToFileURL(serverEntryPath).href).then(
|
|
958
|
+
(mod) => mod.modules
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
return cached;
|
|
962
|
+
};
|
|
963
|
+
return async (spec) => {
|
|
964
|
+
const modules = await ensureLoaded();
|
|
965
|
+
const [file, exportName] = spec.split(":");
|
|
966
|
+
const ns = modules[file];
|
|
967
|
+
if (!ns) {
|
|
968
|
+
throw new NixoError(
|
|
969
|
+
`Module "${file}" not found in SSR bundle "${serverEntryPath}". Available: ${Object.keys(modules).join(", ")}`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
const value = exportName ? ns[exportName] : ns.default;
|
|
973
|
+
if (value === void 0) {
|
|
974
|
+
throw new NixoError(
|
|
975
|
+
`Export "${exportName ?? "default"}" not found in module "${file}"`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
return value;
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
var init_module_loader = __esm({
|
|
982
|
+
"src/server/module-loader.ts"() {
|
|
983
|
+
init_errors();
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
function nixoPlugin(options) {
|
|
987
|
+
const {
|
|
988
|
+
projectDir,
|
|
989
|
+
clientDir,
|
|
990
|
+
config,
|
|
991
|
+
routeTree,
|
|
992
|
+
factoryModules = [],
|
|
993
|
+
globalFactories = {},
|
|
994
|
+
cssMain
|
|
995
|
+
} = options;
|
|
996
|
+
function resolveSpec(spec) {
|
|
997
|
+
const [rawFile] = spec.split(":");
|
|
998
|
+
const file = rawFile.replace(/^\.\//, "");
|
|
999
|
+
const abs = path10.resolve(clientDir, file);
|
|
1000
|
+
let finalAbs = abs;
|
|
1001
|
+
for (const ext of [".tsx", ".ts", ".jsx", ".js", ""]) {
|
|
1002
|
+
if (fs5.existsSync(abs + ext)) {
|
|
1003
|
+
finalAbs = abs + ext;
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
return "/" + path10.relative(projectDir, finalAbs).replace(/\\/g, "/");
|
|
1008
|
+
}
|
|
1009
|
+
function resolveMiddlewarePath(handler) {
|
|
1010
|
+
const [rawFile] = handler.split(":");
|
|
1011
|
+
const file = rawFile.replace(/^\.\//, "");
|
|
1012
|
+
const abs = path10.resolve(projectDir, file);
|
|
1013
|
+
let finalAbs = abs;
|
|
1014
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ""]) {
|
|
1015
|
+
if (fs5.existsSync(abs + ext)) {
|
|
1016
|
+
finalAbs = abs + ext;
|
|
1017
|
+
break;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return "/" + path10.relative(projectDir, finalAbs).replace(/\\/g, "/");
|
|
1021
|
+
}
|
|
1022
|
+
function isServerOnly(def) {
|
|
1023
|
+
if (def.side === "server") return true;
|
|
1024
|
+
if (def.side === "client" || def.side === "both") return false;
|
|
1025
|
+
return def.handler.startsWith("server/");
|
|
1026
|
+
}
|
|
1027
|
+
const routesData = Array.from(routeTree.byId.entries()).map(([id, route]) => {
|
|
1028
|
+
const urlPath = routeTree.paths.get(id);
|
|
1029
|
+
const templatePath = resolveSpec(route.contentConfig.workspaceConfig.template);
|
|
1030
|
+
const templateExport = route.contentConfig.workspaceConfig.template.split(":")[1] ?? "default";
|
|
1031
|
+
return {
|
|
1032
|
+
id,
|
|
1033
|
+
path: urlPath,
|
|
1034
|
+
title: route.title,
|
|
1035
|
+
description: route.description ?? null,
|
|
1036
|
+
templatePath,
|
|
1037
|
+
templateExport,
|
|
1038
|
+
middleware: route.middleware ?? [],
|
|
1039
|
+
meta: route.meta ?? {},
|
|
1040
|
+
contentConfig: route.contentConfig,
|
|
1041
|
+
layoutChain: routeTree.layoutChains.get(id) ?? []
|
|
1042
|
+
};
|
|
1043
|
+
});
|
|
1044
|
+
const layoutPaths = {};
|
|
1045
|
+
for (const [type, rel] of Object.entries(config.layouts)) {
|
|
1046
|
+
layoutPaths[type] = {
|
|
1047
|
+
path: resolveSpec(rel),
|
|
1048
|
+
export: rel.split(":")[1] ?? "default"
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const specToPath = /* @__PURE__ */ new Map();
|
|
1052
|
+
const addSpec = (spec) => {
|
|
1053
|
+
const file = spec.split(":")[0].replace(/^\.\//, "");
|
|
1054
|
+
if (specToPath.has(file)) return;
|
|
1055
|
+
specToPath.set(file, resolveSpec(spec));
|
|
1056
|
+
};
|
|
1057
|
+
for (const route of routeTree.byId.values()) {
|
|
1058
|
+
addSpec(route.contentConfig.workspaceConfig.template);
|
|
1059
|
+
const layoutRel = config.layouts[route.type];
|
|
1060
|
+
if (layoutRel) addSpec(layoutRel);
|
|
1061
|
+
addSpec(route.contentConfig.prefetchConfig.configLoader);
|
|
1062
|
+
}
|
|
1063
|
+
for (const spec of factoryModules) addSpec(spec);
|
|
1064
|
+
for (const def of Object.values(globalFactories)) {
|
|
1065
|
+
addSpec(def.dataFactoryName);
|
|
1066
|
+
}
|
|
1067
|
+
for (const key of ["error", "loading", "notFound"]) {
|
|
1068
|
+
const spec = config.components[key];
|
|
1069
|
+
if (spec) addSpec(spec);
|
|
1070
|
+
}
|
|
1071
|
+
const allSsrSpecs = /* @__PURE__ */ new Set();
|
|
1072
|
+
for (const route of routeTree.byId.values()) {
|
|
1073
|
+
allSsrSpecs.add(route.contentConfig.workspaceConfig.template);
|
|
1074
|
+
const layoutRel = config.layouts[route.type];
|
|
1075
|
+
if (layoutRel) allSsrSpecs.add(layoutRel);
|
|
1076
|
+
allSsrSpecs.add(route.contentConfig.prefetchConfig.configLoader);
|
|
1077
|
+
}
|
|
1078
|
+
for (const spec of factoryModules) allSsrSpecs.add(spec);
|
|
1079
|
+
for (const def of Object.values(globalFactories)) {
|
|
1080
|
+
allSsrSpecs.add(def.dataFactoryName);
|
|
1081
|
+
}
|
|
1082
|
+
for (const key of ["error", "loading", "notFound"]) {
|
|
1083
|
+
const spec = config.components[key];
|
|
1084
|
+
if (spec) allSsrSpecs.add(spec);
|
|
1085
|
+
}
|
|
1086
|
+
const ssrModulePaths = /* @__PURE__ */ new Map();
|
|
1087
|
+
for (const spec of allSsrSpecs) {
|
|
1088
|
+
ssrModulePaths.set(spec, resolveSpec(spec));
|
|
1089
|
+
}
|
|
1090
|
+
const middlewareHandlers = /* @__PURE__ */ new Set();
|
|
1091
|
+
for (const mw of config.middleware ?? []) {
|
|
1092
|
+
if (isServerOnly(mw)) continue;
|
|
1093
|
+
middlewareHandlers.add(mw.handler.split(":")[0]);
|
|
1094
|
+
}
|
|
1095
|
+
for (const route of routeTree.byId.values()) {
|
|
1096
|
+
for (const mw of route.middleware ?? []) {
|
|
1097
|
+
if (isServerOnly(mw)) continue;
|
|
1098
|
+
middlewareHandlers.add(mw.handler.split(":")[0]);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
const middlewarePaths = /* @__PURE__ */ new Map();
|
|
1102
|
+
for (const handler of middlewareHandlers) {
|
|
1103
|
+
middlewarePaths.set(handler, resolveMiddlewarePath(handler));
|
|
1104
|
+
}
|
|
1105
|
+
const componentsMeta = {};
|
|
1106
|
+
for (const key of ["error", "loading", "notFound"]) {
|
|
1107
|
+
const spec = config.components[key];
|
|
1108
|
+
if (spec) {
|
|
1109
|
+
const [, exportName = "default"] = spec.split(":");
|
|
1110
|
+
componentsMeta[key] = {
|
|
1111
|
+
path: resolveSpec(spec),
|
|
1112
|
+
export: exportName
|
|
1113
|
+
};
|
|
1114
|
+
} else {
|
|
1115
|
+
componentsMeta[key] = null;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
name: "nixo",
|
|
1120
|
+
resolveId(id) {
|
|
1121
|
+
if (id === VIRTUAL_ROUTES) return RESOLVED_ROUTES;
|
|
1122
|
+
if (id === VIRTUAL_ENTRY) return RESOLVED_ENTRY;
|
|
1123
|
+
if (id === VIRTUAL_SSR_ENTRY) return RESOLVED_SSR_ENTRY;
|
|
1124
|
+
return null;
|
|
1125
|
+
},
|
|
1126
|
+
async load(id) {
|
|
1127
|
+
if (id === RESOLVED_ROUTES) {
|
|
1128
|
+
return generateRoutesModule(
|
|
1129
|
+
routesData,
|
|
1130
|
+
routeTree.compiled,
|
|
1131
|
+
layoutPaths,
|
|
1132
|
+
specToPath,
|
|
1133
|
+
middlewarePaths,
|
|
1134
|
+
globalFactories,
|
|
1135
|
+
componentsMeta
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
if (id === RESOLVED_ENTRY) return generateEntryModule(cssMain);
|
|
1139
|
+
if (id === RESOLVED_SSR_ENTRY) {
|
|
1140
|
+
return generateSsrEntryModule(ssrModulePaths);
|
|
1141
|
+
}
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function generateRoutesModule(routesData, compiledRoutes, layoutPaths, specToPath, middlewarePaths, globalFactories, componentsMeta) {
|
|
1147
|
+
const uniquePaths = /* @__PURE__ */ new Set();
|
|
1148
|
+
for (const p of specToPath.values()) uniquePaths.add(p);
|
|
1149
|
+
for (const p of middlewarePaths.values()) uniquePaths.add(p);
|
|
1150
|
+
const moduleLoaders = Array.from(uniquePaths).map((p) => ` ${JSON.stringify(p)}: () => import(${JSON.stringify(p)})`).join(",\n");
|
|
1151
|
+
const specToPathObj = {};
|
|
1152
|
+
for (const [spec, p] of specToPath) specToPathObj[spec] = p;
|
|
1153
|
+
const middlewareLoaders = Array.from(middlewarePaths).map(([h, p]) => ` ${JSON.stringify(h)}: () => import(${JSON.stringify(p)})`).join(",\n");
|
|
1154
|
+
return `
|
|
1155
|
+
const moduleLoaders = {
|
|
1156
|
+
${moduleLoaders}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const specToPath = ${JSON.stringify(specToPathObj, null, 2)}
|
|
1160
|
+
|
|
1161
|
+
const middlewareLoaders = {
|
|
1162
|
+
${middlewareLoaders}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const layoutPaths = ${JSON.stringify(layoutPaths, null, 2)}
|
|
1166
|
+
|
|
1167
|
+
export const components = ${JSON.stringify(componentsMeta, null, 2)}
|
|
1168
|
+
|
|
1169
|
+
export const routes = ${JSON.stringify(routesData, null, 2)}
|
|
1170
|
+
|
|
1171
|
+
export const globalFactories = ${JSON.stringify(globalFactories, null, 2)}
|
|
1172
|
+
|
|
1173
|
+
export const compiled = ${JSON.stringify(compiledRoutes, null, 2)}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* \u041D\u0430\u0445\u043E\u0434\u0438\u0442 \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u043F\u043E URL \u0441 \u043C\u0430\u0442\u0447\u0438\u043D\u0433\u043E\u043C \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432.
|
|
1177
|
+
*
|
|
1178
|
+
* @param {string} url \u043F\u0443\u0442\u044C.
|
|
1179
|
+
* @returns {{routeId: string, params: object} | null}
|
|
1180
|
+
*/
|
|
1181
|
+
export function matchRoute(url) {
|
|
1182
|
+
for (const r of compiled) {
|
|
1183
|
+
const m = new RegExp(r.regexSource).exec(url)
|
|
1184
|
+
if (m) {
|
|
1185
|
+
const params = {}
|
|
1186
|
+
for (let i = 0; i < r.paramNames.length; i++) {
|
|
1187
|
+
params[r.paramNames[i]] = decodeURIComponent(m[i + 1])
|
|
1188
|
+
}
|
|
1189
|
+
return { routeId: r.id, params }
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return null
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* \u041D\u0430\u0445\u043E\u0434\u0438\u0442 \u0437\u0430\u043F\u0438\u0441\u044C \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u0430 \u043F\u043E id.
|
|
1197
|
+
*
|
|
1198
|
+
* @param {string} id id.
|
|
1199
|
+
* @returns {object | null}
|
|
1200
|
+
*/
|
|
1201
|
+
export function findRouteById(id) {
|
|
1202
|
+
for (const r of routes) {
|
|
1203
|
+
if (r.id === id) return r
|
|
1204
|
+
}
|
|
1205
|
+
return null
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export function findRouteByPath(path) {
|
|
1209
|
+
for (const r of routes) {
|
|
1210
|
+
if (r.path === path) return r
|
|
1211
|
+
}
|
|
1212
|
+
return null
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
export async function loadRouteModules(route) {
|
|
1216
|
+
const templateMod = await moduleLoaders[route.templatePath]()
|
|
1217
|
+
const Template = templateMod[route.templateExport]
|
|
1218
|
+
if (!Template) {
|
|
1219
|
+
throw new Error('Export "' + route.templateExport + '" not found in ' + route.templatePath)
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const Layouts = []
|
|
1223
|
+
for (const type of (route.layoutChain || [])) {
|
|
1224
|
+
const info = layoutPaths[type]
|
|
1225
|
+
if (!info) continue
|
|
1226
|
+
const layoutMod = await moduleLoaders[info.path]()
|
|
1227
|
+
const Layout = layoutMod[info.export]
|
|
1228
|
+
if (typeof Layout === 'function') Layouts.push(Layout)
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
return { Template, Layouts }
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* \u0420\u0435\u0437\u043E\u043B\u0432\u0438\u0442 \u0441\u043F\u0435\u043A\u0443 \u0432 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 (default \u0438\u043B\u0438 \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u044D\u043A\u0441\u043F\u043E\u0440\u0442).
|
|
1236
|
+
*
|
|
1237
|
+
* @param {string} spec \u0441\u043F\u0435\u043A\u0430.
|
|
1238
|
+
* @returns {Promise<unknown>}
|
|
1239
|
+
*/
|
|
1240
|
+
export async function resolveModule(spec) {
|
|
1241
|
+
const [file, exportName = 'default'] = spec.split(':')
|
|
1242
|
+
const p = specToPath[file]
|
|
1243
|
+
if (!p) {
|
|
1244
|
+
throw new Error('Module not found in client bundle: ' + file)
|
|
1245
|
+
}
|
|
1246
|
+
const mod = await moduleLoaders[p]()
|
|
1247
|
+
const value = mod[exportName]
|
|
1248
|
+
if (value === undefined) {
|
|
1249
|
+
throw new Error('Export "' + exportName + '" not found in ' + p)
|
|
1250
|
+
}
|
|
1251
|
+
return value
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* \u0420\u0435\u0437\u043E\u043B\u0432\u0438\u0442 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442 \u043F\u043E componentsMeta.
|
|
1256
|
+
*
|
|
1257
|
+
* @param {{path: string, export: string} | null} info \u043C\u0435\u0442\u0430\u0434\u0430\u043D\u043D\u044B\u0435.
|
|
1258
|
+
* @returns {Promise<Function | null>}
|
|
1259
|
+
*/
|
|
1260
|
+
export async function resolveComponent(info) {
|
|
1261
|
+
if (!info) return null
|
|
1262
|
+
const loader = moduleLoaders[info.path]
|
|
1263
|
+
if (!loader) throw new Error('Component not in bundle: ' + info.path)
|
|
1264
|
+
const mod = await loader()
|
|
1265
|
+
return mod[info.export] || null
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043F\u0440\u0438\u043A\u043B\u0430\u0434\u043D\u043E\u0439 \u043A\u043B\u0430\u0441\u0441 \u0441\u043B\u0430\u0439\u0441\u0430.
|
|
1270
|
+
*
|
|
1271
|
+
* @param {string} modulePath Vite-\u043F\u0443\u0442\u044C.
|
|
1272
|
+
* @param {string} exportName \u0438\u043C\u044F \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0430.
|
|
1273
|
+
* @returns {Promise<new () => any>}
|
|
1274
|
+
*/
|
|
1275
|
+
export async function loadSliceClass(modulePath, exportName) {
|
|
1276
|
+
const loader = moduleLoaders[modulePath]
|
|
1277
|
+
if (!loader) {
|
|
1278
|
+
throw new Error('Module not in client bundle: ' + modulePath)
|
|
1279
|
+
}
|
|
1280
|
+
const mod = await loader()
|
|
1281
|
+
const impl = mod[exportName]
|
|
1282
|
+
if (!impl || typeof impl.slice !== 'function') {
|
|
1283
|
+
throw new Error('Invalid factory impl at ' + modulePath + ':' + exportName)
|
|
1284
|
+
}
|
|
1285
|
+
return impl.slice
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* \u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043C\u043E\u0434\u0443\u043B\u044C middleware.
|
|
1290
|
+
*
|
|
1291
|
+
* @param {string} handler \u043A\u043B\u044E\u0447.
|
|
1292
|
+
* @returns {Promise<Function>}
|
|
1293
|
+
*/
|
|
1294
|
+
export async function loadMiddlewareModule(handler) {
|
|
1295
|
+
const loader = middlewareLoaders[handler]
|
|
1296
|
+
if (!loader) {
|
|
1297
|
+
throw new Error('Middleware not in client bundle: ' + handler)
|
|
1298
|
+
}
|
|
1299
|
+
const mod = await loader()
|
|
1300
|
+
const fn = mod.client || mod.default
|
|
1301
|
+
if (typeof fn !== 'function') {
|
|
1302
|
+
throw new Error('Middleware "' + handler + '" is not a function')
|
|
1303
|
+
}
|
|
1304
|
+
return fn
|
|
1305
|
+
}
|
|
1306
|
+
`;
|
|
1307
|
+
}
|
|
1308
|
+
function generateEntryModule(cssMain) {
|
|
1309
|
+
const cssImport = cssMain ? `import '${cssMain}'` : "";
|
|
1310
|
+
return `
|
|
1311
|
+
${cssImport}
|
|
1312
|
+
import { createElement } from 'react'
|
|
1313
|
+
import { createRoot, hydrateRoot } from 'react-dom/client'
|
|
1314
|
+
import {
|
|
1315
|
+
NixoProvider,
|
|
1316
|
+
NixoErrorBoundary,
|
|
1317
|
+
NixoLoading,
|
|
1318
|
+
NixoNotFound,
|
|
1319
|
+
Slice,
|
|
1320
|
+
SliceRegistry,
|
|
1321
|
+
renderLayoutChain,
|
|
1322
|
+
runGlobalPrefetch,
|
|
1323
|
+
runPagePrefetch,
|
|
1324
|
+
devalueParse,
|
|
1325
|
+
} from 'setti/client'
|
|
1326
|
+
import {
|
|
1327
|
+
findRouteById,
|
|
1328
|
+
matchRoute,
|
|
1329
|
+
loadRouteModules,
|
|
1330
|
+
loadSliceClass,
|
|
1331
|
+
loadMiddlewareModule,
|
|
1332
|
+
resolveModule,
|
|
1333
|
+
resolveComponent,
|
|
1334
|
+
globalFactories,
|
|
1335
|
+
components,
|
|
1336
|
+
} from '${VIRTUAL_ROUTES}'
|
|
1337
|
+
|
|
1338
|
+
async function hydrateSlicesFromState(stateObj, registry) {
|
|
1339
|
+
if (!stateObj || !stateObj.slices) return
|
|
1340
|
+
const entries = Object.entries(stateObj.slices)
|
|
1341
|
+
await Promise.all(entries.map(async ([name, info]) => {
|
|
1342
|
+
let slice
|
|
1343
|
+
try {
|
|
1344
|
+
const SliceClass = await loadSliceClass(info.modulePath, info.exportName)
|
|
1345
|
+
slice = new SliceClass()
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
console.warn('[nixo] slice class not loaded for "' + name + '":', err)
|
|
1348
|
+
slice = new Slice()
|
|
1349
|
+
}
|
|
1350
|
+
slice.hydrate(info.state)
|
|
1351
|
+
if (info.scope === 'global') registry.setGlobal(name, slice)
|
|
1352
|
+
else registry.setPage(name, slice)
|
|
1353
|
+
}))
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
/**
|
|
1357
|
+
* \u0421\u0442\u0440\u043E\u0438\u0442 NavTarget: pathname \u0434\u043B\u044F \u043C\u0430\u0442\u0447\u0438\u043D\u0433\u0430, query \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E.
|
|
1358
|
+
*
|
|
1359
|
+
* @param {string} path \u043F\u043E\u043B\u043D\u044B\u0439 \u043F\u0443\u0442\u044C (\u0441 query, \u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C).
|
|
1360
|
+
* @returns {object} NavTarget.
|
|
1361
|
+
*/
|
|
1362
|
+
function buildNavTarget(path) {
|
|
1363
|
+
const url = new URL(path, window.location.origin)
|
|
1364
|
+
const matched = matchRoute(url.pathname)
|
|
1365
|
+
const route = matched ? findRouteById(matched.routeId) : null
|
|
1366
|
+
const params = matched ? matched.params : {}
|
|
1367
|
+
const query = {}
|
|
1368
|
+
url.searchParams.forEach((v, k) => { query[k] = v })
|
|
1369
|
+
return { path, pathname: url.pathname, params, query, route }
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
async function runClientMiddlewareChain(chain, to, from) {
|
|
1373
|
+
for (const def of chain) {
|
|
1374
|
+
const fn = await loadMiddlewareModule(def.handler)
|
|
1375
|
+
const result = await fn(def.params || {}, { to, from })
|
|
1376
|
+
if (result && typeof result === 'object' && 'redirect' in result) {
|
|
1377
|
+
return result
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return undefined
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const globalClientMiddleware = (window.__NIXO_MIDDLEWARE__ || []).filter(
|
|
1384
|
+
(m) => !m.side || m.side === 'client' || m.side === 'both',
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
function isServerOnly(def) {
|
|
1388
|
+
if (def.side === 'server') return true
|
|
1389
|
+
if (def.side === 'client' || def.side === 'both') return false
|
|
1390
|
+
return def.handler.startsWith('server/')
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function fullMiddlewareChain(route) {
|
|
1394
|
+
return [
|
|
1395
|
+
...globalClientMiddleware,
|
|
1396
|
+
...((route && route.middleware) || []),
|
|
1397
|
+
].filter((def) => !isServerOnly(def))
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* \u041A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0439 prefetch: \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442 loadData \u0447\u0435\u0440\u0435\u0437 NixoService.
|
|
1402
|
+
*
|
|
1403
|
+
* @param {object} route \u043C\u0430\u0440\u0448\u0440\u0443\u0442.
|
|
1404
|
+
* @param {SliceRegistry} registry \u0440\u0435\u0435\u0441\u0442\u0440 \u0441\u043B\u0430\u0439\u0441\u043E\u0432.
|
|
1405
|
+
* @param {object} navTarget \u043D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0439 target.
|
|
1406
|
+
* @param {boolean} skipExisting \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043D\u044B\u0435 \u0433\u043B\u043E\u0431\u0430\u043B\u044C\u043D\u044B\u0435.
|
|
1407
|
+
*/
|
|
1408
|
+
async function clientPrefetch(route, registry, navTarget, skipExisting) {
|
|
1409
|
+
const ctx = {
|
|
1410
|
+
route,
|
|
1411
|
+
globalRegistry: globalFactories,
|
|
1412
|
+
loadModule: resolveModule,
|
|
1413
|
+
registry,
|
|
1414
|
+
router: {
|
|
1415
|
+
id: route.id,
|
|
1416
|
+
path: navTarget.pathname,
|
|
1417
|
+
params: navTarget.params,
|
|
1418
|
+
query: navTarget.query,
|
|
1419
|
+
request: null,
|
|
1420
|
+
},
|
|
1421
|
+
}
|
|
1422
|
+
const globalStates = await runGlobalPrefetch(ctx, { skipExisting })
|
|
1423
|
+
await runPagePrefetch(ctx, globalStates)
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
async function main() {
|
|
1427
|
+
const rootEl = document.getElementById('root')
|
|
1428
|
+
if (!rootEl) throw new Error('nixo: #root element not found')
|
|
1429
|
+
|
|
1430
|
+
const LoadingComp = (await resolveComponent(components.loading)) || NixoLoading
|
|
1431
|
+
const ErrorComp = (await resolveComponent(components.error)) || undefined
|
|
1432
|
+
|
|
1433
|
+
const initialPath = window.location.pathname
|
|
1434
|
+
const initialMatch = matchRoute(initialPath)
|
|
1435
|
+
const initialRoute = initialMatch ? findRouteById(initialMatch.routeId) : null
|
|
1436
|
+
|
|
1437
|
+
// 404: SSR \u0443\u0436\u0435 \u043E\u0442\u0440\u0435\u043D\u0434\u0435\u0440\u0438\u043B \u043A\u0430\u0441\u0442\u043E\u043C\u043D\u0443\u044E NotFound \u0447\u0435\u0440\u0435\u0437 renderToString.
|
|
1438
|
+
if (!initialRoute) {
|
|
1439
|
+
const NotFoundComp =
|
|
1440
|
+
(await resolveComponent(components.notFound)) || NixoNotFound
|
|
1441
|
+
const notFoundRouter = {
|
|
1442
|
+
path: initialPath,
|
|
1443
|
+
params: {},
|
|
1444
|
+
query: Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
1445
|
+
routeId: '__not-found__',
|
|
1446
|
+
}
|
|
1447
|
+
const nfRegistry = new SliceRegistry()
|
|
1448
|
+
const nfTree = createElement(
|
|
1449
|
+
NixoProvider,
|
|
1450
|
+
{ sliceRegistry: nfRegistry, router: notFoundRouter },
|
|
1451
|
+
createElement(NotFoundComp),
|
|
1452
|
+
)
|
|
1453
|
+
if (window.__NIXO_SSR__ === true) {
|
|
1454
|
+
hydrateRoot(rootEl, nfTree)
|
|
1455
|
+
} else {
|
|
1456
|
+
createRoot(rootEl).render(nfTree)
|
|
1457
|
+
}
|
|
1458
|
+
return
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
const initialParams = initialMatch ? initialMatch.params : {}
|
|
1462
|
+
|
|
1463
|
+
const sliceRegistry = new SliceRegistry()
|
|
1464
|
+
|
|
1465
|
+
let currentRouter = {
|
|
1466
|
+
path: initialPath,
|
|
1467
|
+
params: initialParams,
|
|
1468
|
+
query: Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
1469
|
+
routeId: initialRoute.id,
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
let rootRef
|
|
1473
|
+
|
|
1474
|
+
const renderTree = async (route) => {
|
|
1475
|
+
const { Template, Layouts } = await loadRouteModules(route)
|
|
1476
|
+
const child = createElement(Template)
|
|
1477
|
+
return createElement(
|
|
1478
|
+
NixoErrorBoundary,
|
|
1479
|
+
{ fallback: ErrorComp },
|
|
1480
|
+
renderLayoutChain(child, Layouts),
|
|
1481
|
+
)
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
const navigate = async (path, mode) => {
|
|
1485
|
+
const to = buildNavTarget(path)
|
|
1486
|
+
if (!to.route) {
|
|
1487
|
+
window.location[mode === 'replace' ? 'replace' : 'assign'](path)
|
|
1488
|
+
return
|
|
1489
|
+
}
|
|
1490
|
+
const from = buildNavTarget(window.location.pathname)
|
|
1491
|
+
|
|
1492
|
+
try {
|
|
1493
|
+
const redirect = await runClientMiddlewareChain(
|
|
1494
|
+
fullMiddlewareChain(to.route), to, from,
|
|
1495
|
+
)
|
|
1496
|
+
if (redirect && redirect.redirect) {
|
|
1497
|
+
await navigate(redirect.redirect, 'replace')
|
|
1498
|
+
return
|
|
1499
|
+
}
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
console.error('[nixo] client middleware failed:', err)
|
|
1502
|
+
return
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
try {
|
|
1506
|
+
sliceRegistry.disposePage()
|
|
1507
|
+
await clientPrefetch(to.route, sliceRegistry, to, true)
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
console.error('[nixo] client prefetch failed:', err)
|
|
1510
|
+
return
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
const newTree = await renderTree(to.route)
|
|
1514
|
+
|
|
1515
|
+
if (mode === 'replace') {
|
|
1516
|
+
window.history.replaceState({}, '', path)
|
|
1517
|
+
} else {
|
|
1518
|
+
window.history.pushState({}, '', path)
|
|
1519
|
+
}
|
|
1520
|
+
document.title = to.route.title || 'nixo'
|
|
1521
|
+
const newRouter = {
|
|
1522
|
+
path: to.pathname,
|
|
1523
|
+
params: to.params,
|
|
1524
|
+
query: to.query,
|
|
1525
|
+
routeId: to.route.id,
|
|
1526
|
+
}
|
|
1527
|
+
currentRouter = newRouter
|
|
1528
|
+
|
|
1529
|
+
rootRef.render(
|
|
1530
|
+
createElement(NixoProvider, { sliceRegistry, router: newRouter }, newTree),
|
|
1531
|
+
)
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
window.__nixoNavigate = {
|
|
1535
|
+
push: (p) => navigate(p, 'push'),
|
|
1536
|
+
replace: (p) => navigate(p, 'replace'),
|
|
1537
|
+
back: () => window.history.back(),
|
|
1538
|
+
prefetch: async (p) => {
|
|
1539
|
+
const to = buildNavTarget(p)
|
|
1540
|
+
if (!to.route) return
|
|
1541
|
+
try {
|
|
1542
|
+
// \u0412\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0439 registry \u2014 \u0440\u0435\u0430\u043B\u044C\u043D\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u0438\u0434\u0451\u0442 \u0432 NixoService cache.
|
|
1543
|
+
// \u041F\u0440\u0438 \u043A\u043B\u0438\u043A\u0435 clientPrefetch \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0441\u044F \u0441\u043D\u043E\u0432\u0430, \u043D\u043E service-\u0432\u044B\u0437\u043E\u0432\u044B
|
|
1544
|
+
// \u0432\u043E\u0437\u044C\u043C\u0443\u0442\u0441\u044F \u0438\u0437 \u043A\u044D\u0448\u0430 \u2192 \u043C\u0433\u043D\u043E\u0432\u0435\u043D\u043D\u043E.
|
|
1545
|
+
const tempRegistry = new SliceRegistry()
|
|
1546
|
+
await clientPrefetch(to.route, tempRegistry, to, false)
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
// Prefetch \u2014 best-effort. \u041E\u0448\u0438\u0431\u043A\u0438 \u043D\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0442 \u043A\u043B\u0438\u043A.
|
|
1549
|
+
console.debug('[nixo] prefetch skipped:', err)
|
|
1550
|
+
}
|
|
1551
|
+
},
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
window.addEventListener('popstate', () => {
|
|
1555
|
+
void navigate(window.location.pathname, 'replace')
|
|
1556
|
+
})
|
|
1557
|
+
|
|
1558
|
+
const initialRouter = currentRouter
|
|
1559
|
+
const initialTree = await renderTree(initialRoute)
|
|
1560
|
+
|
|
1561
|
+
if (window.__NIXO_SSR__ === true) {
|
|
1562
|
+
let stateObj
|
|
1563
|
+
try {
|
|
1564
|
+
stateObj = window.__NIXO_STATE_STR__
|
|
1565
|
+
? devalueParse(window.__NIXO_STATE_STR__)
|
|
1566
|
+
: undefined
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
console.warn('[nixo] failed to parse __NIXO_STATE_STR__:', err)
|
|
1569
|
+
stateObj = undefined
|
|
1570
|
+
}
|
|
1571
|
+
await hydrateSlicesFromState(stateObj, sliceRegistry)
|
|
1572
|
+
rootRef = hydrateRoot(
|
|
1573
|
+
rootEl,
|
|
1574
|
+
createElement(NixoProvider, { sliceRegistry, router: initialRouter }, initialTree),
|
|
1575
|
+
)
|
|
1576
|
+
} else {
|
|
1577
|
+
// \u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u043C Loading, \u043F\u043E\u043A\u0430 \u0438\u0434\u0451\u0442 prefetch.
|
|
1578
|
+
rootRef = createRoot(rootEl)
|
|
1579
|
+
rootRef.render(
|
|
1580
|
+
createElement(
|
|
1581
|
+
NixoProvider,
|
|
1582
|
+
{ sliceRegistry, router: initialRouter },
|
|
1583
|
+
createElement(LoadingComp),
|
|
1584
|
+
),
|
|
1585
|
+
)
|
|
1586
|
+
|
|
1587
|
+
try {
|
|
1588
|
+
await clientPrefetch(initialRoute, sliceRegistry, {
|
|
1589
|
+
path: initialPath, pathname: initialPath, params: initialParams, query: initialRouter.query,
|
|
1590
|
+
}, false)
|
|
1591
|
+
document.title = initialRoute.title || 'nixo'
|
|
1592
|
+
rootRef.render(
|
|
1593
|
+
createElement(
|
|
1594
|
+
NixoProvider,
|
|
1595
|
+
{ sliceRegistry, router: initialRouter },
|
|
1596
|
+
initialTree,
|
|
1597
|
+
),
|
|
1598
|
+
)
|
|
1599
|
+
} catch (err) {
|
|
1600
|
+
console.error('[nixo] initial prefetch failed:', err)
|
|
1601
|
+
const errorNode = ErrorComp
|
|
1602
|
+
? createElement(ErrorComp, { error: err })
|
|
1603
|
+
: createElement(
|
|
1604
|
+
'div',
|
|
1605
|
+
{ style: { padding: '1rem', color: 'crimson' } },
|
|
1606
|
+
'Error: ' + err.message,
|
|
1607
|
+
)
|
|
1608
|
+
rootRef.render(
|
|
1609
|
+
createElement(
|
|
1610
|
+
NixoProvider,
|
|
1611
|
+
{ sliceRegistry, router: initialRouter },
|
|
1612
|
+
errorNode,
|
|
1613
|
+
),
|
|
1614
|
+
)
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
main().catch((err) => { console.error('[nixo] hydration failed:', err) })
|
|
1620
|
+
`;
|
|
1621
|
+
}
|
|
1622
|
+
function generateSsrEntryModule(modulePaths) {
|
|
1623
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1624
|
+
for (const [spec, vitePath] of modulePaths) {
|
|
1625
|
+
const file = spec.split(":")[0].replace(/^\.\//, "");
|
|
1626
|
+
if (!byFile.has(file)) byFile.set(file, vitePath);
|
|
1627
|
+
}
|
|
1628
|
+
const imports = [];
|
|
1629
|
+
const entries = [];
|
|
1630
|
+
let i = 0;
|
|
1631
|
+
for (const [file, vitePath] of byFile) {
|
|
1632
|
+
const varName = `m_${i++}`;
|
|
1633
|
+
imports.push(`import * as ${varName} from ${JSON.stringify(vitePath)}`);
|
|
1634
|
+
entries.push(` ${JSON.stringify(file)}: ${varName}`);
|
|
1635
|
+
}
|
|
1636
|
+
return `
|
|
1637
|
+
${imports.join("\n")}
|
|
1638
|
+
|
|
1639
|
+
export {
|
|
1640
|
+
NixoProvider,
|
|
1641
|
+
SliceRegistry,
|
|
1642
|
+
renderLayoutChain,
|
|
1643
|
+
NixoErrorBoundary,
|
|
1644
|
+
NixoLoading,
|
|
1645
|
+
NixoNotFound,
|
|
1646
|
+
} from 'setti/client'
|
|
1647
|
+
|
|
1648
|
+
export const modules = {
|
|
1649
|
+
${entries.join(",\n")}
|
|
1650
|
+
}
|
|
1651
|
+
`;
|
|
1652
|
+
}
|
|
1653
|
+
var VIRTUAL_ROUTES, RESOLVED_ROUTES, VIRTUAL_ENTRY, RESOLVED_ENTRY, VIRTUAL_SSR_ENTRY, RESOLVED_SSR_ENTRY;
|
|
1654
|
+
var init_plugin = __esm({
|
|
1655
|
+
"src/vite/plugin.ts"() {
|
|
1656
|
+
VIRTUAL_ROUTES = "virtual:nixo/routes";
|
|
1657
|
+
RESOLVED_ROUTES = "\0" + VIRTUAL_ROUTES;
|
|
1658
|
+
VIRTUAL_ENTRY = "virtual:nixo/client-entry";
|
|
1659
|
+
RESOLVED_ENTRY = "\0" + VIRTUAL_ENTRY;
|
|
1660
|
+
VIRTUAL_SSR_ENTRY = "virtual:nixo/ssr-entry";
|
|
1661
|
+
RESOLVED_SSR_ENTRY = "\0" + VIRTUAL_SSR_ENTRY;
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
function resolveCssMainPath(projectDir, clientDir, config) {
|
|
1665
|
+
if (!config.css.main) return void 0;
|
|
1666
|
+
const abs = path10.resolve(clientDir, config.css.main);
|
|
1667
|
+
return "/" + path10.relative(projectDir, abs).replace(/\\/g, "/");
|
|
1668
|
+
}
|
|
1669
|
+
var init_css = __esm({
|
|
1670
|
+
"src/utils/css.ts"() {
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
// src/server/rpc/process.ts
|
|
1675
|
+
async function processOne(raw, fallbackId, registry, requestId, extension) {
|
|
1676
|
+
let id = fallbackId;
|
|
1677
|
+
if (raw && typeof raw === "object" && "id" in raw) {
|
|
1678
|
+
const rawId = raw.id;
|
|
1679
|
+
if (typeof rawId === "string" || typeof rawId === "number") id = rawId;
|
|
1680
|
+
else if (rawId === null) id = null;
|
|
1681
|
+
}
|
|
1682
|
+
if (!raw || typeof raw !== "object") {
|
|
1683
|
+
return errorResponse(id, ERROR_CODES.INVALID_REQUEST, "Invalid Request");
|
|
1684
|
+
}
|
|
1685
|
+
const reqObj = raw;
|
|
1686
|
+
if (reqObj.jsonrpc !== "2.0") {
|
|
1687
|
+
return errorResponse(id, ERROR_CODES.INVALID_REQUEST, 'jsonrpc must be "2.0"');
|
|
1688
|
+
}
|
|
1689
|
+
if (typeof reqObj.method !== "string") {
|
|
1690
|
+
return errorResponse(id, ERROR_CODES.INVALID_REQUEST, "method must be a string");
|
|
1691
|
+
}
|
|
1692
|
+
if (reqObj.method === "System.Describe") {
|
|
1693
|
+
const config = globalThis.__nixoRpcConfig;
|
|
1694
|
+
if (config?.describe === false) {
|
|
1695
|
+
return errorResponse(id, ERROR_CODES.METHOD_NOT_FOUND, 'Method "System.Describe" not found');
|
|
1696
|
+
}
|
|
1697
|
+
return {
|
|
1698
|
+
jsonrpc: "2.0",
|
|
1699
|
+
result: registry.describe(),
|
|
1700
|
+
id
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
const method = registry.getMethod(reqObj.method);
|
|
1704
|
+
if (!method || method.method.internal) {
|
|
1705
|
+
return errorResponse(
|
|
1706
|
+
id,
|
|
1707
|
+
ERROR_CODES.METHOD_NOT_FOUND,
|
|
1708
|
+
`Method "${reqObj.method}" not found`
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
const authFail = checkAuth(method.method, extension);
|
|
1712
|
+
if (authFail) {
|
|
1713
|
+
return errorResponse(id, ERROR_CODES.UNAUTHORIZED, authFail);
|
|
1714
|
+
}
|
|
1715
|
+
let params = reqObj.params;
|
|
1716
|
+
if (method.method.params) {
|
|
1717
|
+
try {
|
|
1718
|
+
const result = await method.method.params["~standard"].validate(reqObj.params);
|
|
1719
|
+
if ("issues" in result && result.issues) {
|
|
1720
|
+
return errorResponse(
|
|
1721
|
+
id,
|
|
1722
|
+
ERROR_CODES.INVALID_PARAMS,
|
|
1723
|
+
"Invalid params",
|
|
1724
|
+
result.issues
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
params = result.value;
|
|
1728
|
+
} catch (err) {
|
|
1729
|
+
return errorResponse(
|
|
1730
|
+
id,
|
|
1731
|
+
ERROR_CODES.INVALID_PARAMS,
|
|
1732
|
+
"Invalid params",
|
|
1733
|
+
err.message
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
const ctx = {};
|
|
1738
|
+
ctx.requestId = requestId;
|
|
1739
|
+
ctx.call = async (m, p) => {
|
|
1740
|
+
const target = registry.getMethod(m);
|
|
1741
|
+
if (!target) {
|
|
1742
|
+
throw new NixoError(`Method "${m}" not found`);
|
|
1743
|
+
}
|
|
1744
|
+
let localParams = p;
|
|
1745
|
+
if (target.method.params) {
|
|
1746
|
+
const result = await target.method.params["~standard"].validate(p);
|
|
1747
|
+
if ("issues" in result && result.issues) {
|
|
1748
|
+
throw new NixoError(
|
|
1749
|
+
`Invalid params for "${m}": ${JSON.stringify(result.issues)}`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
localParams = result.value;
|
|
1753
|
+
}
|
|
1754
|
+
return await target.method.handler(localParams, ctx);
|
|
1755
|
+
};
|
|
1756
|
+
for (const [k, v] of Object.entries(extension)) {
|
|
1757
|
+
ctx[k] = v;
|
|
1758
|
+
}
|
|
1759
|
+
try {
|
|
1760
|
+
const result = await method.method.handler(params, ctx);
|
|
1761
|
+
return { jsonrpc: "2.0", result, id };
|
|
1762
|
+
} catch (err) {
|
|
1763
|
+
console.error(`[nixo] RPC ${reqObj.method} error:`, err);
|
|
1764
|
+
const e = err;
|
|
1765
|
+
return errorResponse(
|
|
1766
|
+
id,
|
|
1767
|
+
ERROR_CODES.INTERNAL_ERROR,
|
|
1768
|
+
"Internal error",
|
|
1769
|
+
e.message,
|
|
1770
|
+
e.detail
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async function processBody(body, registry, requestId, extension) {
|
|
1775
|
+
const isBatch = Array.isArray(body);
|
|
1776
|
+
const rawRequests = isBatch ? body : [body];
|
|
1777
|
+
if (isBatch && rawRequests.length === 0) {
|
|
1778
|
+
return {
|
|
1779
|
+
jsonrpc: "2.0",
|
|
1780
|
+
error: { code: ERROR_CODES.INVALID_REQUEST, message: "Empty batch" },
|
|
1781
|
+
id: null
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
const responses = [];
|
|
1785
|
+
for (let i = 0; i < rawRequests.length; i++) {
|
|
1786
|
+
responses.push(
|
|
1787
|
+
await processOne(rawRequests[i], i + 1, registry, requestId, extension)
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
return isBatch ? responses : responses[0];
|
|
1791
|
+
}
|
|
1792
|
+
function checkAuth(method, extension) {
|
|
1793
|
+
if (!method.auth) return null;
|
|
1794
|
+
const user = extension.user;
|
|
1795
|
+
if (!user) return "Unauthorized: user required";
|
|
1796
|
+
if (typeof method.auth === "object" && "roles" in method.auth) {
|
|
1797
|
+
const requiredRoles = method.auth.roles;
|
|
1798
|
+
const userRoles = user.roles ?? [];
|
|
1799
|
+
const hasRole = requiredRoles.some((r) => userRoles.includes(r));
|
|
1800
|
+
if (!hasRole) {
|
|
1801
|
+
return `Unauthorized: required role ${requiredRoles.join(" | ")}`;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return null;
|
|
1805
|
+
}
|
|
1806
|
+
function errorResponse(id, code, message, data, detail) {
|
|
1807
|
+
const error = { code, message };
|
|
1808
|
+
if (detail !== void 0) error.detail = detail;
|
|
1809
|
+
if (data !== void 0) error.data = data;
|
|
1810
|
+
return { jsonrpc: "2.0", error, id };
|
|
1811
|
+
}
|
|
1812
|
+
var ERROR_CODES;
|
|
1813
|
+
var init_process = __esm({
|
|
1814
|
+
"src/server/rpc/process.ts"() {
|
|
1815
|
+
init_errors();
|
|
1816
|
+
ERROR_CODES = {
|
|
1817
|
+
PARSE_ERROR: -32700,
|
|
1818
|
+
INVALID_REQUEST: -32600,
|
|
1819
|
+
METHOD_NOT_FOUND: -32601,
|
|
1820
|
+
INVALID_PARAMS: -32602,
|
|
1821
|
+
INTERNAL_ERROR: -32603,
|
|
1822
|
+
UNAUTHORIZED: -32001
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1826
|
+
function createRpcExecutor(registry, config) {
|
|
1827
|
+
return async (payload, headers) => {
|
|
1828
|
+
const requestId = headers["x-request-id"] ?? randomUUID();
|
|
1829
|
+
const body = JSON.parse(payload);
|
|
1830
|
+
let extension = {};
|
|
1831
|
+
if (config.rpc.extendContext) {
|
|
1832
|
+
try {
|
|
1833
|
+
extension = await config.rpc.extendContext({ headers });
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
console.error("[nixo] rpc.extendContext (executor) failed:", err);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
const response = await processBody(body, registry, requestId, extension);
|
|
1839
|
+
return new Response(JSON.stringify(response), {
|
|
1840
|
+
status: 200,
|
|
1841
|
+
headers: {
|
|
1842
|
+
"content-type": "application/json",
|
|
1843
|
+
"x-request-id": requestId
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
var init_rpc_executor = __esm({
|
|
1849
|
+
"src/server/rpc-executor.ts"() {
|
|
1850
|
+
init_process();
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
function createRpcHandler(options) {
|
|
1854
|
+
const { registry, config } = options;
|
|
1855
|
+
return async function handler(req, reply) {
|
|
1856
|
+
const requestId = req.headers["x-request-id"] ?? randomUUID();
|
|
1857
|
+
reply.header("X-Request-Id", requestId);
|
|
1858
|
+
let extension = {};
|
|
1859
|
+
if (config.rpc.extendContext) {
|
|
1860
|
+
try {
|
|
1861
|
+
extension = await config.rpc.extendContext(req);
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
console.error("[nixo] rpc.extendContext failed:", err);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
const response = await processBody(req.body, registry, requestId, extension);
|
|
1867
|
+
reply.code(200).type("application/json").send(response);
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
var init_rpc_handler = __esm({
|
|
1871
|
+
"src/server/rpc-handler.ts"() {
|
|
1872
|
+
init_process();
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
|
|
1876
|
+
// src/server/server-plugins-loader.ts
|
|
1877
|
+
var server_plugins_loader_exports = {};
|
|
1878
|
+
__export(server_plugins_loader_exports, {
|
|
1879
|
+
loadServerPlugins: () => loadServerPlugins
|
|
1880
|
+
});
|
|
1881
|
+
async function loadServerPlugins(projectDir, pluginsDir) {
|
|
1882
|
+
const abs = path10.resolve(projectDir, pluginsDir);
|
|
1883
|
+
let entries;
|
|
1884
|
+
try {
|
|
1885
|
+
const stat = await promises.stat(abs);
|
|
1886
|
+
if (!stat.isDirectory()) return [];
|
|
1887
|
+
entries = await promises.readdir(abs, { withFileTypes: true });
|
|
1888
|
+
} catch {
|
|
1889
|
+
return [];
|
|
1890
|
+
}
|
|
1891
|
+
let order = null;
|
|
1892
|
+
for (const indexName of ["index.ts", "index.js"]) {
|
|
1893
|
+
const indexPath = path10.join(abs, indexName);
|
|
1894
|
+
try {
|
|
1895
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
1896
|
+
const idxMod = await jiti.import(indexPath, { default: true });
|
|
1897
|
+
if (idxMod && Array.isArray(idxMod.order)) order = idxMod.order;
|
|
1898
|
+
break;
|
|
1899
|
+
} catch {
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
const plugins = [];
|
|
1903
|
+
for (const entry of entries) {
|
|
1904
|
+
if (!entry.isFile()) continue;
|
|
1905
|
+
if (entry.name === "index.ts" || entry.name === "index.js") continue;
|
|
1906
|
+
if (!/\.(ts|js)$/.test(entry.name)) continue;
|
|
1907
|
+
const name = entry.name.replace(/\.(ts|js)$/, "");
|
|
1908
|
+
plugins.push({ name, path: path10.join(abs, entry.name) });
|
|
1909
|
+
}
|
|
1910
|
+
if (order) {
|
|
1911
|
+
const orderMap = new Map(order.map((n, i) => [n, i]));
|
|
1912
|
+
plugins.sort((a, b) => {
|
|
1913
|
+
const ai = orderMap.get(a.name) ?? Number.POSITIVE_INFINITY;
|
|
1914
|
+
const bi = orderMap.get(b.name) ?? Number.POSITIVE_INFINITY;
|
|
1915
|
+
if (ai !== bi) return ai - bi;
|
|
1916
|
+
return a.name.localeCompare(b.name);
|
|
1917
|
+
});
|
|
1918
|
+
} else {
|
|
1919
|
+
plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
1920
|
+
}
|
|
1921
|
+
return plugins;
|
|
1922
|
+
}
|
|
1923
|
+
var init_server_plugins_loader = __esm({
|
|
1924
|
+
"src/server/server-plugins-loader.ts"() {
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
// src/server/api-register.ts
|
|
1929
|
+
var api_register_exports = {};
|
|
1930
|
+
__export(api_register_exports, {
|
|
1931
|
+
registerServerPlugins: () => registerServerPlugins,
|
|
1932
|
+
registerServerRoutes: () => registerServerRoutes
|
|
1933
|
+
});
|
|
1934
|
+
async function registerServerPlugins(fastify, plugins) {
|
|
1935
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
1936
|
+
for (const plugin of plugins) {
|
|
1937
|
+
const mod = await jiti.import(plugin.path);
|
|
1938
|
+
const fn = mod.default;
|
|
1939
|
+
if (typeof fn !== "function") {
|
|
1940
|
+
throw new NixoError(
|
|
1941
|
+
`Fastify plugin "${plugin.name}" at ${plugin.path} must default-export a function`
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
await fastify.register(fn);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
async function registerServerRoutes(fastify, routes, projectDir) {
|
|
1948
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
1949
|
+
for (const route of routes) {
|
|
1950
|
+
fastify.route({
|
|
1951
|
+
method: route.method,
|
|
1952
|
+
url: route.path,
|
|
1953
|
+
async preHandler(request, reply) {
|
|
1954
|
+
for (const mw of route.middleware ?? []) {
|
|
1955
|
+
const fn = await loadHandlerFunction(
|
|
1956
|
+
mw.handler,
|
|
1957
|
+
projectDir,
|
|
1958
|
+
jiti,
|
|
1959
|
+
`middleware "${mw.id}"`
|
|
1960
|
+
);
|
|
1961
|
+
const result = await fn(mw.params ?? {}, {
|
|
1962
|
+
request,
|
|
1963
|
+
reply
|
|
1964
|
+
});
|
|
1965
|
+
if (result === reply) return reply;
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1968
|
+
async handler(request, reply) {
|
|
1969
|
+
const fn = await loadHandlerFunction(
|
|
1970
|
+
route.handler,
|
|
1971
|
+
projectDir,
|
|
1972
|
+
jiti,
|
|
1973
|
+
`route handler "${route.method} ${route.path}"`
|
|
1974
|
+
);
|
|
1975
|
+
const result = await fn(route.params ?? {}, {
|
|
1976
|
+
request,
|
|
1977
|
+
reply,
|
|
1978
|
+
params: request.params ?? {}
|
|
1979
|
+
});
|
|
1980
|
+
if (result === reply) return reply;
|
|
1981
|
+
return result;
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
async function loadHandlerFunction(spec, projectDir, jiti, label) {
|
|
1987
|
+
const [file, exportName] = spec.split(":");
|
|
1988
|
+
const base = path10.resolve(projectDir, file);
|
|
1989
|
+
let abs = null;
|
|
1990
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ""]) {
|
|
1991
|
+
if (await fileExists(base + ext)) {
|
|
1992
|
+
abs = base + ext;
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
if (!abs) {
|
|
1997
|
+
throw new NixoError(`Module not found for ${label}: ${spec}`);
|
|
1998
|
+
}
|
|
1999
|
+
const mod = await jiti.import(abs);
|
|
2000
|
+
const fn = exportName ? mod[exportName] : mod.default;
|
|
2001
|
+
if (typeof fn !== "function") {
|
|
2002
|
+
throw new NixoError(
|
|
2003
|
+
`Export "${exportName ?? "default"}" of ${label} at ${abs} is not a function`
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
return fn;
|
|
2007
|
+
}
|
|
2008
|
+
var init_api_register = __esm({
|
|
2009
|
+
"src/server/api-register.ts"() {
|
|
2010
|
+
init_errors();
|
|
2011
|
+
init_fs();
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
// src/server/server-routes-loader.ts
|
|
2016
|
+
var server_routes_loader_exports = {};
|
|
2017
|
+
__export(server_routes_loader_exports, {
|
|
2018
|
+
loadServerRoutes: () => loadServerRoutes
|
|
2019
|
+
});
|
|
2020
|
+
async function loadServerRoutes(projectDir, routesPath) {
|
|
2021
|
+
const abs = path10.resolve(projectDir, routesPath);
|
|
2022
|
+
if (!await fileExists(abs)) {
|
|
2023
|
+
throw new NixoError(`server.routes not found: ${abs}`);
|
|
2024
|
+
}
|
|
2025
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true });
|
|
2026
|
+
const mod = await jiti.import(abs);
|
|
2027
|
+
const routes = mod.default;
|
|
2028
|
+
if (!Array.isArray(routes)) {
|
|
2029
|
+
throw new NixoError(
|
|
2030
|
+
`server.routes at "${abs}" must default-export an array (defineServerRoutes([...]))`
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
for (const [i, r] of routes.entries()) {
|
|
2034
|
+
if (!r || typeof r !== "object") {
|
|
2035
|
+
throw new NixoError(`Route at index ${i} in "${abs}" must be an object`);
|
|
2036
|
+
}
|
|
2037
|
+
const route = r;
|
|
2038
|
+
if (typeof route.method !== "string") {
|
|
2039
|
+
throw new NixoError(`Route at index ${i} in "${abs}": missing "method"`);
|
|
2040
|
+
}
|
|
2041
|
+
if (typeof route.path !== "string") {
|
|
2042
|
+
throw new NixoError(`Route at index ${i} in "${abs}": missing "path"`);
|
|
2043
|
+
}
|
|
2044
|
+
if (typeof route.handler !== "string") {
|
|
2045
|
+
throw new NixoError(`Route at index ${i} in "${abs}": missing "handler"`);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return routes;
|
|
2049
|
+
}
|
|
2050
|
+
var init_server_routes_loader = __esm({
|
|
2051
|
+
"src/server/server-routes-loader.ts"() {
|
|
2052
|
+
init_fs();
|
|
2053
|
+
init_errors();
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
// src/server/create-server.ts
|
|
2058
|
+
var create_server_exports = {};
|
|
2059
|
+
__export(create_server_exports, {
|
|
2060
|
+
createNixoServer: () => createNixoServer
|
|
2061
|
+
});
|
|
2062
|
+
async function createNixoServer(options) {
|
|
2063
|
+
const {
|
|
2064
|
+
config,
|
|
2065
|
+
projectDir,
|
|
2066
|
+
routeTree,
|
|
2067
|
+
manifest,
|
|
2068
|
+
globalRegistry,
|
|
2069
|
+
factoryModules,
|
|
2070
|
+
rpcRegistry,
|
|
2071
|
+
mode = "development"
|
|
2072
|
+
} = options;
|
|
2073
|
+
const fastify = Fastify({
|
|
2074
|
+
logger: { level: "info" },
|
|
2075
|
+
...config.fastify
|
|
2076
|
+
});
|
|
2077
|
+
const clientDir = path10.resolve(projectDir, config.clientDir);
|
|
2078
|
+
const outDir = path10.resolve(projectDir, config.build.outDir);
|
|
2079
|
+
const cssMainPath = resolveCssMainPath(projectDir, clientDir, config);
|
|
2080
|
+
let NixoProvider;
|
|
2081
|
+
let SliceRegistry;
|
|
2082
|
+
let renderLayoutChain;
|
|
2083
|
+
let NixoErrorBoundary;
|
|
2084
|
+
let NixoLoading;
|
|
2085
|
+
let NixoNotFound;
|
|
2086
|
+
let loadModule;
|
|
2087
|
+
let entryScriptSrc;
|
|
2088
|
+
let clientManifest = null;
|
|
2089
|
+
const clientOutPublicPrefix = "/";
|
|
2090
|
+
const isDev = mode === "development";
|
|
2091
|
+
if (isDev) {
|
|
2092
|
+
const { createServer } = await import('vite');
|
|
2093
|
+
const react = (await import('@vitejs/plugin-react')).default;
|
|
2094
|
+
const userVite = config.vite ?? {};
|
|
2095
|
+
const vite = await createServer({
|
|
2096
|
+
...userVite,
|
|
2097
|
+
root: projectDir,
|
|
2098
|
+
server: { ...userVite.server ?? {}, middlewareMode: true },
|
|
2099
|
+
appType: "custom",
|
|
2100
|
+
configFile: false,
|
|
2101
|
+
ssr: {
|
|
2102
|
+
...userVite.ssr ?? {},
|
|
2103
|
+
noExternal: ["nixo", ...userVite.ssr?.noExternal ?? []]
|
|
2104
|
+
},
|
|
2105
|
+
optimizeDeps: {
|
|
2106
|
+
...userVite.optimizeDeps ?? {},
|
|
2107
|
+
exclude: ["nixo", ...userVite.optimizeDeps?.exclude ?? []]
|
|
2108
|
+
},
|
|
2109
|
+
plugins: [
|
|
2110
|
+
nixoPlugin({
|
|
2111
|
+
projectDir,
|
|
2112
|
+
clientDir,
|
|
2113
|
+
config,
|
|
2114
|
+
routeTree,
|
|
2115
|
+
factoryModules,
|
|
2116
|
+
globalFactories: globalRegistry,
|
|
2117
|
+
cssMain: cssMainPath
|
|
2118
|
+
}),
|
|
2119
|
+
react(),
|
|
2120
|
+
...userVite.plugins ?? []
|
|
2121
|
+
],
|
|
2122
|
+
resolve: { ...userVite.resolve ?? {}, preserveSymlinks: false }
|
|
2123
|
+
});
|
|
2124
|
+
fastify.addHook("onRequest", (req, reply, done) => {
|
|
2125
|
+
const url = req.url.split("?")[0];
|
|
2126
|
+
const isHtml = req.headers.accept?.includes("text/html");
|
|
2127
|
+
const isApi = url.startsWith("/api/") || url.startsWith("/__nixo/") || url === config.rpc.url;
|
|
2128
|
+
if (isHtml || isApi) {
|
|
2129
|
+
done();
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
vite.middlewares(req.raw, reply.raw, done);
|
|
2133
|
+
});
|
|
2134
|
+
const clientEntryPath = fileURLToPath(
|
|
2135
|
+
new URL("../client/index.ts", import.meta.url)
|
|
2136
|
+
);
|
|
2137
|
+
const nixoClient = await vite.ssrLoadModule(clientEntryPath);
|
|
2138
|
+
NixoProvider = nixoClient.NixoProvider;
|
|
2139
|
+
SliceRegistry = nixoClient.SliceRegistry;
|
|
2140
|
+
renderLayoutChain = nixoClient.renderLayoutChain;
|
|
2141
|
+
NixoErrorBoundary = nixoClient.NixoErrorBoundary;
|
|
2142
|
+
NixoLoading = nixoClient.NixoLoading;
|
|
2143
|
+
NixoNotFound = nixoClient.NixoNotFound;
|
|
2144
|
+
loadModule = createDevModuleLoader(clientDir, vite);
|
|
2145
|
+
entryScriptSrc = "/@id/__x00__virtual:nixo/client-entry";
|
|
2146
|
+
} else {
|
|
2147
|
+
const serverEntry = path10.join(outDir, "server", "entry.js");
|
|
2148
|
+
const clientOut = path10.join(outDir, "client");
|
|
2149
|
+
const manifestPath = path10.join(clientOut, ".vite", "manifest.json");
|
|
2150
|
+
try {
|
|
2151
|
+
const raw = await promises.readFile(manifestPath, "utf8");
|
|
2152
|
+
clientManifest = JSON.parse(raw);
|
|
2153
|
+
} catch (err) {
|
|
2154
|
+
fastify.log.warn(
|
|
2155
|
+
`[nixo] client manifest not found at ${manifestPath}: ${err.message}. CSS for production will not be injected.`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
const entryMod = await import(pathToFileURL(serverEntry).href);
|
|
2159
|
+
NixoProvider = entryMod.NixoProvider;
|
|
2160
|
+
SliceRegistry = entryMod.SliceRegistry;
|
|
2161
|
+
renderLayoutChain = entryMod.renderLayoutChain;
|
|
2162
|
+
NixoErrorBoundary = entryMod.NixoErrorBoundary;
|
|
2163
|
+
NixoLoading = entryMod.NixoLoading;
|
|
2164
|
+
NixoNotFound = entryMod.NixoNotFound;
|
|
2165
|
+
loadModule = createProdModuleLoader(serverEntry);
|
|
2166
|
+
entryScriptSrc = "/client.js";
|
|
2167
|
+
await fastify.register(fastifyStatic, {
|
|
2168
|
+
root: clientOut,
|
|
2169
|
+
prefix: "/",
|
|
2170
|
+
wildcard: false
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
let rpcExecutor;
|
|
2174
|
+
if (config.rpc.enabled && rpcRegistry) {
|
|
2175
|
+
rpcExecutor = createRpcExecutor(rpcRegistry, config);
|
|
2176
|
+
globalThis.__nixoRpcGetContext = getRpcContext;
|
|
2177
|
+
const rpcHandler = createRpcHandler({ registry: rpcRegistry, config });
|
|
2178
|
+
fastify.post(config.rpc.url, rpcHandler);
|
|
2179
|
+
}
|
|
2180
|
+
if (config.server.plugins) {
|
|
2181
|
+
const { loadServerPlugins: loadServerPlugins2 } = await Promise.resolve().then(() => (init_server_plugins_loader(), server_plugins_loader_exports));
|
|
2182
|
+
const { registerServerPlugins: registerServerPlugins2 } = await Promise.resolve().then(() => (init_api_register(), api_register_exports));
|
|
2183
|
+
const plugins = await loadServerPlugins2(projectDir, config.server.plugins);
|
|
2184
|
+
await registerServerPlugins2(fastify, plugins);
|
|
2185
|
+
}
|
|
2186
|
+
if (config.server.routes) {
|
|
2187
|
+
const { loadServerRoutes: loadServerRoutes2 } = await Promise.resolve().then(() => (init_server_routes_loader(), server_routes_loader_exports));
|
|
2188
|
+
const { registerServerRoutes: registerServerRoutes2 } = await Promise.resolve().then(() => (init_api_register(), api_register_exports));
|
|
2189
|
+
const routes = await loadServerRoutes2(projectDir, config.server.routes);
|
|
2190
|
+
await registerServerRoutes2(fastify, routes, projectDir);
|
|
2191
|
+
}
|
|
2192
|
+
const globalMiddleware = (config.middleware ?? []).filter(
|
|
2193
|
+
(m) => m.side === "server" || m.side === "both"
|
|
2194
|
+
);
|
|
2195
|
+
const ssrHandler = createSSRHandler({
|
|
2196
|
+
config,
|
|
2197
|
+
projectDir,
|
|
2198
|
+
clientDir,
|
|
2199
|
+
routeTree,
|
|
2200
|
+
manifest,
|
|
2201
|
+
mode,
|
|
2202
|
+
globalRegistry,
|
|
2203
|
+
NixoProvider,
|
|
2204
|
+
SliceRegistry,
|
|
2205
|
+
loadModule,
|
|
2206
|
+
isDev,
|
|
2207
|
+
entryScriptSrc,
|
|
2208
|
+
clientManifest,
|
|
2209
|
+
clientOutPublicPrefix,
|
|
2210
|
+
globalMiddleware,
|
|
2211
|
+
rpcExecutor,
|
|
2212
|
+
renderLayoutChain,
|
|
2213
|
+
NixoErrorBoundary,
|
|
2214
|
+
NixoLoading,
|
|
2215
|
+
NixoNotFound
|
|
2216
|
+
});
|
|
2217
|
+
fastify.get("/*", ssrHandler);
|
|
2218
|
+
return fastify;
|
|
2219
|
+
}
|
|
2220
|
+
var init_create_server = __esm({
|
|
2221
|
+
"src/server/create-server.ts"() {
|
|
2222
|
+
init_ssr_handler();
|
|
2223
|
+
init_module_loader();
|
|
2224
|
+
init_plugin();
|
|
2225
|
+
init_css();
|
|
2226
|
+
init_rpc_executor();
|
|
2227
|
+
init_rpc_handler();
|
|
2228
|
+
init_als();
|
|
2229
|
+
}
|
|
2230
|
+
});
|
|
2231
|
+
|
|
2232
|
+
// src/build/build-client.ts
|
|
2233
|
+
async function buildClient(options) {
|
|
2234
|
+
const { projectDir, clientDir, config, routeTree, outDir, factoryModules, globalRegistry } = options;
|
|
2235
|
+
const { build } = await import('vite');
|
|
2236
|
+
const react = (await import('@vitejs/plugin-react')).default;
|
|
2237
|
+
const userVite = config.vite ?? {};
|
|
2238
|
+
const cssMainPath = resolveCssMainPath(projectDir, clientDir, config);
|
|
2239
|
+
await build({
|
|
2240
|
+
...userVite,
|
|
2241
|
+
root: projectDir,
|
|
2242
|
+
configFile: false,
|
|
2243
|
+
appType: "custom",
|
|
2244
|
+
plugins: [
|
|
2245
|
+
nixoPlugin({
|
|
2246
|
+
projectDir,
|
|
2247
|
+
clientDir,
|
|
2248
|
+
config,
|
|
2249
|
+
routeTree,
|
|
2250
|
+
factoryModules,
|
|
2251
|
+
globalFactories: globalRegistry,
|
|
2252
|
+
cssMain: cssMainPath
|
|
2253
|
+
}),
|
|
2254
|
+
react(),
|
|
2255
|
+
...userVite.plugins ?? []
|
|
2256
|
+
],
|
|
2257
|
+
resolve: { ...userVite.resolve ?? {}, preserveSymlinks: false },
|
|
2258
|
+
build: {
|
|
2259
|
+
...userVite.build ?? {},
|
|
2260
|
+
outDir,
|
|
2261
|
+
emptyOutDir: true,
|
|
2262
|
+
manifest: true,
|
|
2263
|
+
rollupOptions: {
|
|
2264
|
+
input: "virtual:nixo/client-entry",
|
|
2265
|
+
output: {
|
|
2266
|
+
entryFileNames: "client.js",
|
|
2267
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
2268
|
+
assetFileNames: "assets/[name]-[hash][extname]"
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
var init_build_client = __esm({
|
|
2275
|
+
"src/build/build-client.ts"() {
|
|
2276
|
+
init_plugin();
|
|
2277
|
+
init_css();
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
|
|
2281
|
+
// src/build/build-server.ts
|
|
2282
|
+
async function buildServer(options) {
|
|
2283
|
+
const { projectDir, clientDir, config, routeTree, outDir, factoryModules, globalRegistry } = options;
|
|
2284
|
+
const { build } = await import('vite');
|
|
2285
|
+
const react = (await import('@vitejs/plugin-react')).default;
|
|
2286
|
+
const userVite = config.vite ?? {};
|
|
2287
|
+
await build({
|
|
2288
|
+
...userVite,
|
|
2289
|
+
root: projectDir,
|
|
2290
|
+
configFile: false,
|
|
2291
|
+
appType: "custom",
|
|
2292
|
+
plugins: [
|
|
2293
|
+
nixoPlugin({ projectDir, clientDir, config, routeTree, factoryModules, globalFactories: globalRegistry }),
|
|
2294
|
+
react(),
|
|
2295
|
+
...userVite.plugins ?? []
|
|
2296
|
+
],
|
|
2297
|
+
resolve: { ...userVite.resolve ?? {}, preserveSymlinks: false },
|
|
2298
|
+
ssr: {
|
|
2299
|
+
...userVite.ssr ?? {},
|
|
2300
|
+
external: ["react", "react-dom", "react/jsx-runtime"]
|
|
2301
|
+
},
|
|
2302
|
+
build: {
|
|
2303
|
+
...userVite.build ?? {},
|
|
2304
|
+
outDir,
|
|
2305
|
+
emptyOutDir: true,
|
|
2306
|
+
ssr: true,
|
|
2307
|
+
rollupOptions: {
|
|
2308
|
+
input: "virtual:nixo/ssr-entry",
|
|
2309
|
+
external: ["react", "react-dom", "react/jsx-runtime"],
|
|
2310
|
+
output: {
|
|
2311
|
+
entryFileNames: "entry.js",
|
|
2312
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
2313
|
+
format: "esm"
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
var init_build_server = __esm({
|
|
2320
|
+
"src/build/build-server.ts"() {
|
|
2321
|
+
init_plugin();
|
|
2322
|
+
}
|
|
2323
|
+
});
|
|
2324
|
+
|
|
2325
|
+
// src/build/build.ts
|
|
2326
|
+
var build_exports = {};
|
|
2327
|
+
__export(build_exports, {
|
|
2328
|
+
buildProject: () => buildProject
|
|
2329
|
+
});
|
|
2330
|
+
async function buildProject(options) {
|
|
2331
|
+
const { projectDir, config, routeTree, scanResult, factoryModules, globalRegistry } = options;
|
|
2332
|
+
const clientDir = path10.resolve(projectDir, config.clientDir);
|
|
2333
|
+
const outDir = path10.resolve(projectDir, config.build.outDir);
|
|
2334
|
+
const clientOut = path10.join(outDir, "client");
|
|
2335
|
+
const serverOut = path10.join(outDir, "server");
|
|
2336
|
+
await buildClient({
|
|
2337
|
+
projectDir,
|
|
2338
|
+
clientDir,
|
|
2339
|
+
config,
|
|
2340
|
+
routeTree,
|
|
2341
|
+
outDir: clientOut,
|
|
2342
|
+
factoryModules,
|
|
2343
|
+
globalRegistry
|
|
2344
|
+
});
|
|
2345
|
+
await buildServer({
|
|
2346
|
+
projectDir,
|
|
2347
|
+
clientDir,
|
|
2348
|
+
config,
|
|
2349
|
+
routeTree,
|
|
2350
|
+
outDir: serverOut,
|
|
2351
|
+
factoryModules,
|
|
2352
|
+
globalRegistry
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
var init_build = __esm({
|
|
2356
|
+
"src/build/build.ts"() {
|
|
2357
|
+
init_build_client();
|
|
2358
|
+
init_build_server();
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2362
|
+
// src/config.ts
|
|
2363
|
+
init_errors();
|
|
2364
|
+
init_fs();
|
|
2365
|
+
async function loadConfig(projectDir) {
|
|
2366
|
+
const configPath = path10.join(projectDir, "nixo.config.ts");
|
|
2367
|
+
const altPath = path10.join(projectDir, "nixo.config.js");
|
|
2368
|
+
const target = await fileExists(configPath) ? configPath : await fileExists(altPath) ? altPath : null;
|
|
2369
|
+
if (!target) {
|
|
2370
|
+
throw new NixoError(
|
|
2371
|
+
`Config not found: expected "nixo.config.ts" or "nixo.config.js" in ${projectDir}`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
const jiti = createJiti(import.meta.url, {
|
|
2375
|
+
interopDefault: true,
|
|
2376
|
+
jsx: true
|
|
2377
|
+
});
|
|
2378
|
+
const mod = await jiti.import(target, { default: true });
|
|
2379
|
+
if (!mod || typeof mod !== "object") {
|
|
2380
|
+
throw new NixoError(`Config at ${target} must export a default object`);
|
|
2381
|
+
}
|
|
2382
|
+
return {
|
|
2383
|
+
ssr: mod.ssr ?? true,
|
|
2384
|
+
clientDir: mod.clientDir ?? "client",
|
|
2385
|
+
serverDir: mod.serverDir ?? "server",
|
|
2386
|
+
layouts: mod.layouts ?? {},
|
|
2387
|
+
middleware: mod.middleware ?? [],
|
|
2388
|
+
server: {
|
|
2389
|
+
port: mod.server?.port ?? 3e3,
|
|
2390
|
+
host: mod.server?.host ?? "localhost",
|
|
2391
|
+
routes: mod.server?.routes ?? null,
|
|
2392
|
+
plugins: mod.server?.plugins ?? null
|
|
2393
|
+
},
|
|
2394
|
+
vite: mod.vite ?? {},
|
|
2395
|
+
build: { outDir: mod.build?.outDir ?? "dist" },
|
|
2396
|
+
css: {
|
|
2397
|
+
main: mod.css?.main ?? "styles/main.css"
|
|
2398
|
+
},
|
|
2399
|
+
runtimeConfig: { public: mod.runtimeConfig?.public ?? {} },
|
|
2400
|
+
fastify: mod.fastify ?? {},
|
|
2401
|
+
rpc: mod.rpc === false ? {
|
|
2402
|
+
enabled: false,
|
|
2403
|
+
url: "/service",
|
|
2404
|
+
describe: false,
|
|
2405
|
+
extendContext: void 0
|
|
2406
|
+
} : {
|
|
2407
|
+
enabled: mod.rpc?.enabled ?? true,
|
|
2408
|
+
url: mod.rpc?.url ?? "/service",
|
|
2409
|
+
describe: mod.rpc?.describe ?? true,
|
|
2410
|
+
extendContext: mod.rpc?.extendContext
|
|
2411
|
+
},
|
|
2412
|
+
components: {
|
|
2413
|
+
error: mod.components?.error ?? null,
|
|
2414
|
+
loading: mod.components?.loading ?? null,
|
|
2415
|
+
notFound: mod.components?.notFound ?? null
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
// src/scanner.ts
|
|
2421
|
+
init_errors();
|
|
2422
|
+
init_fs();
|
|
2423
|
+
async function scan(clientDir) {
|
|
2424
|
+
const jiti = createJiti(import.meta.url, {
|
|
2425
|
+
interopDefault: true,
|
|
2426
|
+
jsx: true
|
|
2427
|
+
});
|
|
2428
|
+
const routeModules = [];
|
|
2429
|
+
const loaderModules = [];
|
|
2430
|
+
let topLevelEntries;
|
|
2431
|
+
try {
|
|
2432
|
+
topLevelEntries = await promises.readdir(clientDir, { withFileTypes: true });
|
|
2433
|
+
} catch (err) {
|
|
2434
|
+
throw new NixoError(
|
|
2435
|
+
`Failed to read clientDir "${clientDir}": ${err.message}`
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
for (const entry of topLevelEntries) {
|
|
2439
|
+
if (!entry.isDirectory()) continue;
|
|
2440
|
+
const routerPath = path10.join(clientDir, entry.name, "router.ts");
|
|
2441
|
+
if (!await fileExists(routerPath)) continue;
|
|
2442
|
+
const mod = await jiti.import(routerPath);
|
|
2443
|
+
const routes = mod.default;
|
|
2444
|
+
if (!Array.isArray(routes)) {
|
|
2445
|
+
throw new NixoError(
|
|
2446
|
+
`router.ts at "${routerPath}" must default-export an array (use defineRoute([...]))`
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
for (const [i, route] of routes.entries()) {
|
|
2450
|
+
if (!route || typeof route !== "object" || typeof route.id !== "string") {
|
|
2451
|
+
throw new NixoError(
|
|
2452
|
+
`Invalid route at index ${i} in "${routerPath}": missing "id"`
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
routeModules.push({ file: routerPath, routes });
|
|
2457
|
+
}
|
|
2458
|
+
await walkFiles(clientDir, async (file) => {
|
|
2459
|
+
if (!file.endsWith(".loader.ts") && !file.endsWith(".loader.js")) return;
|
|
2460
|
+
const mod = await jiti.import(file);
|
|
2461
|
+
const map = mod.default;
|
|
2462
|
+
if (!map || typeof map !== "object") {
|
|
2463
|
+
throw new NixoError(
|
|
2464
|
+
`Loader file "${file}" must default-export a factory map (defineGlobalLoader({...}))`
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
loaderModules.push({ file, map });
|
|
2468
|
+
});
|
|
2469
|
+
return { routeModules, loaderModules };
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/route-tree.ts
|
|
2473
|
+
init_errors();
|
|
2474
|
+
init_url();
|
|
2475
|
+
function buildRouteTree(routeModules, loaderModules, config) {
|
|
2476
|
+
const allRoutes = [];
|
|
2477
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2478
|
+
for (const { routes } of routeModules) {
|
|
2479
|
+
for (const route of routes) {
|
|
2480
|
+
if (byId.has(route.id)) {
|
|
2481
|
+
throw new NixoError(
|
|
2482
|
+
`Duplicate route id "${route.id}" (found in multiple router.ts files)`
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
byId.set(route.id, route);
|
|
2486
|
+
allRoutes.push(route);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
for (const route of allRoutes) {
|
|
2490
|
+
if (route.parentId && !byId.has(route.parentId)) {
|
|
2491
|
+
throw new NixoError(
|
|
2492
|
+
`Route "${route.id}" references unknown parentId "${route.parentId}"`
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
for (const route of allRoutes) {
|
|
2497
|
+
const seen = /* @__PURE__ */ new Set([route.id]);
|
|
2498
|
+
let cursor = route.parentId;
|
|
2499
|
+
while (cursor) {
|
|
2500
|
+
if (seen.has(cursor)) {
|
|
2501
|
+
throw new NixoError(
|
|
2502
|
+
`Cycle detected in route hierarchy starting at "${route.id}" (via parentId "${cursor}")`
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
seen.add(cursor);
|
|
2506
|
+
cursor = byId.get(cursor)?.parentId;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
const indexRoutes = allRoutes.filter((r) => r.index === true);
|
|
2510
|
+
if (indexRoutes.length > 1) {
|
|
2511
|
+
throw new NixoError(
|
|
2512
|
+
`Multiple index routes globally: ${indexRoutes.map((r) => r.id).join(", ")}. Only one is allowed.`
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
const layoutKeys = new Set(Object.keys(config.layouts));
|
|
2516
|
+
for (const route of allRoutes) {
|
|
2517
|
+
if (!layoutKeys.has(route.type)) {
|
|
2518
|
+
throw new NixoError(
|
|
2519
|
+
`Route "${route.id}" uses unknown layout type "${route.type}". Available: ${[...layoutKeys].join(", ") || "(none)"}`
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
const globalNames = /* @__PURE__ */ new Map();
|
|
2524
|
+
for (const { file, map } of loaderModules) {
|
|
2525
|
+
for (const name of Object.keys(map)) {
|
|
2526
|
+
const existing = globalNames.get(name);
|
|
2527
|
+
if (existing) {
|
|
2528
|
+
throw new NixoError(
|
|
2529
|
+
`Global loader "${name}" is declared in both "${existing}" and "${file}" (A3: no collisions)`
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
globalNames.set(name, file);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
const paths = /* @__PURE__ */ new Map();
|
|
2536
|
+
const compiled = [];
|
|
2537
|
+
const seenPatterns = /* @__PURE__ */ new Map();
|
|
2538
|
+
for (const route of allRoutes) {
|
|
2539
|
+
if (route.path && route.slug) {
|
|
2540
|
+
throw new NixoError(
|
|
2541
|
+
`Route "${route.id}" cannot have both "path" and "slug" (mutually exclusive)`
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
const pattern = computePath(route, byId);
|
|
2545
|
+
paths.set(route.id, pattern);
|
|
2546
|
+
if (seenPatterns.has(pattern)) {
|
|
2547
|
+
throw new NixoError(
|
|
2548
|
+
`Duplicate URL pattern "${pattern}" for routes "${seenPatterns.get(pattern)}" and "${route.id}"`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
seenPatterns.set(pattern, route.id);
|
|
2552
|
+
let compiledPat;
|
|
2553
|
+
try {
|
|
2554
|
+
compiledPat = compileRoute(route, byId);
|
|
2555
|
+
} catch (err) {
|
|
2556
|
+
throw new NixoError(
|
|
2557
|
+
`Invalid route "${route.id}": ${err.message}`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (seenPatterns.has(compiledPat.regexSource)) {
|
|
2561
|
+
const existing = seenPatterns.get(compiledPat.regexSource);
|
|
2562
|
+
if (existing !== route.id) ;
|
|
2563
|
+
}
|
|
2564
|
+
compiled.push({
|
|
2565
|
+
id: route.id,
|
|
2566
|
+
path: pattern,
|
|
2567
|
+
regexSource: compiledPat.regexSource,
|
|
2568
|
+
paramNames: compiledPat.paramNames
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
const regexSeen = /* @__PURE__ */ new Map();
|
|
2572
|
+
for (const c of compiled) {
|
|
2573
|
+
const existing = regexSeen.get(c.regexSource);
|
|
2574
|
+
if (existing) {
|
|
2575
|
+
throw new NixoError(
|
|
2576
|
+
`Ambiguous URL regex "${c.regexSource}" for routes "${existing}" and "${c.id}"`
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
regexSeen.set(c.regexSource, c.id);
|
|
2580
|
+
}
|
|
2581
|
+
const layoutChains = /* @__PURE__ */ new Map();
|
|
2582
|
+
for (const route of allRoutes) {
|
|
2583
|
+
const chain = [];
|
|
2584
|
+
let current = route;
|
|
2585
|
+
if (route.index === true && route.ssr === false) {
|
|
2586
|
+
throw new NixoError(
|
|
2587
|
+
`Route "${route.id}": "index: true" cannot have "ssr: false" (index page must be SSR)`
|
|
2588
|
+
);
|
|
2589
|
+
}
|
|
2590
|
+
while (current) {
|
|
2591
|
+
chain.unshift(current.type);
|
|
2592
|
+
current = current.parentId ? byId.get(current.parentId) : void 0;
|
|
2593
|
+
}
|
|
2594
|
+
layoutChains.set(route.id, chain);
|
|
2595
|
+
}
|
|
2596
|
+
return {
|
|
2597
|
+
byId,
|
|
2598
|
+
paths,
|
|
2599
|
+
indexRouteId: indexRoutes[0]?.id ?? null,
|
|
2600
|
+
compiled,
|
|
2601
|
+
layoutChains
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function buildManifest(scan2, config, clientDir) {
|
|
2605
|
+
const modules = {};
|
|
2606
|
+
const add = (name, kind) => {
|
|
2607
|
+
if (!modules[name]) {
|
|
2608
|
+
modules[name] = { kind, js: name, css: [] };
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
for (const { file } of scan2.routeModules) {
|
|
2612
|
+
add(toModuleName(file, clientDir), "routes");
|
|
2613
|
+
}
|
|
2614
|
+
for (const { file } of scan2.loaderModules) {
|
|
2615
|
+
add(toModuleName(file, clientDir), "globalLoader");
|
|
2616
|
+
}
|
|
2617
|
+
for (const layoutPath of Object.values(config.layouts)) {
|
|
2618
|
+
add(stripExt(toRelativeName(layoutPath)), "layout");
|
|
2619
|
+
}
|
|
2620
|
+
for (const { routes } of scan2.routeModules) {
|
|
2621
|
+
for (const route of routes) {
|
|
2622
|
+
add(stripExport(route.contentConfig.workspaceConfig.template), "component");
|
|
2623
|
+
add(stripExport(route.contentConfig.prefetchConfig.configLoader), "config");
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
return {
|
|
2627
|
+
version: computeVersion(modules),
|
|
2628
|
+
modules
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function toModuleName(absFile, clientDir) {
|
|
2632
|
+
const rel = path10.relative(clientDir, absFile).replace(/\\/g, "/");
|
|
2633
|
+
return stripExt(rel);
|
|
2634
|
+
}
|
|
2635
|
+
function toRelativeName(p) {
|
|
2636
|
+
return p.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
2637
|
+
}
|
|
2638
|
+
function stripExt(p) {
|
|
2639
|
+
return p.replace(/\.(tsx?|jsx?)$/, "");
|
|
2640
|
+
}
|
|
2641
|
+
function stripExport(p) {
|
|
2642
|
+
return p.split(":")[0];
|
|
2643
|
+
}
|
|
2644
|
+
function computeVersion(modules) {
|
|
2645
|
+
const keys = Object.keys(modules).sort().join("|");
|
|
2646
|
+
let hash = 2166136261;
|
|
2647
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2648
|
+
hash ^= keys.charCodeAt(i);
|
|
2649
|
+
hash = hash * 16777619 >>> 0;
|
|
2650
|
+
}
|
|
2651
|
+
return hash.toString(16);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
// src/resolver.ts
|
|
2655
|
+
init_errors();
|
|
2656
|
+
|
|
2657
|
+
// src/utils/interpolate.ts
|
|
2658
|
+
init_errors();
|
|
2659
|
+
function __initResolver(options) {
|
|
2660
|
+
options.jiti ?? createJiti(import.meta.url, {
|
|
2661
|
+
interopDefault: true,
|
|
2662
|
+
jsx: true
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
async function collectFactoryModules(projectDir, routeTree, scanResult, clientDir) {
|
|
2666
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true, jsx: true });
|
|
2667
|
+
const out = /* @__PURE__ */ new Set();
|
|
2668
|
+
for (const route of routeTree.byId.values()) {
|
|
2669
|
+
const cfgSpec = route.contentConfig.prefetchConfig.configLoader;
|
|
2670
|
+
const [file, exportName] = cfgSpec.split(":");
|
|
2671
|
+
const abs = path10.resolve(clientDir, file);
|
|
2672
|
+
try {
|
|
2673
|
+
const mod = await jiti.import(abs);
|
|
2674
|
+
const getConfig = exportName ? mod[exportName] : mod.default;
|
|
2675
|
+
if (typeof getConfig !== "function") continue;
|
|
2676
|
+
const router = {
|
|
2677
|
+
id: route.id,
|
|
2678
|
+
path: routeTree.paths.get(route.id) ?? "/",
|
|
2679
|
+
params: {},
|
|
2680
|
+
query: {},
|
|
2681
|
+
request: {}
|
|
2682
|
+
};
|
|
2683
|
+
const factoryMap = await getConfig(
|
|
2684
|
+
route.contentConfig.prefetchConfig.configParams ?? {},
|
|
2685
|
+
router
|
|
2686
|
+
);
|
|
2687
|
+
for (const def of Object.values(factoryMap)) {
|
|
2688
|
+
out.add(def.dataFactoryName);
|
|
2689
|
+
}
|
|
2690
|
+
} catch (err) {
|
|
2691
|
+
console.warn(
|
|
2692
|
+
`[nixo] collectFactoryModules: getConfig of route "${route.id}" failed: ${err.message}`
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
for (const { map } of scanResult.loaderModules) {
|
|
2697
|
+
for (const def of Object.values(map)) out.add(def.dataFactoryName);
|
|
2698
|
+
}
|
|
2699
|
+
return [...out];
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
// src/cli.ts
|
|
2703
|
+
init_errors();
|
|
2704
|
+
|
|
2705
|
+
// src/server/rpc-scanner.ts
|
|
2706
|
+
init_errors();
|
|
2707
|
+
async function collectMetaFiles(dir) {
|
|
2708
|
+
let entries;
|
|
2709
|
+
try {
|
|
2710
|
+
entries = await promises.readdir(dir, { withFileTypes: true });
|
|
2711
|
+
} catch {
|
|
2712
|
+
return [];
|
|
2713
|
+
}
|
|
2714
|
+
const files = [];
|
|
2715
|
+
for (const entry of entries) {
|
|
2716
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
2717
|
+
const full = path10.join(dir, entry.name);
|
|
2718
|
+
if (entry.isDirectory()) {
|
|
2719
|
+
files.push(...await collectMetaFiles(full));
|
|
2720
|
+
} else if (entry.isFile() && /\.meta\.(ts|js)$/.test(entry.name)) {
|
|
2721
|
+
files.push(full);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
return files;
|
|
2725
|
+
}
|
|
2726
|
+
async function scanRpcObjects(serverDir) {
|
|
2727
|
+
const files = await collectMetaFiles(serverDir);
|
|
2728
|
+
if (files.length === 0) return [];
|
|
2729
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true, jsx: true });
|
|
2730
|
+
const result = [];
|
|
2731
|
+
for (const abs of files) {
|
|
2732
|
+
const mod = await jiti.import(abs);
|
|
2733
|
+
const def = mod.default;
|
|
2734
|
+
if (!def || typeof def !== "object") {
|
|
2735
|
+
throw new NixoError(
|
|
2736
|
+
`RPC meta file "${abs}" must default-export defineObject({...})`
|
|
2737
|
+
);
|
|
2738
|
+
}
|
|
2739
|
+
const obj = def;
|
|
2740
|
+
if (typeof obj.name !== "string") {
|
|
2741
|
+
throw new NixoError(`RPC meta file "${abs}": missing "name"`);
|
|
2742
|
+
}
|
|
2743
|
+
if (!obj.methods || typeof obj.methods !== "object") {
|
|
2744
|
+
throw new NixoError(
|
|
2745
|
+
`RPC meta file "${abs}" (${obj.name}): missing "methods"`
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
result.push(obj);
|
|
2749
|
+
}
|
|
2750
|
+
return result;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// src/server/rpc-registry.ts
|
|
2754
|
+
init_errors();
|
|
2755
|
+
var NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
2756
|
+
var RpcRegistry = class {
|
|
2757
|
+
/** Объекты по имени. */
|
|
2758
|
+
objects = /* @__PURE__ */ new Map();
|
|
2759
|
+
/** Методы по полному имени (`Users.List`). */
|
|
2760
|
+
methods = /* @__PURE__ */ new Map();
|
|
2761
|
+
/**
|
|
2762
|
+
* Регистрирует объект БЛ.
|
|
2763
|
+
*
|
|
2764
|
+
* Валидация:
|
|
2765
|
+
* - `name` соответствует `[A-Za-z][A-Za-z0-9_]*`;
|
|
2766
|
+
* - `name` уникален;
|
|
2767
|
+
* - каждый `methodName` соответствует `[A-Za-z][A-Za-z0-9_]*`;
|
|
2768
|
+
* - `handler` — функция;
|
|
2769
|
+
* - полное имя (`${name}.${methodKey}`) уникально.
|
|
2770
|
+
*
|
|
2771
|
+
* @param obj определение объекта.
|
|
2772
|
+
* @throws {NixoError} при любой валидационной ошибке.
|
|
2773
|
+
*/
|
|
2774
|
+
add(obj) {
|
|
2775
|
+
if (!NAME_RE.test(obj.name)) {
|
|
2776
|
+
throw new NixoError(
|
|
2777
|
+
`Invalid RPC object name "${obj.name}": must match [A-Za-z][A-Za-z0-9_]*`
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
if (this.objects.has(obj.name)) {
|
|
2781
|
+
throw new NixoError(`Duplicate RPC object "${obj.name}"`);
|
|
2782
|
+
}
|
|
2783
|
+
for (const [methodName, method] of Object.entries(obj.methods)) {
|
|
2784
|
+
if (!NAME_RE.test(methodName)) {
|
|
2785
|
+
throw new NixoError(
|
|
2786
|
+
`Invalid RPC method name "${obj.name}.${methodName}": must match [A-Za-z][A-Za-z0-9_]*`
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
if (typeof method.handler !== "function") {
|
|
2790
|
+
throw new NixoError(
|
|
2791
|
+
`RPC method "${obj.name}.${methodName}": handler is not a function`
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
const fullName = `${obj.name}.${methodName}`;
|
|
2795
|
+
if (this.methods.has(fullName)) {
|
|
2796
|
+
throw new NixoError(`Duplicate RPC method "${fullName}"`);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
this.objects.set(obj.name, obj);
|
|
2800
|
+
for (const [methodName, method] of Object.entries(obj.methods)) {
|
|
2801
|
+
const fullName = `${obj.name}.${methodName}`;
|
|
2802
|
+
this.methods.set(fullName, { object: obj, methodName, fullName, method });
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Объект по имени.
|
|
2807
|
+
*
|
|
2808
|
+
* @param name имя объекта.
|
|
2809
|
+
* @returns определение или `undefined`.
|
|
2810
|
+
*/
|
|
2811
|
+
get(name) {
|
|
2812
|
+
return this.objects.get(name);
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* Метод по полному имени.
|
|
2816
|
+
*
|
|
2817
|
+
* @param fullName полное имя (`Users.List`).
|
|
2818
|
+
* @returns зарегистрированный метод или `undefined`.
|
|
2819
|
+
*/
|
|
2820
|
+
getMethod(fullName) {
|
|
2821
|
+
return this.methods.get(fullName);
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* Список полных имён методов (server-only).
|
|
2825
|
+
*
|
|
2826
|
+
* @returns массив имён.
|
|
2827
|
+
*/
|
|
2828
|
+
listMethods() {
|
|
2829
|
+
return [...this.methods.keys()];
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Список объектов.
|
|
2833
|
+
*
|
|
2834
|
+
* @returns массив определений.
|
|
2835
|
+
*/
|
|
2836
|
+
listObjects() {
|
|
2837
|
+
return [...this.objects.values()];
|
|
2838
|
+
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Количество зарегистрированных объектов.
|
|
2841
|
+
*
|
|
2842
|
+
* @returns число объектов.
|
|
2843
|
+
*/
|
|
2844
|
+
get size() {
|
|
2845
|
+
return this.objects.size;
|
|
2846
|
+
}
|
|
2847
|
+
/**
|
|
2848
|
+
* Описание всех зарегистрированных объектов и методов.
|
|
2849
|
+
*
|
|
2850
|
+
* Возвращает данные для JSON-RPC `System.Describe`: имена, описания,
|
|
2851
|
+
* наличие `params`, требования `auth`, флаг `internal`. **Handler'ы не
|
|
2852
|
+
* сериализуются** — только метаданные.
|
|
2853
|
+
*
|
|
2854
|
+
* @returns объект описания.
|
|
2855
|
+
*/
|
|
2856
|
+
describe() {
|
|
2857
|
+
const result = { objects: [] };
|
|
2858
|
+
for (const obj of this.objects.values()) {
|
|
2859
|
+
const methods = [];
|
|
2860
|
+
for (const [methodName, method] of Object.entries(obj.methods)) {
|
|
2861
|
+
let auth = "public";
|
|
2862
|
+
if (method.auth === true) auth = "user";
|
|
2863
|
+
else if (method.auth && typeof method.auth === "object" && "roles" in method.auth) {
|
|
2864
|
+
auth = { roles: method.auth.roles };
|
|
2865
|
+
}
|
|
2866
|
+
methods.push({
|
|
2867
|
+
name: methodName,
|
|
2868
|
+
fullName: `${obj.name}.${methodName}`,
|
|
2869
|
+
description: method.description ?? null,
|
|
2870
|
+
hasParams: method.params !== void 0,
|
|
2871
|
+
auth,
|
|
2872
|
+
internal: method.internal === true,
|
|
2873
|
+
meta: method.meta ?? {}
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
result.objects.push({
|
|
2877
|
+
name: obj.name,
|
|
2878
|
+
description: obj.description ?? null,
|
|
2879
|
+
meta: obj.meta ?? {},
|
|
2880
|
+
methods
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
return result;
|
|
2884
|
+
}
|
|
2885
|
+
};
|
|
2886
|
+
|
|
2887
|
+
// src/cli.ts
|
|
2888
|
+
async function prepare(cwd) {
|
|
2889
|
+
const config = await loadConfig(cwd);
|
|
2890
|
+
const clientDir = path10.resolve(cwd, config.clientDir);
|
|
2891
|
+
const scanResult = await scan(clientDir);
|
|
2892
|
+
const routeTree = buildRouteTree(
|
|
2893
|
+
scanResult.routeModules,
|
|
2894
|
+
scanResult.loaderModules,
|
|
2895
|
+
config
|
|
2896
|
+
);
|
|
2897
|
+
const manifest = buildManifest(scanResult, config, clientDir);
|
|
2898
|
+
__initResolver({ });
|
|
2899
|
+
const globalRegistry = {};
|
|
2900
|
+
const sources = /* @__PURE__ */ new Map();
|
|
2901
|
+
for (const { file, map } of scanResult.loaderModules) {
|
|
2902
|
+
for (const [name, def] of Object.entries(map)) {
|
|
2903
|
+
const existing = sources.get(name);
|
|
2904
|
+
if (existing) {
|
|
2905
|
+
throw new NixoError(
|
|
2906
|
+
`Global loader "${name}" declared in both "${existing}" and "${file}" (A3)`
|
|
2907
|
+
);
|
|
2908
|
+
}
|
|
2909
|
+
globalRegistry[name] = def;
|
|
2910
|
+
sources.set(name, file);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
const factoryModules = await collectFactoryModules(
|
|
2914
|
+
cwd,
|
|
2915
|
+
routeTree,
|
|
2916
|
+
scanResult,
|
|
2917
|
+
clientDir
|
|
2918
|
+
);
|
|
2919
|
+
const serverDir = path10.resolve(cwd, config.serverDir);
|
|
2920
|
+
let rpcRegistry = null;
|
|
2921
|
+
if (config.rpc.enabled) {
|
|
2922
|
+
const rpcObjects = await scanRpcObjects(serverDir);
|
|
2923
|
+
rpcRegistry = new RpcRegistry();
|
|
2924
|
+
for (const obj of rpcObjects) rpcRegistry.add(obj);
|
|
2925
|
+
}
|
|
2926
|
+
return {
|
|
2927
|
+
config,
|
|
2928
|
+
clientDir,
|
|
2929
|
+
scanResult,
|
|
2930
|
+
routeTree,
|
|
2931
|
+
manifest,
|
|
2932
|
+
globalRegistry,
|
|
2933
|
+
factoryModules,
|
|
2934
|
+
rpcRegistry
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
async function inspect() {
|
|
2938
|
+
const cwd = process.cwd();
|
|
2939
|
+
const { routeTree, manifest, rpcRegistry } = await prepare(cwd);
|
|
2940
|
+
console.log("Routes:");
|
|
2941
|
+
if (routeTree.byId.size === 0) {
|
|
2942
|
+
console.log(" (no routes found)");
|
|
2943
|
+
} else {
|
|
2944
|
+
for (const [id, routePath] of routeTree.paths) {
|
|
2945
|
+
const marker = id === routeTree.indexRouteId ? " [index]" : "";
|
|
2946
|
+
console.log(` ${routePath.padEnd(30)} (${id})${marker}`);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
console.log("\nRPC methods:");
|
|
2950
|
+
if (!rpcRegistry || rpcRegistry.size === 0) {
|
|
2951
|
+
console.log(" (none)");
|
|
2952
|
+
} else {
|
|
2953
|
+
for (const name of rpcRegistry.listMethods()) {
|
|
2954
|
+
const m = rpcRegistry.getMethod(name);
|
|
2955
|
+
const desc = m.method.description ? ` // ${m.method.description}` : "";
|
|
2956
|
+
const internal = m.method.internal ? " [internal]" : "";
|
|
2957
|
+
console.log(` ${name}${internal}${desc}`);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
console.log("\nManifest:");
|
|
2961
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
2962
|
+
}
|
|
2963
|
+
async function dev() {
|
|
2964
|
+
const cwd = process.cwd();
|
|
2965
|
+
const prepared = await prepare(cwd);
|
|
2966
|
+
const { createNixoServer: createNixoServer2 } = await Promise.resolve().then(() => (init_create_server(), create_server_exports));
|
|
2967
|
+
const server = await createNixoServer2({
|
|
2968
|
+
config: prepared.config,
|
|
2969
|
+
projectDir: cwd,
|
|
2970
|
+
routeTree: prepared.routeTree,
|
|
2971
|
+
manifest: prepared.manifest,
|
|
2972
|
+
globalRegistry: prepared.globalRegistry,
|
|
2973
|
+
factoryModules: prepared.factoryModules,
|
|
2974
|
+
rpcRegistry: prepared.rpcRegistry,
|
|
2975
|
+
mode: "development"
|
|
2976
|
+
});
|
|
2977
|
+
await server.listen({
|
|
2978
|
+
port: prepared.config.server.port,
|
|
2979
|
+
host: prepared.config.server.host
|
|
2980
|
+
});
|
|
2981
|
+
console.log(
|
|
2982
|
+
`
|
|
2983
|
+
nixo dev: http://${prepared.config.server.host}:${prepared.config.server.port}
|
|
2984
|
+
`
|
|
2985
|
+
);
|
|
2986
|
+
}
|
|
2987
|
+
async function buildCmd() {
|
|
2988
|
+
const cwd = process.cwd();
|
|
2989
|
+
const prepared = await prepare(cwd);
|
|
2990
|
+
const { buildProject: buildProject2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
2991
|
+
await buildProject2({
|
|
2992
|
+
projectDir: cwd,
|
|
2993
|
+
config: prepared.config,
|
|
2994
|
+
routeTree: prepared.routeTree,
|
|
2995
|
+
scanResult: prepared.scanResult,
|
|
2996
|
+
factoryModules: prepared.factoryModules,
|
|
2997
|
+
globalRegistry: prepared.globalRegistry
|
|
2998
|
+
});
|
|
2999
|
+
console.log(`
|
|
3000
|
+
nixo build: done (outDir=${prepared.config.build.outDir})
|
|
3001
|
+
`);
|
|
3002
|
+
}
|
|
3003
|
+
async function start() {
|
|
3004
|
+
const cwd = process.cwd();
|
|
3005
|
+
const prepared = await prepare(cwd);
|
|
3006
|
+
const { createNixoServer: createNixoServer2 } = await Promise.resolve().then(() => (init_create_server(), create_server_exports));
|
|
3007
|
+
const server = await createNixoServer2({
|
|
3008
|
+
config: prepared.config,
|
|
3009
|
+
projectDir: cwd,
|
|
3010
|
+
routeTree: prepared.routeTree,
|
|
3011
|
+
manifest: prepared.manifest,
|
|
3012
|
+
globalRegistry: prepared.globalRegistry,
|
|
3013
|
+
factoryModules: prepared.factoryModules,
|
|
3014
|
+
rpcRegistry: prepared.rpcRegistry,
|
|
3015
|
+
mode: "production"
|
|
3016
|
+
});
|
|
3017
|
+
await server.listen({
|
|
3018
|
+
port: prepared.config.server.port,
|
|
3019
|
+
host: prepared.config.server.host
|
|
3020
|
+
});
|
|
3021
|
+
console.log(
|
|
3022
|
+
`
|
|
3023
|
+
nixo start: http://${prepared.config.server.host}:${prepared.config.server.port}
|
|
3024
|
+
`
|
|
3025
|
+
);
|
|
3026
|
+
}
|
|
3027
|
+
async function main() {
|
|
3028
|
+
const command = process.argv[2] ?? "help";
|
|
3029
|
+
switch (command) {
|
|
3030
|
+
case "inspect":
|
|
3031
|
+
await inspect();
|
|
3032
|
+
break;
|
|
3033
|
+
case "dev":
|
|
3034
|
+
await dev();
|
|
3035
|
+
break;
|
|
3036
|
+
case "build":
|
|
3037
|
+
await buildCmd();
|
|
3038
|
+
break;
|
|
3039
|
+
case "start":
|
|
3040
|
+
await start();
|
|
3041
|
+
break;
|
|
3042
|
+
case "help":
|
|
3043
|
+
default:
|
|
3044
|
+
console.log(`nixo <command>
|
|
3045
|
+
|
|
3046
|
+
Commands:
|
|
3047
|
+
dev Start dev server (Fastify + Vite)
|
|
3048
|
+
build Build for production (client + server)
|
|
3049
|
+
start Start production server
|
|
3050
|
+
inspect Dump routes and manifest
|
|
3051
|
+
`);
|
|
3052
|
+
break;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
main().catch((err) => {
|
|
3056
|
+
console.error(err instanceof Error ? err.message : err);
|
|
3057
|
+
process.exit(1);
|
|
3058
|
+
});
|
|
3059
|
+
//# sourceMappingURL=cli.js.map
|
|
3060
|
+
//# sourceMappingURL=cli.js.map
|