@bakery-framework/plugin-vue 2.0.0-rc.1 → 2.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/plugin-vue",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.0-rc.3",
4
4
  "description": "Bakery vue plugin.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -43,7 +43,7 @@
43
43
  "vue": "^3.5.38"
44
44
  },
45
45
  "dependencies": {
46
- "@bakery-framework/core": "^2.0.0-rc.1"
46
+ "@bakery-framework/core": "^2.0.0-rc.3"
47
47
  },
48
48
  "engines": {
49
49
  "bun": ">=1.4.0"
package/src/index.ts CHANGED
@@ -12,6 +12,17 @@ import { rewriteVueImports } from './utils'
12
12
  */
13
13
  export type { CustomElementsOption, VuePluginOptions } from './types'
14
14
 
15
+ /**
16
+ * The request body inside a `<script server>` block, and anything it calls.
17
+ *
18
+ * Pairs with `getRequest()` from `@bakery-framework/core`. Both replace the
19
+ * `req` and `body` globals this plugin declared in `vue.d.ts`, which only
20
+ * reached a file when that `.d.ts` happened to land in the tsconfig project
21
+ * an editor resolved for it. The globals still work; they are no longer the
22
+ * only way, and they are no longer load-bearing.
23
+ */
24
+ export { getBody } from './server-context'
25
+
15
26
  export default function vuePlugin(options?: VuePluginOptions) {
16
27
  if (options) setVuePluginOptions(options)
17
28
  return definePlugin({
@@ -0,0 +1,37 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+
3
+ /**
4
+ * The parsed body of the request a `<script server>` block is running for.
5
+ *
6
+ * Separate from core's `getRequest()` deliberately: the request is core's and
7
+ * every surface has one, while `body` is this plugin's own computation
8
+ * (`VueHandler.params(req, finalParams)`, which merges route params with the
9
+ * parsed payload). A `.tsx` page receives its body as a function argument and
10
+ * has no use for this.
11
+ *
12
+ * Why it exists at all is the same reason as `getRequest()`. The block used
13
+ * to reach `body` as a global declared in `vue.d.ts`, which only works if
14
+ * that `.d.ts` lands in whatever tsconfig project an editor resolves for the
15
+ * SFC, and for an SFC it routinely does not. An import cannot fail that way,
16
+ * it carries a real type instead of `any`, and it reaches a helper the block
17
+ * calls rather than stopping at the wrapper's parameter list.
18
+ */
19
+ const bodyStore = new AsyncLocalStorage<{ body: unknown }>()
20
+
21
+ /** Run `fn` with `body` visible to `getBody()`. Called by the plugin only. */
22
+ export function runWithServerBody<T>(body: unknown, fn: () => T): T {
23
+ return bodyStore.run({ body }, fn)
24
+ }
25
+
26
+ /**
27
+ * The request body, inside a `<script server>` block or anything it calls.
28
+ *
29
+ * Unlike {@link import('@bakery-framework/core').getRequest} this does **not**
30
+ * throw when absent: a GET with no payload and no route params is an ordinary
31
+ * request, and `undefined` is the honest answer rather than an error
32
+ * condition. The type parameter is the caller's assertion about a shape the
33
+ * framework cannot know, exactly as it was when this was an `any` global.
34
+ */
35
+ export function getBody<T = unknown>(): T | undefined {
36
+ return bodyStore.getStore()?.body as T | undefined
37
+ }
package/src/utils.ts CHANGED
@@ -4,9 +4,11 @@ import {
4
4
  unlink as nodeUnlink,
5
5
  } from 'node:fs/promises'
6
6
  import { LRUCache } from '@bakery-framework/core/cache/lru'
7
- import { Bakery } from '@bakery-framework/core/core/bakery'
7
+ import { Bakery, hostStore } from '@bakery-framework/core/core/bakery'
8
8
  import { Logger } from '@bakery-framework/core/logger'
9
9
  import { fs, is, Try } from '@bakery-framework/core/utils'
10
+ import { runWithServerBody } from './server-context'
11
+ import type { HostContext } from '@bakery-framework/core/core/bakery'
10
12
  import type { ParsedCacheEntry, ServerResponseOptions, VueMeta } from './types'
11
13
 
12
14
  const logger = new Logger('vue')
@@ -434,6 +436,34 @@ ${
434
436
  `
435
437
  }
436
438
 
439
+ /**
440
+ * Run `fn` in a host store that carries `req`.
441
+ *
442
+ * Preserves an outer store when there is one: it holds the host config and
443
+ * hostname the router resolved, and only `req` needs adding. Outside one
444
+ * (a test driving a route directly, a direct handler call) it builds a
445
+ * minimal context from the process config, which is what `Bakery.config`
446
+ * already falls back to.
447
+ */
448
+ function runInRequest<T>(req: Request, fn: () => T): T {
449
+ const outer = hostStore.getStore()
450
+ if (outer) return hostStore.run({ ...outer, req }, fn)
451
+
452
+ // No outer store: carry the request and leave `config` unread.
453
+ //
454
+ // A plain `{ config: Bakery.config, ... }` throws "Config has not been
455
+ // initialized" the moment this runs before `initConfig()`, which is every
456
+ // test that drives a route directly, so the eager read reintroduced the
457
+ // failure it was added to fix. A getter defers it to whoever actually
458
+ // wants the config, and nothing in a block that only needs `req` does.
459
+ const ctx = { hostname: '', req } as HostContext
460
+ Object.defineProperty(ctx, 'config', {
461
+ get: () => Bakery.config,
462
+ enumerable: true,
463
+ })
464
+ return hostStore.run(ctx, fn)
465
+ }
466
+
437
467
  /** Drop stale compiled versions of the same source so the cache dir stays bounded. */
438
468
  async function pruneServerCache(dir: string, id: string, keepName: string) {
439
469
  const entries = await Try(nodeReaddir(dir))
@@ -524,7 +554,21 @@ export async function getServerResponse(options: ServerResponseOptions) {
524
554
  })
525
555
  const execPromise = Promise.resolve(
526
556
  is.function(exported)
527
- ? exported(req, body, actionName, actionArgs)
557
+ ? // The block runs inside a store carrying its own request, so
558
+ // `getRequest()` works whatever the caller did. Depending on an
559
+ // outer store looked fine because the worker always enters one,
560
+ // and then every test that drives a route directly got a throw,
561
+ // which `getServerResponse` swallows into `{}`: a guard block
562
+ // stopped redirecting and served 200. Entering it here is what
563
+ // makes the function total.
564
+ //
565
+ // An outer store is preserved rather than replaced: it carries the
566
+ // host config and hostname this request resolved.
567
+ runInRequest(req, () =>
568
+ runWithServerBody(body, () =>
569
+ exported(req, body, actionName, actionArgs),
570
+ ),
571
+ )
528
572
  : exported,
529
573
  )
530
574
  const params = await Promise.race([execPromise, timeoutPromise])