@stratal/inertia 0.0.26 → 0.1.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +359 -0
  2. package/README.md +158 -14
  3. package/dist/build-seo-tags-DBsHKxX9.mjs.map +1 -1
  4. package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
  5. package/dist/generator/type-generator.worker.d.mts +1 -1
  6. package/dist/generator/type-generator.worker.mjs +1 -1
  7. package/dist/index.d.mts +214 -92
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +390 -130
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/quarry.d.mts +6 -8
  12. package/dist/quarry.d.mts.map +1 -1
  13. package/dist/quarry.mjs +127 -12
  14. package/dist/quarry.mjs.map +1 -1
  15. package/dist/react/access.d.mts +95 -0
  16. package/dist/react/access.d.mts.map +1 -0
  17. package/dist/react/access.mjs +133 -0
  18. package/dist/react/access.mjs.map +1 -0
  19. package/dist/react-dom-server-legacy-stub.d.mts +20 -0
  20. package/dist/react-dom-server-legacy-stub.d.mts.map +1 -0
  21. package/dist/react-dom-server-legacy-stub.mjs +25 -0
  22. package/dist/react-dom-server-legacy-stub.mjs.map +1 -0
  23. package/dist/react.d.mts +4 -6
  24. package/dist/react.d.mts.map +1 -1
  25. package/dist/react.mjs +1 -1
  26. package/dist/react.mjs.map +1 -1
  27. package/dist/seo-runtime.d.mts +1 -1
  28. package/dist/seo-runtime.mjs +8 -6
  29. package/dist/seo-runtime.mjs.map +1 -1
  30. package/dist/services/ssr-exclusion.d.mts +38 -0
  31. package/dist/services/ssr-exclusion.d.mts.map +1 -0
  32. package/dist/services/ssr-exclusion.mjs +0 -0
  33. package/dist/services/ssr-exclusion.mjs.map +1 -0
  34. package/dist/ssr.d.mts +37 -8
  35. package/dist/ssr.d.mts.map +1 -1
  36. package/dist/ssr.mjs +7 -4
  37. package/dist/ssr.mjs.map +1 -1
  38. package/dist/testing.d.mts +3 -2
  39. package/dist/testing.d.mts.map +1 -1
  40. package/dist/testing.mjs +20 -6
  41. package/dist/testing.mjs.map +1 -1
  42. package/dist/{type-generator-DFpha_Fp.mjs → type-generator-BVw8mj1y.mjs} +373 -64
  43. package/dist/type-generator-BVw8mj1y.mjs.map +1 -0
  44. package/dist/types-BltKoOR7.d.mts +193 -0
  45. package/dist/types-BltKoOR7.d.mts.map +1 -0
  46. package/dist/types-D-j_Ee_h.d.mts +52 -0
  47. package/dist/types-D-j_Ee_h.d.mts.map +1 -0
  48. package/dist/types-DzE1pdZs.d.mts.map +1 -1
  49. package/dist/vite.d.mts +19 -6
  50. package/dist/vite.d.mts.map +1 -1
  51. package/dist/vite.mjs +67 -5
  52. package/dist/vite.mjs.map +1 -1
  53. package/package.json +38 -27
  54. package/dist/type-generator-DFpha_Fp.mjs.map +0 -1
  55. package/dist/types-BhgXhWx6.d.mts +0 -82
  56. package/dist/types-BhgXhWx6.d.mts.map +0 -1
package/dist/quarry.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import { t as __decorate } from "./decorate-B7nr7eBl.mjs";
2
- import { n as runTypeGeneration, t as findPagesDir } from "./type-generator-DFpha_Fp.mjs";
1
+ import { t as __decorate } from "./decorate-RQD1h28J.mjs";
2
+ import { n as runTypeGeneration, t as findPagesDir } from "./type-generator-BVw8mj1y.mjs";
3
3
  import { Module } from "stratal/module";
4
4
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join, relative } from "node:path";
6
- import { spawn } from "node:child_process";
6
+ import { execFile, spawn } from "node:child_process";
7
7
  import { Command } from "stratal/quarry";
8
+ import { promisify } from "node:util";
8
9
  import { watch } from "node:fs/promises";
9
10
  //#region src/vite/create-client-vite-config.ts
10
11
  /**
@@ -17,12 +18,13 @@ import { watch } from "node:fs/promises";
17
18
  * the two phases removes the race entirely and keeps each build minimal.
18
19
  */
