@fougere/nuxt 0.4.0-alpha.0 → 0.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/module.d.ts +29 -2
  2. package/dist/module.d.ts.map +1 -1
  3. package/dist/module.js +78 -31
  4. package/dist/module.js.map +1 -1
  5. package/dist/runtime/composables/useCurrentUser.d.ts +8 -0
  6. package/dist/runtime/composables/useCurrentUser.d.ts.map +1 -0
  7. package/dist/runtime/composables/useCurrentUser.js +21 -0
  8. package/dist/runtime/composables/useCurrentUser.js.map +1 -0
  9. package/dist/runtime/composables/useFormFor.d.ts +27 -0
  10. package/dist/runtime/composables/useFormFor.d.ts.map +1 -0
  11. package/dist/runtime/composables/useFormFor.js +76 -0
  12. package/dist/runtime/composables/useFormFor.js.map +1 -0
  13. package/dist/runtime/composables/useFougereData.d.ts +21 -0
  14. package/dist/runtime/composables/useFougereData.d.ts.map +1 -0
  15. package/dist/runtime/composables/useFougereData.js +74 -0
  16. package/dist/runtime/composables/useFougereData.js.map +1 -0
  17. package/dist/runtime/plugins/session.server.d.ts +3 -0
  18. package/dist/runtime/plugins/session.server.d.ts.map +1 -0
  19. package/dist/runtime/plugins/session.server.js +13 -0
  20. package/dist/runtime/plugins/session.server.js.map +1 -0
  21. package/dist/runtime/server/api/crud.d.ts +3 -0
  22. package/dist/runtime/server/api/crud.d.ts.map +1 -0
  23. package/dist/runtime/server/api/crud.js +46 -0
  24. package/dist/runtime/server/api/crud.js.map +1 -0
  25. package/dist/runtime/server/auth/middleware/auth.d.ts +7 -0
  26. package/dist/runtime/server/auth/middleware/auth.d.ts.map +1 -0
  27. package/dist/runtime/server/auth/middleware/auth.js +32 -0
  28. package/dist/runtime/server/auth/middleware/auth.js.map +1 -0
  29. package/dist/runtime/server/auth/routes/api/me.get.d.ts +5 -0
  30. package/dist/runtime/server/auth/routes/api/me.get.d.ts.map +1 -0
  31. package/dist/runtime/server/auth/routes/api/me.get.js +10 -0
  32. package/dist/runtime/server/auth/routes/api/me.get.js.map +1 -0
  33. package/dist/runtime/server/auth/routes/auth/[...].d.ts +3 -0
  34. package/dist/runtime/server/auth/routes/auth/[...].d.ts.map +1 -0
  35. package/dist/runtime/server/auth/routes/auth/[...].js +8 -0
  36. package/dist/runtime/server/auth/routes/auth/[...].js.map +1 -0
  37. package/dist/runtime/server/routes/call.post.d.ts +3 -0
  38. package/dist/runtime/server/routes/call.post.d.ts.map +1 -0
  39. package/dist/runtime/server/routes/call.post.js +125 -0
  40. package/dist/runtime/server/routes/call.post.js.map +1 -0
  41. package/dist/runtime/server/routes/session.get.d.ts +3 -0
  42. package/dist/runtime/server/routes/session.get.d.ts.map +1 -0
  43. package/dist/runtime/server/routes/session.get.js +9 -0
  44. package/dist/runtime/server/routes/session.get.js.map +1 -0
  45. package/dist/runtime/server/utils/boot.d.ts +13 -0
  46. package/dist/runtime/server/utils/boot.d.ts.map +1 -0
  47. package/dist/runtime/server/utils/boot.js +13 -0
  48. package/dist/runtime/server/utils/boot.js.map +1 -0
  49. package/dist/runtime/server/utils/invoke.d.ts +9 -0
  50. package/dist/runtime/server/utils/invoke.d.ts.map +1 -0
  51. package/dist/runtime/server/utils/invoke.js +24 -0
  52. package/dist/runtime/server/utils/invoke.js.map +1 -0
  53. package/package.json +6 -6
  54. package/src/module.ts +98 -28
  55. package/src/runtime/composables/useFougereData.ts +11 -1
  56. package/src/runtime/server/auth/routes/auth/[...].ts +1 -1
  57. package/src/runtime/server/routes/call.post.ts +1 -1
  58. package/src/shims.d.ts +7 -0
