@junejs/db 0.0.12 → 0.0.14
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 +2 -2
- package/src/index.ts +1 -1
- package/src/scope.ts +49 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/db",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "June — the agent-native React framework. Ambient data resources (db/kv/blob).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@junejs/core": "0.0.
|
|
19
|
+
"@junejs/core": "0.0.14"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
package/src/index.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
// or action. The host (@junejs/server) opens the resources and runs each request
|
|
4
4
|
// in the scope these read; this package is the worker-safe seam they cross.
|
|
5
5
|
|
|
6
|
-
export { db, kv, blob, runInScope, ensureScope, type RequestScope } from "./scope";
|
|
6
|
+
export { db, kv, blob, runInScope, ensureScope, requestLocal, registerSqlTagger, type RequestScope } from "./scope";
|
package/src/scope.ts
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
import type { Resources, JuneDb, JuneKv, JuneBlob } from "@junejs/core/resources";
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// `locals` is generic per-request state for layers built ON TOP of the resources
|
|
25
|
+
// (e.g. Juno's batch-loader registry). This package never reads it — it just
|
|
26
|
+
// carries it per request so such state is STRUCTURALLY request-scoped and can't be
|
|
27
|
+
// stashed on a long-lived handle. Lazily created by requestLocal().
|
|
28
|
+
export type RequestScope = { resources: Resources; locals?: Map<symbol, unknown> };
|
|
25
29
|
|
|
26
30
|
// The minimal slice of AsyncLocalStorage we use — kept structural so this module
|
|
27
31
|
// never statically names the runtime.
|
|
@@ -56,6 +60,24 @@ export function runInScope<T>(scope: RequestScope, fn: () => T): T {
|
|
|
56
60
|
return als ? als.run(scope, fn) : fn();
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
// Per-request local state for a layer built on top of the resources, keyed by a
|
|
64
|
+
// symbol that layer owns. Created once per request and discarded with the scope —
|
|
65
|
+
// so the state is structurally request-scoped and unstashable, the property a
|
|
66
|
+
// long-lived handle (e.g. a module-scope `juno(db)`) can't give. Throws outside a
|
|
67
|
+
// scope, like the ambient resources.
|
|
68
|
+
export function requestLocal<T>(key: symbol, make: () => T): T {
|
|
69
|
+
const store = als?.getStore();
|
|
70
|
+
if (!store) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"June: request-scoped state was used outside a request scope. It is only " +
|
|
73
|
+
"available while a request is being handled (a route loader/view or an action).",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const locals = (store.locals ??= new Map<symbol, unknown>());
|
|
77
|
+
if (!locals.has(key)) locals.set(key, make());
|
|
78
|
+
return locals.get(key) as T;
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
function pick<K extends keyof Resources>(name: K): NonNullable<Resources[K]> {
|
|
60
82
|
const store = als?.getStore();
|
|
61
83
|
if (!store) {
|
|
@@ -87,7 +109,31 @@ function ambient<T extends object>(name: keyof Resources): T {
|
|
|
87
109
|
});
|
|
88
110
|
}
|
|
89
111
|
|
|
90
|
-
//
|
|
91
|
-
|
|
112
|
+
// A Tier-3 layer (Juno) registers a tagger so the ambient `db` records the tables
|
|
113
|
+
// each RAW query touches — what lets a raw read inside cache() be auto-invalidated
|
|
114
|
+
// instead of going silently stale. @junejs/db stays generic: it only CALLS the
|
|
115
|
+
// registered fn, never imports it, so the dependency direction stays inward
|
|
116
|
+
// (juno → db, never db → juno). Absent (no Juno) → `db` is raw, untagged.
|
|
117
|
+
let sqlTagger: ((sql: string) => void) | null = null;
|
|
118
|
+
export function registerSqlTagger(tag: (sql: string) => void): void {
|
|
119
|
+
sqlTagger = tag;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The ambient resources. `import { db } from "@junejs/db"` anywhere. `db` is the
|
|
123
|
+
// ONE canonical handle (the framework re-exports it); it auto-tags raw query/get/
|
|
124
|
+
// run when a tagger is registered, so installing Juno upgrades it in place — no
|
|
125
|
+
// second `db` to choose between.
|
|
126
|
+
export const db: JuneDb = new Proxy({} as JuneDb, {
|
|
127
|
+
get(_t, prop) {
|
|
128
|
+
const handle = pick("db") as unknown as Record<string | symbol, unknown>;
|
|
129
|
+
const value = handle[prop];
|
|
130
|
+
if (typeof value !== "function") return value;
|
|
131
|
+
const fn = (value as (...a: unknown[]) => unknown).bind(handle);
|
|
132
|
+
if (sqlTagger && (prop === "query" || prop === "get" || prop === "run")) {
|
|
133
|
+
return (sql: string, params?: unknown[]) => (sqlTagger!(sql), fn(sql, params));
|
|
134
|
+
}
|
|
135
|
+
return fn;
|
|
136
|
+
},
|
|
137
|
+
});
|
|
92
138
|
export const kv: JuneKv = ambient<JuneKv>("kv");
|
|
93
139
|
export const blob: JuneBlob = ambient<JuneBlob>("blob");
|