19
20
  function writeTempClientViteConfig(options) {
20
- const configPath = join(join(options.cwd, "node_modules", ".stratal"), "vite.client.config.mjs");
21
+ const configDir = join(options.cwd, "node_modules", ".stratal");
22
+ const configPath = join(configDir, "vite.client.config.mjs");
21
23
  mkdirSync(dirname(configPath), { recursive: true });
22
24
  const entry = (options.entry ?? "src/inertia/app.tsx").replace(/\\/g, "/");
23
25
  const outDir = (options.outDir ?? "dist/client").replace(/\\/g, "/");
24
26
  const hasUserConfig = existsSync(join(options.cwd, "vite.config.ts"));
25
- writeFileSync(configPath, `
27
+ const content = `
26
28
  import { mergeConfig } from 'vite'
27
29
 
28
30
  const baseConfig = {
@@ -40,13 +42,15 @@ const baseConfig = {
40
42
  ${hasUserConfig ? `const userModule = await import('${join(options.cwd, "vite.config.ts").replace(/\\/g, "/")}')
41
43
  const userConfig = userModule.default ?? userModule
42
44
  export default mergeConfig(userConfig, baseConfig)` : "export default baseConfig"}
43
- `, "utf-8");
45
+ `;
46
+ writeFileSync(configPath, content, "utf-8");
44
47
  return configPath;
45
48
  }
46
49
  //#endregion
47
50
  //#region src/vite/create-vite-config.ts
48
51
  function writeTempViteConfig(options) {
49
- const configPath = join(join(options.cwd, "node_modules", ".stratal"), "vite.config.mjs");
52
+ const configDir = join(options.cwd, "node_modules", ".stratal");
53
+ const configPath = join(configDir, "vite.config.mjs");
50
54
  mkdirSync(dirname(configPath), { recursive: true });
51
55
  const hasUserConfig = existsSync(join(options.cwd, "vite.config.ts"));
52
56
  const serverConfig = options.server ? `server: { port: ${options.server.port}, host: ${options.server.host ? "true" : "undefined"} },` : "";
@@ -54,7 +58,7 @@ function writeTempViteConfig(options) {
54
58
  const cloudflareOptions = [];
55
59
  if (options.persistTo) cloudflareOptions.push(`persistState: { path: ${JSON.stringify(options.persistTo)} }`);
56
60
  if (options.inspectorPort !== void 0) cloudflareOptions.push(`inspectorPort: ${options.inspectorPort === false ? "false" : options.inspectorPort}`);
57
- writeFileSync(configPath, `
61
+ const content = `
58
62
  import { mergeConfig } from 'vite'
59
63
  import { cloudflare } from '@cloudflare/vite-plugin'
60
64
  import { stratalInertia } from '@stratal/inertia/vite'
@@ -82,7 +86,8 @@ const baseConfig = {
82
86
  ${hasUserConfig ? `const userModule = await import('${join(options.cwd, "vite.config.ts").replace(/\\/g, "/")}')
83
87
  const userConfig = userModule.default ?? userModule
84
88
  export default mergeConfig(baseConfig, userConfig)` : "export default baseConfig"}
85
- `, "utf-8");
89
+ `;
90
+ writeFileSync(configPath, content, "utf-8");
86
91
  return configPath;
87
92
  }
88
93
  //#endregion
@@ -160,9 +165,92 @@ var InertiaBuildCommand = class extends Command {
160
165
  }
161
166
  };
162
167
  //#endregion
168
+ //#region src/commands/worker-recycler.ts
169
+ const execFileAsync = promisify(execFile);
170
+ function parsePsProcs(stdout) {
171
+ const procs = [];
172
+ for (const line of stdout.split("\n")) {
173
+ const m = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/.exec(line);
174
+ if (m) procs.push({
175
+ pid: Number(m[1]),
176
+ ppid: Number(m[2]),
177
+ rss: Number(m[3]),
178
+ cmd: m[4]
179
+ });
180
+ }
181
+ return procs;
182
+ }
183
+ function maxWorkerdRssMb(procs, rootPid) {
184
+ const childrenOf = /* @__PURE__ */ new Map();
185
+ for (const p of procs) {
186
+ const arr = childrenOf.get(p.ppid);
187
+ if (arr) arr.push(p.pid);
188
+ else childrenOf.set(p.ppid, [p.pid]);
189
+ }
190
+ const byPid = new Map(procs.map((p) => [p.pid, p]));
191
+ const stack = [rootPid];
192
+ const seen = /* @__PURE__ */ new Set();
193
+ let maxRssKb = 0;
194
+ while (stack.length) {
195
+ const pid = stack.pop();
196
+ if (seen.has(pid)) continue;
197
+ seen.add(pid);
198
+ const p = byPid.get(pid);
199
+ if (p && p.cmd.includes("workerd") && p.rss > maxRssKb) maxRssKb = p.rss;
200
+ for (const c of childrenOf.get(pid) ?? []) stack.push(c);
201
+ }
202
+ return maxRssKb > 0 ? Math.round(maxRssKb / 1024) : null;
203
+ }
204
+ async function readWorkerRssMb(rootPid) {
205
+ try {
206
+ const { stdout } = await execFileAsync("ps", ["-axo", "pid=,ppid=,rss=,command="], { maxBuffer: 67108864 });
207
+ return maxWorkerdRssMb(parsePsProcs(stdout), rootPid);
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+ /**
213
+ * Start sampling the worker's RSS and recycle it (via a Vite restart) before it
214
+ * can reach the V8 OOM ceiling. Returns a stop function.
215
+ */
216
+ function startWorkerRecycler(opts) {
217
+ const intervalMs = opts.intervalMs ?? 1500;
218
+ const cooldownMs = opts.cooldownMs ?? 3e3;
219
+ const rootPid = opts.child.pid;
220
+ const debug = process.env.STRATAL_RECYCLER_DEBUG === "1";
221
+ let lastRecycle = 0;
222
+ let stopped = false;
223
+ if (process.platform !== "darwin" && process.platform !== "linux") {
224
+ opts.log(`memory supervision unavailable on ${process.platform} (needs macOS/Linux)`);
225
+ return () => {};
226
+ }
227
+ if (rootPid == null) return () => {};
228
+ const timer = setInterval(() => {
229
+ if (stopped) return;
230
+ readWorkerRssMb(rootPid).then((rssMb) => {
231
+ if (stopped || rssMb == null) return;
232
+ if (debug) opts.log(`worker RSS ${rssMb}MB / limit ${opts.heapLimitMb}MB`);
233
+ if (rssMb >= opts.heapLimitMb && Date.now() - lastRecycle > cooldownMs) {
234
+ lastRecycle = Date.now();
235
+ opts.log(`recycling dev worker (${rssMb}MB)`);
236
+ try {
237
+ opts.child.stdin?.write("r\n");
238
+ } catch (e) {
239
+ opts.log(`failed to signal Vite restart: ${e.message}`);
240
+ }
241
+ }
242
+ });
243
+ }, intervalMs);
244
+ return () => {
245
+ stopped = true;
246
+ clearInterval(timer);
247
+ };
248
+ }
249
+ //#endregion
163
250
  //#region src/commands/inertia-dev.command.ts
251
+ const DEFAULT_HEAP_LIMIT_MB = 900;
164
252
  var InertiaDevCommand = class extends Command {
165
- static command = "inertia:dev {--port= : Dev server port} {--host : Expose to network} {--inspector-port= : Worker debugger inspector port (number, or \"false\" to disable). Set a distinct value per worker to avoid EADDRINUSE when running multiple Inertia workers concurrently.} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.}";
253
+ static command = "inertia:dev {--port= : Dev server port} {--host : Expose to network} {--inspector-port= : Worker debugger inspector port (number, or \"false\" to disable). Set a distinct value per worker to avoid EADDRINUSE when running multiple Inertia workers concurrently.} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.} {--heap-limit= : Recycle the dev worker isolate when its memory (MB) reaches this, to prevent the HMR-driven V8 OOM. Default 900.}";
166
254
  static description = "Start Inertia.js Vite development server";
167
255
  async handle() {
168
256
  const port = this.number("port");
@@ -178,6 +266,12 @@ var InertiaDevCommand = class extends Command {
178
266
  return 1;
179
267
  }
180
268
  }
269
+ const heapLimitRaw = this.string("heap-limit");
270
+ const heapLimitMb = heapLimitRaw ? Number(heapLimitRaw) : DEFAULT_HEAP_LIMIT_MB;
271
+ if (!Number.isFinite(heapLimitMb) || heapLimitMb <= 0) {
272
+ this.fail(`Invalid --heap-limit "${heapLimitRaw}". Expected a positive number of MB.`);
273
+ return 1;
274
+ }
181
275
  const cwd = process.cwd();
182
276
  if (!existsSync(join(cwd, "src/inertia/app.tsx"))) {
183
277
  this.fail("src/inertia/app.tsx not found. Run `quarry inertia:install` first.");
@@ -203,14 +297,34 @@ var InertiaDevCommand = class extends Command {
203
297
  return new Promise((resolve) => {
204
298
  const child = spawn("npx", args, {
205
299
  cwd,
206
- stdio: "inherit",
300
+ stdio: [
301
+ "pipe",
302
+ "inherit",
303
+ "inherit"
304
+ ],
207
305
  shell: true
208
306
  });
307
+ if (child.stdin && process.stdin.isTTY) {
308
+ process.stdin.pipe(child.stdin);
309
+ child.stdin.on("error", () => {});
310
+ process.stdin.unref();
311
+ }
312
+ const stopRecycler = startWorkerRecycler({
313
+ child,
314
+ heapLimitMb,
315
+ log: (msg) => this.info(`[worker-recycler] ${msg}`)
316
+ });
317
+ const cleanup = () => {
318
+ stopRecycler();
319
+ if (child.stdin && process.stdin.isTTY) process.stdin.unpipe(child.stdin);
320
+ };
209
321
  child.on("error", (err) => {
322
+ cleanup();
210
323
  this.fail(`Failed to start dev server: ${err.message}`);
211
324
  resolve(1);
212
325
  });
213
326
  child.on("close", (code) => {
327
+ cleanup();
214
328
  resolve(code ?? 0);
215
329
  });
216
330
  });
@@ -403,7 +517,8 @@ var InertiaTypesCommand = class extends Command {
403
517
  static description = "Generate Inertia.js page type definitions";
404
518
  async handle() {
405
519
  const cwd = process.cwd();
406
- if (!existsSync(findPagesDir(cwd))) {
520
+ const pagesDir = findPagesDir(cwd);
521
+ if (!existsSync(pagesDir)) {
407
522
  this.fail("src/inertia/pages/ not found. Run `quarry inertia:install` first.");
408
523
  return 1;
409
524
  }
@@ -1 +1 @@
1
- {"version":3,"file":"quarry.mjs","names":[],"sources":["../src/vite/create-client-vite-config.ts","../src/vite/create-vite-config.ts","../src/commands/inertia-build.command.ts","../src/commands/inertia-dev.command.ts","../src/commands/inertia-install.command.ts","../src/commands/inertia-types.command.ts","../src/quarry.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport interface TempClientViteConfigOptions {\n cwd: string\n entry?: string\n outDir?: string\n}\n\n/**\n * Emits a standalone Vite config for building the Inertia browser bundle.\n *\n * This runs as a separate `vite build` invocation BEFORE the worker build so\n * the worker's `stratal:inertia-inject-manifest` plugin has a finished\n * `<outDir>/.vite/manifest.json` to read. `@cloudflare/vite-plugin` builds its\n * environments in parallel, which made a single-config build racy — splitting\n * the two phases removes the race entirely and keeps each build minimal.\n */\nexport function writeTempClientViteConfig(options: TempClientViteConfigOptions): string {\n const configDir = join(options.cwd, 'node_modules', '.stratal')\n const configPath = join(configDir, 'vite.client.config.mjs')\n mkdirSync(dirname(configPath), { recursive: true })\n\n const entry = (options.entry ?? 'src/inertia/app.tsx').replace(/\\\\/g, '/')\n const outDir = (options.outDir ?? 'dist/client').replace(/\\\\/g, '/')\n const hasUserConfig = existsSync(join(options.cwd, 'vite.config.ts'))\n const publicDir = join(options.cwd, 'src', 'inertia', 'public').replace(/\\\\/g, '/')\n\n const content = `\nimport { mergeConfig } from 'vite'\n\nconst baseConfig = {\n publicDir: '${publicDir}',\n build: {\n outDir: '${outDir}',\n manifest: true,\n emptyOutDir: true,\n rollupOptions: {\n input: { app: '${entry}' },\n },\n },\n}\n\n${hasUserConfig\n ? `const userModule = await import('${join(options.cwd, 'vite.config.ts').replace(/\\\\/g, '/')}')\nconst userConfig = userModule.default ?? userModule\nexport default mergeConfig(userConfig, baseConfig)`\n : 'export default baseConfig'\n }\n`\n\n writeFileSync(configPath, content, 'utf-8')\n return configPath\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nexport interface TempViteConfigOptions {\n cwd: string\n server?: { port?: number; host?: boolean }\n outDir?: string\n persistTo?: string\n /**\n * Worker debugger inspector port passed to `@cloudflare/vite-plugin`.\n * Pass a distinct number per worker to avoid the `EADDRINUSE` race that\n * happens when several Inertia workers boot concurrently and all probe the\n * default port (9229). Pass `false` to disable the inspector entirely.\n * Left `undefined` preserves the plugin's default auto-pick behaviour.\n */\n inspectorPort?: number | false\n /**\n * Path (relative to `cwd`) to the Vite client manifest the worker bundle\n * should inline. Defaults to `dist/client/.vite/manifest.json`, matching\n * what `quarry inertia:build` emits in phase 1.\n */\n clientManifestPath?: string\n}\n\nexport function writeTempViteConfig(options: TempViteConfigOptions): string {\n const configDir = join(options.cwd, 'node_modules', '.stratal')\n const configPath = join(configDir, 'vite.config.mjs')\n mkdirSync(dirname(configPath), { recursive: true })\n\n const hasUserConfig = existsSync(join(options.cwd, 'vite.config.ts'))\n\n const serverConfig = options.server\n ? `server: { port: ${options.server.port}, host: ${options.server.host ? 'true' : 'undefined'} },`\n : ''\n\n const outDirConfig = options.outDir\n ? `outDir: '${options.outDir}',`\n : ''\n\n const cloudflareOptions: string[] = []\n if (options.persistTo) {\n cloudflareOptions.push(`persistState: { path: ${JSON.stringify(options.persistTo)} }`)\n }\n if (options.inspectorPort !== undefined) {\n cloudflareOptions.push(`inspectorPort: ${options.inspectorPort === false ? 'false' : options.inspectorPort}`)\n }\n const cloudflareArgs = cloudflareOptions.length ? `{ ${cloudflareOptions.join(', ')} }` : ''\n\n const stratalArgs = options.clientManifestPath\n ? `{ clientManifestPath: ${JSON.stringify(options.clientManifestPath)} }`\n : ''\n\n const content = `\nimport { mergeConfig } from 'vite'\nimport { cloudflare } from '@cloudflare/vite-plugin'\nimport { stratalInertia } from '@stratal/inertia/vite'\n\nlet inertiaPlugin = null\ntry {\n const mod = await import('@inertiajs/vite')\n const inertia = mod.default ?? mod\n inertiaPlugin = inertia()\n} catch {}\n\nconst baseConfig = {\n publicDir: 'src/inertia/public',\n plugins: [\n cloudflare(${cloudflareArgs}),\n ...(inertiaPlugin ? [inertiaPlugin] : []),\n ...stratalInertia(${stratalArgs}),\n ],\n build: {\n ${outDirConfig}\n },\n ${serverConfig}\n}\n\n${hasUserConfig\n ? `const userModule = await import('${join(options.cwd, 'vite.config.ts').replace(/\\\\/g, '/')}')\nconst userConfig = userModule.default ?? userModule\nexport default mergeConfig(baseConfig, userConfig)`\n : 'export default baseConfig'\n }\n`\n\n writeFileSync(configPath, content, 'utf-8')\n return configPath\n}\n","import { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { writeTempClientViteConfig } from '../vite/create-client-vite-config'\nimport { writeTempViteConfig } from '../vite/create-vite-config'\n\nexport class InertiaBuildCommand extends Command {\n static command = 'inertia:build {--outDir=dist : Output directory} {--ssr : Also build SSR bundle}'\n static description = 'Build Inertia.js frontend for production'\n\n async handle(): Promise<number | undefined> {\n const outDir = this.string('outDir') || 'dist'\n const shouldBuildSsr = this.boolean('ssr')\n const cwd = process.cwd()\n\n const entryPath = 'src/inertia/app.tsx'\n if (!existsSync(join(cwd, entryPath))) {\n this.fail('src/inertia/app.tsx not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n // Phase 1: standalone browser-bundle build. Runs without the Cloudflare\n // vite-plugin so it isn't subject to its parallel env orchestration. The\n // resulting `<clientOutDir>/.vite/manifest.json` is what the worker build\n // (phase 2) inlines into the worker entry via `stratal:inertia-inject-manifest`.\n const clientOutDir = join(outDir, 'client').replace(/\\\\/g, '/')\n const clientConfigPath = writeTempClientViteConfig({\n cwd,\n entry: entryPath,\n outDir: clientOutDir,\n })\n\n this.info('Building Inertia.js browser bundle...')\n const browserCode = await this.spawnVite(cwd, clientConfigPath, ['build'])\n if (browserCode !== 0) {\n this.fail('Browser bundle build failed.')\n return browserCode\n }\n this.success(`Browser bundle written to ${clientOutDir}/`)\n\n // Phase 2: worker build (Cloudflare vite-plugin). The injector plugin\n // reads the manifest produced in phase 1 and inlines it onto the worker\n // entry chunk.\n const configPath = writeTempViteConfig({\n cwd,\n outDir,\n clientManifestPath: join(clientOutDir, '.vite', 'manifest.json').replace(/\\\\/g, '/'),\n })\n\n this.info('Building Cloudflare worker bundle...')\n const workerCode = await this.spawnVite(cwd, configPath, ['build'])\n if (workerCode !== 0) {\n this.fail('Worker build failed.')\n return workerCode\n }\n this.success('Worker build complete!')\n\n if (shouldBuildSsr) {\n this.info('Building SSR bundle...')\n const ssrCode = await this.spawnVite(cwd, configPath, ['build', '--ssr'])\n if (ssrCode !== 0) {\n this.fail('SSR build failed.')\n return ssrCode\n }\n this.success('SSR build complete!')\n }\n\n this.success(`Output in ${outDir}/`)\n this.info('Deploy with: npx wrangler deploy')\n return 0\n }\n\n private spawnVite(cwd: string, configPath: string, args: string[]): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn('npx', ['vite', '--config', configPath, ...args], {\n cwd,\n stdio: 'inherit',\n shell: true,\n })\n\n child.on('error', (err) => {\n this.fail(`Vite process error: ${err.message}`)\n resolve(1)\n })\n\n child.on('close', (code) => {\n resolve(code ?? 0)\n })\n })\n }\n}\n","import { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { writeTempViteConfig } from '../vite/create-vite-config'\n\nexport class InertiaDevCommand extends Command {\n static command = 'inertia:dev {--port= : Dev server port} {--host : Expose to network} {--inspector-port= : Worker debugger inspector port (number, or \"false\" to disable). Set a distinct value per worker to avoid EADDRINUSE when running multiple Inertia workers concurrently.} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.}'\n static description = 'Start Inertia.js Vite development server'\n\n async handle(): Promise<number | undefined> {\n const port = this.number('port')\n const host = this.boolean('host')\n const persistTo = this.string('persist-to')\n const inspectorPortRaw = this.string('inspector-port')\n let inspectorPort: number | false | undefined\n if (inspectorPortRaw === 'false') {\n inspectorPort = false\n } else if (inspectorPortRaw !== undefined) {\n inspectorPort = Number(inspectorPortRaw)\n if (!Number.isInteger(inspectorPort) || inspectorPort < 0 || inspectorPort > 65535) {\n this.fail(`Invalid --inspector-port \"${inspectorPortRaw}\". Expected an integer between 0 and 65535, or \"false\" to disable.`)\n return 1\n }\n }\n const cwd = process.cwd()\n\n const entryPath = 'src/inertia/app.tsx'\n if (!existsSync(join(cwd, entryPath))) {\n this.fail('src/inertia/app.tsx not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n const configPath = writeTempViteConfig({\n cwd,\n server: { port, host },\n persistTo,\n inspectorPort,\n })\n\n this.info('Starting Vite dev server...')\n\n const args = ['vite', 'dev', '--config', configPath]\n if (host) args.push('--host')\n\n return new Promise<number>((resolve) => {\n const child = spawn('npx', args, {\n cwd,\n stdio: 'inherit',\n shell: true,\n })\n\n child.on('error', (err) => {\n this.fail(`Failed to start dev server: ${err.message}`)\n resolve(1)\n })\n\n child.on('close', (code) => {\n resolve(code ?? 0)\n })\n })\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport type { SourceFile, SyntaxKind } from 'ts-morph'\nimport { runTypeGeneration } from '../generator/type-generator'\n\n/** Outcome of reconciling `src/app.module.ts` with the SSR-enabled InertiaModule. */\ntype AppModuleUpdate = 'created' | 'ssr-added' | 'unchanged' | 'unwired'\n/**\n * The subset of ts-morph's runtime `SyntaxKind` enum that `ensureSsrWiring` reads.\n * Declared structurally (via the enum-member literal types) so ts-morph stays a\n * type-only import here and is loaded lazily where it's actually used.\n */\ninterface SyntaxKinds {\n CallExpression: SyntaxKind.CallExpression\n ObjectLiteralExpression: SyntaxKind.ObjectLiteralExpression\n}\n\nconst ROOT_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n @viteHead\n @inertiaHead\n</head>\n<body>\n @inertia\n @viteScripts\n</body>\n</html>`\n\nconst APP_TSX = `import { createInertiaApp } from '@inertiajs/react'\n\ncreateInertiaApp({\n resolve: async (name) => {\n const pages = import.meta.glob('./pages/**/*.tsx')\n const page = await pages[\\`./pages/\\${name}.tsx\\`]?.()\n if (!page) throw new Error(\\`Page not found: \\${name}\\`)\n return page\n },\n})`\n\nconst SSR_TSX = `import { createInertiaSsrApp } from '@stratal/inertia/ssr'\n\nexport const { render } = createInertiaSsrApp({\n resolve: async (name) => {\n const pages = import.meta.glob('./pages/**/*.tsx')\n const page = await pages[\\`./pages/\\${name}.tsx\\`]?.()\n if (!page) throw new Error(\\`Page not found: \\${name}\\`)\n return page\n },\n})`\n\nconst HOME_TSX = `export default function Home({ message }: { message: string }) {\n return (\n <div>\n <h1>{message}</h1>\n <p>This page is rendered with Inertia.js and Stratal.</p>\n </div>\n )\n}`\n\nexport class InertiaInstallCommand extends Command {\n static command = 'inertia:install {--skip-deps : Skip installing npm dependencies}'\n static description = 'Scaffold Inertia.js files for a Stratal project'\n\n async handle(): Promise<number | undefined> {\n const skipDeps = this.boolean('skip-deps')\n const cwd = process.cwd()\n const inertiaDir = join(cwd, 'src', 'inertia')\n const pagesDir = join(inertiaDir, 'pages')\n\n // Create directories\n this.info('Creating src/inertia/ directory...')\n mkdirSync(pagesDir, { recursive: true })\n\n const publicDir = join(inertiaDir, 'public')\n mkdirSync(publicDir, { recursive: true })\n const gitkeepPath = join(publicDir, '.gitkeep')\n if (!existsSync(gitkeepPath)) {\n writeFileSync(gitkeepPath, '', 'utf-8')\n }\n this.success('Created src/inertia/public/')\n\n // Write template files\n const files = [\n { path: join(inertiaDir, 'root.html'), content: ROOT_HTML, name: 'root.html' },\n { path: join(inertiaDir, 'app.tsx'), content: APP_TSX, name: 'app.tsx' },\n { path: join(inertiaDir, 'ssr.tsx'), content: SSR_TSX, name: 'ssr.tsx' },\n { path: join(pagesDir, 'Home.tsx'), content: HOME_TSX, name: 'pages/Home.tsx' },\n ]\n\n for (const file of files) {\n if (existsSync(file.path)) {\n this.warn(`Skipping ${file.name} (already exists)`)\n } else {\n writeFileSync(file.path, file.content, 'utf-8')\n this.success(`Created src/inertia/${file.name}`)\n }\n }\n\n // Modify app.module.ts\n const appModulePath = join(cwd, 'src', 'app.module.ts')\n if (existsSync(appModulePath)) {\n this.info('Updating src/app.module.ts...')\n try {\n const result = await this.updateAppModule(appModulePath)\n if (result === 'created') {\n this.success('Updated src/app.module.ts with InertiaModule')\n } else if (result === 'ssr-added') {\n this.success('Enabled streaming SSR in src/app.module.ts')\n } else if (result === 'unchanged') {\n this.info('InertiaModule (with SSR) already configured in app.module.ts')\n } else {\n this.warn('InertiaModule is configured but SSR could not be auto-wired.')\n this.info(\"Add `ssr: { bundle: () => import('./inertia/ssr') }` to your InertiaModule options\")\n }\n } catch (err) {\n this.warn(`Could not auto-update app.module.ts: ${(err as Error).message}`)\n this.info('Please manually add InertiaModule.forRoot() to your module imports')\n }\n } else {\n this.info('No src/app.module.ts found — please manually configure InertiaModule')\n }\n\n // Generate initial type definitions\n try {\n const { outputPath, pageCount } = await runTypeGeneration(cwd)\n const relPath = relative(cwd, outputPath)\n this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? 's' : ''})`)\n } catch {\n this.warn('Could not generate initial type definitions. Run `quarry inertia:types` manually.')\n }\n\n if (!skipDeps) {\n this.newLine()\n this.info('Install the following dependencies:')\n this.line(' npm install @stratal/inertia @inertiajs/react @inertiajs/vite react react-dom')\n this.line(' npm install -D @types/react @types/react-dom vite @cloudflare/vite-plugin')\n }\n\n this.newLine()\n this.success('Inertia.js scaffolding complete!')\n this.info('Run `quarry inertia:dev` to start the dev server')\n\n return 0\n }\n\n private async updateAppModule(modulePath: string): Promise<AppModuleUpdate> {\n const { Project, SyntaxKind } = await import('ts-morph')\n\n const project = new Project({ useInMemoryFileSystem: false })\n const sourceFile = project.addSourceFileAtPath(modulePath)\n\n // Already importing the package — an older install that predates streaming\n // SSR. Wire the existing InertiaModule config to the SSR bundle rather than\n // bailing (which would leave SSR silently disabled).\n const existingImport = sourceFile.getImportDeclaration((decl) =>\n decl.getModuleSpecifierValue() === '@stratal/inertia',\n )\n if (existingImport) {\n const result = this.ensureSsrWiring(sourceFile, SyntaxKind)\n if (result === 'ssr-added') await sourceFile.save()\n return result\n }\n\n // Fresh install: add the imports and an InertiaModule.forRoot wired for SSR.\n sourceFile.addImportDeclaration({\n defaultImport: 'rootView',\n moduleSpecifier: './inertia/root.html?raw',\n })\n sourceFile.addImportDeclaration({\n namedImports: ['InertiaModule'],\n moduleSpecifier: '@stratal/inertia',\n })\n\n // Find the @Module decorator and add InertiaModule to imports\n const classes = sourceFile.getClasses()\n for (const cls of classes) {\n const moduleDecorator = cls.getDecorator('Module')\n if (!moduleDecorator) continue\n\n const args = moduleDecorator.getArguments()\n if (args.length === 0) continue\n\n const objLiteral = args[0].asKind(SyntaxKind.ObjectLiteralExpression)\n if (!objLiteral) continue\n\n const importsProp = objLiteral.getProperty('imports')\n if (importsProp) {\n // Add to existing imports array\n const initializer = importsProp.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()\n const arrayLiteral = initializer?.asKind(SyntaxKind.ArrayLiteralExpression)\n if (arrayLiteral) {\n arrayLiteral.addElement(`InertiaModule.forRoot({\\n rootView,\\n ssr: { bundle: () => import('./inertia/ssr') },\\n })`)\n }\n } else {\n // Add imports property\n objLiteral.addPropertyAssignment({\n name: 'imports',\n initializer: `[\\n InertiaModule.forRoot({\\n rootView,\\n ssr: { bundle: () => import('./inertia/ssr') },\\n }),\\n ]`,\n })\n }\n\n break\n }\n\n await sourceFile.save()\n return 'created'\n }\n\n /**\n * Ensure an existing `InertiaModule.forRoot({...})` call opts into the streaming\n * SSR bundle. Returns `ssr-added` when the option is inserted, `unchanged` when\n * one is already present, or `unwired` when no plain `forRoot({...})` object\n * literal is found (e.g. `forRootAsync`, or a config passed by reference) — in\n * which case the caller surfaces a manual instruction.\n */\n private ensureSsrWiring(sourceFile: SourceFile, syntaxKind: SyntaxKinds): AppModuleUpdate {\n const calls = sourceFile.getDescendantsOfKind(syntaxKind.CallExpression)\n for (const call of calls) {\n if (call.getExpression().getText() !== 'InertiaModule.forRoot') continue\n\n const objLiteral = call.getArguments()[0]?.asKind(syntaxKind.ObjectLiteralExpression)\n if (!objLiteral) continue\n\n if (objLiteral.getProperty('ssr')) return 'unchanged'\n\n objLiteral.addPropertyAssignment({\n name: 'ssr',\n initializer: `{ bundle: () => import('./inertia/ssr') }`,\n })\n return 'ssr-added'\n }\n return 'unwired'\n }\n}\n","import { existsSync } from 'node:fs'\nimport { watch } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { findPagesDir, runTypeGeneration } from '../generator/type-generator'\n\nexport class InertiaTypesCommand extends Command {\n static command = 'inertia:types {--watch : Watch for changes and regenerate}'\n static description = 'Generate Inertia.js page type definitions'\n\n async handle(): Promise<number | undefined> {\n const cwd = process.cwd()\n const pagesDir = findPagesDir(cwd)\n\n if (!existsSync(pagesDir)) {\n this.fail('src/inertia/pages/ not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n const result = await this.generate(cwd)\n if (!result) return 1\n\n if (this.boolean('watch')) {\n this.info('Watching for changes...')\n await this.watchForChanges(cwd)\n }\n\n return 0\n }\n\n private async generate(cwd: string): Promise<boolean> {\n try {\n const { outputPath, pageCount } = await runTypeGeneration(cwd)\n const relPath = relative(cwd, outputPath)\n this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? 's' : ''})`)\n return true\n } catch (err) {\n this.fail(`Type generation failed: ${(err as Error).message}`)\n return false\n }\n }\n\n private async watchForChanges(cwd: string): Promise<void> {\n const srcDir = join(cwd, 'src')\n\n try {\n const watcher = watch(srcDir, { recursive: true })\n for await (const event of watcher) {\n if (event.filename && /\\.(tsx|ts)$/.test(event.filename)) {\n this.info(`Change detected: ${event.filename}`)\n await this.generate(cwd)\n }\n }\n } catch (err) {\n this.fail(`Watch failed: ${(err as Error).message}`)\n }\n }\n}\n","import { Module } from 'stratal/module'\nimport { InertiaBuildCommand } from './commands/inertia-build.command'\nimport { InertiaDevCommand } from './commands/inertia-dev.command'\nimport { InertiaInstallCommand } from './commands/inertia-install.command'\nimport { InertiaTypesCommand } from './commands/inertia-types.command'\n\n@Module({\n providers: [\n InertiaInstallCommand,\n InertiaTypesCommand,\n InertiaDevCommand,\n InertiaBuildCommand,\n ],\n})\nexport class InertiaQuarryModule {}\n\nexport { InertiaBuildCommand } from './commands/inertia-build.command'\nexport { InertiaDevCommand } from './commands/inertia-dev.command'\nexport { InertiaInstallCommand } from './commands/inertia-install.command'\nexport { InertiaTypesCommand } from './commands/inertia-types.command'\nexport { runTypeGeneration } from './generator/type-generator'\n"],"mappings":"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"}
1
+ {"version":3,"file":"quarry.mjs","names":[],"sources":["../src/vite/create-client-vite-config.ts","../src/vite/create-vite-config.ts","../src/commands/inertia-build.command.ts","../src/commands/worker-recycler.ts","../src/commands/inertia-dev.command.ts","../src/commands/inertia-install.command.ts","../src/commands/inertia-types.command.ts","../src/quarry.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nexport interface TempClientViteConfigOptions {\n cwd: string\n entry?: string\n outDir?: string\n}\n\n/**\n * Emits a standalone Vite config for building the Inertia browser bundle.\n *\n * This runs as a separate `vite build` invocation BEFORE the worker build so\n * the worker's `stratal:inertia-inject-manifest` plugin has a finished\n * `<outDir>/.vite/manifest.json` to read. `@cloudflare/vite-plugin` builds its\n * environments in parallel, which made a single-config build racy — splitting\n * the two phases removes the race entirely and keeps each build minimal.\n */\nexport function writeTempClientViteConfig(options: TempClientViteConfigOptions): string {\n const configDir = join(options.cwd, 'node_modules', '.stratal')\n const configPath = join(configDir, 'vite.client.config.mjs')\n mkdirSync(dirname(configPath), { recursive: true })\n\n const entry = (options.entry ?? 'src/inertia/app.tsx').replace(/\\\\/g, '/')\n const outDir = (options.outDir ?? 'dist/client').replace(/\\\\/g, '/')\n const hasUserConfig = existsSync(join(options.cwd, 'vite.config.ts'))\n const publicDir = join(options.cwd, 'src', 'inertia', 'public').replace(/\\\\/g, '/')\n\n const content = `\nimport { mergeConfig } from 'vite'\n\nconst baseConfig = {\n publicDir: '${publicDir}',\n build: {\n outDir: '${outDir}',\n manifest: true,\n emptyOutDir: true,\n rollupOptions: {\n input: { app: '${entry}' },\n },\n },\n}\n\n${hasUserConfig\n ? `const userModule = await import('${join(options.cwd, 'vite.config.ts').replace(/\\\\/g, '/')}')\nconst userConfig = userModule.default ?? userModule\nexport default mergeConfig(userConfig, baseConfig)`\n : 'export default baseConfig'\n }\n`\n\n writeFileSync(configPath, content, 'utf-8')\n return configPath\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nexport interface TempViteConfigOptions {\n cwd: string\n server?: { port?: number; host?: boolean }\n outDir?: string\n persistTo?: string\n /**\n * Worker debugger inspector port passed to `@cloudflare/vite-plugin`.\n * Pass a distinct number per worker to avoid the `EADDRINUSE` race that\n * happens when several Inertia workers boot concurrently and all probe the\n * default port (9229). Pass `false` to disable the inspector entirely.\n * Left `undefined` preserves the plugin's default auto-pick behaviour.\n */\n inspectorPort?: number | false\n /**\n * Path (relative to `cwd`) to the Vite client manifest the worker bundle\n * should inline. Defaults to `dist/client/.vite/manifest.json`, matching\n * what `quarry inertia:build` emits in phase 1.\n */\n clientManifestPath?: string\n}\n\nexport function writeTempViteConfig(options: TempViteConfigOptions): string {\n const configDir = join(options.cwd, 'node_modules', '.stratal')\n const configPath = join(configDir, 'vite.config.mjs')\n mkdirSync(dirname(configPath), { recursive: true })\n\n const hasUserConfig = existsSync(join(options.cwd, 'vite.config.ts'))\n\n const serverConfig = options.server\n ? `server: { port: ${options.server.port}, host: ${options.server.host ? 'true' : 'undefined'} },`\n : ''\n\n const outDirConfig = options.outDir\n ? `outDir: '${options.outDir}',`\n : ''\n\n const cloudflareOptions: string[] = []\n if (options.persistTo) {\n cloudflareOptions.push(`persistState: { path: ${JSON.stringify(options.persistTo)} }`)\n }\n if (options.inspectorPort !== undefined) {\n cloudflareOptions.push(`inspectorPort: ${options.inspectorPort === false ? 'false' : options.inspectorPort}`)\n }\n const cloudflareArgs = cloudflareOptions.length ? `{ ${cloudflareOptions.join(', ')} }` : ''\n\n const stratalArgs = options.clientManifestPath\n ? `{ clientManifestPath: ${JSON.stringify(options.clientManifestPath)} }`\n : ''\n\n const content = `\nimport { mergeConfig } from 'vite'\nimport { cloudflare } from '@cloudflare/vite-plugin'\nimport { stratalInertia } from '@stratal/inertia/vite'\n\nlet inertiaPlugin = null\ntry {\n const mod = await import('@inertiajs/vite')\n const inertia = mod.default ?? mod\n inertiaPlugin = inertia()\n} catch {}\n\nconst baseConfig = {\n publicDir: 'src/inertia/public',\n plugins: [\n cloudflare(${cloudflareArgs}),\n ...(inertiaPlugin ? [inertiaPlugin] : []),\n ...stratalInertia(${stratalArgs}),\n ],\n build: {\n ${outDirConfig}\n },\n ${serverConfig}\n}\n\n${hasUserConfig\n ? `const userModule = await import('${join(options.cwd, 'vite.config.ts').replace(/\\\\/g, '/')}')\nconst userConfig = userModule.default ?? userModule\nexport default mergeConfig(baseConfig, userConfig)`\n : 'export default baseConfig'\n }\n`\n\n writeFileSync(configPath, content, 'utf-8')\n return configPath\n}\n","import { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { writeTempClientViteConfig } from '../vite/create-client-vite-config'\nimport { writeTempViteConfig } from '../vite/create-vite-config'\n\nexport class InertiaBuildCommand extends Command {\n static command = 'inertia:build {--outDir=dist : Output directory} {--ssr : Also build SSR bundle}'\n static description = 'Build Inertia.js frontend for production'\n\n async handle(): Promise<number | undefined> {\n const outDir = this.string('outDir') || 'dist'\n const shouldBuildSsr = this.boolean('ssr')\n const cwd = process.cwd()\n\n const entryPath = 'src/inertia/app.tsx'\n if (!existsSync(join(cwd, entryPath))) {\n this.fail('src/inertia/app.tsx not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n // Phase 1: standalone browser-bundle build. Runs without the Cloudflare\n // vite-plugin so it isn't subject to its parallel env orchestration. The\n // resulting `<clientOutDir>/.vite/manifest.json` is what the worker build\n // (phase 2) inlines into the worker entry via `stratal:inertia-inject-manifest`.\n const clientOutDir = join(outDir, 'client').replace(/\\\\/g, '/')\n const clientConfigPath = writeTempClientViteConfig({\n cwd,\n entry: entryPath,\n outDir: clientOutDir,\n })\n\n this.info('Building Inertia.js browser bundle...')\n const browserCode = await this.spawnVite(cwd, clientConfigPath, ['build'])\n if (browserCode !== 0) {\n this.fail('Browser bundle build failed.')\n return browserCode\n }\n this.success(`Browser bundle written to ${clientOutDir}/`)\n\n // Phase 2: worker build (Cloudflare vite-plugin). The injector plugin\n // reads the manifest produced in phase 1 and inlines it onto the worker\n // entry chunk.\n const configPath = writeTempViteConfig({\n cwd,\n outDir,\n clientManifestPath: join(clientOutDir, '.vite', 'manifest.json').replace(/\\\\/g, '/'),\n })\n\n this.info('Building Cloudflare worker bundle...')\n const workerCode = await this.spawnVite(cwd, configPath, ['build'])\n if (workerCode !== 0) {\n this.fail('Worker build failed.')\n return workerCode\n }\n this.success('Worker build complete!')\n\n if (shouldBuildSsr) {\n this.info('Building SSR bundle...')\n const ssrCode = await this.spawnVite(cwd, configPath, ['build', '--ssr'])\n if (ssrCode !== 0) {\n this.fail('SSR build failed.')\n return ssrCode\n }\n this.success('SSR build complete!')\n }\n\n this.success(`Output in ${outDir}/`)\n this.info('Deploy with: npx wrangler deploy')\n return 0\n }\n\n private spawnVite(cwd: string, configPath: string, args: string[]): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn('npx', ['vite', '--config', configPath, ...args], {\n cwd,\n stdio: 'inherit',\n shell: true,\n })\n\n child.on('error', (err) => {\n this.fail(`Vite process error: ${err.message}`)\n resolve(1)\n })\n\n child.on('close', (code) => {\n resolve(code ?? 0)\n })\n })\n }\n}\n","import { execFile } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst execFileAsync = promisify(execFile)\n\n// Dev-only supervisor that keeps the Inertia worker's memory bounded.\n//\n// `@cloudflare/vite-plugin` runs the app worker in a single long-lived workerd\n// isolate. Under sustained HMR the isolate re-executes module top-level code\n// (DI registration, app boot) without discarding prior instances, so its heap\n// climbs monotonically to the ~1.4 GB V8 limit and aborts with \"JavaScript heap\n// out of memory\" — the worker dies and the browser shows \"Fetch failed\".\n//\n// There is no config-level isolate memory cap in miniflare/workerd, and killing\n// workerd externally wedges the dev server (miniflare never auto-respawns). The\n// only clean reset is a Vite server restart: it drives the plugin through\n// `miniflare.setOptions`, which SIGKILLs workerd and respawns a fresh ~40 MB\n// isolate while keeping ports stable and recovering to healthy within ~1 s.\n//\n// So quarry (the parent that owns the spawned `vite dev` child) samples the\n// workerd process RSS and, when it crosses a threshold, triggers that restart by\n// writing Vite's `r` shortcut to the child's stdin. RSS (not the V8 inspector)\n// is used because the consuming app installs a global undici dispatcher at boot\n// that intercepts the quarry process's own fetch/WebSocket; RSS via `ps` is\n// immune to that and tracks the isolate heap closely (verified: ~1.4 GB at OOM).\n\nexport interface Proc { pid: number, ppid: number, rss: number, cmd: string }\n\n// Parse `ps -axo pid=,ppid=,rss=,command=` output (rss in KB). Exported for tests.\nexport function parsePsProcs(stdout: string): Proc[] {\n const procs: Proc[] = []\n for (const line of stdout.split('\\n')) {\n const m = /^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(.*)$/.exec(line)\n if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), rss: Number(m[3]), cmd: m[4] })\n }\n return procs\n}\n\n// Max RSS (MB) among workerd processes descended from `rootPid` (the spawned vite\n// child). Returns null when no workerd descendant is found (worker still booting).\n// Pure and exported for tests. `rss` is in KB.\nexport function maxWorkerdRssMb(procs: Proc[], rootPid: number): number | null {\n const childrenOf = new Map<number, number[]>()\n for (const p of procs) {\n const arr = childrenOf.get(p.ppid)\n if (arr) arr.push(p.pid)\n else childrenOf.set(p.ppid, [p.pid])\n }\n const byPid = new Map(procs.map(p => [p.pid, p]))\n const stack = [rootPid]\n const seen = new Set<number>()\n let maxRssKb = 0\n while (stack.length) {\n const pid = stack.pop()!\n if (seen.has(pid)) continue\n seen.add(pid)\n const p = byPid.get(pid)\n if (p && p.cmd.includes('workerd') && p.rss > maxRssKb) maxRssKb = p.rss\n for (const c of childrenOf.get(pid) ?? []) stack.push(c)\n }\n return maxRssKb > 0 ? Math.round(maxRssKb / 1024) : null\n}\n\nasync function readWorkerRssMb(rootPid: number): Promise<number | null> {\n try {\n const { stdout } = await execFileAsync('ps', ['-axo', 'pid=,ppid=,rss=,command='], { maxBuffer: 64 * 1024 * 1024 })\n return maxWorkerdRssMb(parsePsProcs(stdout), rootPid)\n } catch {\n return null\n }\n}\n\nexport interface WorkerRecyclerOptions {\n /** The spawned `vite dev` child; its stdin must be piped. */\n child: ChildProcess\n /** Recycle when the worker's RSS reaches this many MB. */\n heapLimitMb: number\n /** How often to sample RSS. */\n intervalMs?: number\n /** Minimum gap between recycles, to let a restart settle. */\n cooldownMs?: number\n log: (msg: string) => void\n}\n\n/**\n * Start sampling the worker's RSS and recycle it (via a Vite restart) before it\n * can reach the V8 OOM ceiling. Returns a stop function.\n */\nexport function startWorkerRecycler(opts: WorkerRecyclerOptions): () => void {\n // Poll fast and keep the post-recycle cooldown just long enough to cover a\n // Vite restart (~1-2 s), so RSS can't overshoot far past the threshold toward\n // the OOM ceiling under intense churn (overshoot ≈ (interval+cooldown)×growth).\n const intervalMs = opts.intervalMs ?? 1500\n const cooldownMs = opts.cooldownMs ?? 3000\n const rootPid = opts.child.pid\n const debug = process.env.STRATAL_RECYCLER_DEBUG === '1'\n let lastRecycle = 0\n let stopped = false\n\n // RSS sampling uses `ps`, which is POSIX-only; elsewhere the worker is left\n // unsupervised. Warn once (it never fires on the common macOS/Linux path) —\n // otherwise the recycler is silent unless it actually recycles.\n if (process.platform !== 'darwin' && process.platform !== 'linux') {\n opts.log(`memory supervision unavailable on ${process.platform} (needs macOS/Linux)`)\n return () => { /* nothing to stop */ }\n }\n\n if (rootPid == null) return () => { /* no child to supervise */ }\n\n const timer = setInterval(() => {\n if (stopped) return\n void readWorkerRssMb(rootPid).then((rssMb) => {\n if (stopped || rssMb == null) return\n if (debug) opts.log(`worker RSS ${rssMb}MB / limit ${opts.heapLimitMb}MB`)\n if (rssMb >= opts.heapLimitMb && Date.now() - lastRecycle > cooldownMs) {\n lastRecycle = Date.now()\n opts.log(`recycling dev worker (${rssMb}MB)`)\n try {\n opts.child.stdin?.write('r\\n')\n } catch (e) {\n opts.log(`failed to signal Vite restart: ${(e as Error).message}`)\n }\n }\n })\n }, intervalMs)\n\n return () => { stopped = true; clearInterval(timer) }\n}\n","import { spawn } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { writeTempViteConfig } from '../vite/create-vite-config'\nimport { startWorkerRecycler } from './worker-recycler'\n\n// Recycle the worker isolate once its RSS reaches this many MB. The worker V8\n// OOMs near ~1.4 GB; a freshly-warmed isolate sits ~250 MB, so 900 MB leaves\n// generous headroom for the restart to complete before the ceiling. See\n// worker-recycler.ts for why this is necessary.\nconst DEFAULT_HEAP_LIMIT_MB = 900\n\nexport class InertiaDevCommand extends Command {\n static command = 'inertia:dev {--port= : Dev server port} {--host : Expose to network} {--inspector-port= : Worker debugger inspector port (number, or \"false\" to disable). Set a distinct value per worker to avoid EADDRINUSE when running multiple Inertia workers concurrently.} {--persist-to= : Shared persist directory for @cloudflare/vite-plugin (relative to cwd; the plugin appends /v3). Use to share R2/KV/cache emulator state across multiple workers in dev.} {--heap-limit= : Recycle the dev worker isolate when its memory (MB) reaches this, to prevent the HMR-driven V8 OOM. Default 900.}'\n static description = 'Start Inertia.js Vite development server'\n\n async handle(): Promise<number | undefined> {\n const port = this.number('port')\n const host = this.boolean('host')\n const persistTo = this.string('persist-to')\n const inspectorPortRaw = this.string('inspector-port')\n let inspectorPort: number | false | undefined\n if (inspectorPortRaw === 'false') {\n inspectorPort = false\n } else if (inspectorPortRaw !== undefined) {\n inspectorPort = Number(inspectorPortRaw)\n if (!Number.isInteger(inspectorPort) || inspectorPort < 0 || inspectorPort > 65535) {\n this.fail(`Invalid --inspector-port \"${inspectorPortRaw}\". Expected an integer between 0 and 65535, or \"false\" to disable.`)\n return 1\n }\n }\n const heapLimitRaw = this.string('heap-limit')\n // Unset resolves to an empty string here, not undefined — treat it as default.\n const heapLimitMb = heapLimitRaw ? Number(heapLimitRaw) : DEFAULT_HEAP_LIMIT_MB\n if (!Number.isFinite(heapLimitMb) || heapLimitMb <= 0) {\n this.fail(`Invalid --heap-limit \"${heapLimitRaw}\". Expected a positive number of MB.`)\n return 1\n }\n const cwd = process.cwd()\n\n const entryPath = 'src/inertia/app.tsx'\n if (!existsSync(join(cwd, entryPath))) {\n this.fail('src/inertia/app.tsx not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n const configPath = writeTempViteConfig({\n cwd,\n server: { port, host },\n persistTo,\n inspectorPort,\n })\n\n this.info('Starting Vite dev server...')\n\n const args = ['vite', 'dev', '--config', configPath]\n if (host) args.push('--host')\n\n return new Promise<number>((resolve) => {\n // stdin is piped (not inherited) so the recycler can inject Vite's `r`\n // restart shortcut; stdout/stderr stay inherited so dev output is unchanged.\n const child = spawn('npx', args, {\n cwd,\n stdio: ['pipe', 'inherit', 'inherit'],\n shell: true,\n })\n\n // Forward the user's keystrokes to Vite so its interactive shortcuts\n // (r/u/o/q/h) keep working — piping stdin for the recycler would otherwise\n // swallow them. The recycler writes `r\\n` to the same stdin independently.\n // Only when stdin is an interactive TTY: keystrokes come from nowhere else,\n // and a non-TTY stdin (background/CI/piped) is a plain stream without\n // unref(). unref() so this forwarded stdin never keeps the process alive on\n // its own; the child process handle does that until it exits.\n if (child.stdin && process.stdin.isTTY) {\n process.stdin.pipe(child.stdin)\n // The child's stdin closes when it exits — ignore the resulting EPIPE.\n child.stdin.on('error', () => { /* child gone */ })\n process.stdin.unref()\n }\n\n // Supervise the worker's memory and recycle the isolate (via a Vite\n // restart) before the HMR-driven V8 OOM. A restart resets the isolate to a\n // fresh ~40 MB. Always on — this is the OOM fix, not an opt-in.\n const stopRecycler = startWorkerRecycler({\n child,\n heapLimitMb,\n log: (msg) => this.info(`[worker-recycler] ${msg}`),\n })\n\n const cleanup = () => {\n stopRecycler()\n if (child.stdin && process.stdin.isTTY) process.stdin.unpipe(child.stdin)\n }\n\n child.on('error', (err) => {\n cleanup()\n this.fail(`Failed to start dev server: ${err.message}`)\n resolve(1)\n })\n\n child.on('close', (code) => {\n cleanup()\n resolve(code ?? 0)\n })\n })\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { join, relative } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport type { SourceFile, SyntaxKind } from 'ts-morph'\nimport { runTypeGeneration } from '../generator/type-generator'\n\n/** Outcome of reconciling `src/app.module.ts` with the SSR-enabled InertiaModule. */\ntype AppModuleUpdate = 'created' | 'ssr-added' | 'unchanged' | 'unwired'\n/**\n * The subset of ts-morph's runtime `SyntaxKind` enum that `ensureSsrWiring` reads.\n * Declared structurally (via the enum-member literal types) so ts-morph stays a\n * type-only import here and is loaded lazily where it's actually used.\n */\ninterface SyntaxKinds {\n CallExpression: SyntaxKind.CallExpression\n ObjectLiteralExpression: SyntaxKind.ObjectLiteralExpression\n}\n\nconst ROOT_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n @viteHead\n @inertiaHead\n</head>\n<body>\n @inertia\n @viteScripts\n</body>\n</html>`\n\nconst APP_TSX = `import { createInertiaApp } from '@inertiajs/react'\n\ncreateInertiaApp({\n resolve: async (name) => {\n const pages = import.meta.glob('./pages/**/*.tsx')\n const page = await pages[\\`./pages/\\${name}.tsx\\`]?.()\n if (!page) throw new Error(\\`Page not found: \\${name}\\`)\n return page\n },\n})`\n\nconst SSR_TSX = `import { createInertiaSsrApp } from '@stratal/inertia/ssr'\n\nexport const { render } = createInertiaSsrApp({\n resolve: async (name) => {\n const pages = import.meta.glob('./pages/**/*.tsx')\n const page = await pages[\\`./pages/\\${name}.tsx\\`]?.()\n if (!page) throw new Error(\\`Page not found: \\${name}\\`)\n return page\n },\n})`\n\nconst HOME_TSX = `export default function Home({ message }: { message: string }) {\n return (\n <div>\n <h1>{message}</h1>\n <p>This page is rendered with Inertia.js and Stratal.</p>\n </div>\n )\n}`\n\nexport class InertiaInstallCommand extends Command {\n static command = 'inertia:install {--skip-deps : Skip installing npm dependencies}'\n static description = 'Scaffold Inertia.js files for a Stratal project'\n\n async handle(): Promise<number | undefined> {\n const skipDeps = this.boolean('skip-deps')\n const cwd = process.cwd()\n const inertiaDir = join(cwd, 'src', 'inertia')\n const pagesDir = join(inertiaDir, 'pages')\n\n // Create directories\n this.info('Creating src/inertia/ directory...')\n mkdirSync(pagesDir, { recursive: true })\n\n const publicDir = join(inertiaDir, 'public')\n mkdirSync(publicDir, { recursive: true })\n const gitkeepPath = join(publicDir, '.gitkeep')\n if (!existsSync(gitkeepPath)) {\n writeFileSync(gitkeepPath, '', 'utf-8')\n }\n this.success('Created src/inertia/public/')\n\n // Write template files\n const files = [\n { path: join(inertiaDir, 'root.html'), content: ROOT_HTML, name: 'root.html' },\n { path: join(inertiaDir, 'app.tsx'), content: APP_TSX, name: 'app.tsx' },\n { path: join(inertiaDir, 'ssr.tsx'), content: SSR_TSX, name: 'ssr.tsx' },\n { path: join(pagesDir, 'Home.tsx'), content: HOME_TSX, name: 'pages/Home.tsx' },\n ]\n\n for (const file of files) {\n if (existsSync(file.path)) {\n this.warn(`Skipping ${file.name} (already exists)`)\n } else {\n writeFileSync(file.path, file.content, 'utf-8')\n this.success(`Created src/inertia/${file.name}`)\n }\n }\n\n // Modify app.module.ts\n const appModulePath = join(cwd, 'src', 'app.module.ts')\n if (existsSync(appModulePath)) {\n this.info('Updating src/app.module.ts...')\n try {\n const result = await this.updateAppModule(appModulePath)\n if (result === 'created') {\n this.success('Updated src/app.module.ts with InertiaModule')\n } else if (result === 'ssr-added') {\n this.success('Enabled streaming SSR in src/app.module.ts')\n } else if (result === 'unchanged') {\n this.info('InertiaModule (with SSR) already configured in app.module.ts')\n } else {\n this.warn('InertiaModule is configured but SSR could not be auto-wired.')\n this.info(\"Add `ssr: { bundle: () => import('./inertia/ssr') }` to your InertiaModule options\")\n }\n } catch (err) {\n this.warn(`Could not auto-update app.module.ts: ${(err as Error).message}`)\n this.info('Please manually add InertiaModule.forRoot() to your module imports')\n }\n } else {\n this.info('No src/app.module.ts found — please manually configure InertiaModule')\n }\n\n // Generate initial type definitions\n try {\n const { outputPath, pageCount } = await runTypeGeneration(cwd)\n const relPath = relative(cwd, outputPath)\n this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? 's' : ''})`)\n } catch {\n this.warn('Could not generate initial type definitions. Run `quarry inertia:types` manually.')\n }\n\n if (!skipDeps) {\n this.newLine()\n this.info('Install the following dependencies:')\n this.line(' npm install @stratal/inertia @inertiajs/react @inertiajs/vite react react-dom')\n this.line(' npm install -D @types/react @types/react-dom vite @cloudflare/vite-plugin')\n }\n\n this.newLine()\n this.success('Inertia.js scaffolding complete!')\n this.info('Run `quarry inertia:dev` to start the dev server')\n\n return 0\n }\n\n private async updateAppModule(modulePath: string): Promise<AppModuleUpdate> {\n const { Project, SyntaxKind } = await import('ts-morph')\n\n const project = new Project({ useInMemoryFileSystem: false })\n const sourceFile = project.addSourceFileAtPath(modulePath)\n\n // Already importing the package — an older install that predates streaming\n // SSR. Wire the existing InertiaModule config to the SSR bundle rather than\n // bailing (which would leave SSR silently disabled).\n const existingImport = sourceFile.getImportDeclaration((decl) =>\n decl.getModuleSpecifierValue() === '@stratal/inertia',\n )\n if (existingImport) {\n const result = this.ensureSsrWiring(sourceFile, SyntaxKind)\n if (result === 'ssr-added') await sourceFile.save()\n return result\n }\n\n // Fresh install: add the imports and an InertiaModule.forRoot wired for SSR.\n sourceFile.addImportDeclaration({\n defaultImport: 'rootView',\n moduleSpecifier: './inertia/root.html?raw',\n })\n sourceFile.addImportDeclaration({\n namedImports: ['InertiaModule'],\n moduleSpecifier: '@stratal/inertia',\n })\n\n // Find the @Module decorator and add InertiaModule to imports\n const classes = sourceFile.getClasses()\n for (const cls of classes) {\n const moduleDecorator = cls.getDecorator('Module')\n if (!moduleDecorator) continue\n\n const args = moduleDecorator.getArguments()\n if (args.length === 0) continue\n\n const objLiteral = args[0].asKind(SyntaxKind.ObjectLiteralExpression)\n if (!objLiteral) continue\n\n const importsProp = objLiteral.getProperty('imports')\n if (importsProp) {\n // Add to existing imports array\n const initializer = importsProp.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()\n const arrayLiteral = initializer?.asKind(SyntaxKind.ArrayLiteralExpression)\n if (arrayLiteral) {\n arrayLiteral.addElement(`InertiaModule.forRoot({\\n rootView,\\n ssr: { bundle: () => import('./inertia/ssr') },\\n })`)\n }\n } else {\n // Add imports property\n objLiteral.addPropertyAssignment({\n name: 'imports',\n initializer: `[\\n InertiaModule.forRoot({\\n rootView,\\n ssr: { bundle: () => import('./inertia/ssr') },\\n }),\\n ]`,\n })\n }\n\n break\n }\n\n await sourceFile.save()\n return 'created'\n }\n\n /**\n * Ensure an existing `InertiaModule.forRoot({...})` call opts into the streaming\n * SSR bundle. Returns `ssr-added` when the option is inserted, `unchanged` when\n * one is already present, or `unwired` when no plain `forRoot({...})` object\n * literal is found (e.g. `forRootAsync`, or a config passed by reference) — in\n * which case the caller surfaces a manual instruction.\n */\n private ensureSsrWiring(sourceFile: SourceFile, syntaxKind: SyntaxKinds): AppModuleUpdate {\n const calls = sourceFile.getDescendantsOfKind(syntaxKind.CallExpression)\n for (const call of calls) {\n if (call.getExpression().getText() !== 'InertiaModule.forRoot') continue\n\n const objLiteral = call.getArguments()[0]?.asKind(syntaxKind.ObjectLiteralExpression)\n if (!objLiteral) continue\n\n if (objLiteral.getProperty('ssr')) return 'unchanged'\n\n objLiteral.addPropertyAssignment({\n name: 'ssr',\n initializer: `{ bundle: () => import('./inertia/ssr') }`,\n })\n return 'ssr-added'\n }\n return 'unwired'\n }\n}\n","import { existsSync } from 'node:fs'\nimport { watch } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport { Command } from 'stratal/quarry'\nimport { findPagesDir, runTypeGeneration } from '../generator/type-generator'\n\nexport class InertiaTypesCommand extends Command {\n static command = 'inertia:types {--watch : Watch for changes and regenerate}'\n static description = 'Generate Inertia.js page type definitions'\n\n async handle(): Promise<number | undefined> {\n const cwd = process.cwd()\n const pagesDir = findPagesDir(cwd)\n\n if (!existsSync(pagesDir)) {\n this.fail('src/inertia/pages/ not found. Run `quarry inertia:install` first.')\n return 1\n }\n\n const result = await this.generate(cwd)\n if (!result) return 1\n\n if (this.boolean('watch')) {\n this.info('Watching for changes...')\n await this.watchForChanges(cwd)\n }\n\n return 0\n }\n\n private async generate(cwd: string): Promise<boolean> {\n try {\n const { outputPath, pageCount } = await runTypeGeneration(cwd)\n const relPath = relative(cwd, outputPath)\n this.success(`Generated ${relPath} (${pageCount} page${pageCount !== 1 ? 's' : ''})`)\n return true\n } catch (err) {\n this.fail(`Type generation failed: ${(err as Error).message}`)\n return false\n }\n }\n\n private async watchForChanges(cwd: string): Promise<void> {\n const srcDir = join(cwd, 'src')\n\n try {\n const watcher = watch(srcDir, { recursive: true })\n for await (const event of watcher) {\n if (event.filename && /\\.(tsx|ts)$/.test(event.filename)) {\n this.info(`Change detected: ${event.filename}`)\n await this.generate(cwd)\n }\n }\n } catch (err) {\n this.fail(`Watch failed: ${(err as Error).message}`)\n }\n }\n}\n","import { Module } from 'stratal/module'\nimport { InertiaBuildCommand } from './commands/inertia-build.command'\nimport { InertiaDevCommand } from './commands/inertia-dev.command'\nimport { InertiaInstallCommand } from './commands/inertia-install.command'\nimport { InertiaTypesCommand } from './commands/inertia-types.command'\n\n@Module({\n providers: [\n InertiaInstallCommand,\n InertiaTypesCommand,\n InertiaDevCommand,\n InertiaBuildCommand,\n ],\n})\nexport class InertiaQuarryModule {}\n\nexport { InertiaBuildCommand } from './commands/inertia-build.command'\nexport { InertiaDevCommand } from './commands/inertia-dev.command'\nexport { InertiaInstallCommand } from './commands/inertia-install.command'\nexport { InertiaTypesCommand } from './commands/inertia-types.command'\nexport { runTypeGeneration } from './generator/type-generator'\n"],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { a as RoleName, i as Permission, o as SharedAccess, r as Check, t as AccessControlRegistry } from "../types-D-j_Ee_h.mjs";
2
+ import { ReactNode } from "react";
3
+ //#region src/react/access/components.d.ts
4
+ /**
5
+ * Exactly one of the three forms, enforced by the union: supplying two at once
6
+ * is a compile error rather than a precedence rule to remember.
7
+ */
8
+ type GateProps<TKey extends string, TValue> = (Record<TKey, TValue> & {
9
+ any?: never;
10
+ all?: never;
11
+ children?: ReactNode;
12
+ }) | ({
13
+ any: readonly TValue[];
14
+ } & Partial<Record<TKey, never>> & {
15
+ all?: never;
16
+ children?: ReactNode;
17
+ }) | ({
18
+ all: readonly TValue[];
19
+ } & Partial<Record<TKey, never>> & {
20
+ any?: never;
21
+ children?: ReactNode;
22
+ });
23
+ type CanProps = GateProps<'do', Permission>;
24
+ type RoleProps = GateProps<'is', RoleName>;
25
+ /**
26
+ * Renders its children when the current user holds the permission(s).
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * <Can do="posts:update"><EditButton /></Can>
31
+ * <Can any={['posts:update', 'posts:delete']}><Toolbar /></Can>
32
+ * <Can all={['posts:read', 'admin:access']}><AuditLog /></Can>
33
+ * ```
34
+ */
35
+ export declare function Can(props: CanProps): ReactNode;
36
+ /** Renders its children when the current user does **not** hold the permission(s). */
37
+ export declare function Cannot(props: CanProps): ReactNode;
38
+ /**
39
+ * Renders its children when the current user holds the role(s).
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * <HasRole is="admin"><AdminPanel /></HasRole>
44
+ * <HasRole any={['editor', 'reviewer']}><ReviewQueue /></HasRole>
45
+ * ```
46
+ */
47
+ export declare function HasRole(props: RoleProps): ReactNode;
48
+ /** Renders its children when the current user holds **none** of the role(s). */
49
+ export declare function HasNoRole(props: RoleProps): ReactNode;
50
+ //#endregion
51
+ //#region src/react/access/missing-access-props.error.d.ts
52
+ /**
53
+ * Thrown when a component or hook from `@stratal/inertia/react/access` runs on
54
+ * a page with no `access` prop.
55
+ *
56
+ * This is always a wiring problem, never a permission problem — a user with no
57
+ * permissions gets `{ roles: [], permissions: {} }`. Failing loudly keeps
58
+ * "misconfigured" from being indistinguishable from "denied", which would fail
59
+ * open the moment the middleware stopped running.
60
+ */
61
+ export declare class MissingAccessPropsError extends Error {
62
+ constructor();
63
+ }
64
+ //#endregion
65
+ //#region src/react/access/use-access.d.ts
66
+ /**
67
+ * Returns the current user's roles and merged permissions.
68
+ *
69
+ * @throws {MissingAccessPropsError} when the page carries no `access` prop.
70
+ */
71
+ export declare function useAccess(): SharedAccess;
72
+ /**
73
+ * Whether the current user holds the given permission(s).
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const canEdit = useCan('posts:update')
78
+ * const canEither = useCan({ any: ['posts:update', 'posts:delete'] })
79
+ * const canBoth = useCan({ all: ['posts:read', 'admin:access'] })
80
+ * ```
81
+ */
82
+ export declare function useCan(check: Check<Permission>): boolean;
83
+ /**
84
+ * Whether the current user holds the given role(s).
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const isAdmin = useRole('admin')
89
+ * const isStaff = useRole({ any: ['editor', 'reviewer'] })
90
+ * ```
91
+ */
92
+ export declare function useRole(check: Check<RoleName>): boolean;
93
+ //#endregion
94
+ export type { AccessControlRegistry, CanProps, Check, Permission, RoleName, RoleProps, SharedAccess };
95
+ //# sourceMappingURL=access.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.d.mts","names":[],"sources":["../../src/react/access/components.ts","../../src/react/access/missing-access-props.error.ts","../../src/react/access/use-access.ts"],"mappings":";;;;;;;KAQK,UAAU,qBAAqB,WAC/B,OAAO,MAAM;EAAY;EAAa;EAAa,WAAW;;EAC5D,cAAc;IAAa,QAAQ,OAAO;EAAkB;EAAa,WAAW;;EACpF,cAAc;IAAa,QAAQ,OAAO;EAAkB;EAAa,WAAW;;KAE/E,WAAW,gBAAgB;KAC3B,YAAY,gBAAgB;;;;;;;;;;;wBAsBxB,IAAI,OAAO,WAAW;;wBAKtB,OAAO,OAAO,WAAW;;;;;;;;;;wBAazB,QAAQ,OAAO,YAAY;;wBAK3B,UAAU,OAAO,YAAY;;;;;;;;;;;;qBClDhC,gCAAgC;EAC3C;;;;;;;;;wBCKc,aAAa;;;;;;;;;;;wBAgBb,OAAO,OAAO,MAAM;;;;;;;;;;wBAapB,QAAQ,OAAO,MAAM"}
@@ -0,0 +1,133 @@
1
+ import { usePage } from "@inertiajs/react";
2
+ //#region src/react/access/match.ts
3
+ /**
4
+ * Resolves a permission string against a shared permission set.
5
+ *
6
+ * The grammar matches `AuthGuard` exactly, so a string copy-pasted between a
7
+ * guard and a component means the same thing in both places:
8
+ *
9
+ * - `posts:update` — that specific action
10
+ * - `posts` or `posts:*` — any action on the resource
11
+ *
12
+ * A single-permission check here is equivalent to the server's `AuthGuard`
13
+ * check. An `all` group is not: `evaluate()` ANDs over the caller's merged
14
+ * permissions across every held role, while `AuthGuard` requires one single
15
+ * role to satisfy the entire set. A user with two roles that each satisfy
16
+ * half of an `all` list can pass here and still get a 403 from `AuthGuard`.
17
+ */
18
+ function matchesPermission(access, permission) {
19
+ const colon = permission.indexOf(":");
20
+ const resource = colon === -1 ? permission : permission.slice(0, colon);
21
+ const action = colon === -1 ? "*" : permission.slice(colon + 1);
22
+ const actions = access.permissions[resource];
23
+ if (!actions || actions.length === 0) return false;
24
+ return action === "*" || actions.includes(action);
25
+ }
26
+ /** Resolves a role name against the user's held roles. */
27
+ function matchesRole(access, role) {
28
+ return access.roles.includes(role);
29
+ }
30
+ /** Applies a matcher across a single value, an `any` group, or an `all` group. */
31
+ function evaluate(access, check, matches) {
32
+ if (typeof check === "string") return matches(access, check);
33
+ if ("any" in check) return check.any.some((value) => matches(access, value));
34
+ return check.all.every((value) => matches(access, value));
35
+ }
36
+ //#endregion
37
+ //#region src/react/access/missing-access-props.error.ts
38
+ /**
39
+ * Thrown when a component or hook from `@stratal/inertia/react/access` runs on
40
+ * a page with no `access` prop.
41
+ *
42
+ * This is always a wiring problem, never a permission problem — a user with no
43
+ * permissions gets `{ roles: [], permissions: {} }`. Failing loudly keeps
44
+ * "misconfigured" from being indistinguishable from "denied", which would fail
45
+ * open the moment the middleware stopped running.
46
+ */
47
+ var MissingAccessPropsError = class extends Error {
48
+ constructor() {
49
+ super("The `access` page prop is missing. Configure `accessControl` on AuthModule.forRootAsync() — @stratal/framework shares it automatically on every Inertia render once access control is enabled.");
50
+ this.name = "MissingAccessPropsError";
51
+ }
52
+ };
53
+ //#endregion
54
+ //#region src/react/access/use-access.ts
55
+ /**
56
+ * Returns the current user's roles and merged permissions.
57
+ *
58
+ * @throws {MissingAccessPropsError} when the page carries no `access` prop.
59
+ */
60
+ function useAccess() {
61
+ const access = usePage().props.access;
62
+ if (!access) throw new MissingAccessPropsError();
63
+ return access;
64
+ }
65
+ /**
66
+ * Whether the current user holds the given permission(s).
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const canEdit = useCan('posts:update')
71
+ * const canEither = useCan({ any: ['posts:update', 'posts:delete'] })
72
+ * const canBoth = useCan({ all: ['posts:read', 'admin:access'] })
73
+ * ```
74
+ */
75
+ function useCan(check) {
76
+ return evaluate(useAccess(), check, matchesPermission);
77
+ }
78
+ /**
79
+ * Whether the current user holds the given role(s).
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const isAdmin = useRole('admin')
84
+ * const isStaff = useRole({ any: ['editor', 'reviewer'] })
85
+ * ```
86
+ */
87
+ function useRole(check) {
88
+ return evaluate(useAccess(), check, matchesRole);
89
+ }
90
+ //#endregion
91
+ //#region src/react/access/components.ts
92
+ function toCheck(props, key) {
93
+ if (props.any !== void 0) return { any: props.any };
94
+ if (props.all !== void 0) return { all: props.all };
95
+ return props[key];
96
+ }
97
+ /**
98
+ * Renders its children when the current user holds the permission(s).
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * <Can do="posts:update"><EditButton /></Can>
103
+ * <Can any={['posts:update', 'posts:delete']}><Toolbar /></Can>
104
+ * <Can all={['posts:read', 'admin:access']}><AuditLog /></Can>
105
+ * ```
106
+ */
107
+ function Can(props) {
108
+ return useCan(toCheck(props, "do")) ? props.children : null;
109
+ }
110
+ /** Renders its children when the current user does **not** hold the permission(s). */
111
+ function Cannot(props) {
112
+ return useCan(toCheck(props, "do")) ? null : props.children;
113
+ }
114
+ /**
115
+ * Renders its children when the current user holds the role(s).
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * <HasRole is="admin"><AdminPanel /></HasRole>
120
+ * <HasRole any={['editor', 'reviewer']}><ReviewQueue /></HasRole>
121
+ * ```
122
+ */
123
+ function HasRole(props) {
124
+ return useRole(toCheck(props, "is")) ? props.children : null;
125
+ }
126
+ /** Renders its children when the current user holds **none** of the role(s). */
127
+ function HasNoRole(props) {
128
+ return useRole(toCheck(props, "is")) ? null : props.children;
129
+ }
130
+ //#endregion
131
+ export { Can, Cannot, HasNoRole, HasRole, MissingAccessPropsError, useAccess, useCan, useRole };
132
+
133
+ //# sourceMappingURL=access.mjs.map