package/src/module.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  import type { Nuxt } from '@nuxt/schema';
18
18
  import { orderSeeds } from '@fougere/core';
19
19
  import {
20
- scanProject, emitScan, frondAliases, frondPackage, watchPathsOf, resolveConventions, type Conventions,
20
+ scanProject, emitStatement, frondAliases, frondPackage, watchPathsOf, resolveConventions, type Conventions,
21
21
  setModuleLoader, loadCascadedConfig,
22
22
  } from '@fougere/core/node';
23
23
  import { declaresStorage } from '@fougere/defaults';
@@ -35,6 +35,40 @@ export interface FougereModuleOptions {
35
35
  * fronds are shared at the root. Config and `.fougere` stay app-local.
36
36
  */
37
37
  root?: string;
38
+
39
+ /**
40
+ * One key per EXTENSION, and the key is the package suffix AND the export name —
41
+ * `calls` is `import { calls } from '@fougere/calls'`. So the module holds no table to
42
+ * keep in step with the packages, the same rule the scan applies to `@fronds/<name>`.
43
+ *
44
+ * An extension is a FUNCTION and this module writes a FILE, so a project cannot pass one
45
+ * here: it names one, and the options travel beside it as data.
46
+ *
47
+ * The keys are declared rather than left open on purpose. An open record would derive
48
+ * everything and accept `callz: {}` in silence — the failure this repo already records
49
+ * for `adapters:`, which "fails OPEN, giving neither completion nor a guard". The price
50
+ * is that a new extension package adds a key here.
51
+ */
52
+ /** `@fougere/observability` — a span per operation, the four signals, OTLP. */
53
+ observability?: Record<string, unknown> | false;
54
+ /** `@fougere/calls` — the dev panel: a bounded log of what this process dispatched. */
55
+ calls?: Record<string, unknown> | false;
56
+ }
57
+
58
+ /**
59
+ * The extension keys, in the order they are mounted.
60
+ *
61
+ * A list and not the options object's own keys: the ORDER is ours, not the project's.
62
+ * `observability` installs the middleware that opens the span every log line written
63
+ * inside a call will carry — mounted the other way round, the lines leave uncorrelated.
64
+ */
65
+ const EXTENSION_KEYS = ['observability', 'calls'] as const;
66
+
67
+ /** What the project asked for, in mount order. `false` is how a key is turned off. */
68
+ export function extensionsOf(options: FougereModuleOptions): { key: string; options: unknown }[] {
69
+ return EXTENSION_KEYS
70
+ .filter((key) => options[key] !== undefined && options[key] !== false)
71
+ .map((key) => ({ key, options: options[key] }));
38
72
  }
39
73
 
40
74
 
