@_linked/server 2.0.3 → 2.1.1

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 (69) hide show
  1. package/.github/workflows/publish.yml +13 -1
  2. package/CHANGELOG.md +32 -0
  3. package/lib/esm/index.d.ts +1 -1
  4. package/lib/esm/index.js +1 -1
  5. package/lib/esm/index.js.map +1 -1
  6. package/lib/esm/ontologies/lincd-server.d.ts +42 -25
  7. package/lib/esm/shapes/LincdAPI.d.ts +3 -3
  8. package/lib/esm/shapes/LincdWebApp.d.ts +1 -1
  9. package/lib/esm/shapes/{LincdServer.d.ts → LinkedServer.d.ts} +30 -2
  10. package/lib/esm/shapes/{LincdServer.js → LinkedServer.js} +444 -158
  11. package/lib/esm/shapes/LinkedServer.js.map +1 -0
  12. package/lib/esm/shapes/quadstores/BackendAPIStore.d.ts +1 -1
  13. package/lib/esm/shapes/quadstores/BackendAPIStore.js +7 -7
  14. package/lib/esm/shapes/quadstores/BackendAPIStore.js.map +1 -1
  15. package/lib/esm/shapes/quadstores/BackendAPIStoreProvider.d.ts +10 -5
  16. package/lib/esm/shapes/quadstores/BackendAPIStoreProvider.js +15 -9
  17. package/lib/esm/shapes/quadstores/BackendAPIStoreProvider.js.map +1 -1
  18. package/lib/esm/utils/Shapes.d.ts +1 -1
  19. package/lib/esm/utils/Shapes.js +4 -3
  20. package/lib/esm/utils/Shapes.js.map +1 -1
  21. package/package.json +15 -17
  22. package/readme.md +100 -0
  23. package/tsconfig-esm.json +1 -1
  24. package/lib/cjs/backend.d.ts +0 -6
  25. package/lib/cjs/backend.js +0 -58
  26. package/lib/cjs/backend.js.map +0 -1
  27. package/lib/cjs/data/lincd-server.json +0 -47
  28. package/lib/cjs/index.d.ts +0 -9
  29. package/lib/cjs/index.js +0 -13
  30. package/lib/cjs/index.js.map +0 -1
  31. package/lib/cjs/ontologies/lincd-server.d.ts +0 -31
  32. package/lib/cjs/ontologies/lincd-server.js +0 -86
  33. package/lib/cjs/ontologies/lincd-server.js.map +0 -1
  34. package/lib/cjs/package.d.ts +0 -4
  35. package/lib/cjs/package.js +0 -7
  36. package/lib/cjs/package.js.map +0 -1
  37. package/lib/cjs/package.json +0 -90
  38. package/lib/cjs/shapes/LincdAPI.d.ts +0 -41
  39. package/lib/cjs/shapes/LincdAPI.js +0 -124
  40. package/lib/cjs/shapes/LincdAPI.js.map +0 -1
  41. package/lib/cjs/shapes/LincdServer.d.ts +0 -79
  42. package/lib/cjs/shapes/LincdServer.js +0 -1339
  43. package/lib/cjs/shapes/LincdServer.js.map +0 -1
  44. package/lib/cjs/shapes/LincdWebApp.d.ts +0 -7
  45. package/lib/cjs/shapes/LincdWebApp.js +0 -43
  46. package/lib/cjs/shapes/LincdWebApp.js.map +0 -1
  47. package/lib/cjs/shapes/filestores/LocalFileStore.d.ts +0 -42
  48. package/lib/cjs/shapes/filestores/LocalFileStore.js +0 -146
  49. package/lib/cjs/shapes/filestores/LocalFileStore.js.map +0 -1
  50. package/lib/cjs/shapes/quadstores/BackendAPIStore.d.ts +0 -33
  51. package/lib/cjs/shapes/quadstores/BackendAPIStore.js +0 -79
  52. package/lib/cjs/shapes/quadstores/BackendAPIStore.js.map +0 -1
  53. package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.d.ts +0 -13
  54. package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.js +0 -30
  55. package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.js.map +0 -1
  56. package/lib/cjs/types/RouteConfig.d.ts +0 -56
  57. package/lib/cjs/types/RouteConfig.js +0 -3
  58. package/lib/cjs/types/RouteConfig.js.map +0 -1
  59. package/lib/cjs/types.d.ts +0 -12
  60. package/lib/cjs/types.js +0 -1
  61. package/lib/cjs/types.js.map +0 -1
  62. package/lib/cjs/utils/Shapes.d.ts +0 -7
  63. package/lib/cjs/utils/Shapes.js +0 -76
  64. package/lib/cjs/utils/Shapes.js.map +0 -1
  65. package/lib/cjs/utils/accessUrl.d.ts +0 -6
  66. package/lib/cjs/utils/accessUrl.js +0 -13
  67. package/lib/cjs/utils/accessUrl.js.map +0 -1
  68. package/lib/esm/package.json +0 -90
  69. package/lib/esm/shapes/LincdServer.js.map +0 -1
@@ -9,20 +9,23 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  import chalk from 'chalk';
12
+ import { timingSafeEqual } from 'crypto';
12
13
  import events from 'events';
13
14
  import express from 'express';
14
15
  import fetchCookie from 'fetch-cookie';
15
16
  import * as fsNative from 'fs';
16
17
  import * as fs from 'fs/promises';
17
- import { getWebpackAppConfig } from '@_linked/cli/config-webpack-app';
18
- import { getLincdPackages } from '@_linked/cli/cli-methods';
18
+ // Plan-011: imports from @_linked/cli now go through ./lifecycle (a
19
+ // dynamic-import-free file) so Vite's SSR module graph doesn't pull
20
+ // in the legacy webpack flow and emit dozens of unanalyzable-import
21
+ // warnings on every boot.
22
+ import { getLincdPackages } from '@_linked/cli/lifecycle';
19
23
  import { getPackageJSON } from '@_linked/cli/utils';
20
24
  import { AppContextProvider } from '@_linked/server-utils/components/AppContext';
21
25
  import { JSONParser } from '@_linked/server-utils/utils/JSONParser';
22
26
  import { JSONWriter } from '@_linked/server-utils/utils/JSONWriter';
23
27
  import { Server } from '@_linked/server-utils/utils/Server';
