@cosmicdrift/kumiko-dev-server 0.65.0 → 0.67.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.65.0",
3
+ "version": "0.67.0",
4
4
  "description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -50,8 +50,8 @@
50
50
  "kumiko-schema-check": "./bin/kumiko-schema-check.ts"
51
51
  },
52
52
  "dependencies": {
53
- "@cosmicdrift/kumiko-bundled-features": "0.65.0",
54
- "@cosmicdrift/kumiko-framework": "0.65.0",
53
+ "@cosmicdrift/kumiko-bundled-features": "0.67.0",
54
+ "@cosmicdrift/kumiko-framework": "0.67.0",
55
55
  "ts-morph": "^28.0.0"
56
56
  },
57
57
  "publishConfig": {
@@ -173,6 +173,11 @@ export type CreateKumikoServerOptions = {
173
173
  * Handler `roles: ["anonymous"]` deklariert. Tenant-Resolution per
174
174
  * Header/Cookie/Default; siehe AnonymousAccessConfig. */
175
175
  readonly anonymousAccess?: TestStackOptions["anonymousAccess"];
176
+ /** File-Storage-Provider — durchgereicht an setupTestStack. Wenn gesetzt,
177
+ * mountet der Stack die Upload-Routes (POST/GET/DELETE /api/files) +
178
+ * `ctx.files` und legt die file_refs-Tabelle an. Für Demos:
179
+ * `{ storageProvider: createInMemoryFileProvider() }`. */
180
+ readonly files?: TestStackOptions["files"];
176
181
  /** Feature-toggle resolver — durchgereicht an setupTestStack. Wenn
177
182
  * gesetzt, konsultiert der dispatcher-feature-gate, hook-filter, MSP-
178
183
  * filter den callback; absent = alle features always-on. Erforderlich
@@ -660,6 +665,7 @@ export async function createKumikoServer(
660
665
  ...(options.auth !== undefined && { authConfig: options.auth }),
661
666
  ...(options.extraContext !== undefined && { extraContext: options.extraContext }),
662
667
  ...(options.anonymousAccess !== undefined && { anonymousAccess: options.anonymousAccess }),
668
+ ...(options.files !== undefined && { files: options.files }),
663
669
  ...(options.effectiveFeatures !== undefined && {
664
670
  effectiveFeatures: options.effectiveFeatures,
665
671
  }),
@@ -163,6 +163,9 @@ export type RunDevAppOptions = {
163
163
  * als Pseudo-User mit Rolle `anonymous` durch, wenn der Handler die
164
164
  * Rolle in `access.roles` führt. */
165
165
  readonly anonymousAccess?: CreateKumikoServerOptions["anonymousAccess"];
166
+ /** File-Storage-Provider — aktiviert die Upload-Routes (/api/files) +
167
+ * `ctx.files`. Demos: `{ storageProvider: createInMemoryFileProvider() }`. */
168
+ readonly files?: CreateKumikoServerOptions["files"];
166
169
  /** App-eigene HTTP-Routes (z.B. /feed.xml, /sitemap.xml) — wird ans
167
170
  * Hono-app gehängt, läuft VOR dem static-asset-Pfad. Symmetrisch zur
168
171
  * gleichnamigen Option in runProdApp. */
@@ -187,6 +190,13 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
187
190
  ...(composeAuthOptions && { authOptions: composeAuthOptions }),
188
191
  });
189
192
 
193
+ // Ein explizit gewireter File-Provider (options.files) erfüllt das
194
+ // FILE_STORAGE_PROVIDER-Boot-Gate — der Provider IST konfiguriert, nur
195
+ // nicht über die env-Bridge. Setzen bevor validateBoot greift.
196
+ if (options.files !== undefined && process.env["FILE_STORAGE_PROVIDER"] === undefined) {
197
+ process.env["FILE_STORAGE_PROVIDER"] = "configured";
198
+ }
199
+
190
200
  // Boot-Validation als allererstes — vor fs-Watcher und Server. Dieselbe
191
201
  // Fehlerklasse (unqualifizierte nav-/handler-QNs, screen-access etc.),
192
202
  // die früher nur runProdApp fing und sonst erst den Prod-Pod im
@@ -224,18 +234,28 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
224
234
  const finalEffectiveFeatures: EffectiveFeaturesResolver | undefined =
225
235
  options.effectiveFeatures ??
226
236
  (tierResolverUsage
227
- ? (tenantId: TenantId) => {
228
- // Defensive: Server starts AFTER onAfterSetup completes, so the
229
- // resolver is filled before any request comes in. Throwing here
230
- // means a programming error (boot order) rather than silent
231
- // "all-features-on" misbehavior.
232
- if (!tierResolverHolder.resolver) {
233
- throw new Error(
234
- "tier-resolver: extension found but resolver not yet built — boot order issue?",
235
- );
236
- }
237
- return tierResolverHolder.resolver(tenantId);
238
- }
237
+ ? Object.assign(
238
+ (tenantId: TenantId) => {
239
+ // Defensive: Server starts AFTER onAfterSetup completes, so the
240
+ // resolver is filled before any request comes in. Throwing here
241
+ // means a programming error (boot order) rather than silent
242
+ // "all-features-on" misbehavior.
243
+ if (!tierResolverHolder.resolver) {
244
+ throw new Error(
245
+ "tier-resolver: extension found but resolver not yet built — boot order issue?",
246
+ );
247
+ }
248
+ return tierResolverHolder.resolver(tenantId);
249
+ },
250
+ {
251
+ // Late-bind: der echte trialGate hängt erst nach plugin.build() am
252
+ // holder.resolver. Delegieren (nicht kopieren), weil dieses
253
+ // Wrapper-Closure vor build() konstruiert wird. Kein Trial → false.
254
+ trialGate: (tenantId: TenantId, featureName: string): Promise<boolean> =>
255
+ tierResolverHolder.resolver?.trialGate?.(tenantId, featureName) ??
256
+ Promise.resolve(false),
257
+ },
258
+ )
239
259
  : undefined);
240
260
 
241
261
  // configResolver-default fürs config-feature — im auth-mode immer
@@ -298,6 +318,7 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
298
318
  }),
299
319
  ...(extraContext !== undefined && { extraContext }),
300
320
  ...(options.anonymousAccess !== undefined && { anonymousAccess: options.anonymousAccess }),
321
+ ...(options.files !== undefined && { files: options.files }),
301
322
  ...(options.extraRoutes !== undefined && { extraRoutes: options.extraRoutes }),
302
323
  ...(finalEffectiveFeatures !== undefined && {
303
324
  effectiveFeatures: finalEffectiveFeatures,