@@ -54,7 +88,7 @@ const module = defineNuxtModule<FougereModuleOptions>({
54
88
  async setup(options: FougereModuleOptions, nuxt: Nuxt) {
55
89
  const { resolve: resolveModule } = createResolver(import.meta.url);
56
90
  const runtimeResolve = (...path: string[]) =>
57
- resolveModule('../src/runtime', ...path);
91
+ resolveModule('./runtime', ...path);
58
92
  const rootDir = nuxt.options.rootDir;
59
93
  // Fronds may live at the workspace root (app under apps/*); config/.fougere stay app-local.
60
94
  const scanRoot = options.root ? resolve(rootDir, options.root) : rootDir;
@@ -242,29 +276,43 @@ const module = defineNuxtModule<FougereModuleOptions>({
242
276
  });
243
277
  }
244
278
 
245
- // ── 6. The scan, written down (virtual — lives in .nuxt/) ───
279
+ // ── 6. What this app hosts ───
246
280
  //
247
- // The module ALREADY scanned, above. Without this the generated plugin scanned a
248
- // second time, at runtime — which is right on a server and impossible on a Worker:
249
- // measured on workerd, `readdir` throws through unenv's shim and the app boots with
250
- // ZERO fronds. Every page renders, every door answers NOT_FOUND, and only the scan
251
- // diagnostics say why. One scan, one value, handed to the boot.
281
+ // A `fronds.ts` beside `fougere.config.ts` is the app STATING it — the same shape
282
+ // `configureFougere({ fronds })` gives every other host, made where the author can
283
+ // make it. The scan still runs at build and stays a CHECK: its diagnostics reach the
284
+ // console, and it becomes no module.
252
285
  //
253
- // `emitScan` writes imports relative to where the module SITS, so the destination is
254
- // settled before the source is produced — `dst` is that path.
255
- const scanTpl = addTemplate({
256
- filename: 'fougere-scan.ts',
286
+ // Nothing is generated, because a generated module was imported by ABSOLUTE path,
287
+ // which a bundler sees as `file://` and therefore external. Externalized, Node read
288
+ // the frond's `.ts` itself — stripping types but resolving `../entities/Post.js`
289
+ // literally, a file that is not on disk. Measured on demos/nuxt-blog in-process:
290
+ // 500 on every call in `nuxt dev`, 200 once the statement is the only source.
291
+ // A codegen inside one repo also had nothing to carry: `fougere sync` writes classes
292
+ // because its source is ANOTHER repo, and here the author can just import.
293
+ const stated = ['fronds.ts', 'fronds.mts', 'fronds.js', 'fronds.mjs']
294
+ .map((f) => resolve(scanRoot, f)).find((f) => existsSync(f));
295
+
296
+ // No statement of its own: the build writes the one the scan implies, so the runtime
297
+ // never reaches `scanProject`. That fallback works — it has a disk under Nuxt — but it
298
+ // loads `typescript` into a production boot, which is the whole of what a build is for.
299
+ // What is written is the STATEMENT (imports + `frond()`), never the scan result.
300
+ // `.ts`, and it is not cosmetic: the file spells its imports the way TypeScript sources
301
+ // do (`Post.js` for `Post.ts`), and Nitro externalizes a `.mjs` it can hand to Node —
302
+ // which then resolves `@fronds/*` against a package that does not exist, since an alias
303
+ // is a bundler's. Measured both ways, in-process: `.mjs` answers 500, `.ts` answers.
304
+ const statedPath = stated ?? addTemplate({
305
+ filename: 'fougere-fronds.ts',
257
306
  write: true,
258
- getContents: ({ nuxt: n }) =>
259
- emitScan(scan, { outFile: resolve(n!.options.buildDir, 'fougere-scan.ts') }),
260
- });
307
+ getContents: () => emitStatement(scan),
308
+ }).dst;
261
309
 
262
310
  // ── 6b. Boot plugin (virtual — lives in .nuxt/) ───
263
311
  const allSeeds = orderSeeds(fronds);
264
312
  const bootTpl = addTemplate({
265
313
  filename: 'fougere-boot.ts',
266
314
  write: true,
267
- getContents: () => generateBootPlugin(config, allSeeds, runtimeResolve('server/utils/boot'), scanTpl.dst),
315
+ getContents: () => generateBootPlugin(config, allSeeds, runtimeResolve('server/utils/boot'), extensionsOf(options), statedPath),
268
316
  });
269
317
  addServerPlugin(bootTpl.dst);
270
318
 
@@ -324,8 +372,10 @@ export function generateBootPlugin(
324
372
  config: FougereConfig,
325
373
  seeds: SeedEntry[],
326
374
  fougereAppPath: string,
327
- /** The module `emitScan` wrote. Absent only in the tests that predate it. */
328
- scanPath?: string,
375
+ /** What `fougere:` named, in mount order. Empty is the whole of "changed nothing". */
376
+ extensions: { key: string; options: unknown }[] = [],
377
+ /** The app's own `fronds.ts`, when it states what it hosts instead of being scanned. */
378
+ statedPath?: string,
329
379
  ): string {
330
380
  const lines: string[] = [];
331
381
  lines.push(`// Auto-generated by @fougere/nuxt — do not edit`);
@@ -334,7 +384,12 @@ export function generateBootPlugin(
334
384
  lines.push(`import { configureFougere } from '${fougereAppPath}';`);
335
385
  // A STATIC import, because that is the one thing a bundler needs spelled out — and the
336
386
  // whole point is that nothing reads a directory once this file is built.
337
- if (scanPath) lines.push(`import { scan } from '${scanPath}';`);
387
+ // The extension is DROPPED, and that is what decides who reads the file: an extensionless
388
+ // specifier cannot be resolved by Node, so the bundler resolves it — and only a bundler
389
+ // applies the `@fronds/*` aliases the statement's own imports are written with. Left in
390
+ // place, the path resolves natively, Node reads the frond's `.ts` and every call answers
391
+ // 500. Measured both ways on demos/nuxt-blog, in-process.
392
+ if (statedPath) lines.push(`import fronds from '${statedPath.replace(/\.(m?)[jt]sx?$/, '')}';`);
338
393
 
339
394
  const db = config.db ?? 'sqlite';
340
395
  const sources = (config as { sources?: unknown }).sources;
@@ -349,7 +404,10 @@ export function generateBootPlugin(
349
404
  if (!declaresStorage(db as Parameters<typeof declaresStorage>[0])) {
350
405
  lines.push(``);
351
406
  lines.push(`export default defineNitroPlugin(() => {`);
352
- if (scanPath) lines.push(` configureFougere({ scan, config: ${JSON.stringify(carried(config))} });`);
407
+ // Always configure, even with nothing to host: `hostedBy` is where the absence is
408
+ // answered, by name and with both keys. Skipping the call boots a silent app instead.
409
+ const states = statedPath ? 'fronds, ' : '';
410
+ lines.push(` configureFougere({ ${states}config: ${JSON.stringify(carried(config))} });`);
353
411
  lines.push(`});`);
354
412
  return lines.join('\n') + '\n';
355
413
  }
@@ -359,6 +417,12 @@ export function generateBootPlugin(
359
417
  lines.push(`import { resolveStorage } from '@fougere/defaults';`);
360
418
  // After the early return above: an app that declares no storage generates no imports.
361
419
  lines.push(`import { migrating${seeds.length ? ', runSeeds' : ''} } from '@fougere/core';`);
420
+ // One import per key the project named. The key IS the export and the package suffix, so
421
+ // a missing package is a build error naming the package — not a boot that silently lacks
422
+ // the panel someone asked for.
423
+ for (const { key } of extensions) {
424
+ lines.push(`import { ${key} } from '@fougere/${key}';`);
425
+ }
362
426
  lines.push(``);
363
427
 
364
428
  // Seed imports
@@ -369,14 +433,14 @@ export function generateBootPlugin(
369
433
 
370
434
  // Wrap all init in the plugin callback to avoid top-level native calls
371
435
  lines.push(`export default defineNitroPlugin(async () => {`);
372
- // The SCAN first, and on its own line: it names no engine, so nothing below can stop it
373
- // from being stated. Measured on workerd — `resolveStorage` threw on a native driver,
436
+ // What it HOSTS first, and on its own line: it names no engine, so nothing below can stop
437
+ // it from being stated. Measured on workerd — `resolveStorage` threw on a native driver,
374
438
  // Nitro swallowed the plugin whole, and the app came up with zero fronds and not a word.
375
- // Two unrelated facts had been sharing one failure.
376
- // The scan AND what the config says about topology — both read at build, both stated
377
- // here rather than re-derived. `remotes` is the whole reason a consumer boots at all:
378
- // without it the app hosts nothing and reaches nothing, and its pages render empty.
379
- if (scanPath) lines.push(` configureFougere({ scan, config: ${JSON.stringify(carried(config))} });`);
439
+ // Two unrelated facts had been sharing one failure. `remotes` rides along for the same
440
+ // reason: it is the whole of why a consumer that hosts nothing boots at all.
441
+ const states = statedPath ? 'fronds, ' : '';
442
+ lines.push(` configureFougere({ ${states}config: ${JSON.stringify(carried(config))} });`);
443
+ lines.push(``);
380
444
  lines.push(` try {`);
381
445
  // Pass `db` through unchanged — resolveStorage (@fougere/defaults → setupSqlite)
382
446
  // is the one place that defaults an absent path, so both call sites (this
@@ -387,7 +451,7 @@ export function generateBootPlugin(
387
451
  lines.push(` const storage = resolveStorage(${JSON.stringify(db)}${sourcesArg});`);
388
452
  lines.push(``);
389
453
  lines.push(` configureFougere({`);
390
- if (scanPath) lines.push(` scan,`);
454
+ if (statedPath) lines.push(` fronds,`);
391
455
  lines.push(` config: ${JSON.stringify(carried(config))},`);
392
456
  lines.push(` db: storage.db,`);
393
457
  lines.push(` ormFactory: storage.ormFactory,`);
@@ -415,6 +479,12 @@ export function generateBootPlugin(
415
479
  lines.push(` ], (message) => console.log('[fougere:seed]' + message)) },`);
416
480
  }
417
481
 
482
+ // After the framework's own members, never before: tables, then rows, then what the host
483
+ // adds. A key named 'seeds' would REPLACE core's, which is the delta rule of AppLifecycle.
484
+ for (const { key, options } of extensions) {
485
+ lines.push(` ${key}(${JSON.stringify(options ?? {})}),`);
486
+ }
487
+
418
488
  lines.push(` ],`);
419
489
  lines.push(` });`);
420
490
  // A storage that could not be opened is REPORTED, never swallowed. Nitro takes a
@@ -63,7 +63,17 @@ export async function useQuery<T = Record<string, unknown>>(
63
63
  const total = computed(() => pageOf(data.value).total);
64
64
  const hasMore = computed(() => pageOf(data.value).hasMore);
65
65
 
66
- return { data: data as Ref<T | null>, items, total, hasMore, loading: pending, error, refresh };
66
+ return {
67
+ data: data as Ref<T | null>,
68
+ items,
69
+ total,
70
+ hasMore,
71
+ loading: pending,
72
+ error,
73
+ // Nuxt does not export AsyncDataExecuteOptions, so the inferred signature cannot
74
+ // be named from outside; callers pass nothing.
75
+ refresh: refresh as () => Promise<void>,
76
+ };
67
77
  }
68
78
 
69
79
  export function useCommand<T = unknown>(entity: EntityClass, op: string) {
@@ -1,4 +1,4 @@
1
- import { defineEventHandler } from 'h3';
1
+ import { defineEventHandler, toWebRequest, sendWebResponse } from 'h3';
2
2
  import { useFougereAuth } from '@fougere/app';
3
3
 
4
4
  export default defineEventHandler(async (event) => {
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { defineEventHandler } from 'h3';
11
11
  import { serveRpc, rpcParseError, useFougereApp } from '@fougere/app';
12
+ import { MAX_BODY_BYTES } from '@fougere/core';
12
13
 
13
14
  type NodeReq = {
14
15
  body?: unknown;
@@ -22,7 +23,6 @@ type WebReq = {
22
23
  json?: () => Promise<unknown>;
23
24
  };
24
25
 
25
- const MAX_BODY_BYTES = 1024 * 1024;
26
26
 
27
27
  function payloadTooLarge(): Error & { statusCode: number; statusMessage: string } {
28
28
  return Object.assign(new Error('Payload too large'), { statusCode: 413, statusMessage: 'Payload too large' });
package/src/shims.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ // Nuxt's virtual barrel has no file to resolve to; the shim says where its types are,
2
+ // never what they say. (`nitropack/runtime` is redirected by tsconfig paths instead:
3
+ // it DOES resolve, and an ambient declaration cannot override a resolution.)
4
+ declare module '#imports' {
5
+ // Nuxt's virtual barrel, resolved when the app builds.
6
+ export * from 'nuxt/app';
7
+ }