@noego/forge 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +5 -1
  2. package/dist/client.cjs +1 -1
  3. package/dist/client.cjs.map +1 -1
  4. package/dist/client.d.ts +5 -0
  5. package/dist/client.mjs +32 -32
  6. package/dist/client.mjs.map +1 -1
  7. package/dist/index.plugins.cjs +9 -0
  8. package/dist/index.plugins.cjs.map +1 -0
  9. package/dist/index.plugins.d.ts +2 -0
  10. package/dist/index.plugins.js +2 -0
  11. package/dist/index.plugins.js.map +1 -0
  12. package/dist/page.mjs +1 -1
  13. package/dist/{page.svelte-C4chAYK2.js → page.svelte-Bq1Q01H0.js} +2 -7
  14. package/dist/{page.svelte-C4chAYK2.js.map → page.svelte-Bq1Q01H0.js.map} +1 -1
  15. package/dist/page.svelte-Dvj7306U.cjs.map +1 -1
  16. package/dist/plugins/serverOnlyStub.cjs +226 -0
  17. package/dist/plugins/serverOnlyStub.cjs.map +1 -0
  18. package/dist/plugins/serverOnlyStub.js +200 -0
  19. package/dist/plugins/serverOnlyStub.js.map +1 -0
  20. package/dist/shared.mjs +1 -1
  21. package/dist/stubs/server-only.d.ts +3 -0
  22. package/dist/stubs/server-only.js +7 -0
  23. package/dist/stubs/server-only.js.map +1 -0
  24. package/dist-ssr/{path-ODk1FhWY.js → path-9twSsimy.js} +5 -2
  25. package/dist-ssr/{path-ODk1FhWY.js.map → path-9twSsimy.js.map} +1 -1
  26. package/dist-ssr/{path-CyGuWUeq.cjs → path-Dm_4PXDW.cjs} +5 -2
  27. package/dist-ssr/{path-CyGuWUeq.cjs.map → path-Dm_4PXDW.cjs.map} +1 -1
  28. package/dist-ssr/server.cjs +250 -74
  29. package/dist-ssr/server.cjs.map +1 -1
  30. package/dist-ssr/server.d.ts +6 -1
  31. package/dist-ssr/server.js +245 -69
  32. package/dist-ssr/server.js.map +1 -1
  33. package/dist-ssr/shared.cjs +2 -7
  34. package/dist-ssr/shared.cjs.map +1 -1
  35. package/dist-ssr/shared.js +2 -7
  36. package/dist-ssr/shared.js.map +1 -1
  37. package/package.json +12 -4
@@ -10,7 +10,8 @@ import fs$1 from "fs/promises";
10
10
  import yaml from "js-yaml";
11
11
  import "clone-deep";
12
12
  import join from "url-join";
13
- import { p as parsePathConfig } from "./path-ODk1FhWY.js";
13
+ import { p as parsePathConfig } from "./path-9twSsimy.js";
14
+ import { pathToFileURL } from "url";
14
15
  import { render } from "svelte/server";
15
16
  import pathToRegex from "path-to-regex";
16
17
  import ApplicationRenderer from "../src/components/RecursiveRender.svelte";
@@ -31,12 +32,15 @@ class ViteComponentLoader {
31
32
  absoluteComponentPath = componentPath;
32
33
  }
33
34
  }
35
+ console.log(`[ViteComponentLoader] Loading component from path: ${absoluteComponentPath}`);
34
36
  let vitePath = path.relative(process.cwd(), absoluteComponentPath);
35
37
  vitePath = vitePath.replace(/\\/g, "/");
36
38
  if (!vitePath.startsWith("/")) {
37
39
  vitePath = "/" + vitePath;
38
40
  }
