@cosmicdrift/kumiko-dev-server 0.386.0 → 0.387.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.
- package/LICENSE +21 -57
- package/dist/build-server-bundle.d.ts +6 -6
- package/dist/build-server-bundle.js +51 -51
- package/dist/build.js +3 -3
- package/dist/compose-stacks.d.ts +2 -2
- package/dist/compose-stacks.js +1 -2
- package/dist/crash-tracker.d.ts +3 -7
- package/dist/crash-tracker.js +4 -13
- package/dist/create-kumiko-server.d.ts +47 -51
- package/dist/create-kumiko-server.js +122 -152
- package/dist/env-schema.d.ts +1 -1
- package/dist/env-schema.js +4 -4
- package/dist/index.js +14 -14
- package/dist/run-dev-app.d.ts +48 -46
- package/dist/run-dev-app.js +57 -55
- package/dist/scaffold-app-feature.js +5 -4
- package/dist/scaffold-app.js +5 -7
- package/dist/schema-apply.d.ts +2 -2
- package/dist/schema-apply.js +12 -12
- package/dist/schema-tables-auth-mode.js +16 -15
- package/dist/schema-tables-minimal.js +7 -7
- package/dist/setup-test-stack-from-features.js +1 -1
- package/dist/welcome-banner.js +2 -2
- package/package.json +10 -10
package/dist/run-dev-app.d.ts
CHANGED
|
@@ -19,16 +19,16 @@ export type RunDevAppAuthOptions = {
|
|
|
19
19
|
/** Optional override of the login error → HTTP status map. Default
|
|
20
20
|
* maps invalidCredentials → 401, noMembership → 403. */
|
|
21
21
|
readonly loginErrorStatusMap?: Readonly<Record<string, number>>;
|
|
22
|
-
/** Auth-
|
|
23
|
-
*
|
|
24
|
-
* hmacSecret
|
|
25
|
-
* `SMTP_HOST
|
|
26
|
-
*
|
|
22
|
+
/** Auth-mail convenience, symmetric with RunProdAppAuthOptions.mail.
|
|
23
|
+
* Wires all four mail flows from env-based SMTP settings and the default
|
|
24
|
+
* templates; hmacSecret is the `JWT_SECRET` environment value, with a dev
|
|
25
|
+
* fallback when unset. Without `SMTP_HOST`, the flows remain unwired
|
|
26
|
+
* (the null-transport guard). Explicit flow setups override the mail default. */
|
|
27
27
|
readonly mail?: AuthMailOptions;
|
|
28
|
-
/** Password-reset flow.
|
|
29
|
-
* reset
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
/** Password-reset flow. When set, mounts /api/auth/request-password-
|
|
29
|
+
* reset and /api/auth/reset-password as public routes and registers the
|
|
30
|
+
* request/confirm handlers in the auth-email-password feature. Symmetric
|
|
31
|
+
* with RunProdAppAuthOptions.passwordReset. */
|
|
32
32
|
readonly passwordReset?: PasswordResetSetup;
|
|
33
33
|
/** Email-verification flow. Symmetric to passwordReset. */
|
|
34
34
|
readonly emailVerification?: EmailVerificationSetup;
|
|
@@ -36,11 +36,11 @@ export type RunDevAppAuthOptions = {
|
|
|
36
36
|
readonly signup?: SignupSetup;
|
|
37
37
|
/** Tenant-invite flow (magic link). Symmetric. */
|
|
38
38
|
readonly invite?: InviteSetup;
|
|
39
|
-
/** Account-unlock flow
|
|
39
|
+
/** Account-unlock flow. Symmetric to RunProdAppAuthOptions. */
|
|
40
40
|
readonly accountUnlock?: AccountUnlockSetup;
|
|
41
41
|
/** Brute-force guard on the login handler (maxFailedAttempts,
|
|
42
42
|
* lockoutDurationMinutes). Mounts no routes. Symmetric to
|
|
43
|
-
* RunProdAppAuthOptions.accountLockout
|
|
43
|
+
* RunProdAppAuthOptions.accountLockout. */
|
|
44
44
|
readonly accountLockout?: AccountLockoutSetup;
|
|
45
45
|
/** Domain attribute for both auth cookies (see
|
|
46
46
|
* AuthRoutesConfig.cookieDomain). Symmetric to RunProdAppAuthOptions. */
|
|
@@ -55,7 +55,7 @@ export type RunDevAppAuthOptions = {
|
|
|
55
55
|
/** Opt out of the Origin guard. Symmetric to RunProdAppAuthOptions. */
|
|
56
56
|
readonly unsafeSkipOriginCheck?: boolean;
|
|
57
57
|
/** Number of trusted reverse-proxy hops for client-IP derivation (see
|
|
58
|
-
* AuthRoutesConfig.trustedProxyHops
|
|
58
|
+
* AuthRoutesConfig.trustedProxyHops). Symmetric to
|
|
59
59
|
* RunProdAppAuthOptions — dev usually runs unproxied, so this is normally
|
|
60
60
|
* left unset (default 0). */
|
|
61
61
|
readonly trustedProxyHops?: number;
|
|
@@ -72,9 +72,9 @@ export type RunDevAppSeedsConfig = Omit<ProdSeedsConfig, "run"> & {
|
|
|
72
72
|
readonly run?: readonly SeedFn[];
|
|
73
73
|
};
|
|
74
74
|
export type RunDevAppOptions = {
|
|
75
|
-
/** App-
|
|
76
|
-
* auth-email-password
|
|
77
|
-
*
|
|
75
|
+
/** App-specific features. In auth mode, config/user/tenant/
|
|
76
|
+
* auth-email-password are mixed in automatically, so they must not be
|
|
77
|
+
* added manually as well. */
|
|
78
78
|
readonly features: readonly FeatureDefinition[];
|
|
79
79
|
/** Opt-in boot-validator warnings — see ValidateBootOptions. */
|
|
80
80
|
readonly validateBootOptions?: ValidateBootOptions;
|
|
@@ -94,38 +94,39 @@ export type RunDevAppOptions = {
|
|
|
94
94
|
* through `runDevApp` show the same resolved head prod does. See
|
|
95
95
|
* `CreateKumikoServerOptions.resolvePageHead` for the full contract. */
|
|
96
96
|
readonly resolvePageHead?: PageHeadResolver;
|
|
97
|
-
/** CSS
|
|
98
|
-
*
|
|
97
|
+
/** CSS entry. Defaults to the package export
|
|
98
|
+
* `@cosmicdrift/kumiko-renderer/web/styles.css` when a client entry is set.
|
|
99
|
+
* `false` disables the CSS pipeline. */
|
|
99
100
|
readonly stylesheet?: string | false;
|
|
100
101
|
/** Tailwind `--watch` process on/off, see `CreateKumikoServerOptions.stylesheetWatch`. */
|
|
101
102
|
readonly stylesheetWatch?: boolean;
|
|
102
|
-
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
103
|
+
/** Custom HTML template; otherwise uses a minimal default (#root + client.js).
|
|
104
|
+
* In multi-entry mode, this is the fallback template when an entry does not
|
|
105
|
+
* define its own htmlPath. */
|
|
105
106
|
readonly htmlPath?: string;
|
|
106
|
-
/**
|
|
107
|
+
/** Listening port. Defaults to 4173, or `$PORT` when set. */
|
|
107
108
|
readonly port?: number;
|
|
108
|
-
/**
|
|
109
|
+
/** Additional directories for the file watcher to trigger hot reload. */
|
|
109
110
|
readonly watchDirs?: readonly string[];
|
|
110
|
-
/** SIGINT/SIGTERM
|
|
111
|
-
*
|
|
111
|
+
/** Installs SIGINT/SIGTERM handlers. Defaults to true; tests can disable
|
|
112
|
+
* them so repeated boots do not accumulate listeners. */
|
|
112
113
|
readonly installSignalHandlers?: boolean;
|
|
113
114
|
/** Auth mode: standard features mixed in, auth routes wired, seedAdminGuarded
|
|
114
115
|
* runs in onAfterSetup. Without `auth` the server stays in auto-mint JWT mode. */
|
|
115
116
|
readonly auth?: RunDevAppAuthOptions;
|
|
116
|
-
/**
|
|
117
|
-
*
|
|
117
|
+
/** App-defined seed functions, run after the admin seed when auth is active,
|
|
118
|
+
* in array order. */
|
|
118
119
|
readonly seeds?: readonly SeedFn[] | RunDevAppSeedsConfig;
|
|
119
|
-
/**
|
|
120
|
-
* secrets
|
|
121
|
-
*
|
|
120
|
+
/** Additional AppContext keys. `templateResolver` is always added; `secrets`
|
|
121
|
+
* is added when the secrets feature is mounted; and `configResolver` is
|
|
122
|
+
* added in auth mode. App values take precedence. Symmetric with runProdApp. */
|
|
122
123
|
/** Boot default locale (BCP-47) for ctx.locale when the request carries
|
|
123
124
|
* no locale signal. Merged into AppContext before extraContext (app wins). */
|
|
124
125
|
readonly defaultLocale?: string;
|
|
125
126
|
readonly extraContext?: CreateKumikoServerOptions["extraContext"];
|
|
126
|
-
/** MasterKeyProvider
|
|
127
|
-
* `createEnvMasterKeyProvider
|
|
128
|
-
*
|
|
127
|
+
/** MasterKeyProvider for the automatically wired `ctx.secrets`. Defaults to
|
|
128
|
+
* `createEnvMasterKeyProvider`; override it for KMS backends. Only relevant
|
|
129
|
+
* when the secrets feature is mounted. Symmetric with runProdApp. */
|
|
129
130
|
readonly masterKey?: MasterKeyProvider;
|
|
130
131
|
/** Subject KMS for PII-annotated fields, the dev counterpart of runProdApp({ kms }).
|
|
131
132
|
* Unset: an ephemeral DB gets an InMemoryKmsAdapter plus a throwaway blind-index key;
|
|
@@ -133,32 +134,33 @@ export type RunDevAppOptions = {
|
|
|
133
134
|
* createPgKmsAdapter against that DB there (in-memory DEKs vanish on restart).
|
|
134
135
|
* `false`: explicit plaintext, with the boot warning. */
|
|
135
136
|
readonly kms?: KmsAdapter | false;
|
|
136
|
-
/** 32-
|
|
137
|
-
*
|
|
138
|
-
*
|
|
137
|
+
/** 32-byte base64 key for blind-index HMACs. Required when `kms` is set and
|
|
138
|
+
* lookupable fields are mounted; otherwise boot aborts. Symmetric with
|
|
139
|
+
* runProdApp. */
|
|
139
140
|
readonly blindIndexKey?: string;
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
141
|
+
/** Environment source for the ENV-to-config app-override bridge. Keys with
|
|
142
|
+
* `env:` receive their environment value as the app-override default,
|
|
143
|
+
* symmetric with runProdApp.
|
|
144
|
+
* Defaults to `process.env`; injectable as a test seam so tests can provide
|
|
145
|
+
* custom environment values without mutating the global `process.env`. */
|
|
144
146
|
readonly envSource?: Record<string, string | undefined>;
|
|
145
|
-
/** Anonymous
|
|
146
|
-
*
|
|
147
|
-
*
|
|
147
|
+
/** Anonymous access for public endpoints. Requests without a JWT proceed as
|
|
148
|
+
* a pseudo-user with the `anonymous` role when the handler includes that
|
|
149
|
+
* role in `access.roles`. */
|
|
148
150
|
readonly anonymousAccess?: CreateKumikoServerOptions["anonymousAccess"];
|
|
149
151
|
/** File-Storage-Provider — aktiviert die Upload-Routes (/api/files) +
|
|
150
152
|
* `ctx.files`. Demos: `{ storageProvider: createInMemoryFileProvider() }`. */
|
|
151
153
|
readonly files?: CreateKumikoServerOptions["files"];
|
|
152
|
-
/** App-
|
|
153
|
-
* Hono
|
|
154
|
-
*
|
|
154
|
+
/** App-defined HTTP routes, such as /feed.xml and /sitemap.xml. They are
|
|
155
|
+
* attached to the Hono app before the static-asset path. Symmetric with the
|
|
156
|
+
* same-named option in runProdApp. */
|
|
155
157
|
readonly extraRoutes?: CreateKumikoServerOptions["extraRoutes"];
|
|
156
158
|
/** Framework-managed unsubscribe links and routes, same contract as
|
|
157
159
|
* `RunProdAppOptions.unsubscribe`; the link base URL comes from `auth.mail.baseUrl`
|
|
158
160
|
* or `APP_BASE_URL` (dev has no `auth.publicOrigin`). */
|
|
159
161
|
readonly unsubscribe?: UnsubscribeOption;
|
|
160
162
|
/** Hook for app-wired co-running components — symmetric to
|
|
161
|
-
* runProdApp.wire
|
|
163
|
+
* runProdApp.wire. */
|
|
162
164
|
readonly wire?: CreateKumikoServerOptions["wire"];
|
|
163
165
|
/** Same as `RunProdAppOptions["mail"]`: mounts `channel-email` and wires the auth mail flows. */
|
|
164
166
|
readonly mail?: MailBootOption;
|
package/dist/run-dev-app.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// High-level dev-server wrapper for sample apps and showcases. In auth mode it
|
|
2
2
|
// mixes in the standard features (config/user/tenant/auth-email-password),
|
|
3
|
-
// wires AuthRoutesConfig plus the login error map and calls seedAdminGuarded()
|
|
4
|
-
// in onAfterSetup,
|
|
3
|
+
// wires AuthRoutesConfig plus the login error map, and calls seedAdminGuarded()
|
|
4
|
+
// in onAfterSetup, reducing a sample bootstrap from roughly 50 lines to 5–10.
|
|
5
5
|
//
|
|
6
|
-
// Auto-
|
|
7
|
-
//
|
|
8
|
-
// Showcases
|
|
9
|
-
//
|
|
6
|
+
// Auto-mixing occurs only in auth mode. Without an `auth` block, the server
|
|
7
|
+
// remains in auto-mint-JWT mode (the dev default) and adds none of these
|
|
8
|
+
// features. Showcases that demonstrate a single domain without a login flow
|
|
9
|
+
// therefore do not unexpectedly gain four additional registry features.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// For maximum control, such as custom auth wiring, a different membership
|
|
12
|
+
// query, or a custom LoginRateLimiter, use `createKumikoServer` directly from
|
|
13
|
+
// @cosmicdrift/kumiko-dev-server.
|
|
14
14
|
import { AuthErrors, AuthHandlers, AuthQueries, } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
|
|
15
15
|
import { seedAdminGuarded, } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
|
|
16
16
|
import { EXT_SESSION_STORE, resolveSessionStore, resolveTokenVerifier, } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
@@ -37,10 +37,9 @@ import { captureEphemeralDevPiiKms, resolveDevPiiKms, } from "./resolve-dev-pii-
|
|
|
37
37
|
import { renderWelcomeBanner } from "./welcome-banner.js";
|
|
38
38
|
import { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, requireEnv, resolveAuthMail, resolveUnsubscribeWiring, } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
|
|
39
39
|
export async function runDevApp(options) {
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// auseinanderdriften können).
|
|
40
|
+
// Automatically mixes standard features in auth mode through composeFeatures.
|
|
41
|
+
// runProdApp and the per-app Drizzle schema generator use the same helper,
|
|
42
|
+
// keeping migrations and runtime feature composition aligned.
|
|
44
43
|
const rawEnvSource = options.envSource ?? process.env;
|
|
45
44
|
const seedsConfig = options.seeds !== undefined && !isSeedFnList(options.seeds) ? options.seeds : undefined;
|
|
46
45
|
const stackSeeds = options.seeds === undefined
|
|
@@ -80,9 +79,10 @@ export async function runDevApp(options) {
|
|
|
80
79
|
features: options.features,
|
|
81
80
|
...(options.featureToggles && { featureToggles: true }),
|
|
82
81
|
}, ["featureToggles"]);
|
|
83
|
-
// auth.mail
|
|
84
|
-
// runProdApp
|
|
85
|
-
//
|
|
82
|
+
// Normalizes auth.mail into the explicit flow fields, symmetrically with
|
|
83
|
+
// runProdApp. hmacSecret is read from the JWT_SECRET environment value and
|
|
84
|
+
// fails fast when unavailable. From this point on, use effectiveAuth rather
|
|
85
|
+
// than options.auth.
|
|
86
86
|
const effectiveAuth = options.auth
|
|
87
87
|
? resolveAuthMail(options.auth, requireEnv("JWT_SECRET", envSource, "runDevApp"), envSource, {
|
|
88
88
|
mailConfigured: wiring.mailConfigured,
|
|
@@ -120,13 +120,13 @@ export async function runDevApp(options) {
|
|
|
120
120
|
: hasBootEnvDefaults
|
|
121
121
|
? { ...options.validateBootOptions, env: baseBootEnv }
|
|
122
122
|
: options.validateBootOptions;
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// CrashLoopBackOff
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// encrypted-field gate
|
|
123
|
+
// Run boot validation before creating the server. This catches the same
|
|
124
|
+
// errors runProdApp catches (unqualified navigation or handler QNs,
|
|
125
|
+
// screen-access violations), which would otherwise only show up as a prod pod
|
|
126
|
+
// in CrashLoopBackOff. It throws
|
|
127
|
+
// synchronously before a socket is opened.
|
|
128
|
+
// Resolve this before validateBoot: an app-supplied masterKey provider must
|
|
129
|
+
// satisfy the encrypted-field gate exactly as it does in runProdApp.
|
|
130
130
|
const bootCrypto = resolveBootCrypto(envSource, options.masterKey);
|
|
131
131
|
validateBoot(features, {
|
|
132
132
|
...(bootCrypto.entityFieldCipher && { entityFieldCipher: bootCrypto.entityFieldCipher }),
|
|
@@ -134,7 +134,7 @@ export async function runDevApp(options) {
|
|
|
134
134
|
});
|
|
135
135
|
warnIfNonUtcServerTimeZone();
|
|
136
136
|
validateAppCustomScreenWriteQns(process.cwd(), collectWriteHandlerQns(features));
|
|
137
|
-
//
|
|
137
|
+
// Tier-Composition auto-wire: scan features for a
|
|
138
138
|
// tenantTierResolver-extension and wire a late-bound wrapper here; it is
|
|
139
139
|
// filled in onAfterSetup (where stack.db + stack.registry are available).
|
|
140
140
|
// The global operator toggles layer over the same wrapper.
|
|
@@ -155,27 +155,27 @@ export async function runDevApp(options) {
|
|
|
155
155
|
}
|
|
156
156
|
return tierResolverHolder.resolver(tenantId);
|
|
157
157
|
}, {
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
158
|
+
// The real trialGate lands on holder.resolver only after plugin.build(). Delegate rather than
|
|
159
|
+
// copy it because this wrapper closure is created before build().
|
|
160
|
+
// Return false when no trial gate is available.
|
|
161
161
|
trialGate: (tenantId, featureName) => tierResolverHolder.resolver?.trialGate?.(tenantId, featureName) ??
|
|
162
162
|
Promise.resolve(false),
|
|
163
163
|
})
|
|
164
164
|
: undefined;
|
|
165
|
-
// configResolver
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// runProdApp) — executors resolve it lazily.
|
|
165
|
+
// Provide the configResolver default for the config feature in auth mode;
|
|
166
|
+
// in no-auth mode, leave that choice to the caller. Wrap the factory so the
|
|
167
|
+
// spread applies to its result; spreading the function itself is a no-op. The
|
|
168
|
+
// ENV-to-config bridge supplies app-override defaults from `env:` keys.
|
|
169
|
+
// The temporary registry extracts only config keys because the resolver is
|
|
170
|
+
// built before server boot, and stack.registry only exists from onAfterSetup;
|
|
171
|
+
// createKumikoServer builds its own registry internally.
|
|
172
|
+
// The app-wide cipher for `encrypted: true` entity fields is resolved lazily
|
|
173
|
+
// by executors, symmetrically with runProdApp.
|
|
175
174
|
configureEntityFieldEncryption(bootCrypto.entityFieldCipher);
|
|
176
|
-
// Subject-KMS
|
|
177
|
-
//
|
|
178
|
-
//
|
|
175
|
+
// Configure Subject-KMS and blind-index handling, symmetrically with
|
|
176
|
+
// runProdApp. Dev warns instead of failing for plaintext PII, but kms with
|
|
177
|
+
// lookupable fields still requires blindIndexKey because lookups would fail
|
|
178
|
+
// in every mode.
|
|
179
179
|
const piiKms = options.kmsMode !== undefined
|
|
180
180
|
? {
|
|
181
181
|
kms: wiring.kms.kms,
|
|
@@ -206,7 +206,7 @@ export async function runDevApp(options) {
|
|
|
206
206
|
logInfo("[runDevApp] PII encrypted with in-memory KMS (ephemeral DB); pass kms: false to opt out");
|
|
207
207
|
}
|
|
208
208
|
// Throwaway registry, built once and reused below — mirrors runProdApp's
|
|
209
|
-
// `createRegistry(features)` right after validateBoot
|
|
209
|
+
// `createRegistry(features)` right after validateBoot. Symmetric
|
|
210
210
|
// sessionStoreProviderMounted check to runProdApp's (registry.getExtension-
|
|
211
211
|
// Usages, not a raw features.some(...) scan) so dev and prod never ask the
|
|
212
212
|
// same question two different ways again.
|
|
@@ -220,11 +220,12 @@ export async function runDevApp(options) {
|
|
|
220
220
|
const cfgExtra = registry
|
|
221
221
|
? mergeConfigResolverDefault(options.extraContext, registry, envSource, bootCrypto.configCipher)
|
|
222
222
|
: options.extraContext;
|
|
223
|
-
//
|
|
224
|
-
// runProdApp.
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
223
|
+
// Automatically wire templateResolver and feature-gated secrets, symmetrically
|
|
224
|
+
// with runProdApp. Unlike prod, the database is available here only in the
|
|
225
|
+
// factory dependencies because createKumikoServer builds the stack. Therefore
|
|
226
|
+
// use a factory: buildBootExtraContext runs with hasAuth:false because
|
|
227
|
+
// configResolver already comes from cfgExtra, and cfgExtra values override
|
|
228
|
+
// the boot defaults.
|
|
228
229
|
const extraContext = (deps) => {
|
|
229
230
|
const base = typeof cfgExtra === "function" ? cfgExtra(deps) : (cfgExtra ?? {});
|
|
230
231
|
const bootLogger = loggerFromExtraContext(base);
|
|
@@ -248,7 +249,7 @@ export async function runDevApp(options) {
|
|
|
248
249
|
};
|
|
249
250
|
// Sessions: late-bound holder — resolveSessionStore needs concrete db+
|
|
250
251
|
// registry (only after setupTestStack). Wired when sessions feature is
|
|
251
|
-
// mounted
|
|
252
|
+
// mounted; no auth.sessions opt-in anymore.
|
|
252
253
|
let sessionStore;
|
|
253
254
|
const requireSessionStore = () => {
|
|
254
255
|
if (!sessionStore) {
|
|
@@ -289,10 +290,10 @@ export async function runDevApp(options) {
|
|
|
289
290
|
})(),
|
|
290
291
|
}
|
|
291
292
|
: {};
|
|
292
|
-
// Parity with runProdApp
|
|
293
|
+
// Parity with runProdApp: gate on sessionStore provider, not
|
|
293
294
|
// the sessions feature name — a custom provider must also wire auth
|
|
294
295
|
// callbacks. sessionStoreProviderMounted computed above via the registry
|
|
295
|
-
// (same check as runProdApp
|
|
296
|
+
// (same check as runProdApp) — not recomputed here.
|
|
296
297
|
const sessionAuthFragment = effectiveAuth && sessionStoreProviderMounted
|
|
297
298
|
? {
|
|
298
299
|
sessionCreator: (user, meta) => requireSessionStore().creator(user, meta),
|
|
@@ -397,7 +398,7 @@ export async function runDevApp(options) {
|
|
|
397
398
|
},
|
|
398
399
|
}),
|
|
399
400
|
onAfterSetup: async (stack) => {
|
|
400
|
-
//
|
|
401
|
+
// Build tier-resolver BEFORE any seeds so seeds can rely
|
|
401
402
|
// on the resolver being live (e.g. seed that writes a SystemAdmin's
|
|
402
403
|
// tier-assignment can immediately read tier-cuts).
|
|
403
404
|
if (tierResolverUsage) {
|
|
@@ -417,7 +418,7 @@ export async function runDevApp(options) {
|
|
|
417
418
|
tierResolverHolder.resolver = bound.resolver;
|
|
418
419
|
}
|
|
419
420
|
if (effectiveAuth && stack.registry.getExtensionUsages(EXT_SESSION_STORE).length > 0) {
|
|
420
|
-
// Secure-by-default
|
|
421
|
+
// Secure-by-default: resolve sessionStore provider; bind
|
|
421
422
|
// password-change mass-revoke + MFA revokeAllOthers.
|
|
422
423
|
const store = await resolveSessionStore({
|
|
423
424
|
db: stack.db,
|
|
@@ -512,10 +513,11 @@ export function mergeConfigResolverDefault(ctx, registry, envSource, cipher) {
|
|
|
512
513
|
...(cipher && { cipher }),
|
|
513
514
|
}),
|
|
514
515
|
};
|
|
515
|
-
// ctx.config
|
|
516
|
-
// addConfigAccessorFactory)
|
|
517
|
-
//
|
|
518
|
-
//
|
|
516
|
+
// Build ctx.config per request through configAccessorFactory (see
|
|
517
|
+
// addConfigAccessorFactory); otherwise
|
|
518
|
+
// ctx.config remains undefined and handlers such as createFileProviderForTenant
|
|
519
|
+
// throw. Build it from the effective resolver so a caller override wins,
|
|
520
|
+
// symmetrically with runProdApp.
|
|
519
521
|
if (ctx === undefined)
|
|
520
522
|
return addConfigAccessorFactory(defaults, registry);
|
|
521
523
|
if (typeof ctx === "function") {
|
|
@@ -25,10 +25,11 @@ export function scaffoldAppFeature(options) {
|
|
|
25
25
|
throw new Error(`scaffoldAppFeature: ${featureDir} already exists — refusing to overwrite`);
|
|
26
26
|
}
|
|
27
27
|
mkdirSync(featureDir, { recursive: true });
|
|
28
|
-
//
|
|
29
|
-
// feature.ts
|
|
30
|
-
//
|
|
31
|
-
//
|
|
28
|
+
// App-feature layout (see bundled-features/tenant):
|
|
29
|
+
// feature.ts only registers the feature; schema/ contains entities; i18n.ts
|
|
30
|
+
// contains server keys; web/ contains the client side; __tests__/ contains
|
|
31
|
+
// the boot test.
|
|
32
|
+
// handlers/ and lib/ are created when the first handler or logic module is added.
|
|
32
33
|
mkdirSync(join(featureDir, "schema"), { recursive: true });
|
|
33
34
|
mkdirSync(join(featureDir, "web"), { recursive: true });
|
|
34
35
|
mkdirSync(join(featureDir, "__tests__"), { recursive: true });
|
package/dist/scaffold-app.js
CHANGED
|
@@ -192,12 +192,10 @@ const FOUNDATION_FEATURES = [
|
|
|
192
192
|
callExpression: "createSessionsFeature()",
|
|
193
193
|
},
|
|
194
194
|
];
|
|
195
|
-
// composeFeatures({ includeBundled: true }) auto-
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
// jemand scaffoldApp() direkt mit allen Bundled-Feature-Entries aufruft
|
|
200
|
-
// rutschen die 4 nicht in run-config.ts.
|
|
195
|
+
// composeFeatures({ includeBundled: true }) auto-mounts these four foundation features.
|
|
196
|
+
// Listing them again in APP_FEATURES caused duplicate-registration warnings in
|
|
197
|
+
// scaffolded `bun dev`; filtering also protects direct scaffoldApp() calls that
|
|
198
|
+
// pass all bundled-feature entries, keeping these four out of run-config.ts.
|
|
201
199
|
const COMPOSE_AUTO_MOUNTED_NAMES = new Set(["config", "user", "tenant", "auth-email-password"]);
|
|
202
200
|
const MAX_LINE_WIDTH = 100;
|
|
203
201
|
// Mirrors Biome's import sort so generated files pass `biome check` untouched:
|
|
@@ -367,7 +365,7 @@ function renderMain(appName) {
|
|
|
367
365
|
'displayName: "Admin",',
|
|
368
366
|
]);
|
|
369
367
|
// Subject-key KMS is optional locally (plaintext + loud warning) but required
|
|
370
|
-
// under NODE_ENV=production
|
|
368
|
+
// under NODE_ENV=production.
|
|
371
369
|
return `// Production-bootstrap. KUMIKO_DRY_RUN_ENV=boot exits after
|
|
372
370
|
// composeFeatures + validateBoot + createRegistry without DB/Redis-connect
|
|
373
371
|
// (see @cosmicdrift/kumiko-server-runtime runProdApp).
|
package/dist/schema-apply.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { type ComposeFeaturesOptions } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
3
3
|
export type SchemaApplyOptions = ComposeFeaturesOptions & {
|
|
4
|
-
/** App
|
|
5
|
-
*
|
|
4
|
+
/** App features (e.g. APP_FEATURES from run-config), composed with the
|
|
5
|
+
* bundled features for the projection rebuild registry. */
|
|
6
6
|
readonly features: readonly FeatureDefinition[];
|
|
7
7
|
/** Default INIT_CWD ?? process.cwd(); Migrations unter <appCwd>/kumiko/migrations. */
|
|
8
8
|
readonly appCwd?: string;
|
package/dist/schema-apply.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Standalone `kumiko schema apply`
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Standalone `kumiko schema apply` for production bundles. Apps bundle a thin
|
|
2
|
+
// bin/kumiko.ts that only calls runStandaloneSchemaCli with the app features;
|
|
3
|
+
// all orchestration (infrastructure bootstrap, migrations, projection rebuild)
|
|
4
|
+
// lives here instead of being repeated as ~100 lines of boilerplate per app.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// kumiko-build
|
|
6
|
+
// The Pulumi-migrate-initContainer calls `bun /app/kumiko.js schema apply`;
|
|
7
|
+
// kumiko-build discovers the app bin via findRepoRoot() and bundles it.
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { createDbConnection, runMigrationsFromDir } from "@cosmicdrift/kumiko-framework/db";
|
|
@@ -27,11 +27,11 @@ export async function runSchemaApply(opts) {
|
|
|
27
27
|
}
|
|
28
28
|
const { db, close } = createDbConnection({ url: dbUrl });
|
|
29
29
|
try {
|
|
30
|
-
//
|
|
31
|
-
// tableExists-gated idempotent:
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
30
|
+
// Create infrastructure tables (event store + pipeline state) first; all are
|
|
31
|
+
// tableExists-gated and idempotent: on an existing DB this is a no-op, while
|
|
32
|
+
// on an empty Greenfield DB they create kumiko_events/_consumers/_projections
|
|
33
|
+
// before the app migrations run. Without this, an empty DB (e.g. cashcolt
|
|
34
|
+
// on a fresh CNPG) fails with `relation "kumiko_events" does not exist`.
|
|
35
35
|
console.log("\n Lege Framework-Infra-Tabellen an (idempotent)…");
|
|
36
36
|
await createEventsTable(db);
|
|
37
37
|
await createEventConsumerStateTable(db);
|
|
@@ -52,7 +52,7 @@ export async function runSchemaApply(opts) {
|
|
|
52
52
|
// Projection rebuild: persistent queue instead of "only this run's
|
|
53
53
|
// result.applied" — otherwise a failed rebuild stays silently stuck
|
|
54
54
|
// forever, since the migration is already tracked applied and gets
|
|
55
|
-
// skipped on the next apply
|
|
55
|
+
// skipped on the next apply. queueRebuildsFromMarkers persists
|
|
56
56
|
// the marker tables BEFORE the rebuild; runPendingRebuilds unconditionally
|
|
57
57
|
// also picks up open entries from earlier, failed runs — not just the
|
|
58
58
|
// ones this run freshly applied.
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
// Drizzle
|
|
1
|
+
// Drizzle schema barrel: framework infrastructure tables.
|
|
2
2
|
//
|
|
3
|
-
// Re-
|
|
4
|
-
//
|
|
3
|
+
// Re-exports the framework-owned tables (event store + pipeline state)
|
|
4
|
+
// that every app must have in its database. The app author writes:
|
|
5
5
|
//
|
|
6
6
|
// // drizzle/schema.ts
|
|
7
7
|
// export * from "@cosmicdrift/kumiko-dev-server/drizzle-tables-auth-mode";
|
|
8
|
-
// export * from "./schema.generated"; // App-
|
|
8
|
+
// export * from "./schema.generated"; // App-owned + bundle entity tables
|
|
9
9
|
//
|
|
10
|
-
// drizzle-kit
|
|
11
|
-
//
|
|
10
|
+
// drizzle-kit only looks for pgTable instances in the schema module and
|
|
11
|
+
// ignores all other exports — `export *` is safe here.
|
|
12
12
|
//
|
|
13
|
-
// Bundle
|
|
14
|
-
// userSessionTable, tenantMembershipsTable etc.)
|
|
15
|
-
//
|
|
16
|
-
// r.entity()
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// Bundle entity tables (configValuesTable, tenantTable, userTable,
|
|
14
|
+
// userSessionTable, tenantMembershipsTable, etc.) are intentionally NOT here:
|
|
15
|
+
// they come through schema.generated.ts via buildEntityTable from the
|
|
16
|
+
// r.entity() definitions, which has been the single source of truth since the
|
|
17
|
+
// entity.indexes API. Duplicate re-exports would create two pgTable instances
|
|
18
|
+
// with identical index names, causing a drizzle-kit warning.
|
|
19
19
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
20
|
+
// The file name "auth-mode" is historical — its contents are now purely
|
|
21
|
+
// framework infrastructure. Renaming it would be a breaking change for app
|
|
22
|
+
// imports.
|
|
23
|
+
// Framework infrastructure (always present, regardless of features)
|
|
23
24
|
export { archivedStreamsTable, eventsTable, snapshotsTable, } from "@cosmicdrift/kumiko-framework/event-store";
|
|
24
25
|
export { eventConsumerStateTable, projectionStateTable, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// Drizzle
|
|
1
|
+
// Minimal Drizzle schema barrel (framework infrastructure only, no auth).
|
|
2
2
|
//
|
|
3
|
-
// Apps
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Apps WITHOUT an auth stack (anonymous-only, embedded demos, headless APIs)
|
|
4
|
+
// need only the framework infrastructure tables — event store and
|
|
5
|
+
// pipeline state. Use in drizzle/schema.ts:
|
|
6
6
|
//
|
|
7
7
|
// export * from "@cosmicdrift/kumiko-dev-server/drizzle-tables-minimal";
|
|
8
|
-
// export * from "./schema.generated"; // App-
|
|
8
|
+
// export * from "./schema.generated"; // App-specific entity tables
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
// plus
|
|
10
|
+
// To include auth tables, use drizzle-tables-auth-mode (includes Minimal
|
|
11
|
+
// plus bundle tables for config, tenant, user, and sessions).
|
|
12
12
|
export { archivedStreamsTable, eventsTable, snapshotsTable, upcasterDeadLetterTable, } from "@cosmicdrift/kumiko-framework/event-store";
|
|
13
13
|
export { eventConsumerStateTable, projectionStateTable, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
@@ -25,7 +25,7 @@ export function mergeExtraContext(base, presets) {
|
|
|
25
25
|
const fromBase = typeof base === "function" ? base(deps) : base !== undefined ? { ...base } : {};
|
|
26
26
|
const merged = { ...fromBase };
|
|
27
27
|
if (presets.includes("config")) {
|
|
28
|
-
// Same precedence as mergeConfigResolverDefault (run-dev-app.ts) — a base-supplied resolver wins, factory always derives from it
|
|
28
|
+
// Same precedence as mergeConfigResolverDefault (run-dev-app.ts) — a base-supplied resolver wins, factory always derives from it.
|
|
29
29
|
const configResolver = resolveBaseConfigResolver(fromBase) ?? createConfigResolver();
|
|
30
30
|
Object.assign(merged, addConfigAccessorFactory({ configResolver }, deps.registry));
|
|
31
31
|
}
|
package/dist/welcome-banner.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// First-run welcome banner for `runDevApp({ welcomeBanner: true })`.
|
|
2
2
|
// Opt-in only — `bun dev` should not normalize this for apps that
|
|
3
3
|
// already log their own startup. Scaffolded apps (create-kumiko-app)
|
|
4
|
-
// flip it on so the first `bun dev` doesn't end on "
|
|
5
|
-
// the user wondering where to click.
|
|
4
|
+
// flip it on so the first `bun dev` doesn't end on a bare "server running"
|
|
5
|
+
// line with the user wondering where to click.
|
|
6
6
|
export function renderWelcomeBanner(input) {
|
|
7
7
|
const featuresDir = input.featuresDir ?? "src/features/";
|
|
8
8
|
const docsUrl = input.docsUrl ?? "https://docs.kumiko.rocks/quickstart";
|