@cosmicdrift/kumiko-dev-server 0.329.0 → 0.330.2

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.
Files changed (125) hide show
  1. package/README.md +17 -0
  2. package/bin/kumiko-build.ts +3 -2
  3. package/bin/kumiko-dev.ts +1 -1
  4. package/bin/kumiko-init-deploy.ts +1 -1
  5. package/bin/kumiko-schema-check.ts +4 -1
  6. package/dist/build-server-bundle.d.ts +31 -0
  7. package/dist/build-server-bundle.js +282 -0
  8. package/dist/build.d.ts +2 -0
  9. package/dist/build.js +5 -0
  10. package/dist/cli.d.ts +5 -0
  11. package/dist/cli.js +9 -0
  12. package/dist/codegen/index.d.ts +4 -0
  13. package/dist/codegen/index.js +4 -0
  14. package/dist/codegen/render.d.ts +55 -0
  15. package/dist/codegen/render.js +273 -0
  16. package/dist/codegen/run-codegen.d.ts +20 -0
  17. package/dist/codegen/run-codegen.js +219 -0
  18. package/dist/codegen/scan-events.d.ts +76 -0
  19. package/dist/codegen/scan-events.js +447 -0
  20. package/dist/codegen/watch.d.ts +33 -0
  21. package/dist/codegen/watch.js +112 -0
  22. package/dist/compose-stacks.d.ts +60 -0
  23. package/dist/compose-stacks.js +127 -0
  24. package/dist/crash-tracker.d.ts +17 -0
  25. package/dist/crash-tracker.js +37 -0
  26. package/dist/create-kumiko-server.d.ts +195 -0
  27. package/dist/create-kumiko-server.js +1040 -0
  28. package/dist/env-schema.d.ts +15 -0
  29. package/dist/env-schema.js +60 -0
  30. package/dist/few-shot-corpus.d.ts +72 -0
  31. package/dist/few-shot-corpus.js +262 -0
  32. package/dist/framework-version.d.ts +1 -0
  33. package/dist/framework-version.js +31 -0
  34. package/dist/index.d.ts +22 -0
  35. package/dist/index.js +33 -0
  36. package/dist/init-deploy-cli.d.ts +10 -0
  37. package/dist/init-deploy-cli.js +111 -0
  38. package/dist/kebab.d.ts +1 -0
  39. package/{src/kebab.ts → dist/kebab.js} +2 -3
  40. package/dist/run-dev-app.d.ts +174 -0
  41. package/dist/run-dev-app.js +403 -0
  42. package/dist/scaffold-app-feature.d.ts +16 -0
  43. package/dist/scaffold-app-feature.js +240 -0
  44. package/dist/scaffold-app.d.ts +45 -0
  45. package/dist/scaffold-app.js +852 -0
  46. package/dist/scaffold-demo-tasks.d.ts +8 -0
  47. package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
  48. package/dist/scaffold-deploy.d.ts +92 -0
  49. package/dist/scaffold-deploy.js +254 -0
  50. package/dist/scaffold-feature.d.ts +26 -0
  51. package/dist/scaffold-feature.js +163 -0
  52. package/dist/schema-apply.d.ts +11 -0
  53. package/dist/schema-apply.js +95 -0
  54. package/dist/schema-check-core.d.ts +2 -0
  55. package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
  56. package/dist/schema-tables-auth-mode.d.ts +2 -0
  57. package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
  58. package/dist/schema-tables-minimal.d.ts +2 -0
  59. package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
  60. package/dist/setup-test-stack-from-features.d.ts +11 -0
  61. package/dist/setup-test-stack-from-features.js +42 -0
  62. package/dist/welcome-banner.d.ts +15 -0
  63. package/dist/welcome-banner.js +27 -0
  64. package/package.json +71 -24
  65. package/src/changes.json +6 -0
  66. package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
  67. package/src/__tests__/build-server-bundle.test.ts +0 -228
  68. package/src/__tests__/classify-change.test.ts +0 -87
  69. package/src/__tests__/compose-stacks.test.ts +0 -273
  70. package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
  71. package/src/__tests__/crash-tracker.test.ts +0 -89
  72. package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
  73. package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
  74. package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
  75. package/src/__tests__/discover-format.test.ts +0 -32
  76. package/src/__tests__/env-schema.integration.test.ts +0 -248
  77. package/src/__tests__/env-schema.test.ts +0 -132
  78. package/src/__tests__/few-shot-corpus.test.ts +0 -316
  79. package/src/__tests__/framework-version.test.ts +0 -45
  80. package/src/__tests__/init-deploy-cli.test.ts +0 -113
  81. package/src/__tests__/kumiko-schema-check.test.ts +0 -56
  82. package/src/__tests__/merge-extra-context.test.ts +0 -67
  83. package/src/__tests__/resolve-stylesheet.test.ts +0 -132
  84. package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
  85. package/src/__tests__/run-dev-app.integration.test.ts +0 -290
  86. package/src/__tests__/runtime-classification.test.ts +0 -26
  87. package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
  88. package/src/__tests__/scaffold-app-feature.test.ts +0 -216
  89. package/src/__tests__/scaffold-app.test.ts +0 -630
  90. package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
  91. package/src/__tests__/scaffold-deploy.test.ts +0 -464
  92. package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
  93. package/src/__tests__/scaffold-feature.test.ts +0 -143
  94. package/src/__tests__/schema-apply.integration.test.ts +0 -191
  95. package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
  96. package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
  97. package/src/__tests__/walkthrough.integration.test.ts +0 -140
  98. package/src/__tests__/welcome-banner.test.ts +0 -42
  99. package/src/build-server-bundle.ts +0 -346
  100. package/src/build.ts +0 -28
  101. package/src/codegen/__tests__/render-codegen.test.ts +0 -100
  102. package/src/codegen/__tests__/run-codegen.test.ts +0 -665
  103. package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
  104. package/src/codegen/__tests__/watch.test.ts +0 -222
  105. package/src/codegen/index.ts +0 -19
  106. package/src/codegen/render.ts +0 -305
  107. package/src/codegen/run-codegen.ts +0 -278
  108. package/src/codegen/scan-events.ts +0 -565
  109. package/src/codegen/watch.ts +0 -140
  110. package/src/compose-stacks.ts +0 -193
  111. package/src/crash-tracker.ts +0 -56
  112. package/src/create-kumiko-server.ts +0 -1397
  113. package/src/env-schema.ts +0 -72
  114. package/src/few-shot-corpus.ts +0 -370
  115. package/src/framework-version.ts +0 -39
  116. package/src/index.ts +0 -105
  117. package/src/init-deploy-cli.ts +0 -148
  118. package/src/run-dev-app.ts +0 -694
  119. package/src/scaffold-app-feature.ts +0 -283
  120. package/src/scaffold-app.ts +0 -1033
  121. package/src/scaffold-deploy.ts +0 -393
  122. package/src/scaffold-feature.ts +0 -226
  123. package/src/schema-apply.ts +0 -123
  124. package/src/setup-test-stack-from-features.ts +0 -80
  125. package/src/welcome-banner.ts +0 -43