41
+ console.log(`[ViteComponentLoader] Resolved Vite path: ${vitePath} from componentPath: ${componentPath}`);
39
42
  try {
43
+ console.log(`[ViteComponentLoader] Loading module for vitePath: ${vitePath}`);
40
44
  const module = await this.vite.ssrLoadModule(vitePath);
41
45
  console.log(`[ViteComponentLoader] Module loaded successfully for: ${vitePath}`);
42
46
  if (!module || !module.default) {
@@ -114,6 +118,23 @@ class LiveHTMLRender {
114
118
  return file_content;
115
119
  }
116
120
  }
121
+ const HTTP_METHODS = /* @__PURE__ */ new Set([
122
+ "get",
123
+ "post",
124
+ "put",
125
+ "delete",
126
+ "patch",
127
+ "head",
128
+ "options",
129
+ "trace"
130
+ ]);
131
+ function getGlobalPathsMiddleware(openapi) {
132
+ if (!openapi || !openapi.paths) {
133
+ return [];
134
+ }
135
+ const value = openapi.paths["x-middleware"];
136
+ return Array.isArray(value) ? [...value] : [];
137
+ }
117
138
  function parseConfigfile(file_content) {
118
139
  let config = null;
119
140
  try {
@@ -123,7 +144,7 @@ function parseConfigfile(file_content) {
123
144
  }
124
145
  return config;
125
146
  }
126
- function parse_modules(openapi) {
147
+ function parse_modules(openapi, inheritedMiddleware = []) {
127
148
  const modules_config = openapi.modules || openapi.module;
128
149
  if (!modules_config) {
129
150
  return;
@@ -131,16 +152,20 @@ function parse_modules(openapi) {
131
152
  const modules = Object.entries(modules_config).map(([_, module]) => {
132
153
  return module;
133
154
  });
155
+ const globalMiddleware = [...inheritedMiddleware, ...getGlobalPathsMiddleware(openapi)];
134
156
  const modules_path = modules.map((module) => {
135
157
  const basePath = module.basePath || "";
136
158
  const baseLayouts = module.baseLayouts || [];
159
+ const moduleMiddleware = Array.isArray(module["x-middleware"]) ? module["x-middleware"] : [];
160
+ const inherited = [...globalMiddleware, ...moduleMiddleware];
137
161
  const paths = module.paths;
138
162
  if (!paths) {
139
163
  return;
140
164
  }
141
- const configurations = Object.entries(paths).map(([path2, method_config]) => {
142
- return Object.entries(method_config).map(([method, config]) => {
143
- return parsePathConfig(path2, method, config);
165
+ const configurations = Object.entries(paths).filter(([path2]) => typeof path2 === "string" && path2.startsWith("/")).map(([path2, method_config]) => {
166
+ return Object.entries(method_config).filter(([method]) => HTTP_METHODS.has(String(method).toLowerCase())).map(([method, config]) => {
167
+ const methodName = String(method);
168
+ return parsePathConfig(path2, methodName, config, inherited);
144
169
  });
145
170
  });
146
171
  const routes = configurations.reduce((flat_config, config) => {
@@ -154,19 +179,24 @@ function parse_modules(openapi) {
154
179
  });
155
180
  return modules_path.reduce(
156
181
  (flat_config, config) => {
182
+ if (!config) {
183
+ return flat_config;
184
+ }
157
185
  return flat_config.concat(config);
158
186
  },
159
187
  []
160
188
  );
161
189
  }
162
- function parse_paths(openapi) {
190
+ function parse_paths(openapi, inheritedMiddleware = []) {
163
191
  const paths = openapi.paths;
164
192
  if (!paths) {
165
193
  return;
166
194
  }
167
- const configurations = Object.entries(paths).map(([path2, method_config]) => {
168
- return Object.entries(method_config).map(([method, config]) => {
169
- return parsePathConfig(path2, method, config);
195
+ const globalMiddleware = [...inheritedMiddleware, ...getGlobalPathsMiddleware(openapi)];
196
+ const configurations = Object.entries(paths).filter(([path2]) => typeof path2 === "string" && path2.startsWith("/")).map(([path2, method_config]) => {
197
+ return Object.entries(method_config).filter(([method]) => HTTP_METHODS.has(String(method).toLowerCase())).map(([method, config]) => {
198
+ const methodName = String(method);
199
+ return parsePathConfig(path2, methodName, config, globalMiddleware);
170
200
  });
171
201
  });
172
202
  const routes = configurations.reduce((flat_config, config) => {
@@ -206,13 +236,16 @@ async function parse_openapi_config(openapi_config_path) {
206
236
  } else {
207
237
  throw new Error(`Invalid OpenAPI or stitch configuration file: ${openapi_config_path}. File must contain either 'stitch', 'paths', 'module', or 'modules' at the root level.`);
208
238
  }
239
+ const globalPathsMiddleware = getGlobalPathsMiddleware(openapi_config);
209
240
  let modules = parse_modules(openapi_config) || [];
210
241
  let paths = parse_paths(openapi_config) || [];
211
242
  let fallback_layout = openapi_config["x-fallback-layout"] || null;
212
243
  let fallback_view = openapi_config["x-fallback-view"] || null;
244
+ const fallback_middleware = Array.isArray(openapi_config["x-fallback-middleware"]) ? [...openapi_config["x-fallback-middleware"]] : [...globalPathsMiddleware];
213
245
  let fallback = {
214
246
  layout: fallback_layout ? [fallback_layout] : [],
215
- view: fallback_view
247
+ view: fallback_view,
248
+ middleware: fallback_middleware
216
249
  };
217
250
  const routes = [...modules, ...paths];
218
251
  let config = {
@@ -238,7 +271,8 @@ const defaultOptions = {
238
271
  build_dir: "dist_ssr",
239
272
  renderer: "default",
240
273
  open_api_path: path.join(process.cwd(), "openapi.yaml"),
241
- manifest_endpoint: "/manifest.json"
274
+ manifest_endpoint: "/manifest.json",
275
+ middleware_path: void 0
242
276
  };
243
277
  class ManifestBuilder {
244
278
  constructor(openapi_path, loader, options) {
@@ -378,61 +412,186 @@ class ComponentManager {
378
412
  return view.default;
379
413
  }
380
414
  }
415
+ const EXTENSION_CANDIDATES = [".js", ".mjs", ".cjs", ".ts", ".tsx"];
381
416
  class MiddlewareAdapter {
382
- constructor(manager) {
383
- this.manager = manager;
417
+ constructor(middlewarePath, fallbackPath) {
418
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
419
+ this.middlewarePath = middlewarePath;
420
+ this.fallbackPath = fallbackPath;
384
421
  }
385
- async handleMiddleware(req, reply, route, request_data, callback) {
386
- let components = await this.manager.getLayoutComponents(route);
387
- let middleware_stack = await Promise.all(components.map(async (component) => {
388
- const middleware = [...component.middleware || [], ...component.middlewares || []];
389
- return middleware;
390
- }));
391
- const layout_middlewares = middleware_stack.flat() || [];
392
- console.log("middleware stack loaded", layout_middlewares);
393
- req.params = request_data.params;
394
- await new Promise((resolve) => {
395
- this.runMiddleware(layout_middlewares, req, reply, resolve);
396
- });
397
- console.log("layout middleware executed");
398
- console.log("view middleware loaded");
399
- console.log("view middleware", route.view);
400
- let view = {};
401
- if (route.view) {
402
- view = await this.manager.getViewComponent(route);
403
- }
404
- let view_middlewares = [...view.middleware || [], ...view.middlewares || []];
405
- await new Promise((resolve) => {
406
- this.runMiddleware(view_middlewares, req, reply, resolve);
407
- });
408
- callback();
422
+ static build(middlewarePath, fallbackPath) {
423
+ return new MiddlewareAdapter(middlewarePath, fallbackPath);
409
424
  }
410
- runMiddleware(middleware, req, reply, callback) {
411
- middleware = this.dedupe(middleware);
412
- let index = 0;
413
- if (middleware.length === 0) {
414
- callback();
425
+ async handleMiddleware(req, res, middlewareConfig = []) {
426
+ if (!Array.isArray(middlewareConfig) || middlewareConfig.length === 0) {
415
427
  return;
416
428
  }
417
- let next = () => {
418
- if (index >= middleware.length) {
419
- callback();
420
- return;
429
+ const resolution = await this.resolveMiddlewareFunctions(middlewareConfig);
430
+ console.log(resolution);
431
+ if (resolution.errors.length > 0) {
432
+ throw new Error(`Middleware resolution failed: ${resolution.errors.join(", ")}`);
433
+ }
434
+ for (const middleware of resolution.functions) {
435
+ await this.executeMiddleware(middleware, req, res);
436
+ if (res.headersSent) {
437
+ break;
421
438
  }
422
- const mw = middleware[index];
423
- index++;
424
- if (typeof mw === "function") {
425
- if (!reply.sent)
426
- mw(req, reply, next);
427
- } else {
428
- if (!reply.sent)
429
- next();
439
+ }
440
+ }
441
+ async resolveMiddlewareFunctions(middlewareConfig) {
442
+ const functions = [];
443
+ const errors = [];
444
+ for (const spec of middlewareConfig) {
445
+ if (!spec) {
446
+ continue;
430
447
  }
448
+ try {
449
+ let resolved = this.cache.get(spec);
450
+ if (!resolved) {
451
+ resolved = await this.resolveMiddlewareSpec(spec);
452
+ this.cache.set(spec, [...resolved]);
453
+ }
454
+ functions.push(...resolved);
455
+ } catch (error) {
456
+ errors.push(`${spec}: ${error.message}`);
457
+ }
458
+ }
459
+ return {
460
+ functions: this.dedupe(functions),
461
+ errors
431
462
  };
432
- next();
433
463
  }
434
- dedupe(middlewares) {
435
- return Array.from(new Set(middlewares));
464
+ async resolveMiddlewareSpec(spec) {
465
+ const [filePath, selector] = this.parseMiddlewareSpec(spec);
466
+ const fullPath = this.resolveMiddlewareFilePath(filePath);
467
+ const module = await this.importMiddlewareModule(fullPath, spec);
468
+ return this.extractMiddlewareFunctions(module, selector, spec);
469
+ }
470
+ parseMiddlewareSpec(spec) {
471
+ const colonIndex = spec.indexOf(":");
472
+ if (colonIndex === -1) {
473
+ return [spec, void 0];
474
+ }
475
+ return [spec.substring(0, colonIndex), spec.substring(colonIndex + 1)];
476
+ }
477
+ resolveMiddlewareFilePath(filePath) {
478
+ const basePath = this.middlewarePath || this.fallbackPath || process.cwd();
479
+ const transformed = this.middlewareFileTransform(filePath);
480
+ const baseCandidate = path.resolve(process.cwd(), path.join(basePath, transformed));
481
+ const resolvedWithExt = this.findExistingFile(baseCandidate);
482
+ if (resolvedWithExt) {
483
+ return resolvedWithExt;
484
+ }
485
+ throw new Error(`Middleware file not found: ${transformed}`);
486
+ }
487
+ middlewareFileTransform(filePath) {
488
+ return filePath.split(".").join(path.sep);
489
+ }
490
+ findExistingFile(basePathWithoutExt) {
491
+ const ext = path.extname(basePathWithoutExt);
492
+ if (ext) {
493
+ return fs.existsSync(basePathWithoutExt) ? basePathWithoutExt : null;
494
+ }
495
+ const directCandidates = EXTENSION_CANDIDATES.map((extCandidate) => `${basePathWithoutExt}${extCandidate}`);
496
+ for (const candidate of directCandidates) {
497
+ if (fs.existsSync(candidate)) {
498
+ return candidate;
499
+ }
500
+ }
501
+ const indexCandidates = EXTENSION_CANDIDATES.map((extCandidate) => path.join(basePathWithoutExt, `index${extCandidate}`));
502
+ for (const candidate of indexCandidates) {
503
+ if (fs.existsSync(candidate)) {
504
+ return candidate;
505
+ }
506
+ }
507
+ return null;
508
+ }
509
+ async importMiddlewareModule(fullPath, spec) {
510
+ try {
511
+ const url = pathToFileURL(fullPath).href;
512
+ return await import(url);
513
+ } catch (error) {
514
+ throw new Error(`Unable to import middleware '${spec}' from ${fullPath}`);
515
+ }
516
+ }
517
+ extractMiddlewareFunctions(module, selector, spec) {
518
+ if (!selector) {
519
+ return this.extractDefaultFunction(module, spec);
520
+ }
521
+ if (selector === "*") {
522
+ return this.extractAllFunctions(module, spec);
523
+ }
524
+ return this.extractSpecificFunctions(module, selector, spec);
525
+ }
526
+ extractDefaultFunction(module, spec) {
527
+ if (!(module == null ? void 0 : module.default)) {
528
+ throw new Error(`Middleware '${spec}' is missing a default export`);
529
+ }
530
+ if (typeof module.default !== "function") {
531
+ throw new Error(`Default export for middleware '${spec}' is not a function`);
532
+ }
533
+ return [module.default];
534
+ }
535
+ extractAllFunctions(module, spec) {
536
+ const functions = Object.values(module).filter((value) => typeof value === "function");
537
+ if (functions.length === 0) {
538
+ throw new Error(`No functions exported by middleware '${spec}'`);
539
+ }
540
+ return functions;
541
+ }
542
+ extractSpecificFunctions(module, selector, spec) {
543
+ const names = selector.split(",").map((name) => name.trim()).filter(Boolean);
544
+ const functions = [];
545
+ const missing = [];
546
+ const invalid = [];
547
+ for (const name of names) {
548
+ const value = name === "default" ? module == null ? void 0 : module.default : module == null ? void 0 : module[name];
549
+ if (value === void 0) {
550
+ missing.push(name);
551
+ continue;
552
+ }
553
+ if (typeof value !== "function") {
554
+ invalid.push(name);
555
+ continue;
556
+ }
557
+ functions.push(value);
558
+ }
559
+ if (missing.length) {
560
+ throw new Error(`Middleware '${spec}' is missing export(s): ${missing.join(", ")}`);
561
+ }
562
+ if (invalid.length) {
563
+ throw new Error(`Middleware '${spec}' export(s) not functions: ${invalid.join(", ")}`);
564
+ }
565
+ if (functions.length === 0) {
566
+ throw new Error(`Middleware '${spec}' did not resolve any functions for selector '${selector}'`);
567
+ }
568
+ return functions;
569
+ }
570
+ executeMiddleware(fn, req, res) {
571
+ return new Promise((resolve, reject) => {
572
+ try {
573
+ fn(req, res, (error) => {
574
+ if (error) {
575
+ reject(error);
576
+ return;
577
+ }
578
+ resolve();
579
+ });
580
+ } catch (error) {
581
+ reject(error);
582
+ }
583
+ });
584
+ }
585
+ dedupe(functions) {
586
+ const seen = /* @__PURE__ */ new Set();
587
+ const ordered = [];
588
+ for (const fn of functions) {
589
+ if (!seen.has(fn)) {
590
+ seen.add(fn);
591
+ ordered.push(fn);
592
+ }
593
+ }
594
+ return ordered;
436
595
  }
437
596
  }
438
597
  function makeUrlParser(pattern) {
@@ -452,13 +611,14 @@ function initialize_route_matchers(routes) {
452
611
  class ServerAdapter {
453
612
  }
454
613
  class ExpressServerAdapter extends ServerAdapter {
455
- constructor(server, manager, htmlRender, api_adapter, middleware_adapter) {
614
+ constructor(server, manager, htmlRender, api_adapter, middleware_adapter, context_builder) {
456
615
  super();
457
616
  this.server = server;
458
617
  this.manager = manager;
459
618
  this.htmlRender = htmlRender;
460
619
  this.api_adapter = api_adapter;
461
620
  this.middleware_adapter = middleware_adapter;
621
+ this.context_builder = context_builder;
462
622
  }
463
623
  async handleRoutes(routes, manifest) {
464
624
  const matchers = initialize_route_matchers(routes);
@@ -498,7 +658,12 @@ class ExpressServerAdapter extends ServerAdapter {
498
658
  var _a, _b;
499
659
  let accepts = req.headers.accept || "";
500
660
  let server_api_call = accepts.toLowerCase().includes("application/json");
661
+ console.log(JSON.stringify(route, null, 2));
501
662
  const context = {};
663
+ if (this.context_builder) {
664
+ const built_context = await this.context_builder(req, res);
665
+ Object.assign(context, built_context);
666
+ }
502
667
  req["context"] = context;
503
668
  const cleanPath = url.split("?")[0];
504
669
  const pathParams = matcher.parser(cleanPath);
@@ -522,9 +687,7 @@ class ExpressServerAdapter extends ServerAdapter {
522
687
  context
523
688
  };
524
689
  try {
525
- await new Promise((resolve, reject) => {
526
- this.middleware_adapter.handleMiddleware(req, res, route, request_data, resolve).catch(reject);
527
- });
690
+ await this.middleware_adapter.handleMiddleware(req, res, route.middleware || []);
528
691
  } catch (e) {
529
692
  console.log("Error in middleware", e);
530
693
  res.status(500).send(`
@@ -625,9 +788,16 @@ view: ${route.view}</pre>`;
625
788
  context
626
789
  };
627
790
  let request_data = fallback_data;
628
- await new Promise((resolve) => {
629
- this.middleware_adapter.handleMiddleware(req, res, route, request_data, resolve);
630
- });
791
+ try {
792
+ await this.middleware_adapter.handleMiddleware(req, res, route.middleware || []);
793
+ } catch (e) {
794
+ console.log("Error in fallback middleware", e);
795
+ res.status(500).send(`
796
+ <h1>500 Internal Server Error</h1>
797
+ <pre>${e}</pre>
798
+ `);
799
+ return;
800
+ }
631
801
  console.log("Completed fallback middleware");
632
802
  let server_data = await this.api_adapter.getApiData(route, request_data);
633
803
  console.log("Completed fallback API call");
@@ -662,11 +832,11 @@ async function createServer(app, options) {
662
832
  full_options.open_api_path = ensureFullPath(full_options.viteOptions.root, full_options.open_api_path);
663
833
  const isProd = full_options.development === false;
664
834
  console.log(`Running in ${isProd ? "production" : "development"} mode`);
665
- const COMPONENT_DIR = path.join(full_options.viteOptions.root, full_options.component_dir);
835
+ const COMPONENT_DIR = !full_options.component_dir ? full_options.viteOptions.root : path.join(full_options.viteOptions.root, full_options.component_dir);
666
836
  const root = full_options.viteOptions.root;
667
837
  let componentLoader;
668
838
  let vite;
669
- console.log(`Serving components from ${COMPONENT_DIR} at /components/`);
839
+ console.log(`Serving components from ${COMPONENT_DIR}`);
670
840
  if (full_options.assets) {
671
841
  for (const [asset_path, asset_dir] of Object.entries(full_options.assets)) {
672
842
  for (const asset of asset_dir) {
@@ -682,8 +852,9 @@ async function createServer(app, options) {
682
852
  console.log("Starting Vite in development mode...");
683
853
  const { createServer: createViteServer } = await import("vite");
684
854
  vite = await createViteServer(options.viteOptions || full_options.viteOptions);
855
+ console.log(`Vite server created options
856
+ ${JSON.stringify(options, null, 2)}`);
685
857
  componentLoader = new ViteComponentLoader(COMPONENT_DIR, vite);
686
- console.log("loaded");
687
858
  } else {
688
859
  console.log("Starting Vite in production mode...");
689
860
  app.use("/", express.static(path.join(options.viteOptions.root, full_options.build_dir)));
@@ -700,13 +871,18 @@ async function createServer(app, options) {
700
871
  const routeDefs = server_config.routes;
701
872
  const manager = new ComponentManager(componentLoader);
702
873
  const api_adapter = new ApiAdapter(manager);
703
- const middleware_adapter = new MiddlewareAdapter(manager);
874
+ let middlewareRoot;
875
+ if (full_options.middleware_path) {
876
+ middlewareRoot = path.isAbsolute(full_options.middleware_path) ? full_options.middleware_path : path.resolve(full_options.viteOptions.root, full_options.middleware_path);
877
+ }
878
+ const middleware_adapter = MiddlewareAdapter.build(middlewareRoot, COMPONENT_DIR);
704
879
  const adapter = new ExpressServerAdapter(
705
880
  app,
706
881
  manager,
707
882
  htmlRenderer,
708
883
  api_adapter,
709
- middleware_adapter
884
+ middleware_adapter,
885
+ full_options.context_builder
710
886
  );
711
887
  await adapter.handleRoutes(routeDefs, manifest);
712
888
  console.log("Routes registered");