24
28
  import { ShapeProvider } from '@_linked/server-utils/utils/ShapeProvider';
25
- import { CoreMap } from '@_linked/core/collections/CoreMap';
26
29
  import { Shape } from '@_linked/core/shapes/Shape';
27
30
  import { LinkedErrorLogging } from '@_linked/core/utils/LinkedErrorLogging';
28
31
  import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
@@ -34,16 +37,12 @@ import process from 'process';
34
37
  import * as React from 'react';
35
38
  import { renderToPipeableStream, renderToStaticMarkup } from 'react-dom/server';
36
39
  import { StaticRouter } from 'react-router-dom/server.js';
37
- import { rimraf } from 'rimraf';
38
40
  import sharp from 'sharp';
39
41
  import { Transform } from 'stream';
40
42
  import { CookieJar } from 'tough-cookie';
41
- import webpack from 'webpack';
42
- import webpackDevMiddleware from 'webpack-dev-middleware';
43
- import webpackHotMiddleware from 'webpack-hot-middleware';
44
43
  import { lincdServer } from '../ontologies/lincd-server.js';
45
44
  import { linkedShape } from '../package.js';
46
- import { syncShapes } from '../utils/Shapes.js';
45
+ import { indexShapesIntoMemory } from '../utils/Shapes.js';
47
46
  import { LincdAPI } from './LincdAPI.js';
48
47
  //prevent errors in node.js when (s)css files are imported in js
49
48
  const isProduction = process.env.NODE_ENV === 'production';
@@ -79,7 +78,7 @@ events.EventEmitter.prototype.setMaxListeners(500);
79
78
  // global['document'] = document;
80
79
  global['reactStaticRenderer'] = renderToStaticMarkup;
81
80
  autoLoadOntologyData(true);