@@ -1,140 +0,0 @@
1
- // watchAndRegenerate — file-watcher der bei jeder TS-Änderung unter
2
- // `<appRoot>/src/**` `runCodegen` neu fährt. Lebendige IDE-DX: User
3
- // editiert ein r.defineEvent, drückt save, das `.kumiko/types.generated.d.ts`
4
- // ist innerhalb von ~50ms aktualisiert, der TS-Sprachserver merkt es,
5
- // neue Auto-Complete-Vorschläge erscheinen ohne Server-Restart.
6
- //
7
- // Implementation: node:fs.watch (recursive) auf `<appRoot>/src/`.
8
- // Debounced damit ein batch-edit (z.B. find+replace via sed) nicht
9
- // 50× hintereinander feuert. Idempotent — runCodegen schreibt nur bei
10
- // echter Änderung, der Watcher kann ohne Schaden über-ruft werden.
11
- //
12
- // Nicht-Ziele: kein Watcher auf `node_modules`, `.kumiko`, `dist*`,
13
- // `__tests__` (gleicher SKIP-Set wie scan-events). fs.watch's
14
- // recursive-Mode liefert events für jede Subdir; wir filtern im
15
- // Callback.
16
-
17
- import { type FSWatcher, watch } from "node:fs";
18
- import { join } from "node:path";
19
- import { runCodegen } from "./run-codegen";
20
- import { formatScanWarning } from "./scan-events";
21
-
22
- export type WatchOptions = {
23
- /** App-Wurzel — gleiche Bedeutung wie für `runCodegen`. */
24
- readonly appRoot: string;
25
- /** Optional: alle registrierten Write-Handler-QNs. Durchgereicht an
26
- * `runCodegen` für die Generierung der `WriteHandlerQn`-Union.
27
- * Der Dev-Server extrahiert sie beim Boot aus den Features und
28
- * reicht sie hier rein — der Watcher reicht sie bei jedem Re-Run
29
- * an `runCodegen` weiter, damit der generated-Type nicht verschwindet. */
30
- readonly handlerQns?: readonly string[];
31
- /** Wartet diese Millisekunden zusätzliche Events ab, bevor codegen
32
- * einmal fährt. 50ms catched typische save-bursts (Editor-Saves
33
- * feuern oft 2-3 Events: temp-file → rename → cleanup), bleibt aber
34
- * unmerklich für den User. */
35
- readonly debounceMs?: number;
36
- /** Callback nach jedem erfolgreichen Codegen-Pass. Default: stderr-
37
- * Log mit event-count + warnings. Wer den Output strukturiert
38
- * konsumieren will (Dev-Server-UI, IDE-Plugin), gibt einen eigenen
39
- * Handler. */
40
- readonly onResult?: (result: ReturnType<typeof runCodegen>) => void;
41
- /** Callback bei runCodegen-Fehlern. Default: stderr-Warning. */
42
- readonly onError?: (err: unknown) => void;
43
- };
44
-
45
- export type WatchHandle = {
46
- /** Beendet den Watcher. Idempotent — mehrfacher Aufruf ist no-op. */
47
- readonly close: () => void;
48
- };
49
-
50
- const SKIP_SUBSTRINGS = ["/node_modules/", "/.kumiko/", "/dist/", "/dist-server/", "/__tests__/"];
51
-
52
- const DEFAULT_DEBOUNCE_MS = 50;
53
-
54
- /**
55
- * Startet den Watcher. Beim Boot fährt einmalig `runCodegen` (sodass
56
- * die generated Files sofort frisch sind), dann hängt sich an `fs.watch`
57
- * und re-runs bei file-changes mit Debounce.
58
- */
59
- export function watchAndRegenerate(opts: WatchOptions): WatchHandle {
60
- const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
61
- const srcDir = join(opts.appRoot, "src");
62
- let timer: ReturnType<typeof setTimeout> | undefined;
63
- let watcher: FSWatcher | undefined;
64
- let closed = false;
65
-
66
- const fire = () => {
67
- try {
68
- const result = runCodegen({
69
- appRoot: opts.appRoot,
70
- ...(opts.handlerQns !== undefined && { handlerQns: opts.handlerQns }),
71
- });
72
- if (opts.onResult) {
73
- opts.onResult(result);
74
- } else {
75
- if (result.warnings.length > 0) {
76
- for (const w of result.warnings) {
77
- // biome-ignore lint/suspicious/noConsole: codegen-watcher logs to terminal
78
- console.warn(`[codegen] ${formatScanWarning(w)}`);
79
- }
80
- }
81
- }
82
- } catch (err) {
83
- if (opts.onError) opts.onError(err);
84
- else {
85
- // biome-ignore lint/suspicious/noConsole: codegen-watcher logs to terminal
86
- console.warn(
87
- `[codegen] regenerate failed: ${err instanceof Error ? err.message : String(err)}`,
88
- );
89
- }
90
- }
91
- };
92
-
93
- // Initial run — gleiches Verhalten wie wenn ein file-change kam, aber
94
- // ohne Debounce (User wartet auf den ersten codegen).
95
- fire();
96
-
97
- try {
98
- watcher = watch(srcDir, { recursive: true }, (_eventType, filename) => {
99
- // skip: watcher closed or no filename reported, nothing to act on
100
- if (closed || !filename) return;
101
- // node liefert filename relativ zu srcDir, kann aber posix oder
102
- // windows-style separators haben. Wir prüfen substring-tolerant.
103
- const normalised = `/${filename.toString().replace(/\\/g, "/")}`;
104
- // skip: path matches an excluded segment (node_modules/dist/etc.)
105
- if (SKIP_SUBSTRINGS.some((seg) => normalised.includes(seg))) return;
106
- // skip: Nur .ts/.tsx interessieren — alles andere (CSS, MD, JSON) hat
107
- // keinen Einfluss auf r.defineEvent-Calls.
108
- if (!normalised.endsWith(".ts") && !normalised.endsWith(".tsx")) return;
109
- // skip: .d.ts ausgenommen — die kommen meistens vom codegen selbst.
110
- if (normalised.endsWith(".d.ts")) return;
111
- // skip: .test.ts/.test.tsx ausgenommen — Tests definieren keine
112
- // Production-Features.
113
- if (normalised.endsWith(".test.ts") || normalised.endsWith(".test.tsx")) return;
114
-
115
- if (timer) clearTimeout(timer);
116
- timer = setTimeout(fire, debounceMs);
117
- });
118
- } catch (err) {
119
- // Watch failed (z.B. fs nicht recursive-fähig auf Linux ohne
120
- // patches) — degraded mode: codegen läuft nur beim initial-call.
121
- // User kriegt keine live-updates, aber das ist nicht fatal.
122
- if (opts.onError) opts.onError(err);
123
- else {
124
- // biome-ignore lint/suspicious/noConsole: codegen-watcher logs to terminal
125
- console.warn(
126
- `[codegen] watcher failed to start (live-updates disabled): ${err instanceof Error ? err.message : String(err)}`,
127
- );
128
- }
129
- }
130
-
131
- return {
132
- close: () => {
133
- // skip: already closed, avoid double-close
134
- if (closed) return;
135
- closed = true;
136
- if (timer) clearTimeout(timer);
137
- watcher?.close();
138
- },
139
- };
140
- }
@@ -1,193 +0,0 @@
1
- // @runtime runtime
2
- // Despite living in this "dev"-marked package, every preset here composes
3
- // @cosmicdrift/kumiko-bundled-features factories, and run-config.ts in every
4
- // app repo feeds it into the runProdApp boot path, not just runDevApp
5
- // (kumiko-framework#2337).
6
- //
7
- // Composable SaaS stack presets — explicit opt-in blocks for run-config.ts.
8
- // Presets export feature instances only; no implicit PAT scopes, anonymousAccess,
9
- // or tier maps (those stay in bin/server.ts / app run-config).
10
-
11
- import { createAuditFeature } from "@cosmicdrift/kumiko-bundled-features/audit";
12
- import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
13
- import {
14
- type AuthMfaFeatureOptions,
15
- createAuthMfaFeature,
16
- } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
17
- import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";
18
- import { createDataRetentionFeature } from "@cosmicdrift/kumiko-bundled-features/data-retention";
19
- import { createDeliveryFeature } from "@cosmicdrift/kumiko-bundled-features/delivery";
20
- import { fileFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/file-foundation";
21
- import { fileProviderInMemoryFeature } from "@cosmicdrift/kumiko-bundled-features/file-provider-inmemory";
22
- import { fileProviderS3Feature } from "@cosmicdrift/kumiko-bundled-features/file-provider-s3";
23
- import { fileProviderS3EnvFeature } from "@cosmicdrift/kumiko-bundled-features/file-provider-s3-env";
24
- import { createFilesFeature } from "@cosmicdrift/kumiko-bundled-features/files";
25
- import { createJobsFeature } from "@cosmicdrift/kumiko-bundled-features/jobs";
26
- import {
27
- createLegalPagesFeature,
28
- type LegalPagesOptions,
29
- } from "@cosmicdrift/kumiko-bundled-features/legal-pages";
30
- import { mailFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/mail-foundation";
31
- import { mailTransportInMemoryFeature } from "@cosmicdrift/kumiko-bundled-features/mail-transport-inmemory";
32
- import { mailTransportSmtpFeature } from "@cosmicdrift/kumiko-bundled-features/mail-transport-smtp";
33
- import { createRateLimitingFeature } from "@cosmicdrift/kumiko-bundled-features/rate-limiting";
34
- import { createRendererFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-foundation";
35
- import { createRendererSimpleFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-simple";
36
- import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
37
- import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
38
- import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
39
-
40
- import {
41
- createUserDataRightsFeature,
42
- type UserDataRightsOptions,
43
- } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
44
- import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights-defaults";
45
- import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
46
-
47
- export type FileProviderKind = "inmemory" | "s3" | "s3-env";
48
- export type MailTransportKind = "inmemory" | "smtp";
49
- export type GdprStackOrder = "retention-first" | "compliance-first";
50
-
51
- export type FileStackOptions = {
52
- readonly providers: readonly FileProviderKind[];
53
- readonly includeFilesFeature?: boolean;
54
- };
55
-
56
- export type MailStackOptions = {
57
- readonly transports: readonly MailTransportKind[];
58
- };
59
-
60
- export type PagesStackOptions = {
61
- readonly wrapLayout?: LegalPagesOptions["wrapLayout"];
62
- };
63
-
64
- export type GdprStackOptions = {
65
- readonly order?: GdprStackOrder;
66
- readonly sessions?: boolean;
67
- readonly tenantLifecycle?: boolean;
68
- /** Optional tokenVerifier provider(s) (e.g. PAT) when `sessions` is on.
69
- * Session-only stacks need none — sessions registers sessionStore, which
70
- * satisfies auth-foundation's boot check (#1570). Pass providers when the
71
- * app also wants Bearer/PAT auth. */
72
- readonly providers?: readonly FeatureDefinition[];
73
- };
74
-
75
- export type UserDataRightsStackOptions = {
76
- readonly userDataRights?: UserDataRightsOptions;
77
- readonly includeDefaults?: boolean;
78
- };
79
-
80
- // `sessions` may live on composeIdentityStack OR composeGdprStack; both with default
81
- // options is deduped at boot, differing options throw "Duplicate feature".
82
- export type OpsStackOptions = {
83
- readonly delivery?: boolean;
84
- readonly audit?: boolean;
85
- readonly jobs?: boolean;
86
- readonly rateLimiting?: boolean;
87
- };
88
-
89
- /** sessions (+ optional auth-mfa). config/user/tenant/auth-email-password stay
90
- * on composeFeatures(includeBundled). Pass `mfa` options to mount TOTP.
91
- * When `sessions` is on (the default), auth-foundation is mounted alongside it —
92
- * the framework's registry now hard-requires it (sessions.requires("auth-foundation")).
93
- * Session-only is enough for boot (#1570); optional Bearer/PAT still needs a
94
- * tokenVerifier provider from the caller (e.g. createPersonalAccessTokensFeature
95
- * ({ scopes })) — PAT scopes are app-specific, so they stay caller-owned. */
96
- export type IdentityStackOptions = {
97
- readonly sessions?: boolean;
98
- readonly mfa?: AuthMfaFeatureOptions;
99
- };
100
-
101
- export function stackFeatureNames(features: readonly FeatureDefinition[]): string[] {
102
- return features.map((f) => f.name);
103
- }
104
-
105
- export function composePagesStack(options: PagesStackOptions = {}): FeatureDefinition[] {
106
- // Pages content lives in template-resolver (kind text-block), which the
107
- // renderer stack already mounts — an app composing pages WITHOUT the renderer
108
- // stack has to add createTemplateResolverFeature() itself, or legal-pages'
109
- // r.requires fails the boot. Mounting it here instead would collide with
110
- // composeRendererStack (createRegistry rejects duplicate features).
111
- return [
112
- createLegalPagesFeature(
113
- options.wrapLayout !== undefined ? { wrapLayout: options.wrapLayout } : {},
114
- ),
115
- ];
116
- }
117
-
118
- export function composeRendererStack(): FeatureDefinition[] {
119
- return [
120
- createTemplateResolverFeature(),
121
- createRendererFoundationFeature(),
122
- createRendererSimpleFeature(),
123
- ];
124
- }
125
-
126
- export function composeMailStack(options: MailStackOptions): FeatureDefinition[] {
127
- const out: FeatureDefinition[] = [mailFoundationFeature];
128
- for (const transport of options.transports) {
129
- if (transport === "inmemory") out.push(mailTransportInMemoryFeature);
130
- if (transport === "smtp") out.push(mailTransportSmtpFeature);
131
- }
132
- return out;
133
- }
134
-
135
- export function composeFileStack(options: FileStackOptions): FeatureDefinition[] {
136
- const out: FeatureDefinition[] = [fileFoundationFeature];
137
- for (const provider of options.providers) {
138
- if (provider === "inmemory") out.push(fileProviderInMemoryFeature);
139
- if (provider === "s3") out.push(fileProviderS3Feature);
140
- if (provider === "s3-env") out.push(fileProviderS3EnvFeature);
141
- }
142
- if (options.includeFilesFeature ?? true) out.push(createFilesFeature());
143
- return out;
144
- }
145
-
146
- /** Retention + compliance (+ optional lifecycle/sessions). UDR/files mount separately — order varies per app. */
147
- export function composeGdprStack(options: GdprStackOptions = {}): FeatureDefinition[] {
148
- const order = options.order ?? "retention-first";
149
- const retention = createDataRetentionFeature();
150
- const compliance = createComplianceProfilesFeature();
151
- const out: FeatureDefinition[] =
152
- order === "compliance-first" ? [compliance, retention] : [retention, compliance];
153
- if (options.tenantLifecycle) out.push(createTenantLifecycleFeature());
154
- if (options.sessions) {
155
- out.push(authFoundationFeature, createSessionsFeature());
156
- if (options.providers) out.push(...options.providers);
157
- }
158
- return out;
159
- }
160
-
161
- export function composeUserDataRightsStack(
162
- options: UserDataRightsStackOptions = {},
163
- ): FeatureDefinition[] {
164
- const includeDefaults = options.includeDefaults ?? true;
165
- const out: FeatureDefinition[] = [createUserDataRightsFeature(options.userDataRights ?? {})];
166
- if (includeDefaults) out.push(createUserDataRightsDefaultsFeature());
167
- return out;
168
- }
169
-
170
- export function composeOpsStack(options: OpsStackOptions = {}): FeatureDefinition[] {
171
- const delivery = options.delivery ?? true;
172
- const audit = options.audit ?? true;
173
- const jobs = options.jobs ?? true;
174
- const rateLimiting = options.rateLimiting ?? false;
175
- const out: FeatureDefinition[] = [];
176
- if (delivery) out.push(createDeliveryFeature());
177
- if (audit) out.push(createAuditFeature());
178
- if (jobs) out.push(createJobsFeature());
179
- if (rateLimiting) out.push(createRateLimitingFeature());
180
- return out;
181
- }
182
-
183
- /** Identity opt-ins: sessions by default; auth-mfa when `mfa` options given.
184
- * Do not also pass `sessions: true` to composeGdprStack — duplicate feature. */
185
- export function composeIdentityStack(options: IdentityStackOptions = {}): FeatureDefinition[] {
186
- const sessions = options.sessions ?? true;
187
- const out: FeatureDefinition[] = [];
188
- if (sessions) {
189
- out.push(authFoundationFeature, createSessionsFeature());
190
- }
191
- if (options.mfa !== undefined) out.push(createAuthMfaFeature(options.mfa));
192
- return out;
193
- }
@@ -1,56 +0,0 @@
1
- // Tracks server-Crashes in einem rollenden Zeitfenster und entscheidet,
2
- // ob der dev-wrapper noch respawnen darf oder den Crash-Loop aufgeben
3
- // muss. Liegt als eigener File, damit `kumiko-dev.ts` (bin-Script ohne
4
- // Test-Setup) eine pure, injizierbare API bekommt — die Logik ist
5
- // testbar via createCrashTracker, ohne den Wrapper-Prozess fahren zu
6
- // müssen.
7
- //
8
- // Semantik: ein Crash "zählt im Fenster", wenn er innerhalb der letzten
9
- // `windowMs` ms vor `now` registriert wurde. Wenn nach Aufnahme des
10
- // neuen Crashes mehr als `maxCrashes` im Fenster liegen, gibt der
11
- // Tracker `false` zurück → Wrapper soll aufgeben.
12
-
13
- export type CrashTrackerOptions = {
14
- readonly maxCrashes: number;
15
- readonly windowMs: number;
16
- };
17
-
18
- export type CrashTracker = {
19
- /** Crash bei `now` registrieren. Returns `true` wenn der Wrapper
20
- * noch respawnen darf, `false` wenn das Limit überschritten ist.
21
- *
22
- * Der Crash wird IMMER aufgenommen, auch wenn `false` zurückkommt —
23
- * der Caller terminiert in dem Fall normalerweise eh. Reuse mit
24
- * "rejected → state unchanged"-Semantik wird nicht unterstützt. */
25
- readonly noteCrash: (now: number) => boolean;
26
- /** Anzahl Crashes im Fenster relativ zu `now` — prunt lazy, damit
27
- * der Aufruf auch ohne vorheriges noteCrash konsistent ist. */
28
- readonly crashCountInWindow: (now: number) => number;
29
- };
30
-
31
- export function createCrashTracker(options: CrashTrackerOptions): CrashTracker {
32
- const timestamps: number[] = [];
33
-
34
- const pruneBefore = (cutoff: number): void => {
35
- // Crashes älter als `cutoff` raus. Gleicher Timestamp wie cutoff
36
- // bleibt drin (`<` statt `<=`) — symmetrisch zur Fenster-Definition
37
- // "innerhalb der letzten windowMs", also Endpoint inklusive.
38
- while (timestamps.length > 0) {
39
- const head = timestamps[0];
40
- if (head !== undefined && head < cutoff) timestamps.shift();
41
- else break;
42
- }
43
- };
44
-
45
- return {
46
- noteCrash: (now) => {
47
- pruneBefore(now - options.windowMs);
48
- timestamps.push(now);
49
- return timestamps.length <= options.maxCrashes;
50
- },
51
- crashCountInWindow: (now) => {
52
- pruneBefore(now - options.windowMs);
53
- return timestamps.length;
54
- },
55
- };
56
- }