okengine 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/elements/gate/config.ts +13 -3
- package/src/elements/gate/declare.ts +1 -1
- package/src/elements/gate/strategies.ts +2 -12
- package/src/kernel/app-auth.ts +98 -0
- package/src/kernel/app.ts +37 -68
- package/src/kernel/boot.test.ts +4 -18
- package/src/release/build-lib.ts +3 -0
- package/src/shared/lazy-src.ts +79 -0
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `oke({ gate })` — nested Gate bag (auth · policies · rate · posture).
|
|
3
|
+
*
|
|
4
|
+
* Auth resolution is sync-lazy via {@link requirePackageModule} so HTTP-only
|
|
5
|
+
* apps never evaluate `auth/config` (and so `dist/` can ship a separate chunk).
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
|
-
import {
|
|
8
|
+
import type { GateAuthOptions, ResolvedGateAuth } from "../../auth/config.ts";
|
|
9
|
+
import { requirePackageModule } from "../../shared/lazy-src.ts";
|
|
6
10
|
import type { GateDecl } from "./declare.ts";
|
|
7
11
|
|
|
8
12
|
/** Rate-limit defaults under Gate. */
|
|
@@ -51,8 +55,14 @@ export interface ResolveGateConfigOptions {
|
|
|
51
55
|
*/
|
|
52
56
|
export function resolveGateConfig(options: ResolveGateConfigOptions = {}): ResolvedGateConfig {
|
|
53
57
|
const bag = options.gate ?? {};
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
let auth: ResolvedGateAuth | undefined;
|
|
59
|
+
if (bag.auth !== undefined) {
|
|
60
|
+
const { resolveGateAuth } = requirePackageModule<typeof import("../../auth/config.ts")>(
|
|
61
|
+
"auth/config",
|
|
62
|
+
"auth-config",
|
|
63
|
+
);
|
|
64
|
+
auth = resolveGateAuth({ auth: bag.auth, env: options.env });
|
|
65
|
+
}
|
|
56
66
|
const rateLimitEnabled =
|
|
57
67
|
bag.rateLimit?.enabled !== undefined
|
|
58
68
|
? bag.rateLimit.enabled
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { RateStrategy } from "../../manifest/types.ts";
|
|
8
|
-
import { DEFAULT_RATE_STRATEGY } from "./
|
|
8
|
+
import { DEFAULT_RATE_STRATEGY } from "./constants.ts";
|
|
9
9
|
|
|
10
10
|
/** Context passed to policy predicates at evaluation time. */
|
|
11
11
|
export interface GatePolicyContext {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import { registerLuaScript, type LuaKvStore } from "../../drivers/kv-lua.ts";
|
|
12
12
|
import type { RateStrategy } from "../../manifest/types.ts";
|
|
13
13
|
|
|
14
|
+
export { ALL_RATE_STRATEGIES, DEFAULT_RATE_STRATEGY } from "./constants.ts";
|
|
15
|
+
|
|
14
16
|
/** Result of a rate-limit take attempt. */
|
|
15
17
|
export interface RateTakeResult {
|
|
16
18
|
/** Whether the request is allowed. */
|
|
@@ -338,18 +340,6 @@ export const RATE_STRATEGIES: Record<RateStrategy, StrategyDef> = {
|
|
|
338
340
|
"leaky-bucket": leakyBucket,
|
|
339
341
|
};
|
|
340
342
|
|
|
341
|
-
/** Default rate strategy (unified-theory §16). */
|
|
342
|
-
export const DEFAULT_RATE_STRATEGY: RateStrategy = "sliding-window-counter";
|
|
343
|
-
|
|
344
|
-
/** All five strategy ids. */
|
|
345
|
-
export const ALL_RATE_STRATEGIES: readonly RateStrategy[] = [
|
|
346
|
-
"fixed-window",
|
|
347
|
-
"sliding-window-counter",
|
|
348
|
-
"sliding-log",
|
|
349
|
-
"token-bucket",
|
|
350
|
-
"leaky-bucket",
|
|
351
|
-
];
|
|
352
|
-
|
|
353
343
|
for (const def of Object.values(RATE_STRATEGIES)) {
|
|
354
344
|
registerLuaScript(def.lua, (store, keys, args) => def.run(store, keys, args));
|
|
355
345
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync auth wiring for `oke({ gate: { auth } })`.
|
|
3
|
+
*
|
|
4
|
+
* Loaded only when auth is configured so HTTP-only cold graphs never import
|
|
5
|
+
* bindings / sessions / identity (`requirePackageModule` in `app.ts`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ResolvedGateAuth } from "../auth/config.ts";
|
|
9
|
+
import { auth as authPlugin } from "../auth/plugin.ts";
|
|
10
|
+
import { createAuthHttpBindings, type AuthHttpMaterialization } from "../auth/bindings.ts";
|
|
11
|
+
import { tokenFromCookieHeader } from "../auth/cookies.ts";
|
|
12
|
+
import { setActiveGateAuthContext } from "../auth/method-context.ts";
|
|
13
|
+
import type { ResolvedGateConfig } from "../elements/gate/config.ts";
|
|
14
|
+
import { createAppAuthBinding, verifyBearerToken, type AppAuthBinding } from "./auth-resolve.ts";
|
|
15
|
+
import type { PluginDef } from "./plugin.ts";
|
|
16
|
+
|
|
17
|
+
/** Result of wiring Gate auth at construction. */
|
|
18
|
+
export interface WiredGateAuth {
|
|
19
|
+
readonly materialization: AuthHttpMaterialization | undefined;
|
|
20
|
+
readonly authPlugin: PluginDef | undefined;
|
|
21
|
+
readonly authBinding: AppAuthBinding;
|
|
22
|
+
readonly verifyBearerToken: typeof verifyBearerToken;
|
|
23
|
+
readonly tokenFromCookieHeader: typeof tokenFromCookieHeader;
|
|
24
|
+
/** Rebuild binding when boot supplies a clock. */
|
|
25
|
+
rebind(now: () => number): AppAuthBinding;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Options for {@link wireGateAuth}. */
|
|
29
|
+
export interface WireGateAuthOptions {
|
|
30
|
+
readonly gateConfig: ResolvedGateConfig & { readonly auth: ResolvedGateAuth };
|
|
31
|
+
/** Clock fallback when `gate.auth.now` is omitted (`oke({ fx: { now } })`). */
|
|
32
|
+
readonly now?: () => number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Materialize auth HTTP bindings, absorb the builtin auth plugin, and publish
|
|
37
|
+
* the active Gate auth context for `.plug(username())` etc.
|
|
38
|
+
*
|
|
39
|
+
* @param options - Resolved Gate bag + optional clock
|
|
40
|
+
*/
|
|
41
|
+
export function wireGateAuth(options: WireGateAuthOptions): WiredGateAuth {
|
|
42
|
+
const { gateConfig } = options;
|
|
43
|
+
const auth = gateConfig.auth;
|
|
44
|
+
let materialization: AuthHttpMaterialization | undefined;
|
|
45
|
+
if (auth.http) {
|
|
46
|
+
materialization = createAuthHttpBindings(auth, {
|
|
47
|
+
rateLimitEnabled: gateConfig.rateLimitEnabled,
|
|
48
|
+
sessions: auth.sessions,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pluginDef = authPlugin({
|
|
53
|
+
secret: auth.secret,
|
|
54
|
+
accessTtlMs: auth.session.accessTtlMs,
|
|
55
|
+
refreshTtlMs: auth.session.refreshTtlMs,
|
|
56
|
+
session: {
|
|
57
|
+
accessTtlMs: auth.session.accessTtlMs,
|
|
58
|
+
refreshTtlMs: auth.session.refreshTtlMs,
|
|
59
|
+
idleTtlMs: auth.session.idleTtlMs,
|
|
60
|
+
absoluteTtlMs: auth.session.absoluteTtlMs,
|
|
61
|
+
singleSessionPerUser: auth.session.singleSessionPerUser,
|
|
62
|
+
},
|
|
63
|
+
password: auth.password,
|
|
64
|
+
passwordPolicy: auth.passwordPolicy,
|
|
65
|
+
breachCheck: auth.breachCheck,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let authBinding = createAppAuthBinding({
|
|
69
|
+
secret: auth.secret,
|
|
70
|
+
sessions: materialization?.ctx.sessions ?? auth.sessions,
|
|
71
|
+
now: auth.now ?? options.now,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
setActiveGateAuthContext({
|
|
75
|
+
secret: authBinding.secret,
|
|
76
|
+
sessions: authBinding.sessions,
|
|
77
|
+
now: authBinding.now,
|
|
78
|
+
passwordPolicy: auth.passwordPolicy,
|
|
79
|
+
password: auth.password,
|
|
80
|
+
breachCheck: auth.breachCheck,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
materialization,
|
|
85
|
+
authPlugin: pluginDef,
|
|
86
|
+
authBinding,
|
|
87
|
+
verifyBearerToken,
|
|
88
|
+
tokenFromCookieHeader,
|
|
89
|
+
rebind(now) {
|
|
90
|
+
authBinding = createAppAuthBinding({
|
|
91
|
+
secret: authBinding.secret,
|
|
92
|
+
sessions: authBinding.sessions,
|
|
93
|
+
now,
|
|
94
|
+
});
|
|
95
|
+
return authBinding;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
package/src/kernel/app.ts
CHANGED
|
@@ -6,12 +6,9 @@
|
|
|
6
6
|
* pipeline, and wires `fx.call` to untriggered (and triggered) flows.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
encodeFailure,
|
|
13
|
-
type CompiledRoute,
|
|
14
|
-
} from "../compiler/index.ts";
|
|
9
|
+
import { compileRoute } from "../compiler/dynamic.ts";
|
|
10
|
+
import type { CompiledRoute } from "../compiler/aot.ts";
|
|
11
|
+
import { encodeExecuteResult, encodeFailure } from "../compiler/response.ts";
|
|
15
12
|
import { validate } from "../validation/standard-schema.ts";
|
|
16
13
|
import {
|
|
17
14
|
accumulateAdoptArgs,
|
|
@@ -25,16 +22,15 @@ import {
|
|
|
25
22
|
// when an app actually boots (AGENTS.md / unified-theory budget: cold start < 75 ms).
|
|
26
23
|
import type { BootOptions, BootResult, ElementRuntimes } from "./boot.ts";
|
|
27
24
|
import type { CapabilityToken } from "./capability.ts";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { tokenFromCookieHeader } from "../auth/cookies.ts";
|
|
32
|
-
import { setActiveGateAuthContext } from "../auth/method-context.ts";
|
|
25
|
+
import type { AppAuthBinding } from "./auth-resolve.ts";
|
|
26
|
+
import type { AuthHttpMaterialization } from "../auth/bindings.ts";
|
|
27
|
+
import type { WiredGateAuth } from "./app-auth.ts";
|
|
33
28
|
import {
|
|
34
29
|
resolveGateConfig,
|
|
35
30
|
type GateOptions,
|
|
36
31
|
type ResolvedGateConfig,
|
|
37
32
|
} from "../elements/gate/config.ts";
|
|
33
|
+
import { requirePackageModule } from "../shared/lazy-src.ts";
|
|
38
34
|
import type { TemplateCatalog } from "../elements/channel/runtime.ts";
|
|
39
35
|
import { parseAcceptLanguage } from "../elements/channel/locale.ts";
|
|
40
36
|
import { runWithLocale } from "../i18n/locale-context.ts";
|
|
@@ -534,14 +530,6 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
534
530
|
gate: options.gate,
|
|
535
531
|
env: options.env,
|
|
536
532
|
});
|
|
537
|
-
let authMaterialization: AuthHttpMaterialization | undefined;
|
|
538
|
-
if (gateConfig.auth?.http) {
|
|
539
|
-
authMaterialization = createAuthHttpBindings(gateConfig.auth, {
|
|
540
|
-
rateLimitEnabled: gateConfig.rateLimitEnabled,
|
|
541
|
-
sessions: gateConfig.auth.sessions,
|
|
542
|
-
});
|
|
543
|
-
adopted.push(...authMaterialization.bindings);
|
|
544
|
-
}
|
|
545
533
|
|
|
546
534
|
/** Retained for test harness / boot merges. */
|
|
547
535
|
const $options = options;
|
|
@@ -555,27 +543,26 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
555
543
|
/** Runtime route table — types accumulate on the returned {@link OkeApp}. */
|
|
556
544
|
const routes: RuntimeRouteMap = {};
|
|
557
545
|
|
|
558
|
-
|
|
546
|
+
let authMaterialization: AuthHttpMaterialization | undefined;
|
|
547
|
+
let wiredAuth: WiredGateAuth | undefined;
|
|
548
|
+
let authBinding: AppAuthBinding | undefined;
|
|
559
549
|
if (gateConfig.auth) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
secret: gateConfig.auth.secret,
|
|
564
|
-
accessTtlMs: gateConfig.auth.session.accessTtlMs,
|
|
565
|
-
refreshTtlMs: gateConfig.auth.session.refreshTtlMs,
|
|
566
|
-
session: {
|
|
567
|
-
accessTtlMs: gateConfig.auth.session.accessTtlMs,
|
|
568
|
-
refreshTtlMs: gateConfig.auth.session.refreshTtlMs,
|
|
569
|
-
idleTtlMs: gateConfig.auth.session.idleTtlMs,
|
|
570
|
-
absoluteTtlMs: gateConfig.auth.session.absoluteTtlMs,
|
|
571
|
-
singleSessionPerUser: gateConfig.auth.session.singleSessionPerUser,
|
|
572
|
-
},
|
|
573
|
-
password: gateConfig.auth.password,
|
|
574
|
-
passwordPolicy: gateConfig.auth.passwordPolicy,
|
|
575
|
-
breachCheck: gateConfig.auth.breachCheck,
|
|
576
|
-
}),
|
|
577
|
-
appPluginScope,
|
|
550
|
+
const { wireGateAuth } = requirePackageModule<typeof import("./app-auth.ts")>(
|
|
551
|
+
"kernel/app-auth",
|
|
552
|
+
"app-auth",
|
|
578
553
|
);
|
|
554
|
+
wiredAuth = wireGateAuth({
|
|
555
|
+
gateConfig: { ...gateConfig, auth: gateConfig.auth },
|
|
556
|
+
now: options.fx?.now,
|
|
557
|
+
});
|
|
558
|
+
authMaterialization = wiredAuth.materialization;
|
|
559
|
+
authBinding = wiredAuth.authBinding;
|
|
560
|
+
if (authMaterialization) {
|
|
561
|
+
adopted.push(...authMaterialization.bindings);
|
|
562
|
+
}
|
|
563
|
+
if (wiredAuth.authPlugin) {
|
|
564
|
+
applyPlugin(pluginRegistry, wiredAuth.authPlugin, appPluginScope);
|
|
565
|
+
}
|
|
579
566
|
}
|
|
580
567
|
|
|
581
568
|
const smart = createRouter<Binding>(options.router ?? "default");
|
|
@@ -671,29 +658,10 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
671
658
|
let bootPromise: Promise<BootResult> | undefined;
|
|
672
659
|
let readyState: ReadyState = "booting";
|
|
673
660
|
let bootEnv: BootOptions["env"] = options.env;
|
|
674
|
-
let authBinding: AppAuthBinding | undefined =
|
|
675
|
-
gateConfig.auth !== undefined
|
|
676
|
-
? createAppAuthBinding({
|
|
677
|
-
secret: gateConfig.auth.secret,
|
|
678
|
-
sessions: authMaterialization?.ctx.sessions ?? gateConfig.auth.sessions,
|
|
679
|
-
now: gateConfig.auth.now ?? options.fx?.now,
|
|
680
|
-
})
|
|
681
|
-
: undefined;
|
|
682
|
-
if (authBinding) {
|
|
683
|
-
setActiveGateAuthContext({
|
|
684
|
-
secret: authBinding.secret,
|
|
685
|
-
sessions: authBinding.sessions,
|
|
686
|
-
now: authBinding.now,
|
|
687
|
-
passwordPolicy: gateConfig.auth?.passwordPolicy,
|
|
688
|
-
password: gateConfig.auth?.password,
|
|
689
|
-
breachCheck: gateConfig.auth?.breachCheck,
|
|
690
|
-
});
|
|
691
|
-
} else {
|
|
692
|
-
setActiveGateAuthContext(undefined);
|
|
693
|
-
}
|
|
694
661
|
// Fallback journal for pre-boot / `autoBoot: false` unit tests. A booted
|
|
695
662
|
// app replaces this with the bound `drivers.journal` store (postgres etc.).
|
|
696
|
-
|
|
663
|
+
// Constructed on first use so non-durable HTTP apps skip the alloc.
|
|
664
|
+
let fallbackJournalStore: JournalStore | undefined;
|
|
697
665
|
const fallbackJournalInstanceId = `app-${crypto.randomUUID()}`;
|
|
698
666
|
const sleepingRuns = new Map<
|
|
699
667
|
string,
|
|
@@ -715,6 +683,7 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
715
683
|
if (bound) {
|
|
716
684
|
return { store: bound.store, instanceId: bound.instanceId, leaseMs: bound.leaseMs };
|
|
717
685
|
}
|
|
686
|
+
fallbackJournalStore ??= createMemoryJournalStore();
|
|
718
687
|
return {
|
|
719
688
|
store: fallbackJournalStore,
|
|
720
689
|
instanceId: fallbackJournalInstanceId,
|
|
@@ -978,12 +947,9 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
978
947
|
readyState = "ready";
|
|
979
948
|
}
|
|
980
949
|
// Prefer the booted clock for access-token expiry checks.
|
|
981
|
-
if (authBinding) {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
sessions: authBinding.sessions,
|
|
985
|
-
now: result.clock?.now.bind(result.clock) ?? authBinding.now,
|
|
986
|
-
});
|
|
950
|
+
if (wiredAuth && authBinding) {
|
|
951
|
+
const now = result.clock?.now.bind(result.clock) ?? authBinding.now;
|
|
952
|
+
authBinding = wiredAuth.rebind(now);
|
|
987
953
|
}
|
|
988
954
|
return result;
|
|
989
955
|
}
|
|
@@ -1144,21 +1110,24 @@ export function oke(options: OkeOptions): OkeApp {
|
|
|
1144
1110
|
|
|
1145
1111
|
const binding = authBinding;
|
|
1146
1112
|
const cookieOpts = gateConfig.auth?.cookies;
|
|
1113
|
+
const verifyBearer = wiredAuth?.verifyBearerToken;
|
|
1114
|
+
const cookieToken = wiredAuth?.tokenFromCookieHeader;
|
|
1147
1115
|
const elementHooks = createElementPipelineHooks({
|
|
1148
1116
|
gates: booted.gate,
|
|
1149
1117
|
principals,
|
|
1150
1118
|
telemetry,
|
|
1151
1119
|
allowTestPrincipals: testMode,
|
|
1152
|
-
verifyBearer:
|
|
1120
|
+
verifyBearer:
|
|
1121
|
+
binding && verifyBearer ? async (token) => verifyBearer(binding, token) : undefined,
|
|
1153
1122
|
// Phase 1a: opt-in cookie → Bearer when Authorization is absent.
|
|
1154
1123
|
resolveToken:
|
|
1155
|
-
binding && cookieOpts?.enabled
|
|
1124
|
+
binding && cookieOpts?.enabled && cookieToken
|
|
1156
1125
|
? (request) => {
|
|
1157
1126
|
const header = request.headers.get("authorization");
|
|
1158
1127
|
if (header?.startsWith("Bearer ")) {
|
|
1159
1128
|
return header.slice("Bearer ".length).trim() || undefined;
|
|
1160
1129
|
}
|
|
1161
|
-
return
|
|
1130
|
+
return cookieToken(request.headers.get("cookie"), cookieOpts);
|
|
1162
1131
|
}
|
|
1163
1132
|
: undefined,
|
|
1164
1133
|
});
|
package/src/kernel/boot.test.ts
CHANGED
|
@@ -79,7 +79,7 @@ describe("boot — lazy element needs", () => {
|
|
|
79
79
|
expect(needs.signal).toBe(false);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
test("oke() Store-only graph stays under the prior
|
|
82
|
+
test("oke() Store-only graph stays under the prior 39 kB baseline", async () => {
|
|
83
83
|
const dir = await mkdtemp(join(tmpdir(), "oke-store-only-"));
|
|
84
84
|
const entry = join(dir, "entry.ts");
|
|
85
85
|
const appPath = join(import.meta.dir, "app.ts");
|
|
@@ -116,23 +116,9 @@ describe("boot — lazy element needs", () => {
|
|
|
116
116
|
if (raw.byteLength === 0) continue;
|
|
117
117
|
total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
|
|
118
118
|
}
|
|
119
|
-
// Rebased after
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
// shared surface (~53.3 kB → 54 kB cap). Clock/channel/journal drivers
|
|
123
|
-
// stay lazy-bound; far below eager bind. Rebased again for the
|
|
124
|
-
// store/vault/signal/channel auto-registry drain (`element-registries.ts`
|
|
125
|
-
// + the `oke()` merge/dedup logic) — every app now carries this
|
|
126
|
-
// regardless of which elements it uses, since the drain must run
|
|
127
|
-
// synchronously at construction (same isolation guarantee as the
|
|
128
|
-
// `on()` binding registry) and can't be deferred behind a lazy import
|
|
129
|
-
// (~54.0 kB → 54.3 kB cap). Rebased again for `assertAdoptBarrelFresh`
|
|
130
|
-
// + `tryListFlowsUnits` in `boot.ts` (`.adopt()` barrel staleness
|
|
131
|
-
// check, OKE1009) — part of the already-lazily-loaded boot graph, not
|
|
132
|
-
// the `oke()` construction path, but `Bun.build` still traces and
|
|
133
|
-
// sums the dynamic-import chunk (~54.3 kB → 54.7 kB → 55.0 kB after
|
|
134
|
-
// ConfigEnv local/docker → dev/test/prod + sqlite removal).
|
|
135
|
-
expect(total).toBeLessThan(55_000);
|
|
119
|
+
// Rebased after lazy `gate.auth` / auth-config chunks + `gate.public`
|
|
120
|
+
// decoupling from kv-lua strategies (~38.9 kB gzip with export externals).
|
|
121
|
+
expect(total).toBeLessThan(40_000);
|
|
136
122
|
} finally {
|
|
137
123
|
await rm(dir, { recursive: true, force: true });
|
|
138
124
|
}
|
package/src/release/build-lib.ts
CHANGED
|
@@ -22,6 +22,9 @@ const ENTRIES: readonly { readonly src: string; readonly out: string }[] = [
|
|
|
22
22
|
{ src: "src/i18n-entry.ts", out: "dist/i18n-entry.js" },
|
|
23
23
|
{ src: "src/compiler-entry.ts", out: "dist/compiler-entry.js" },
|
|
24
24
|
{ src: "src/journal-entry.ts", out: "dist/journal-entry.js" },
|
|
25
|
+
// Lazy sync chunks — loaded via requirePackageModule when gate.auth is set.
|
|
26
|
+
{ src: "src/kernel/app-auth.ts", out: "dist/app-auth.js" },
|
|
27
|
+
{ src: "src/auth/config.ts", out: "dist/auth-config.js" },
|
|
25
28
|
];
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync-load package-local modules that Bun.build leaves as runtime requires
|
|
3
|
+
* (not inlined). Bun `src/` first; published `dist/` chunks for `"import"`.
|
|
4
|
+
*
|
|
5
|
+
* Uses Bun APIs (`import.meta.dir`, `import.meta.require`) and try/catch load
|
|
6
|
+
* instead of exists()+require — Bun’s preferred pattern (the exists syscall
|
|
7
|
+
* is an extra round trip).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
|
|
12
|
+
let cachedRoot: string | undefined;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Try a sync `import.meta.require`; return `undefined` only when the file is missing.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - Module namespace shape
|
|
18
|
+
* @param path - Absolute module path
|
|
19
|
+
*/
|
|
20
|
+
function tryRequire<T>(path: string): T | undefined {
|
|
21
|
+
try {
|
|
22
|
+
return import.meta.require(path) as T;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
const code =
|
|
25
|
+
err !== null && typeof err === "object" && "code" in err
|
|
26
|
+
? String((err as { code?: unknown }).code)
|
|
27
|
+
: undefined;
|
|
28
|
+
if (
|
|
29
|
+
code === "MODULE_NOT_FOUND" ||
|
|
30
|
+
code === "ENOENT" ||
|
|
31
|
+
(err instanceof Error && /Cannot find module|ENOENT/i.test(err.message))
|
|
32
|
+
) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Walk up from this module to the okengine package root (`package.json` name).
|
|
41
|
+
*/
|
|
42
|
+
export function okengineRoot(): string {
|
|
43
|
+
if (cachedRoot) return cachedRoot;
|
|
44
|
+
let dir = import.meta.dir;
|
|
45
|
+
for (let i = 0; i < 12; i++) {
|
|
46
|
+
const pkg = tryRequire<{ name?: string }>(join(dir, "package.json"));
|
|
47
|
+
if (pkg?.name === "okengine") {
|
|
48
|
+
cachedRoot = dir;
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
const parent = dirname(dir);
|
|
52
|
+
if (parent === dir) break;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
throw new Error("okengine: package root not found from " + import.meta.dir);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Synchronously load `src/<rel>.ts` (Bun) or published `dist/<distName>.js`.
|
|
60
|
+
*
|
|
61
|
+
* @typeParam T - Module namespace shape
|
|
62
|
+
* @param relSrc - Path under `src/` without extension (e.g. `auth/config`)
|
|
63
|
+
* @param distName - Filename under `dist/` without extension (e.g. `auth-config`)
|
|
64
|
+
*/
|
|
65
|
+
export function requirePackageModule<T>(relSrc: string, distName: string): T {
|
|
66
|
+
const root = okengineRoot();
|
|
67
|
+
const srcPath = join(root, "src", `${relSrc}.ts`);
|
|
68
|
+
const distPath = join(root, "dist", `${distName}.js`);
|
|
69
|
+
// Bun native TS — prefer source so local `oke` / tests stay on one graph.
|
|
70
|
+
if (typeof Bun !== "undefined") {
|
|
71
|
+
const fromSrc = tryRequire<T>(srcPath);
|
|
72
|
+
if (fromSrc !== undefined) return fromSrc;
|
|
73
|
+
}
|
|
74
|
+
const fromDist = tryRequire<T>(distPath);
|
|
75
|
+
if (fromDist !== undefined) return fromDist;
|
|
76
|
+
const fallback = tryRequire<T>(srcPath);
|
|
77
|
+
if (fallback !== undefined) return fallback;
|
|
78
|
+
throw new Error(`okengine: missing lazy module src/${relSrc}.ts or dist/${distName}.js`);
|
|
79
|
+
}
|