82
- let LincdServer = class LincdServer extends Shape {
81
+ let LinkedServer = class LinkedServer extends Shape {
83
82
  /**
84
83
  * yarn linked start sends the contents of linked.config.js as an object to this constructor
85
84
  * @param n
@@ -92,8 +91,8 @@ let LincdServer = class LincdServer extends Shape {
92
91
  this.latestManifest = null;
93
92
  this.cssMode = 'scss-modules';
94
93
  this.analyse = false;
95
- this.shapeProviders = new CoreMap();
96
- this.genericProviders = new CoreMap();
94
+ this.shapeProviders = new Map();
95
+ this.genericProviders = new Map();
97
96
  //from resizedFileName to full resized path (CDN or similar)
98
97
  this.resizePathsMap = new Map();
99
98
  if (config && typeof config !== 'string' && !('id' in config)) {
@@ -152,9 +151,57 @@ let LincdServer = class LincdServer extends Shape {
152
151
  this.initPackage();
153
152
  this.server = express();
154
153
  await this.initBackendProviders();
155
- syncShapes();
154
+ indexShapesIntoMemory();
155
+ await this.materializeShapesIntoStore();
156
156
  return this;
157
157
  }
158
+ /**
159
+ * Materialize this server's registered shapes into the RDF store as SHACL
160
+ * (core `syncShapes` → pure `sh:NodeShape` + property shapes), so a running
161
+ * app's app-data holds the shapes its instances validate/query against
162
+ * (plan-010 T1e.2). This is the canonical, uniform reuse+create mechanism:
163
+ * whatever shapes the app package registers (published re-exports + generated)
164
+ * get materialized on boot (and re-run on shape-file HMR).
165
+ *
166
+ * Gated to servers whose DEFAULT dataset is a **concrete** materializable store
167
+ * (detected via `rawQuery`) — i.e. an app pointing at its app-data FusekiStore.
168
+ * CN's default is the context-routing `AppDataRouter` (no `rawQuery`; a bare
169
+ * `syncShapes` orphan-read would throw with no active project), so CN is skipped
170
+ * here — it materializes its own pinned native shapes in its storage config.
171
+ */
172
+ async materializeShapesIntoStore() {
173
+ var _a;
174
+ // Opt-out via `linked.config` `syncShapesOnBoot` (default true); env
175
+ // LINKED_SYNC_SHAPES_ON_BOOT overrides for deployment. CN sets
176
+ // `syncShapesOnBoot: false` in its own linked.config — its default dataset is
177
+ // a context-router that can't materialize context-free, and it syncs its own
178
+ // pinned shapes separately (linked.backend.storage.js).
179
+ const configFlag = (_a = this.config) === null || _a === void 0 ? void 0 : _a.syncShapesOnBoot;
180
+ const envFlag = process.env.LINKED_SYNC_SHAPES_ON_BOOT;
181
+ if (configFlag === false || envFlag === 'false') {
182
+ return;
183
+ }
184
+ const appData = LinkedStorage.getDefaultDataset();
185
+ if (!appData)
186
+ return;
187
+ try {
188
+ const { syncShapes } = await import('@_linked/core');
189
+ // Explicit target: materialize EVERY registered shape into the app's own
190
+ // data store regardless of per-shape routing/pins — syncShapes(ds) threads
191
+ // ds through the orphan-read + every delete→recreate. (No reliance on
192
+ // "whatever the default resolves to per shape".)
193
+ const thunks = await syncShapes(appData);
194
+ // Batched (not all-at-once) so we don't overwhelm Fuseki — the API hands
195
+ // back unexecuted thunks precisely so the caller paces them.
196
+ for (let i = 0; i < thunks.length; i += 8) {
197
+ await Promise.all(thunks.slice(i, i + 8).map((run) => run()));
198
+ }
199
+ console.log(`[LinkedServer] materialized ${thunks.length} shape(s) into app-data`);
200
+ }
201
+ catch (err) {
202
+ console.warn('[LinkedServer] shape materialization failed (non-fatal):', err);
203
+ }
204
+ }
158
205
  // async serveData(req,res) {
159
206
  // let nodeURI = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
160
207
  //
@@ -163,6 +210,7 @@ let LincdServer = class LincdServer extends Shape {
163
210
  // })
164
211
  // }
165
212
  async start() {
213
+ var _a, _b, _c, _d;
166
214
  this.initPackage();
167
215
  // Static assets should come from the static store URL (versioned path),
168
216
  // not the upload store URL. storage-config sets STATIC_ACCESS_URL accordingly.
@@ -177,21 +225,40 @@ let LincdServer = class LincdServer extends Shape {
177
225
  LinkedFileStorage.accessURL ||
178
226
  '').replace(/\/$/, '');
179
227
  const staticAsset = (assetPath) => `${staticAccessURL}/public${assetPath}`;
180
- //for apps with multiple bundles this should be read from the webpack build manifest
228
+ // Bundle/manifest read (plan-011: Vite is the only supported build).
229
+ // Vite manifest lives at public/bundles/.vite/manifest.json.
181
230
  this.assets = {
182
- 'main.js': staticAsset('/bundles/main.bundle.js') + '?v=' + this.package.version, //output js bundle from Webpack
183
- 'main.css': staticAsset('/bundles/main.css'), //output css from Webpack
231
+ 'main.js': staticAsset('/bundles/main.bundle.js') + '?v=' + this.package.version,
232
+ 'main.css': staticAsset('/bundles/main.css'),
184
233
  };
185
234
  try {
186
- const manifestPath = path.resolve(process.cwd(), 'public/bundles/manifest.json');
187
- if (fsNative.existsSync(manifestPath)) {
188
- const manifestRaw = fsNative.readFileSync(manifestPath, 'utf-8');
189
- this.assets.manifest = JSON.parse(manifestRaw);
235
+ const viteManifestPath = path.resolve(process.cwd(), 'public/bundles/.vite/manifest.json');
236
+ if (fsNative.existsSync(viteManifestPath)) {
237
+ const manifestRaw = fsNative.readFileSync(viteManifestPath, 'utf-8');
238
+ const viteManifest = JSON.parse(manifestRaw);
239
+ this.assets.manifest = viteManifest;
240
+ // Resolve main entry per Vite manifest shape:
241
+ // { "src/index.tsx": { file: "assets/main-<hash>.js", css: [...] } }
242
+ const mainEntry = viteManifest['src/index.tsx'] ||
243
+ viteManifest['src/index.ts'] ||
244
+ Object.values(viteManifest).find((e) => e === null || e === void 0 ? void 0 : e.isEntry);
245
+ if (mainEntry === null || mainEntry === void 0 ? void 0 : mainEntry.file) {
246
+ this.assets['main.js'] = staticAsset(`/bundles/${mainEntry.file}`);
247
+ }
248
+ if ((_a = mainEntry === null || mainEntry === void 0 ? void 0 : mainEntry.css) === null || _a === void 0 ? void 0 : _a[0]) {
249
+ this.assets['main.css'] = staticAsset(`/bundles/${mainEntry.css[0]}`);
250
+ }
190
251
  }
191
252
  }
192
253
  catch (err) {
193
254
  console.warn('Could not load bundle manifest:', err);
194
255
  }
256
+ // Vite dev marker: when Vite is in use AND no Vite manifest exists yet
257
+ // (= dev mode, no `vite build` run), the HTML renderer skips
258
+ // pre-built CSS/JS link tags. Vite handles asset injection itself.
259
+ if (((_b = this.config.server) === null || _b === void 0 ? void 0 : _b.vite) && !((_c = this.assets.manifest) === null || _c === void 0 ? void 0 : _c['src/index.tsx'])) {
260
+ this.assets['__viteDev'] = '1';
261
+ }
195
262
  const isProduction = process.env.NODE_ENV === 'production';
196
263
  if (!isProduction && this.config.cssMode === 'tailwind') {
197
264
  this.assets['tailwind-cdn'] = 'https://cdn.tailwindcss.com';
@@ -214,7 +281,8 @@ let LincdServer = class LincdServer extends Shape {
214
281
  // });
215
282
  // before controllers
216
283
  await this.initBackendProviders();
217
- syncShapes();
284
+ indexShapesIntoMemory();
285
+ await this.materializeShapesIntoStore();
218
286
  //START OF EXPRESS ROUTES AND MIDDLEWARE
219
287
  //use cors
220
288
  // var corsOptions = {
@@ -230,116 +298,20 @@ let LincdServer = class LincdServer extends Shape {
230
298
  //UPDATE ive set level to 2 (low compression fast speed) because responses were taking too long (98% of server time was used by compress)
231
299
  // this.server.use(compress({level: 2}));
232
300
  await this.callGenericBackendProvidersMethod('setupBeforeControllers');
233
- // if development, run webpack from the server
234
- // for production you need to build the bundles before starting the server
235
- const skipBuild = process.env.NO_WEBPACK === 'true';
236
- if (isDevelopment && !skipBuild) {
237
- //three levels up because of lib/esm (2) instead of src (1)
238
- //@ts-ignore
239
- // const getWebpackConfig = (await import('../../../site.webpack.config.cjs')).default;
240
- // let webpackConfig = await getWebpackConfig();
241
- let webpackConfig = await getWebpackAppConfig();
242
- // const compare = (c1,c2,path?='') => {
243
- // for(let key1 of Object.keys(c1)) {
244
- // if(typeof c1[key1] === 'object') {
245
- // compare(c1[key1],c2[key1],path+='.'+key1);
246
- // } else {
247
- // if(c1[key1] !== c2[key1]) {
248
- // console.log("Difference "+path+": ");
249
- // console.log(c1[key1]);
250
- // console.log(c2[key1]);
251
- // }
252
- // }
253
- // }
254
- // }
255
- // compare(webpackConfig,webpackConfig2);
256
- //default to having webpack cache on and filesystem, unless explicitly set to false
257
- if (this.cacheWebpack === false) {
258
- webpackConfig.cache.type = 'memory';
259
- }
260
- else {
261
- webpackConfig.cache.type = 'filesystem';
262
- }
263
- //clean dist/build folder
264
- await rimraf(webpackConfig.output.path).catch((err) => {
265
- if (err) {
266
- console.warn(err);
267
- }
268
- });
269
- const compiler = webpack(webpackConfig, (err, stats) => {
270
- //watch build completed
271
- if (err) {
272
- console.error(err);
273
- }
274
- // Output stats JSON for analysis
275
- if (this.analyse) {
276
- fs.writeFile('./data/webpack-stats.json', JSON.stringify(stats.toJson({ all: true }), null, 2)).then(() => {
277
- console.log('Webpack stats written to ./data/webpack-stats.json');
278
- });
279
- }
280
- });
281
- if (!compiler) {
282
- //something went wrong with webpack config, error will be logged above
283
- return;
284
- }
285
- compiler.hooks.afterEmit.tap('cleanup-the-require-cache', () => {
286
- // After webpack rebuild, clear the files from the require cache,
287
- // so that next server side render wil be in sync
288
- // console.log(Object.keys(require.cache).filter(k => k.includes(dirName)).join("\n"));
289
- // if (typeof require !== 'undefined') {
290
- // Object.keys(require.cache)
291
- // .filter((key) => key.includes(dirName))
292
- // .forEach((key) => delete require.cache[key]);
293
- // }
294
- });
295
- //after the first emit (which means bundles are ready and the site is running) also rebuild the index files of the site
296
- let updatedMetadata = false;
297
- compiler.hooks.afterEmit.tap('update-metadata', () => {
298
- if (!updatedMetadata) {
299
- updatedMetadata = true;
300
- //log updated paths
301
- // buildMetadata()
302
- // .then((updatedPaths) => {
303
- // // if(updatedPaths && updatedPaths.length)
304
- // // {
305
- // // console.log(chalk.blueBright('Updated metadata:\n - '+updatedPaths.join('\n - ')));
306
- // // }
307
- // })
308
- // .catch((err) => {
309
- // console.warn('Could not update metadata: ' + err);
310
- // });
311
- }
312
- });
313
- this.server.use(webpackDevMiddleware(compiler, {
314
- serverSideRender: true,
315
- publicPath: webpackConfig.output.publicPath,
316
- stats: {
317
- children: true,
318
- version: false,
319
- chunks: false,
320
- assets: false,
321
- entrypoints: false,
322
- modules: false,
323
- },
324
- writeToDisk: true,
325
- }));
326
- compiler.hooks.afterEmit.tap('store-manifest', (compilation) => {
327
- try {
328
- // Read manifest from the filesystem after webpack writes it
329
- const manifestPath = path.resolve(compilation.options.output.path, 'manifest.json');
330
- if (fsNative.existsSync(manifestPath)) {
331
- const manifestContent = fsNative.readFileSync(manifestPath, 'utf-8');
332
- this.latestManifest = JSON.parse(manifestContent);
333
- }
334
- }
335
- catch (err) {
336
- console.warn('Failed to parse manifest from webpack emit:', err);
337
- }
338
- });
339
- this.server.use(webpackHotMiddleware(compiler, {
340
- log: false,
341
- }));
301
+ // Vite middleware (plan 010): when the user app starts via Vite
302
+ // (`linked start --vite`), the orchestrator passes the Vite handle
303
+ // through config.server.viteMiddleware. We mount it and skip the
304
+ // entire webpack-dev-middleware setup below.
305
+ const viteMiddleware = (_d = this.config.server) === null || _d === void 0 ? void 0 : _d.viteMiddleware;
306
+ if (viteMiddleware) {
307
+ this.server.use(viteMiddleware);
342
308
  }
309
+ // Plan-011: legacy webpack-dev-middleware branch removed. Under Vite
310
+ // (the only supported dev path now) `viteMiddleware` is set, which
311
+ // previously forced `skipBuild = true` and made this whole block
312
+ // dead. Removing it ends the static dependency on @_linked/cli/
313
+ // config-webpack-app (full of dynamic imports Vite couldn't analyze)
314
+ // and shrinks LinkedServer's import surface considerably.
343
315
  // //map URL routes to file paths
344
316
  const oneYear = 1000 * 60 * 60 * 24 * 365; // in milliseconds
345
317
  const oneMonth = 1000 * 60 * 60 * 24 * 30; // in milliseconds
@@ -392,6 +364,43 @@ let LincdServer = class LincdServer extends Shape {
392
364
  this.server.head('__health', (_req, res) => {
393
365
  res.sendStatus(200);
394
366
  });
367
+ // CN control channel (plan-010 T1b.3). Present only when CN spawned this
368
+ // process with a shared secret (CN_APP_ADMIN_SECRET); a standalone app run
369
+ // without the secret leaves the channel closed (404). Auth is the
370
+ // x-cn-admin-secret header matching that env value.
371
+ const adminSecret = process.env.CN_APP_ADMIN_SECRET;
372
+ // Constant-time compare so the secret can't be recovered by timing the 401
373
+ // response. timingSafeEqual requires equal-length buffers, so guard length.
374
+ const secretMatches = (provided) => {
375
+ if (!adminSecret || !provided)
376
+ return false;
377
+ const a = Buffer.from(provided);
378
+ const b = Buffer.from(adminSecret);
379
+ return a.length === b.length && timingSafeEqual(a, b);
380
+ };
381
+ const requireAdmin = (req, res) => {
382
+ if (!adminSecret) {
383
+ res.sendStatus(404); // no control channel in this mode
384
+ return false;
385
+ }
386
+ if (!secretMatches(req.get('x-cn-admin-secret'))) {
387
+ res.sendStatus(401);
388
+ return false;
389
+ }
390
+ return true;
391
+ };
392
+ this.server.get('/admin/health', (req, res) => {
393
+ if (!requireAdmin(req, res))
394
+ return;
395
+ res.status(200).json({ status: 'ok' });
396
+ });
397
+ this.server.post('/admin/restart', (req, res) => {
398
+ if (!requireAdmin(req, res))
399
+ return;
400
+ res.sendStatus(202);
401
+ // CN's provisioner respawns on exit (see ChildProcessProvisioner).
402
+ setTimeout(() => process.exit(0), 50);
403
+ });
395
404
  this.server.get('*', this.handleErrors(async (req, res) => {
396
405
  //make sure the frontend bundle has finished building
397
406
  // await this.waitForWebpack();
@@ -491,7 +500,19 @@ let LincdServer = class LincdServer extends Shape {
491
500
  .readFile(path.join(process.cwd(), 'package.json'), 'utf-8')
492
501
  .then(async (contents) => {
493
502
  let pkg = JSON.parse(contents);
494
- await this.indexPackageBackendProviders(pkg.name, false, path.join(process.cwd(), 'src', 'backend.ts'));
503
+ // Route through default ${pkg}/backend resolution. The app's
504
+ // package.json `exports`./backend field handles dev vs prod:
505
+ // "exports": {
506
+ // "./backend": {
507
+ // "development": "./src/backend.ts",
508
+ // "default": "./lib/esm/backend.js"
509
+ // }
510
+ // }
511
+ // Removed plan-010 D10: the source-direct
512
+ // path.join(cwd, 'src', 'backend.ts') override that forced TS
513
+ // runtime loading. Vite SSR's ssrLoadModule handles the
514
+ // transform; production runs from lib/esm/.
515
+ await this.indexPackageBackendProviders(pkg.name, false);
495
516
  });
496
517
  }
497
518
  catch (err) {
@@ -727,6 +748,7 @@ let LincdServer = class LincdServer extends Shape {
727
748
  res.sendFile(resizedFilePath);
728
749
  }
729
750
  async indexLincdPackage(pkg, warnIfNotFound = false) {
751
+ var _a;
730
752
  if (pkg === this.package.name) {
731
753
  return;
732
754
  }
@@ -737,7 +759,22 @@ let LincdServer = class LincdServer extends Shape {
737
759
  //@ts-ignore
738
760
  // await import.meta.resolve(pkg)
739
761
  // );
740
- await import(pkg);
762
+ // plan-011 §P6 — prefer Vite's SSR loader so workspace packages register
763
+ // on the SINGLE @_linked/core instance the rest of the SSR graph uses.
764
+ // The bare Node `import()` fallback resolves `default`→lib, evaluating a
765
+ // SECOND core copy (benign post-§P1, but a needless second instance).
766
+ // Node import stays as the fallback when Vite isn't active (production /
767
+ // Node-only CLI commands). Mirrors indexPackageBackendProviders.
768
+ const vite = (_a = this.config.server) === null || _a === void 0 ? void 0 : _a.vite;
769
+ if (vite && typeof vite.ssrLoadModule === 'function') {
770
+ await vite.ssrLoadModule(pkg);
771
+ }
772
+ else {
773
+ // @vite-ignore — dynamic specifier is intentional: this checks
774
+ // whether `pkg` is resolvable at runtime, then catches the error.
775
+ // eslint-disable-next-line no-unsanitized/method
776
+ await import(/* @vite-ignore */ pkg);
777
+ }
741
778
  // console.log(`✅ Successfully loaded: ${pkg}`);
742
779
  }
743
780
  catch (e) {
@@ -761,13 +798,66 @@ let LincdServer = class LincdServer extends Shape {
761
798
  }
762
799
  }
763
800
  async indexPackageBackendProviders(pkg, warnIfNotFound = false, backendIndexFilePath) {
801
+ var _a;
764
802
  if (!backendIndexFilePath) {
765
803
  backendIndexFilePath = `${pkg}/backend`;
766
804
  }
767
805
  let backendProviderExports;
768
806
  let genericBackendProvider;
769
807
  let shapeProviders = [];
770
- await import(backendIndexFilePath)
808
+ // Plan-010 phase 4/5: when Vite SSR is active, route module loading
809
+ // through vite.ssrLoadModule. This handles the case where the user app
810
+ // is the consuming workspace itself (self-reference like
811
+ // @semantu/create-now/backend), which Node's ESM resolver can't resolve
812
+ // from inside an installed package like @_linked/server.
813
+ const vite = (_a = this.config.server) === null || _a === void 0 ? void 0 : _a.vite;
814
+ const loadModule = async (specifier) => {
815
+ var _a, _b, _c;
816
+ if (vite && typeof vite.ssrLoadModule === 'function') {
817
+ // For the user app's own backend, resolve to ./src/backend.ts directly.
818
+ // Vite reads the app's package.json exports.development condition.
819
+ if (specifier === `${this.package.name}/backend`) {
820
+ return await vite.ssrLoadModule('/src/backend.ts');
821
+ }
822
+ // Plan-011: ssr.external is now an npm-only allowlist. Workspace
823
+ // packages MUST go through Vite's SSR loader so they share the
824
+ // same module instance as everything else in the SSR call graph
825
+ // (otherwise we get duplicate React contexts, duplicate Linked
826
+ // package registrations, etc).
827
+ //
828
+ // Pre-check whether the specifier actually resolves before
829
+ // calling ssrLoadModule — many packages have no `/backend`
830
+ // export, and ssrLoadModule logs a "Failed to load url" error
831
+ // INTERNALLY before throwing, which spams stdout even when the
832
+ // surrounding catch handles it. pluginContainer.resolveId is
833
+ // quiet: it returns null when no plugin can resolve the id.
834
+ try {
835
+ const resolved = await ((_b = (_a = vite.pluginContainer) === null || _a === void 0 ? void 0 : _a.resolveId) === null || _b === void 0 ? void 0 : _b.call(_a, specifier));
836
+ if (!resolved) {
837
+ // Synthesise the same error shape the catch expects so the
838
+ // "no backend export, that's fine" branch fires.
839
+ const err = new Error(`Failed to load url ${specifier} (resolved id: ${specifier}). Does the file exist?`);
840
+ throw err;
841
+ }
842
+ }
843
+ catch (e) {
844
+ // If the pre-check itself errored (e.g. plugin threw on a
845
+ // pkg name it doesn't know), let the call below surface the
846
+ // real error path.
847
+ if (!((_c = e === null || e === void 0 ? void 0 : e.message) === null || _c === void 0 ? void 0 : _c.includes('Failed to load url'))) {
848
+ // fall through to ssrLoadModule which will throw + log
849
+ }
850
+ else {
851
+ throw e;
852
+ }
853
+ }
854
+ return await vite.ssrLoadModule(specifier);
855
+ }
856
+ // No-vite path (e.g. production runtime). Fall back to Node's
857
+ // resolver. The dynamic specifier is intentional.
858
+ return await import(/* @vite-ignore */ specifier);
859
+ };
860
+ await loadModule(backendIndexFilePath)
771
861
  .then((backendProviderExports) => {
772
862
  //instantiate the exported provider classes and add them to the right place
773
863
  Object.keys(backendProviderExports).forEach((key) => {
@@ -793,13 +883,22 @@ let LincdServer = class LincdServer extends Shape {
793
883
  });
794
884
  })
795
885
  .catch((e) => {
796
- const match = e.message.match(/module \'([^\']+)'/);
797
- //check that the imported /backend path is not found (and only that path, not an import IN that file that is not found, that should still throw an error)
798
- let providerNotFound = e.code === 'ERR_MODULE_NOT_FOUND' &&
799
- e.message.indexOf(`Cannot find module`) !== -1 &&
800
- match &&
801
- match[1] &&
802
- match[1].includes('/backend');
886
+ var _a;
887
+ // Recognize "no /backend export" failures across two loader
888
+ // shapes:
889
+ // Node's import() → ERR_MODULE_NOT_FOUND + "Cannot find module 'X/backend'"
890
+ // Vite's ssrLoadModule() → "Failed to load url X/backend"
891
+ // In either case, missing /backend on a package that doesn't
892
+ // ship a backend is expected; loud-error only on REAL load
893
+ // failures (syntax error inside an existing backend.ts, etc).
894
+ const nodeMatch = e.message.match(/module \'([^\']+)'/);
895
+ const viteMatch = e.message.match(/Failed to load url ([^\s]+)/);
896
+ const matchedSpec = (_a = nodeMatch === null || nodeMatch === void 0 ? void 0 : nodeMatch[1]) !== null && _a !== void 0 ? _a : viteMatch === null || viteMatch === void 0 ? void 0 : viteMatch[1];
897
+ let providerNotFound = !!matchedSpec &&
898
+ matchedSpec.includes('/backend') &&
899
+ ((e.code === 'ERR_MODULE_NOT_FOUND' &&
900
+ e.message.indexOf(`Cannot find module`) !== -1) ||
901
+ e.message.indexOf('Failed to load url') !== -1);
803
902
  if (providerNotFound) {
804
903
  // console.warn('Error loading ' + providerPath + ': ' + e.stack);
805
904
  if (warnIfNotFound) {
@@ -818,6 +917,69 @@ let LincdServer = class LincdServer extends Shape {
818
917
  this.shapeProviders.set(pkg, shapeProviders);
819
918
  return { backendProviderExports, shapeProviders };
820
919
  }
920
+ /**
921
+ * Plan-011 phase 2 — HMR re-index entry point.
922
+ *
923
+ * Called by the CLI orchestrator's Vite watcher whenever a `.ts`/`.tsx`
924
+ * file inside a workspace package changes. Disposes the package's
925
+ * existing providers (so routes, listeners, timers don't accumulate),
926
+ * drops them from the registry, and re-runs indexPackageBackendProviders
927
+ * to pick up the freshly-imported module.
928
+ *
929
+ * Dispose calls have a 5 s soft timeout. A hanging dispose logs a
930
+ * warning and is abandoned so HMR doesn't stall the whole dev loop.
931
+ */
932
+ async onSourceChange(pkg) {
933
+ var _a, _b, _c, _d, _e;
934
+ const disposeWithTimeout = async (p, label) => {
935
+ if (!(p === null || p === void 0 ? void 0 : p.dispose))
936
+ return;
937
+ try {
938
+ await Promise.race([
939
+ Promise.resolve().then(() => p.dispose()),
940
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`dispose timeout after 5s`)), 5000)),
941
+ ]);
942
+ }
943
+ catch (err) {
944
+ console.warn(chalk.yellow(`[linked] ${label} dispose failed: ${err.message}`));
945
+ }
946
+ };
947
+ const generic = this.genericProviders.get(pkg);
948
+ if (generic) {
949
+ await disposeWithTimeout(generic, `${pkg} generic provider`);
950
+ this.genericProviders.delete(pkg);
951
+ }
952
+ const shapeProvs = (_a = this.shapeProviders.get(pkg)) !== null && _a !== void 0 ? _a : [];
953
+ for (const p of shapeProvs) {
954
+ await disposeWithTimeout(p, `${pkg} ${(_d = (_c = (_b = Object.getPrototypeOf(p)) === null || _b === void 0 ? void 0 : _b.constructor) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : 'shape provider'}`);
955
+ }
956
+ this.shapeProviders.delete(pkg);
957
+ await this.indexPackageBackendProviders(pkg, true);
958
+ // Run the boot lifecycle on the FRESHLY-loaded provider so it
959
+ // re-registers its Express routes/middleware after dispose. Without
960
+ // this, dispose() tears the old routes off and the new instance is
961
+ // constructed but never gets a chance to register the replacements.
962
+ const fresh = this.genericProviders.get(pkg);
963
+ if (fresh === null || fresh === void 0 ? void 0 : fresh.setupBeforeControllers) {
964
+ try {
965
+ await fresh.setupBeforeControllers();
966
+ }
967
+ catch (err) {
968
+ console.warn(chalk.yellow(`[linked] ${pkg} setupBeforeControllers after reload failed: ${err.message}`));
969
+ }
970
+ }
971
+ const freshShapes = (_e = this.shapeProviders.get(pkg)) !== null && _e !== void 0 ? _e : [];
972
+ for (const p of freshShapes) {
973
+ if (p === null || p === void 0 ? void 0 : p.setupBeforeControllers) {
974
+ try {
975
+ await p.setupBeforeControllers();
976
+ }
977
+ catch (err) {
978
+ console.warn(chalk.yellow(`[linked] ${pkg} shape-provider setupBeforeControllers after reload failed: ${err.message}`));
979
+ }
980
+ }
981
+ }
982
+ }
821
983
  async processBackendMethodCall(request, response) {
822
984
  this.noCache(response);
823
985
  await this.initRequest(request, response);
@@ -1001,7 +1163,7 @@ let LincdServer = class LincdServer extends Shape {
1001
1163
  }
1002
1164
  }
1003
1165
  async render(req, res) {
1004
- var _a, _b, _c, _d;
1166
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1005
1167
  res.socket.on('error', (error) => {
1006
1168
  console.error('Fatal socket error', error);
1007
1169
  });
@@ -1029,6 +1191,69 @@ let LincdServer = class LincdServer extends Shape {
1029
1191
  let App = ((_b = this.config.server) === null || _b === void 0 ? void 0 : _b.loadAppComponent)
1030
1192
  ? await this.config.server.loadAppComponent()
1031
1193
  : null;
1194
+ // Vite dev CSS collection (plan-010 iteration 1 — gap A):
1195
+ // 1. Preload the matched route's module via ssrLoadModule so all
1196
+ // page-level dependencies (SigninLayout, CreateAccount, etc.)
1197
+ // enter Vite's moduleGraph BEFORE we collect CSS. Without this,
1198
+ // only App's eager imports are in the graph and lazy routes'
1199
+ // CSS would be missing — page renders unstyled until hydration.
1200
+ // 2. Walk moduleGraph for CSS entries, load each via `?inline`
1201
+ // (Vite returns raw CSS string as default export).
1202
+ // 3. Inject as inline <style> in HTML head (Html.tsx).
1203
+ const vite = (_c = this.config.server) === null || _c === void 0 ? void 0 : _c.vite;
1204
+ // Vite SSR module preload (plan-010 iter1 gap A):
1205
+ // Pages are loaded lazily via React.lazy — their modules only enter
1206
+ // Vite's moduleGraph after the lazy resolves. For SSR CSS collection
1207
+ // we need the page modules in the graph BEFORE the first render of
1208
+ // each route. The orchestrator passes a preloadPagesFn that lists
1209
+ // all page files (typically via `import.meta.glob`); we call
1210
+ // ssrLoadModule on each path once. After the first render of a
1211
+ // session everything is cached.
1212
+ const preloadPagesFn = (_d = this.config.server) === null || _d === void 0 ? void 0 : _d.viteSsrPreload;
1213
+ if ((vite === null || vite === void 0 ? void 0 : vite.ssrLoadModule) && preloadPagesFn && !this._viteSsrPreloaded) {
1214
+ try {
1215
+ const pagePaths = await preloadPagesFn();
1216
+ for (const p of pagePaths) {
1217
+ try {
1218
+ await vite.ssrLoadModule(p);
1219
+ }
1220
+ catch ( /* skip — page may not exist or have ssr errors */_k) { /* skip — page may not exist or have ssr errors */ }
1221
+ }
1222
+ }
1223
+ catch ( /* ignore */_l) { /* ignore */ }
1224
+ this._viteSsrPreloaded = true;
1225
+ }
1226
+ let ssrCss = '';
1227
+ if ((_e = vite === null || vite === void 0 ? void 0 : vite.moduleGraph) === null || _e === void 0 ? void 0 : _e.idToModuleMap) {
1228
+ const cssChunks = [];
1229
+ const seen = new Set();
1230
+ // Walk the moduleGraph for CSS modules pulled in by app/pages.
1231
+ // Note: Tailwind v4 `@theme` directives in app theme CSS files are
1232
+ // NOT processed at SSR collection time — the @tailwindcss/vite
1233
+ // plugin expands them at production build only. In dev mode, the
1234
+ // theme variables (—color-primary-*, etc.) are injected by Vite's
1235
+ // runtime AFTER client hydration, which causes a brief flash of
1236
+ // unthemed content (logos render black until ~hydration+200ms).
1237
+ // Production (vite build) is unaffected — the static main.css
1238
+ // contains expanded :root variables.
1239
+ for (const [id] of vite.moduleGraph.idToModuleMap) {
1240
+ if (seen.has(id))
1241
+ continue;
1242
+ if (!/\.(module\.)?css(\?(?!.*inline)[^?]*)?$/.test(id))
1243
+ continue;
1244
+ seen.add(id);
1245
+ try {
1246
+ const inlineUrl = id + (id.includes('?') ? '&' : '?') + 'inline';
1247
+ const cssModule = await vite.ssrLoadModule(inlineUrl);
1248
+ if (typeof (cssModule === null || cssModule === void 0 ? void 0 : cssModule.default) === 'string') {
1249
+ cssChunks.push(cssModule.default);
1250
+ }
1251
+ }
1252
+ catch ( /* skip */_m) { /* skip */ }
1253
+ }
1254
+ ssrCss = cssChunks.join('\n');
1255
+ }
1256
+ this.assets['__viteSsrCss'] = ssrCss;
1032
1257
  await this.initRequest(req, res);
1033
1258
  let { requestLD, requestObject } = await this.getRequestData(req, res);
1034
1259
  //on the backend we need to inform the hook of the request-data value
@@ -1052,12 +1277,18 @@ let LincdServer = class LincdServer extends Shape {
1052
1277
  let preloadScripts = [];
1053
1278
  let preloadStyles = [];
1054
1279
  let matchedRouteKey = null;
1280
+ // Plan-010 Vite dev mode: skip preload resolution entirely. Vite
1281
+ // handles dynamic import() at runtime — there are no pre-built
1282
+ // chunks to preload, and the webpack-shape fallback would pick up
1283
+ // stale bundles on disk (public/bundles/*.bundle.js from a prior
1284
+ // webpack build) and link them, breaking hydration.
1285
+ const usingViteDev = !!((_f = this.config.server) === null || _f === void 0 ? void 0 : _f.vite) && !this.latestManifest;
1055
1286
  // Load routes config if available and extract preload chunks for the current route
1056
- if ((_c = this.config.server) === null || _c === void 0 ? void 0 : _c.loadRoutes) {
1287
+ if ((_g = this.config.server) === null || _g === void 0 ? void 0 : _g.loadRoutes) {
1057
1288
  try {
1058
1289
  const routesModule = await this.config.server.loadRoutes();
1059
1290
  const ROUTES = routesModule.ROUTES ||
1060
- ((_d = routesModule.default) === null || _d === void 0 ? void 0 : _d.ROUTES) ||
1291
+ ((_h = routesModule.default) === null || _h === void 0 ? void 0 : _h.ROUTES) ||
1061
1292
  routesModule;
1062
1293
  // Match the current request path to a route
1063
1294
  let matchedRoute = null;
@@ -1074,18 +1305,52 @@ let LincdServer = class LincdServer extends Shape {
1074
1305
  break;
1075
1306
  }
1076
1307
  }
1077
- // If we found a matching route with preloadChunks, resolve them to URLs (both JS and CSS)
1078
- if ((matchedRoute === null || matchedRoute === void 0 ? void 0 : matchedRoute.preloadChunks) &&
1308
+ // If we found a matching route with preloadChunks, resolve them to URLs (both JS and CSS).
1309
+ // Manifest format detection: Vite manifest entries are objects with .file/.css; webpack
1310
+ // manifest entries are bare strings. Try Vite shape first, fall back to webpack.
1311
+ // Skip entirely in Vite dev mode — no pre-built chunks exist; Vite handles dynamic
1312
+ // imports at runtime.
1313
+ if (!usingViteDev &&
1314
+ (matchedRoute === null || matchedRoute === void 0 ? void 0 : matchedRoute.preloadChunks) &&
1079
1315
  Array.isArray(matchedRoute.preloadChunks)) {
1316
+ const resolveJs = (chunkName) => {
1317
+ // Vite shape: keyed by source path. Look up by source-path keys
1318
+ // that match the chunk name's tail.
1319
+ const viteEntry = manifest[`src/pages/${chunkName}.tsx`] ||
1320
+ manifest[`src/pages/${chunkName}.ts`] ||
1321
+ Object.values(manifest).find((e) => {
1322
+ var _a, _b;
1323
+ return ((_a = e === null || e === void 0 ? void 0 : e.src) === null || _a === void 0 ? void 0 : _a.endsWith(`${chunkName}.tsx`)) ||
1324
+ ((_b = e === null || e === void 0 ? void 0 : e.src) === null || _b === void 0 ? void 0 : _b.endsWith(`${chunkName}.ts`));
1325
+ });
1326
+ if (viteEntry && typeof viteEntry === 'object' && viteEntry.file) {
1327
+ return `/bundles/${viteEntry.file}`;
1328
+ }
1329
+ // Webpack shape: keyed by output name.
1330
+ return (manifest[`${chunkName}.js`] ||
1331
+ manifest[`${chunkName}.bundle.js`] ||
1332
+ manifest[`${chunkName}.mjs`] ||
1333
+ null);
1334
+ };
1335
+ const resolveCss = (chunkName) => {
1336
+ const viteEntry = manifest[`src/pages/${chunkName}.tsx`] ||
1337
+ manifest[`src/pages/${chunkName}.ts`] ||
1338
+ Object.values(manifest).find((e) => {
1339
+ var _a, _b;
1340
+ return ((_a = e === null || e === void 0 ? void 0 : e.src) === null || _a === void 0 ? void 0 : _a.endsWith(`${chunkName}.tsx`)) ||
1341
+ ((_b = e === null || e === void 0 ? void 0 : e.src) === null || _b === void 0 ? void 0 : _b.endsWith(`${chunkName}.ts`));
1342
+ });
1343
+ if (viteEntry && typeof viteEntry === 'object' && Array.isArray(viteEntry.css)) {
1344
+ return viteEntry.css.map((c) => `/bundles/${c}`);
1345
+ }
1346
+ const webpackCss = manifest[`${chunkName}.css`];
1347
+ return webpackCss ? [webpackCss] : [];
1348
+ };
1080
1349
  preloadScripts = matchedRoute.preloadChunks
1081
- .map((chunkName) => manifest[`${chunkName}.js`] ||
1082
- manifest[`${chunkName}.bundle.js`] ||
1083
- manifest[`${chunkName}.mjs`])
1350
+ .map(resolveJs)
1084
1351
  .filter(Boolean);
1085
- // Also collect CSS chunks for the same routes
1086
1352
  preloadStyles = matchedRoute.preloadChunks
1087
- .map((chunkName) => manifest[`${chunkName}.css`])
1088
- .filter(Boolean);
1353
+ .flatMap(resolveCss);
1089
1354
  }
1090
1355
  }
1091
1356
  catch (err) {
@@ -1098,9 +1363,30 @@ let LincdServer = class LincdServer extends Shape {
1098
1363
  React.createElement(StaticRouter, { location: req.url },
1099
1364
  React.createElement(AppContextProvider, { assets: this.assets, requestLD: requestLD, requestObject: requestObject, preloadScripts: preloadScripts, preloadStyles: preloadStyles, expressRequest: req, expressResponse: res },
1100
1365
  React.createElement(App, null)))), {
1101
- bootstrapScripts: [
1102
- this.assets['main.js'], //generated webpack bundle from frontend/src
1103
- ],
1366
+ // Bootstrap scripts:
1367
+ // - Vite dev mode: use bootstrapModules so the browser loads them
1368
+ // as ES modules. Vite middleware intercepts /src/index.tsx and
1369
+ // transforms+serves it; /@vite/client provides the HMR client.
1370
+ // @vitejs/plugin-react requires a "preamble" inline script
1371
+ // defining $RefreshReg$/$RefreshSig$ — without it, the first
1372
+ // React module throws "can't detect preamble" and hydration
1373
+ // blows up. Inline via bootstrapScriptContent.
1374
+ // - Production: bootstrapScripts with the hashed main.js from the
1375
+ // Vite (or legacy webpack) build manifest.
1376
+ ...(((_j = this.config.server) === null || _j === void 0 ? void 0 : _j.vite)
1377
+ ? {
1378
+ bootstrapScriptContent: `
1379
+ import("/@vite/client");
1380
+ import("/@react-refresh").then(RefreshRuntime => {
1381
+ RefreshRuntime.injectIntoGlobalHook(window);
1382
+ window.$RefreshReg$ = () => {};
1383
+ window.$RefreshSig$ = () => (type) => type;
1384
+ window.__vite_plugin_react_preamble_installed__ = true;
1385
+ return import("/src/index.tsx");
1386
+ });
1387
+ `,
1388
+ }
1389
+ : { bootstrapScripts: [this.assets['main.js']] }),
1104
1390
  onShellReady: function () {
1105
1391
  var _a;
1106
1392
  res.statusCode = didError ? 500 : 200;
@@ -1248,10 +1534,10 @@ let LincdServer = class LincdServer extends Shape {
1248
1534
  /**
1249
1535
  * indicates that instances of this shape need to have this rdf.type
1250
1536
  */
1251
- LincdServer.targetClass = lincdServer.LincdServer;
1252
- LincdServer = __decorate([
1537
+ LinkedServer.targetClass = lincdServer.LincdServer;
1538
+ LinkedServer = __decorate([
1253
1539
  linkedShape,
1254
1540
  __metadata("design:paramtypes", [Object])
1255
- ], LincdServer);
1256
- export { LincdServer };
1257
- //# sourceMappingURL=LincdServer.js.map
1541
+ ], LinkedServer);
1542
+ export { LinkedServer };
1543
+ //# sourceMappingURL=LinkedServer.js.map