@cosmicdrift/kumiko-framework 0.2.2 → 0.3.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/CHANGELOG.md +54 -0
- package/package.json +124 -38
- package/src/__tests__/full-stack.integration.ts +2 -2
- package/src/api/auth-routes.ts +5 -5
- package/src/api/jwt.ts +2 -2
- package/src/api/route-registrars.ts +1 -1
- package/src/api/routes.ts +3 -3
- package/src/api/server.ts +6 -7
- package/src/auth/__tests__/roles.test.ts +24 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/roles.ts +42 -0
- package/src/compliance/__tests__/duration-spec.test.ts +72 -0
- package/src/compliance/__tests__/profiles.test.ts +308 -0
- package/src/compliance/__tests__/sub-processors.test.ts +139 -0
- package/src/compliance/duration-spec.ts +44 -0
- package/src/compliance/index.ts +31 -0
- package/src/compliance/override-schema.ts +136 -0
- package/src/compliance/profiles.ts +427 -0
- package/src/compliance/sub-processors.ts +152 -0
- package/src/db/__tests__/big-int-field.test.ts +131 -0
- package/src/db/assert-exists-in.ts +2 -2
- package/src/db/cursor.ts +3 -3
- package/src/db/event-store-executor.ts +19 -13
- package/src/db/located-timestamp.ts +1 -1
- package/src/db/money.ts +12 -2
- package/src/db/pg-error.ts +1 -1
- package/src/db/row-helpers.ts +1 -1
- package/src/db/table-builder.ts +20 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
- package/src/engine/__tests__/build-target.test.ts +135 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +4 -4
- package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
- package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
- package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
- package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
- package/src/engine/__tests__/raw-table.test.ts +2 -2
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
- package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
- package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
- package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
- package/src/engine/__tests__/steps-read.test.ts +142 -0
- package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
- package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
- package/src/engine/__tests__/steps-workflow.test.ts +198 -0
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
- package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
- package/src/engine/boot-validator/api-ext.ts +77 -0
- package/src/engine/boot-validator/config-deps.ts +163 -0
- package/src/engine/boot-validator/entity-handler.ts +466 -0
- package/src/engine/boot-validator/index.ts +159 -0
- package/src/engine/boot-validator/ownership.ts +198 -0
- package/src/engine/boot-validator/pii-retention.ts +155 -0
- package/src/engine/boot-validator/screens-nav.ts +624 -0
- package/src/engine/boot-validator.ts +1 -1528
- package/src/engine/build-app-schema.ts +1 -1
- package/src/engine/build-target.ts +99 -0
- package/src/engine/codemod/index.ts +15 -0
- package/src/engine/codemod/pipeline-codemod.ts +641 -0
- package/src/engine/config-helpers.ts +9 -19
- package/src/engine/constants.ts +1 -1
- package/src/engine/define-feature.ts +127 -9
- package/src/engine/define-handler.ts +89 -3
- package/src/engine/define-roles.ts +2 -2
- package/src/engine/define-step.ts +28 -0
- package/src/engine/define-workflow.ts +110 -0
- package/src/engine/entity-handlers.ts +10 -9
- package/src/engine/event-helpers.ts +4 -4
- package/src/engine/extension-names.ts +105 -0
- package/src/engine/extensions/user-data.ts +106 -0
- package/src/engine/factories.ts +26 -16
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
- package/src/engine/feature-ast/extractors/index.ts +74 -0
- package/src/engine/feature-ast/extractors/round1.ts +110 -0
- package/src/engine/feature-ast/extractors/round2.ts +253 -0
- package/src/engine/feature-ast/extractors/round3.ts +471 -0
- package/src/engine/feature-ast/extractors/round4.ts +1365 -0
- package/src/engine/feature-ast/extractors/round5.ts +72 -0
- package/src/engine/feature-ast/extractors/round6.ts +66 -0
- package/src/engine/feature-ast/extractors/shared.ts +177 -0
- package/src/engine/feature-ast/parse.ts +13 -0
- package/src/engine/feature-ast/patch.ts +9 -1
- package/src/engine/feature-ast/patcher.ts +10 -3
- package/src/engine/feature-ast/patterns.ts +71 -1
- package/src/engine/feature-ast/render.ts +31 -1
- package/src/engine/index.ts +66 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
- package/src/engine/pattern-library/library.ts +78 -2
- package/src/engine/pipeline.ts +88 -0
- package/src/engine/projection-helpers.ts +1 -1
- package/src/engine/read-claim.ts +1 -1
- package/src/engine/registry.ts +30 -2
- package/src/engine/resolve-config-or-param.ts +4 -0
- package/src/engine/run-pipeline.ts +162 -0
- package/src/engine/schema-builder.ts +10 -4
- package/src/engine/state-machine.ts +1 -1
- package/src/engine/steps/_drizzle-boundary.ts +19 -0
- package/src/engine/steps/_duration-utils.ts +33 -0
- package/src/engine/steps/_no-return-guard.ts +21 -0
- package/src/engine/steps/_resolver-utils.ts +42 -0
- package/src/engine/steps/_step-dispatch-constants.ts +38 -0
- package/src/engine/steps/aggregate-append-event.ts +56 -0
- package/src/engine/steps/aggregate-create.ts +56 -0
- package/src/engine/steps/aggregate-update.ts +68 -0
- package/src/engine/steps/branch.ts +84 -0
- package/src/engine/steps/call-feature.ts +49 -0
- package/src/engine/steps/compute.ts +41 -0
- package/src/engine/steps/for-each.ts +111 -0
- package/src/engine/steps/mail-send.ts +44 -0
- package/src/engine/steps/read-find-many.ts +51 -0
- package/src/engine/steps/read-find-one.ts +58 -0
- package/src/engine/steps/retry.ts +87 -0
- package/src/engine/steps/return.ts +34 -0
- package/src/engine/steps/unsafe-projection-delete.ts +46 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
- package/src/engine/steps/wait-for-event.ts +71 -0
- package/src/engine/steps/wait.ts +69 -0
- package/src/engine/steps/webhook-send.ts +71 -0
- package/src/engine/system-user.ts +1 -1
- package/src/engine/types/feature.ts +143 -1
- package/src/engine/types/fields.ts +134 -10
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +15 -1
- package/src/engine/types/step.ts +334 -0
- package/src/engine/types/target-ref.ts +21 -0
- package/src/engine/types/tree-node.ts +130 -0
- package/src/engine/types/workspace.ts +7 -0
- package/src/engine/validate-projection-allowlist.ts +161 -0
- package/src/event-store/snapshot.ts +1 -1
- package/src/event-store/upcaster-dead-letter.ts +1 -1
- package/src/event-store/upcaster.ts +1 -1
- package/src/files/__tests__/read-stream.test.ts +105 -0
- package/src/files/__tests__/write-stream.test.ts +233 -0
- package/src/files/__tests__/zip-stream.test.ts +357 -0
- package/src/files/file-routes.ts +1 -1
- package/src/files/in-memory-provider.ts +38 -0
- package/src/files/index.ts +3 -0
- package/src/files/local-provider.ts +58 -1
- package/src/files/types.ts +36 -8
- package/src/files/zip-stream.ts +251 -0
- package/src/jobs/job-runner.ts +10 -10
- package/src/lifecycle/lifecycle.ts +0 -3
- package/src/logging/index.ts +1 -0
- package/src/logging/pino-logger.ts +11 -7
- package/src/logging/utils.ts +24 -0
- package/src/observability/prometheus-meter.ts +7 -5
- package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
- package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
- package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
- package/src/pipeline/append-event-core.ts +22 -6
- package/src/pipeline/dispatcher-utils.ts +188 -0
- package/src/pipeline/dispatcher.ts +63 -283
- package/src/pipeline/distributed-lock.ts +1 -1
- package/src/pipeline/entity-cache.ts +2 -2
- package/src/pipeline/event-consumer-state.ts +0 -13
- package/src/pipeline/event-dispatcher.ts +4 -4
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/lifecycle-pipeline.ts +6 -12
- package/src/pipeline/msp-rebuild.ts +5 -5
- package/src/pipeline/multi-stream-apply-context.ts +6 -7
- package/src/pipeline/projection-rebuild.ts +2 -2
- package/src/pipeline/projection-state.ts +0 -12
- package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
- package/src/rate-limit/resolver.ts +1 -1
- package/src/search/in-memory-adapter.ts +1 -1
- package/src/search/meilisearch-adapter.ts +3 -3
- package/src/search/types.ts +1 -1
- package/src/secrets/leak-guard.ts +2 -2
- package/src/stack/request-helper.ts +9 -5
- package/src/stack/test-stack.ts +1 -1
- package/src/testing/handler-context.ts +4 -4
- package/src/testing/http-cookies.ts +1 -1
- package/src/time/tz-context.ts +1 -2
- package/src/ui-types/index.ts +4 -0
- package/src/engine/feature-ast/extractors.ts +0 -2562
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
SaveContext,
|
|
14
14
|
} from "../engine/types";
|
|
15
15
|
import { HookPhases } from "../engine/types";
|
|
16
|
+
import { createFallbackLogger } from "../logging/utils";
|
|
16
17
|
import { getFallbackTracer, type Tracer } from "../observability";
|
|
17
18
|
import type { EventDedup } from "./event-dedup";
|
|
18
19
|
|
|
@@ -253,14 +254,9 @@ export function createLifecycleHooks(
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
if (errors.length > 0) {
|
|
256
|
-
const
|
|
257
|
+
const logError = createFallbackLogger("lifecycle", opts.context.log);
|
|
257
258
|
const msg = `${opts.phaseLabel} errors for ${opts.handlerName}`;
|
|
258
|
-
|
|
259
|
-
if (log) {
|
|
260
|
-
log.error(msg, { errors: details });
|
|
261
|
-
} else {
|
|
262
|
-
console.error(`[lifecycle] ${msg}:`, details);
|
|
263
|
-
}
|
|
259
|
+
logError.error(msg, { details: errors.map((e) => `${e.name}: ${e.error}`) });
|
|
264
260
|
}
|
|
265
261
|
}
|
|
266
262
|
|
|
@@ -397,11 +393,9 @@ export function createLifecycleHooks(
|
|
|
397
393
|
// skip: all batch hooks succeeded, nothing to log
|
|
398
394
|
if (failures.length === 0) return;
|
|
399
395
|
|
|
400
|
-
const
|
|
396
|
+
const logError = createFallbackLogger("lifecycle", opts.context.log);
|
|
401
397
|
const msg = `${opts.phaseLabel} errors`;
|
|
402
|
-
|
|
403
|
-
if (log) log.error(msg, { errors: details });
|
|
404
|
-
else console.error(`[lifecycle] ${msg}:`, details);
|
|
398
|
+
logError.error(msg, { details: failures.map((f) => `${f.name}: ${f.outcome.reason}`) });
|
|
405
399
|
}
|
|
406
400
|
}
|
|
407
401
|
|
|
@@ -422,6 +416,6 @@ export function buildEventId(handlerName: string, payload: unknown, phase: strin
|
|
|
422
416
|
if (typeof rawId !== "string" && typeof rawId !== "number") return null;
|
|
423
417
|
if (rawId === 0 || rawId === "") return null;
|
|
424
418
|
const data = p["data"] as Record<string, unknown> | undefined; // @cast-boundary engine-payload
|
|
425
|
-
const version = data?.["version"] as number | undefined;
|
|
419
|
+
const version = data?.["version"] as number | undefined; // @cast-boundary engine-payload
|
|
426
420
|
return `${handlerName}:${rawId}:${version ?? 0}:${phase}`;
|
|
427
421
|
}
|
|
@@ -67,7 +67,7 @@ function createRebuildCtx(
|
|
|
67
67
|
};
|
|
68
68
|
return {
|
|
69
69
|
appendEvent: refuseAppend as MultiStreamApplyContext["appendEvent"], // @cast-boundary engine-bridge
|
|
70
|
-
|
|
70
|
+
unsafeAppendEvent: refuseAppend,
|
|
71
71
|
loadAggregate: async (aggregateId, options) => {
|
|
72
72
|
const events = options?.asOf
|
|
73
73
|
? await loadAggregateAsOf(db, aggregateId, tenantId, options.asOf)
|
|
@@ -142,7 +142,7 @@ export async function rebuildMultiStreamProjection(
|
|
|
142
142
|
// msp.table is narrowed by the upfront guard; the assertion here is
|
|
143
143
|
// for TS inside the async closure (narrowing doesn't cross the
|
|
144
144
|
// transaction boundary).
|
|
145
|
-
const tableName = getTableName(msp.table as NonNullable<typeof msp.table>);
|
|
145
|
+
const tableName = getTableName(msp.table as NonNullable<typeof msp.table>); // @cast-boundary db-operator
|
|
146
146
|
await tx.execute(sql.raw(`TRUNCATE TABLE ${quoteIdent(tableName)}`));
|
|
147
147
|
|
|
148
148
|
const subscribedTypes = Object.keys(msp.apply);
|
|
@@ -151,7 +151,7 @@ export async function rebuildMultiStreamProjection(
|
|
|
151
151
|
.select()
|
|
152
152
|
.from(eventsTable)
|
|
153
153
|
.where(inArray(eventsTable.type, subscribedTypes))
|
|
154
|
-
.orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>;
|
|
154
|
+
.orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>; // @cast-boundary db-row
|
|
155
155
|
|
|
156
156
|
const upcasters = registry.getEventUpcasters();
|
|
157
157
|
for (const row of events) {
|
|
@@ -170,11 +170,11 @@ export async function rebuildMultiStreamProjection(
|
|
|
170
170
|
};
|
|
171
171
|
const storedEvent = await upcastStoredEvent(raw, upcasters, {
|
|
172
172
|
db: tx,
|
|
173
|
-
tenantId: row.tenantId as TenantId,
|
|
173
|
+
tenantId: row.tenantId as TenantId, // @cast-boundary db-row
|
|
174
174
|
});
|
|
175
175
|
const applyFn = msp.apply[row.type];
|
|
176
176
|
if (!applyFn) continue;
|
|
177
|
-
const rebuildCtx = createRebuildCtx(registry, tx, row.tenantId as TenantId);
|
|
177
|
+
const rebuildCtx = createRebuildCtx(registry, tx, row.tenantId as TenantId); // @cast-boundary db-row
|
|
178
178
|
await applyFn(storedEvent, tx, rebuildCtx);
|
|
179
179
|
eventsProcessed++;
|
|
180
180
|
lastProcessedEventId = row.id;
|
|
@@ -2,10 +2,10 @@ import type { DbRunner } from "../db/connection";
|
|
|
2
2
|
import type {
|
|
3
3
|
AppendEventArgs,
|
|
4
4
|
AppendEventFn,
|
|
5
|
-
AppendEventUnsafeFn,
|
|
6
5
|
KumikoEventTypeMap,
|
|
7
6
|
Registry,
|
|
8
7
|
TenantId,
|
|
8
|
+
UnsafeAppendEventFn,
|
|
9
9
|
} from "../engine/types";
|
|
10
10
|
import { loadAggregate, loadAggregateAsOf, type StoredEvent } from "../event-store/event-store";
|
|
11
11
|
import { upcastStoredEvents } from "../event-store/upcaster";
|
|
@@ -20,7 +20,7 @@ import { appendDomainEventCore } from "./append-event-core";
|
|
|
20
20
|
//
|
|
21
21
|
// TMap propagates the strict event-type-map (see HandlerContext). Default
|
|
22
22
|
// matches the global KumikoEventTypeMap; runtime-pluggable callers route
|
|
23
|
-
// through
|
|
23
|
+
// through unsafeAppendEvent.
|
|
24
24
|
export type MultiStreamApplyContext<TMap extends object = KumikoEventTypeMap> = {
|
|
25
25
|
// Append a domain event onto an aggregate stream in the CURRENT tx.
|
|
26
26
|
// Schema-validated, archive-guarded, stream-version derived. Metadata
|
|
@@ -31,7 +31,7 @@ export type MultiStreamApplyContext<TMap extends object = KumikoEventTypeMap> =
|
|
|
31
31
|
readonly appendEvent: AppendEventFn<TMap>;
|
|
32
32
|
// Escape hatch for runtime-pluggable events without compile-time
|
|
33
33
|
// augmentation. Same runtime semantics; type-surface is `payload: unknown`.
|
|
34
|
-
readonly
|
|
34
|
+
readonly unsafeAppendEvent: UnsafeAppendEventFn;
|
|
35
35
|
// Read an aggregate stream — useful when a saga needs to inspect the
|
|
36
36
|
// current state of a different aggregate before deciding what to emit.
|
|
37
37
|
readonly loadAggregate: (
|
|
@@ -74,7 +74,6 @@ export function createMultiStreamApplyContext(
|
|
|
74
74
|
): MultiStreamApplyContext {
|
|
75
75
|
return {
|
|
76
76
|
...(deps.files ? { files: deps.files } : {}),
|
|
77
|
-
// @cast-boundary engine-bridge — concrete impl conforms to AppendEventFn overload
|
|
78
77
|
appendEvent: (async (args: AppendEventArgs) => {
|
|
79
78
|
await appendDomainEventCore(
|
|
80
79
|
{
|
|
@@ -87,15 +86,15 @@ export function createMultiStreamApplyContext(
|
|
|
87
86
|
},
|
|
88
87
|
args,
|
|
89
88
|
);
|
|
90
|
-
}) as AppendEventFn,
|
|
91
|
-
|
|
89
|
+
}) as AppendEventFn, // @cast-boundary engine-bridge
|
|
90
|
+
unsafeAppendEvent: async (args) => {
|
|
92
91
|
await appendDomainEventCore(
|
|
93
92
|
{
|
|
94
93
|
registry: deps.registry,
|
|
95
94
|
db: deps.db,
|
|
96
95
|
tenantId: deps.tenantId,
|
|
97
96
|
userId: deps.userId,
|
|
98
|
-
callSiteLabel: "MSP-apply ctx.
|
|
97
|
+
callSiteLabel: "MSP-apply ctx.unsafeAppendEvent",
|
|
99
98
|
...(deps.callerFeature && { callerFeature: deps.callerFeature }),
|
|
100
99
|
},
|
|
101
100
|
args,
|
|
@@ -127,7 +127,7 @@ export async function rebuildProjection(
|
|
|
127
127
|
subscribed,
|
|
128
128
|
)}`,
|
|
129
129
|
)
|
|
130
|
-
.orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>;
|
|
130
|
+
.orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>; // @cast-boundary db-row
|
|
131
131
|
|
|
132
132
|
// Upcasters run at read time: older stored payloads get walked
|
|
133
133
|
// through the registered r.eventMigration chain until their shape
|
|
@@ -151,7 +151,7 @@ export async function rebuildProjection(
|
|
|
151
151
|
};
|
|
152
152
|
const storedEvent = await upcastStoredEvent(raw, upcasters, {
|
|
153
153
|
db: tx,
|
|
154
|
-
tenantId: row.tenantId as TenantId,
|
|
154
|
+
tenantId: row.tenantId as TenantId, // @cast-boundary db-row
|
|
155
155
|
});
|
|
156
156
|
const applyFn = projection.apply[row.type];
|
|
157
157
|
// skip: apply-key validation ensures every subscribed type has a
|
|
@@ -49,18 +49,6 @@ export const ProjectionStatuses = {
|
|
|
49
49
|
} as const;
|
|
50
50
|
export type ProjectionStatus = (typeof ProjectionStatuses)[keyof typeof ProjectionStatuses];
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* @deprecated Use `ProjectionStatuses` (object form) or the `ProjectionStatus`
|
|
54
|
-
* union type. Tuple alias kept for back-compat with callers that relied on
|
|
55
|
-
* the array form (`z.enum(...)`, runtime iteration) — scheduled for removal
|
|
56
|
-
* after downstream migration.
|
|
57
|
-
*/
|
|
58
|
-
export const PROJECTION_STATUSES = [
|
|
59
|
-
"idle",
|
|
60
|
-
"rebuilding",
|
|
61
|
-
"failed",
|
|
62
|
-
] as const satisfies readonly ProjectionStatus[];
|
|
63
|
-
|
|
64
52
|
// Idempotent table bootstrap. Called by setupTestStack (and createApp once
|
|
65
53
|
// that wires it up) — same pattern as createEventsTable. If the table is
|
|
66
54
|
// already there (second stack in same test DB, production boot after
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
2
2
|
import { RateLimitError } from "../../errors";
|
|
3
3
|
import { createTestRedis, type TestRedis } from "../../stack";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createRateLimitResolver,
|
|
6
|
+
type RateLimitDecision,
|
|
7
|
+
type RateLimitResolver,
|
|
8
|
+
} from "../resolver";
|
|
5
9
|
|
|
6
10
|
let testRedis: TestRedis;
|
|
7
11
|
let resolver: RateLimitResolver;
|
|
@@ -32,7 +36,7 @@ beforeEach(async () => {
|
|
|
32
36
|
describe("createRateLimitResolver — token bucket basics", () => {
|
|
33
37
|
test("first N requests within limit are allowed, N+1 is rejected", async () => {
|
|
34
38
|
const config = { limit: 5, windowSeconds: 60 };
|
|
35
|
-
const decisions = [];
|
|
39
|
+
const decisions: RateLimitDecision[] = [];
|
|
36
40
|
for (let i = 0; i < 5; i++) {
|
|
37
41
|
decisions.push(await resolver.check("user:42", config));
|
|
38
42
|
}
|
|
@@ -66,7 +70,7 @@ describe("createRateLimitResolver — token bucket basics", () => {
|
|
|
66
70
|
|
|
67
71
|
// Advance 5s = window/2 → ~5 tokens refilled
|
|
68
72
|
mockNowMs += 5000;
|
|
69
|
-
const decisions = [];
|
|
73
|
+
const decisions: RateLimitDecision[] = [];
|
|
70
74
|
for (let i = 0; i < 5; i++) {
|
|
71
75
|
decisions.push(await resolver.check("refill:user", config));
|
|
72
76
|
}
|
|
@@ -84,7 +88,7 @@ describe("createRateLimitResolver — token bucket basics", () => {
|
|
|
84
88
|
// Advance 10× the window → bucket would overflow without the cap.
|
|
85
89
|
mockNowMs += 10 * 10 * 1000;
|
|
86
90
|
|
|
87
|
-
const decisions = [];
|
|
91
|
+
const decisions: RateLimitDecision[] = [];
|
|
88
92
|
for (let i = 0; i < 5; i++) {
|
|
89
93
|
decisions.push(await resolver.check("idle:user", config));
|
|
90
94
|
}
|
|
@@ -185,7 +185,7 @@ function ensureCommand(redis: Redis): CommandClient {
|
|
|
185
185
|
});
|
|
186
186
|
REGISTERED.add(redis);
|
|
187
187
|
}
|
|
188
|
-
return redis as CommandClient;
|
|
188
|
+
return redis as CommandClient; // @cast-boundary engine-bridge
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
export function createRateLimitResolver(opts: RateLimitResolverOptions): RateLimitResolver {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { EntityId, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
1
|
import { Meilisearch } from "meilisearch";
|
|
2
|
+
import type { EntityId, TenantId } from "../engine/types/identifiers";
|
|
3
3
|
import type { SearchAdapter, SearchResult } from "./types";
|
|
4
4
|
|
|
5
5
|
export type MeilisearchAdapterOptions = {
|
|
@@ -92,8 +92,8 @@ export function createMeilisearchAdapter(options: MeilisearchAdapterOptions): Se
|
|
|
92
92
|
|
|
93
93
|
return results.hits.map(
|
|
94
94
|
(hit: Record<string, unknown>): SearchResult => ({
|
|
95
|
-
entityType: hit["_type"] as string,
|
|
96
|
-
entityId: hit["_entityId"] as EntityId,
|
|
95
|
+
entityType: hit["_type"] as string, // @cast-boundary engine-bridge
|
|
96
|
+
entityId: hit["_entityId"] as EntityId, // @cast-boundary engine-bridge
|
|
97
97
|
}),
|
|
98
98
|
);
|
|
99
99
|
},
|
package/src/search/types.ts
CHANGED
|
@@ -80,8 +80,8 @@ export function assertNoSecretLeak(value: unknown, path = "$", depth = 0): void
|
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const obj = value as Record<string, unknown>; // @cast-boundary recursive-walk
|
|
84
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
85
85
|
assertNoSecretLeak(v, `${path}.${k}`, depth + 1);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -112,7 +112,9 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
|
|
|
112
112
|
const res = await writeRaw(type, payload, user, requestId);
|
|
113
113
|
// wire-body shape direkt nach JSON.parse — Caller-Code prüft danach
|
|
114
114
|
// selber ob isSuccess/error/data tatsächlich da sind.
|
|
115
|
-
const
|
|
115
|
+
const rawBody = await res.json();
|
|
116
|
+
const body = rawBody as {
|
|
117
|
+
// @cast-boundary engine-bridge
|
|
116
118
|
isSuccess?: boolean;
|
|
117
119
|
data?: unknown;
|
|
118
120
|
error?: { code?: string } | string;
|
|
@@ -126,7 +128,7 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
|
|
|
126
128
|
(typeof body.error === "string" ? body.error : "unknown");
|
|
127
129
|
throw new Error(`Expected write "${type}" to succeed but got error: ${code}`);
|
|
128
130
|
}
|
|
129
|
-
return body.data as T;
|
|
131
|
+
return body.data as T; // @cast-boundary engine-bridge
|
|
130
132
|
},
|
|
131
133
|
|
|
132
134
|
async writeErr(
|
|
@@ -135,7 +137,9 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
|
|
|
135
137
|
user: SessionUser,
|
|
136
138
|
): Promise<import("../errors").WriteErrorInfo> {
|
|
137
139
|
const res = await writeRaw(type, payload, user);
|
|
138
|
-
const
|
|
140
|
+
const rawErrorBody = await res.json();
|
|
141
|
+
const body = rawErrorBody as {
|
|
142
|
+
// @cast-boundary engine-bridge
|
|
139
143
|
isSuccess?: boolean;
|
|
140
144
|
error?: Omit<import("../errors").WriteErrorInfo, "httpStatus">;
|
|
141
145
|
};
|
|
@@ -156,8 +160,8 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
|
|
|
156
160
|
|
|
157
161
|
async queryOk<T = unknown>(type: string, payload: unknown, user: SessionUser): Promise<T> {
|
|
158
162
|
const res = await queryRaw(type, payload, user);
|
|
159
|
-
const body = (await res.json()) as { data: unknown };
|
|
160
|
-
return body.data as T;
|
|
163
|
+
const body = (await res.json()) as { data: unknown }; // @cast-boundary engine-bridge
|
|
164
|
+
return body.data as T; // @cast-boundary engine-bridge
|
|
161
165
|
},
|
|
162
166
|
|
|
163
167
|
async writeWithHeaders(type, payload, user, extraHeaders) {
|
package/src/stack/test-stack.ts
CHANGED
|
@@ -209,7 +209,7 @@ export async function setupTestStack(options: TestStackOptions): Promise<TestSta
|
|
|
209
209
|
const { getTableName } = await import("drizzle-orm");
|
|
210
210
|
const missing: Record<string, unknown> = {};
|
|
211
211
|
for (const [key, tbl] of Object.entries(projectionTables)) {
|
|
212
|
-
const physical = getTableName(tbl as Parameters<typeof getTableName>[0]);
|
|
212
|
+
const physical = getTableName(tbl as Parameters<typeof getTableName>[0]); // @cast-boundary drizzle-bridge
|
|
213
213
|
if (await tableExists(testDb.db, `public.${physical}`)) continue;
|
|
214
214
|
missing[key] = tbl;
|
|
215
215
|
}
|
|
@@ -45,7 +45,7 @@ export function bridgeStub(opts?: {
|
|
|
45
45
|
| "write"
|
|
46
46
|
| "writeAs"
|
|
47
47
|
| "appendEvent"
|
|
48
|
-
| "
|
|
48
|
+
| "unsafeAppendEvent"
|
|
49
49
|
| "fetchForWriting"
|
|
50
50
|
| "loadAggregate"
|
|
51
51
|
| "archiveStream"
|
|
@@ -68,12 +68,12 @@ export function bridgeStub(opts?: {
|
|
|
68
68
|
// SessionUser hier und bekommt ihn am ctx zurück.
|
|
69
69
|
const stubUser: SessionUser = opts?.user ?? {
|
|
70
70
|
id: "00000000-0000-0000-0000-000000000000",
|
|
71
|
-
tenantId: "00000000-0000-0000-0000-000000000000" as SessionUser["tenantId"],
|
|
71
|
+
tenantId: "00000000-0000-0000-0000-000000000000" as SessionUser["tenantId"], // @cast-boundary engine-bridge
|
|
72
72
|
roles: ["all"],
|
|
73
73
|
};
|
|
74
74
|
return {
|
|
75
75
|
user: stubUser,
|
|
76
|
-
query: notAvailable("query") as HandlerContext["query"],
|
|
76
|
+
query: notAvailable("query") as HandlerContext["query"], // @cast-boundary engine-bridge
|
|
77
77
|
queryAs: notAvailable("queryAs") as unknown as (
|
|
78
78
|
user: SessionUser,
|
|
79
79
|
qn: string,
|
|
@@ -89,7 +89,7 @@ export function bridgeStub(opts?: {
|
|
|
89
89
|
payload: unknown,
|
|
90
90
|
) => Promise<WriteResult>,
|
|
91
91
|
appendEvent: notAvailable("appendEvent") as unknown as (args: AppendEventArgs) => Promise<void>,
|
|
92
|
-
|
|
92
|
+
unsafeAppendEvent: notAvailable("unsafeAppendEvent") as unknown as (
|
|
93
93
|
args: AppendEventArgs,
|
|
94
94
|
) => Promise<void>,
|
|
95
95
|
fetchForWriting: notAvailable("fetchForWriting") as unknown as (
|
|
@@ -22,7 +22,7 @@ export type ParsedSetCookie = {
|
|
|
22
22
|
// is acceptable here because tests that set multiple cookies run on a
|
|
23
23
|
// runtime that supports getSetCookie.
|
|
24
24
|
export function getSetCookies(res: Response): Map<string, ParsedSetCookie> {
|
|
25
|
-
const getter = (res.headers as { getSetCookie?: () => string[] }).getSetCookie;
|
|
25
|
+
const getter = (res.headers as { getSetCookie?: () => string[] }).getSetCookie; // @cast-boundary engine-bridge
|
|
26
26
|
const raws = getter ? getter.call(res.headers) : [res.headers.get("set-cookie") ?? ""];
|
|
27
27
|
const out = new Map<string, ParsedSetCookie>();
|
|
28
28
|
for (const raw of raws) {
|
package/src/time/tz-context.ts
CHANGED
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
// kommt in einer späteren Iteration, wenn alle existing usages migriert sind.
|
|
12
12
|
//
|
|
13
13
|
// `tenant` + `user` sind die TZ-Defaults für den aktuellen Request. Aktueller
|
|
14
|
-
// Stand
|
|
14
|
+
// Stand: beide default auf "UTC" — sobald tenant.timezone +
|
|
15
15
|
// user.timezone Felder existieren, lese ich sie aus dem Request-Context.
|
|
16
|
-
// TODO(Iteration 6): aus tenant entity / user profile lesen.
|
|
17
16
|
|
|
18
17
|
import { ensureTemporalPolyfill, getTemporal } from "./polyfill";
|
|
19
18
|
|
package/src/ui-types/index.ts
CHANGED
|
@@ -55,10 +55,14 @@ export type {
|
|
|
55
55
|
FieldRenderer,
|
|
56
56
|
ListColumnSpec,
|
|
57
57
|
PlatformComponent,
|
|
58
|
+
RowAction,
|
|
59
|
+
RowActionNavigate,
|
|
60
|
+
RowActionWriteHandler,
|
|
58
61
|
ScreenDefinition,
|
|
59
62
|
ScreenFilter,
|
|
60
63
|
ScreenFilterOp,
|
|
61
64
|
ScreenSlots,
|
|
65
|
+
ToolbarAction,
|
|
62
66
|
} from "../engine/types/screen";
|
|
63
67
|
export { normalizeEditField, normalizeListColumn } from "../engine/types/screen";
|
|
64
68
|
export type { WorkspaceDefinition } from "../engine/types/workspace";
|