@cosmicdrift/kumiko-framework 0.366.0 → 0.368.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/AGENTS.md +1 -7
- package/README.md +1 -1
- package/agents/examples/access-control.md +2 -2
- package/agents/examples/anonymous-access-multitenant.md +2 -2
- package/agents/examples/anonymous-access.md +3 -3
- package/agents/examples/apex-landing.md +1 -1
- package/agents/examples/apex-surface-auth.md +6 -7
- package/agents/examples/api-worker-split.md +7 -8
- package/agents/examples/auth-claims.md +2 -1
- package/agents/examples/auth-foundation-providers.md +3 -3
- package/agents/examples/auth-mfa.md +3 -4
- package/agents/examples/basic-entity.md +3 -4
- package/agents/examples/chat-channels.md +4 -4
- package/agents/examples/compliance-profiles-demo.md +2 -3
- package/agents/examples/cross-feature-events.md +4 -4
- package/agents/examples/crypto-shredding-hr.md +14 -9
- package/agents/examples/currencies-global.md +3 -4
- package/agents/examples/currencies-per-tenant.md +3 -4
- package/agents/examples/custom-fields-basic.md +2 -3
- package/agents/examples/custom-handlers.md +3 -5
- package/agents/examples/dashboard-stats.md +2 -3
- package/agents/examples/delivery-notifications.md +5 -6
- package/agents/examples/e2e-generator.md +1 -1
- package/agents/examples/embedded-entity-form.md +2 -3
- package/agents/examples/embedded.md +2 -3
- package/agents/examples/encrypted-tenant-config.md +5 -6
- package/agents/examples/error-contract.md +3 -4
- package/agents/examples/event-sourcing.md +19 -23
- package/agents/examples/feature-toggles.md +4 -4
- package/agents/examples/field-access.md +3 -4
- package/agents/examples/files-post-processing.md +12 -13
- package/agents/examples/golden-path.md +208 -0
- package/agents/examples/idempotency.md +2 -2
- package/agents/examples/inbound-mail-triage.md +7 -7
- package/agents/examples/lane-routing.md +3 -3
- package/agents/examples/ledger-banking.md +2 -3
- package/agents/examples/ledger-invoicing.md +2 -3
- package/agents/examples/legal-pages.md +1 -1
- package/agents/examples/lifecycle-hooks.md +3 -3
- package/agents/examples/managed-config.md +3 -4
- package/agents/examples/managed-pages.md +2 -2
- package/agents/examples/notes-history-basic.md +4 -5
- package/agents/examples/ownership.md +3 -5
- package/agents/examples/pdf-audit-export.md +2 -3
- package/agents/examples/pipeline-basics.md +2 -2
- package/agents/examples/public-share-token.md +3 -3
- package/agents/examples/rate-limiting.md +2 -1
- package/agents/examples/realtime-sse.md +2 -3
- package/agents/examples/record-detail-layout.md +1 -1
- package/agents/examples/reference-data.md +3 -3
- package/agents/examples/relations.md +2 -2
- package/agents/examples/screens-nav.md +1 -1
- package/agents/examples/search.md +2 -2
- package/agents/examples/secrets-demo.md +8 -8
- package/agents/examples/security-baseline.md +2 -2
- package/agents/examples/session-revocation.md +7 -8
- package/agents/examples/state-machine.md +181 -165
- package/agents/examples/tags-basic.md +2 -3
- package/agents/examples/tenant-isolation.md +2 -3
- package/agents/examples/tenant-settings.md +2 -2
- package/agents/examples/tier-admin.md +3 -4
- package/agents/examples/timezones.md +3 -3
- package/agents/examples/user-data-rights.md +22 -33
- package/agents/examples/user-profile.md +3 -3
- package/agents/examples/webhook-step.md +2 -3
- package/agents/examples/wizard-form.md +4 -5
- package/agents/examples/workflow-engine.md +7 -3
- package/agents/examples/writeform-section.md +1 -1
- package/agents/examples.md +1 -0
- package/agents/features.md +4 -4
- package/dist/consumer-cli.js +2 -3
- package/dist/crypto/index.d.ts +1 -1
- package/dist/crypto/index.js +1 -1
- package/dist/crypto/owner-transition-rekey.js +2 -0
- package/dist/crypto/pg-kms-adapter.js +1 -1
- package/dist/crypto/subject-resolver.d.ts +2 -0
- package/dist/crypto/subject-resolver.js +30 -15
- package/dist/db/bun-db-deprecated.d.ts +10 -0
- package/dist/{bun-db/index.js → db/bun-db-deprecated.js} +4 -3
- package/dist/db/config-seed.js +1 -1
- package/dist/db/connection.d.ts +23 -5
- package/dist/db/connection.js +10 -5
- package/dist/db/dialect.js +1 -1
- package/dist/db/entity-field-encryption.d.ts +5 -0
- package/dist/db/entity-field-encryption.js +29 -0
- package/dist/db/entity-table-meta.js +6 -5
- package/dist/db/event-store-executor-read.js +26 -5
- package/dist/db/index.d.ts +4 -4
- package/dist/db/index.js +1 -1
- package/dist/db/migrate-runner.js +1 -1
- package/dist/db/queries/backfill-pii.js +16 -9
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/event-store.d.ts +2 -0
- package/dist/db/queries/event-store.js +26 -0
- package/dist/db/queries/seed-context.js +3 -3
- package/dist/db/queries/test-stack.js +1 -1
- package/dist/db/query.d.ts +105 -1
- package/dist/db/query.js +1450 -3
- package/dist/{stack/table-helpers.d.ts → db/table-push.d.ts} +5 -15
- package/dist/{stack/table-helpers.js → db/table-push.js} +6 -46
- package/dist/db/tenant-db.d.ts +11 -0
- package/dist/db/tenant-db.js +51 -12
- package/dist/derivatives/derivatives-context.d.ts +1 -1
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/gdpr-storage.js +3 -3
- package/dist/engine/boot-validator/pii-retention.js +3 -3
- package/dist/engine/boot-validator/record-owned.js +3 -3
- package/dist/engine/boot-validator/screens.js +8 -1
- package/dist/engine/bridge-stub.d.ts +4 -0
- package/dist/engine/bridge-stub.js +62 -0
- package/dist/engine/event-helpers.d.ts +2 -1
- package/dist/engine/event-helpers.js +1 -0
- package/dist/engine/feature-config-events-jobs.d.ts +4 -0
- package/dist/engine/feature-config-events-jobs.js +4 -2
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +1 -0
- package/dist/engine/registry-ingest.js +3 -0
- package/dist/engine/template-variables.d.ts +3 -0
- package/dist/engine/template-variables.js +3 -0
- package/dist/es-ops/operations-schema.js +1 -1
- package/dist/event-store/admin-api.js +1 -1
- package/dist/event-store/archive.js +1 -1
- package/dist/event-store/errors.d.ts +2 -1
- package/dist/event-store/errors.js +6 -2
- package/dist/event-store/event-store.js +17 -9
- package/dist/event-store/events-schema.js +1 -1
- package/dist/event-store/rebuild-dead-letter.js +3 -3
- package/dist/event-store/snapshot.js +1 -1
- package/dist/event-store/upcaster-dead-letter.js +3 -3
- package/dist/files/create-file-ref-from-bytes.d.ts +1 -0
- package/dist/files/create-file-ref-from-bytes.js +8 -3
- package/dist/files/file-routes.js +1 -1
- package/dist/internal/stack-support.d.ts +12 -0
- package/dist/internal/stack-support.js +12 -0
- package/dist/internal/testing.d.ts +4 -0
- package/dist/internal/testing.js +5 -0
- package/dist/jobs/job-runner.js +23 -11
- package/dist/migrations/pending-rebuilds.js +1 -1
- package/dist/migrations/pii-event-backfill.js +2 -2
- package/dist/pipeline/dispatch-shared.js +43 -23
- package/dist/pipeline/dispatch-write.js +6 -2
- package/dist/pipeline/dispatcher-utils.js +6 -2
- package/dist/pipeline/event-consumer-state.js +1 -1
- package/dist/pipeline/projection-rebuild.d.ts +1 -0
- package/dist/pipeline/projection-rebuild.js +1 -1
- package/dist/pipeline/projection-state.js +1 -1
- package/dist/pipeline/system-hooks.js +1 -1
- package/dist/schema-cli.js +3 -3
- package/dist/search/purge-subject.js +20 -10
- package/dist/seeding/index.d.ts +1 -0
- package/dist/seeding/index.js +1 -0
- package/dist/stack/index.d.ts +10 -9
- package/dist/stack/index.js +11 -19
- package/dist/testing/e2e-generator.d.ts +5 -49
- package/dist/testing/e2e-generator.js +5 -382
- package/dist/testing/handler-context.d.ts +5 -4
- package/dist/testing/handler-context.js +5 -69
- package/dist/testing/index.d.ts +10 -26
- package/dist/testing/index.js +10 -34
- package/dist/testing/real-providers.d.ts +5 -3
- package/dist/testing/real-providers.js +5 -21
- package/package.json +37 -9
- package/src/changes.json +70 -0
- package/src/scripts/codemod/README.md +43 -0
- package/src/scripts/codemod/codemod-support.ts +276 -0
- package/src/scripts/codemod/db-factory.ts +810 -0
- package/src/scripts/codemod/feature-factory.ts +429 -0
- package/src/scripts/codemod/s3-imports.ts +274 -0
- package/src/scripts/codemod/tenant-db-options.ts +454 -0
- package/src/scripts/codemod/testing-move-targets.ts +161 -0
- package/src/scripts/codemod/testing-move.ts +520 -0
- package/dist/bun-db/connection.d.ts +0 -17
- package/dist/bun-db/connection.js +0 -40
- package/dist/bun-db/index.d.ts +0 -4
- package/dist/bun-db/query.d.ts +0 -106
- package/dist/bun-db/query.js +0 -1457
- package/dist/db/api.d.ts +0 -19
- package/dist/db/api.js +0 -23
- package/dist/db/bun-provider.d.ts +0 -2
- package/dist/db/bun-provider.js +0 -28
- package/dist/db/postgres-provider.d.ts +0 -2
- package/dist/db/postgres-provider.js +0 -21
- package/dist/db/query-api.d.ts +0 -2
- package/dist/db/query-api.js +0 -6
- package/dist/stack/db.d.ts +0 -22
- package/dist/stack/db.js +0 -67
- package/dist/stack/drain-event-consumers.d.ts +0 -4
- package/dist/stack/drain-event-consumers.js +0 -51
- package/dist/stack/drain-jobs.d.ts +0 -22
- package/dist/stack/drain-jobs.js +0 -141
- package/dist/stack/event-collector.d.ts +0 -9
- package/dist/stack/event-collector.js +0 -12
- package/dist/stack/push-entity-projection-tables.d.ts +0 -24
- package/dist/stack/push-entity-projection-tables.js +0 -74
- package/dist/stack/redis.d.ts +0 -17
- package/dist/stack/redis.js +0 -52
- package/dist/stack/request-helper.d.ts +0 -35
- package/dist/stack/request-helper.js +0 -144
- package/dist/stack/test-stack.d.ts +0 -192
- package/dist/stack/test-stack.js +0 -429
- package/dist/testing/access-assertions.d.ts +0 -2
- package/dist/testing/access-assertions.js +0 -13
- package/dist/testing/assertions.d.ts +0 -10
- package/dist/testing/assertions.js +0 -22
- package/dist/testing/boot-validator-fixture.d.ts +0 -3
- package/dist/testing/boot-validator-fixture.js +0 -101
- package/dist/testing/closed-connection-error.d.ts +0 -7
- package/dist/testing/closed-connection-error.js +0 -46
- package/dist/testing/db-cleanup.d.ts +0 -9
- package/dist/testing/db-cleanup.js +0 -31
- package/dist/testing/expect-error.d.ts +0 -2
- package/dist/testing/expect-error.js +0 -22
- package/dist/testing/file-provider-contract.d.ts +0 -2
- package/dist/testing/file-provider-contract.js +0 -102
- package/dist/testing/http-cookies.d.ts +0 -7
- package/dist/testing/http-cookies.js +0 -47
- package/dist/testing/late-bound.d.ts +0 -9
- package/dist/testing/late-bound.js +0 -31
- package/dist/testing/manual-clock.d.ts +0 -14
- package/dist/testing/manual-clock.js +0 -12
- package/dist/testing/multipart-helper.d.ts +0 -22
- package/dist/testing/multipart-helper.js +0 -77
- package/dist/testing/mutable-master-key-provider.d.ts +0 -7
- package/dist/testing/mutable-master-key-provider.js +0 -35
- package/dist/testing/observability-recorder.d.ts +0 -9
- package/dist/testing/observability-recorder.js +0 -29
- package/dist/testing/seed.d.ts +0 -9
- package/dist/testing/seed.js +0 -21
- package/dist/testing/shared-entities.d.ts +0 -286
- package/dist/testing/shared-entities.js +0 -56
- package/dist/testing/utils.d.ts +0 -1
- package/dist/testing/utils.js +0 -1
- package/dist/testing/wait-for.d.ts +0 -20
- package/dist/testing/wait-for.js +0 -47
- package/dist/testing/without-ambient-temporal.d.ts +0 -5
- package/dist/testing/without-ambient-temporal.js +0 -17
- package/dist/testing/zip-fixture.d.ts +0 -1
- package/dist/testing/zip-fixture.js +0 -33
- /package/dist/{stack/test-users.d.ts → seeding/seed-users.d.ts} +0 -0
- /package/dist/{stack/test-users.js → seeding/seed-users.js} +0 -0
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { deleteMany } from "../db/query.js";
|
|
2
|
-
// A full-table wipe (empty where) only needs the table name — give deleteMany
|
|
3
|
-
// a minimal-but-canonical EntityTableMeta so extractTableInfo accepts it
|
|
4
|
-
// without inferring columns.
|
|
5
|
-
function tableFromName(name) {
|
|
6
|
-
return { tableName: name, columns: [], indexes: [], source: "unmanaged" };
|
|
7
|
-
}
|
|
8
|
-
function resolveClearableTable(table) {
|
|
9
|
-
if (typeof table === "string")
|
|
10
|
-
return tableFromName(table);
|
|
11
|
-
if (typeof table === "object" &&
|
|
12
|
-
table !== null &&
|
|
13
|
-
"tableName" in table &&
|
|
14
|
-
typeof table.tableName === "string") {
|
|
15
|
-
return table;
|
|
16
|
-
}
|
|
17
|
-
return table;
|
|
18
|
-
}
|
|
19
|
-
/** Delete all rows from each table (order preserved — FK-sensitive callers order explicitly). */
|
|
20
|
-
export async function clearTables(db, tables) {
|
|
21
|
-
for (const table of tables) {
|
|
22
|
-
// Test-teardown truncate: resolveClearableTable returns `unknown` (string
|
|
23
|
-
// name or table object). The brand-strip cast is the sanctioned bypass —
|
|
24
|
-
// clearing a managed projection between tests is not a production write.
|
|
25
|
-
await deleteMany(db, resolveClearableTable(table), {});
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/** Alias — same as clearTables, reads better in beforeEach. */
|
|
29
|
-
export async function resetTestTables(db, tables) {
|
|
30
|
-
await clearTables(db, tables);
|
|
31
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { expect } from "bun:test";
|
|
2
|
-
// Vitest's toContain doesn't operate on plain objects, so after the move from
|
|
3
|
-
// string errors to typed WriteErrorInfo the legacy `expect(error).toContain(x)`
|
|
4
|
-
// assertions break. This helper concatenates code, message, and serialized
|
|
5
|
-
// details so existing substring checks against short reason strings keep
|
|
6
|
-
// working without each call site needing to know the new shape.
|
|
7
|
-
//
|
|
8
|
-
// Accepts `string | null` too, so call sites that still get raw strings
|
|
9
|
-
// (e.g. helpers that haven't moved to typed errors yet) keep working.
|
|
10
|
-
export function expectErrorIncludes(err, substring) {
|
|
11
|
-
let haystack;
|
|
12
|
-
if (err === null || err === undefined) {
|
|
13
|
-
haystack = String(err);
|
|
14
|
-
}
|
|
15
|
-
else if (typeof err === "string") {
|
|
16
|
-
haystack = err;
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
haystack = `${err.code} ${err.message} ${JSON.stringify(err.details ?? {})}`;
|
|
20
|
-
}
|
|
21
|
-
expect(haystack).toContain(substring);
|
|
22
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
const bytes = (s) => new TextEncoder().encode(s);
|
|
3
|
-
const decode = (u) => new TextDecoder().decode(u);
|
|
4
|
-
async function* fromChunks(chunks) {
|
|
5
|
-
for (const c of chunks)
|
|
6
|
-
yield c;
|
|
7
|
-
}
|
|
8
|
-
// Mirrors describeKmsAdapterContract (crypto/__tests__/kms-adapter-contract.ts):
|
|
9
|
-
// pins the FileStorageProvider contract against whatever implementation the
|
|
10
|
-
// factory returns. Provider-specific behavior (error messages, buffer
|
|
11
|
-
// copies, multipart details) stays in each provider's dedicated tests.
|
|
12
|
-
export function describeFileProviderContract(name, factory) {
|
|
13
|
-
describe(`${name} — FileStorageProvider contract`, () => {
|
|
14
|
-
let provider;
|
|
15
|
-
// Self-cleaning: every key this contract creates gets deleted in
|
|
16
|
-
// afterEach, so a persistent backend (local `kumiko dev` against a
|
|
17
|
-
// long-lived Minio, as opposed to CI's ephemeral one) doesn't leak
|
|
18
|
-
// `contract/<uuid>` objects into the next developer's session. delete()
|
|
19
|
-
// on a key that was never actually written is covered by the
|
|
20
|
-
// "no-op on a missing key" contract test above, so tracking generated
|
|
21
|
-
// keys unconditionally (even ones a test never got around to writing)
|
|
22
|
-
// is safe.
|
|
23
|
-
let writtenKeys;
|
|
24
|
-
function trackedKey(ext) {
|
|
25
|
-
const key = `contract/${crypto.randomUUID()}.${ext}`;
|
|
26
|
-
writtenKeys.push(key);
|
|
27
|
-
return key;
|
|
28
|
-
}
|
|
29
|
-
beforeEach(async () => {
|
|
30
|
-
provider = await factory();
|
|
31
|
-
writtenKeys = [];
|
|
32
|
-
});
|
|
33
|
-
afterEach(async () => {
|
|
34
|
-
for (const key of writtenKeys)
|
|
35
|
-
await provider.delete(key);
|
|
36
|
-
});
|
|
37
|
-
test("write + read roundtrip preserves bytes", async () => {
|
|
38
|
-
const key = trackedKey("bin");
|
|
39
|
-
await provider.write(key, bytes("hello contract"));
|
|
40
|
-
expect(decode(await provider.read(key))).toBe("hello contract");
|
|
41
|
-
});
|
|
42
|
-
test("write on an existing key overwrites it (last-write-wins)", async () => {
|
|
43
|
-
const key = trackedKey("bin");
|
|
44
|
-
await provider.write(key, bytes("first"));
|
|
45
|
-
await provider.write(key, bytes("second"));
|
|
46
|
-
expect(decode(await provider.read(key))).toBe("second");
|
|
47
|
-
});
|
|
48
|
-
test("read throws for a missing key", async () => {
|
|
49
|
-
await expect(provider.read(`contract/missing-${crypto.randomUUID()}`)).rejects.toThrow();
|
|
50
|
-
});
|
|
51
|
-
test("exists reflects write + delete", async () => {
|
|
52
|
-
const key = trackedKey("txt");
|
|
53
|
-
expect(await provider.exists(key)).toBe(false);
|
|
54
|
-
await provider.write(key, bytes("x"));
|
|
55
|
-
expect(await provider.exists(key)).toBe(true);
|
|
56
|
-
await provider.delete(key);
|
|
57
|
-
expect(await provider.exists(key)).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
test("delete on a missing key is a no-op", async () => {
|
|
60
|
-
await expect(provider.delete(`contract/never-existed-${crypto.randomUUID()}`)).resolves.toBeUndefined();
|
|
61
|
-
});
|
|
62
|
-
test("writeStream + readStream roundtrip preserves bytes", async () => {
|
|
63
|
-
const key = trackedKey("stream");
|
|
64
|
-
await provider.writeStream(key, fromChunks([bytes("foo"), bytes("bar")]));
|
|
65
|
-
let out = "";
|
|
66
|
-
for await (const chunk of provider.readStream(key))
|
|
67
|
-
out += decode(chunk);
|
|
68
|
-
expect(out).toBe("foobar");
|
|
69
|
-
});
|
|
70
|
-
test("readStream on a missing key throws on the first chunk pull", async () => {
|
|
71
|
-
const it = provider
|
|
72
|
-
.readStream(`contract/missing-${crypto.randomUUID()}`)[Symbol.asyncIterator]();
|
|
73
|
-
await expect(it.next()).rejects.toThrow();
|
|
74
|
-
});
|
|
75
|
-
test("list returns only keys under the given prefix", async () => {
|
|
76
|
-
const group = `contract/list-${crypto.randomUUID()}`;
|
|
77
|
-
const keyA = `${group}/a.txt`;
|
|
78
|
-
const keyB = `${group}/b.txt`;
|
|
79
|
-
const outsideKey = `contract/list-${crypto.randomUUID()}/c.txt`;
|
|
80
|
-
writtenKeys.push(keyA, keyB, outsideKey);
|
|
81
|
-
await provider.write(keyA, bytes("a"));
|
|
82
|
-
await provider.write(keyB, bytes("b"));
|
|
83
|
-
await provider.write(outsideKey, bytes("c"));
|
|
84
|
-
const listed = await provider.list(`${group}/`);
|
|
85
|
-
expect(new Set(listed)).toEqual(new Set([keyA, keyB]));
|
|
86
|
-
});
|
|
87
|
-
test("list on a prefix with no matches returns an empty array", async () => {
|
|
88
|
-
const listed = await provider.list(`contract/list-missing-${crypto.randomUUID()}/`);
|
|
89
|
-
expect(listed).toEqual([]);
|
|
90
|
-
});
|
|
91
|
-
test("getSignedUrl, when implemented, returns a URL string", async () => {
|
|
92
|
-
// skip: getSignedUrl is optional on the contract — feature-detected
|
|
93
|
-
if (!provider.getSignedUrl)
|
|
94
|
-
return;
|
|
95
|
-
const key = trackedKey("txt");
|
|
96
|
-
await provider.write(key, bytes("signed"));
|
|
97
|
-
const url = await provider.getSignedUrl(key, 60);
|
|
98
|
-
expect(typeof url).toBe("string");
|
|
99
|
-
expect(url.length).toBeGreaterThan(0);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export type ParsedSetCookie = {
|
|
2
|
-
readonly value: string;
|
|
3
|
-
readonly raw: string;
|
|
4
|
-
};
|
|
5
|
-
export declare function getSetCookies(res: Response): Map<string, ParsedSetCookie>;
|
|
6
|
-
export declare function getSetCookieValue(res: Response, name: string): string | undefined;
|
|
7
|
-
export declare function getSetCookieRaw(res: Response, name: string): string | undefined;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Shared Set-Cookie parser for tests. Cookie-auth tests (auth-middleware,
|
|
2
|
-
// csrf-middleware, auth-routes, auth.integration) all need the same
|
|
3
|
-
// primitive: "given a Response, give me the cookies the server tried to
|
|
4
|
-
// set". Before this helper each test file rolled its own — three near-
|
|
5
|
-
// duplicates that drift the moment one adds Domain/Partitioned/Priority
|
|
6
|
-
// parsing the others don't.
|
|
7
|
-
//
|
|
8
|
-
// The helper returns both the parsed value AND the raw string so callers
|
|
9
|
-
// can assert on attributes like SameSite, HttpOnly, Max-Age without a
|
|
10
|
-
// second parse step.
|
|
11
|
-
// Pull every Set-Cookie the response carries. Prefers the standard
|
|
12
|
-
// `Headers.getSetCookie()` (Node 20+, Bun, modern undici/whatwg-fetch);
|
|
13
|
-
// falls back to the single-header-value for environments that don't
|
|
14
|
-
// expose it. The fallback path only sees the FIRST cookie if multiple
|
|
15
|
-
// were set in one response — a limitation of RFC 7230 headers — which
|
|
16
|
-
// is acceptable here because tests that set multiple cookies run on a
|
|
17
|
-
// runtime that supports getSetCookie.
|
|
18
|
-
export function getSetCookies(res) {
|
|
19
|
-
const getter = res.headers.getSetCookie; // @cast-boundary engine-bridge
|
|
20
|
-
const raws = getter ? getter.call(res.headers) : [res.headers.get("set-cookie") ?? ""];
|
|
21
|
-
const out = new Map();
|
|
22
|
-
for (const raw of raws) {
|
|
23
|
-
if (!raw)
|
|
24
|
-
continue;
|
|
25
|
-
const first = raw.split(";")[0];
|
|
26
|
-
if (!first)
|
|
27
|
-
continue;
|
|
28
|
-
const eq = first.indexOf("=");
|
|
29
|
-
if (eq === -1)
|
|
30
|
-
continue;
|
|
31
|
-
const name = first.slice(0, eq).trim();
|
|
32
|
-
const value = first.slice(eq + 1).trim();
|
|
33
|
-
out.set(name, { value, raw });
|
|
34
|
-
}
|
|
35
|
-
return out;
|
|
36
|
-
}
|
|
37
|
-
// Convenience for the common case: "give me the value of cookie X".
|
|
38
|
-
// Returns undefined when the cookie isn't set — no throw, so negative
|
|
39
|
-
// assertions read cleanly (`expect(cookies.get("foo")).toBeUndefined()`).
|
|
40
|
-
export function getSetCookieValue(res, name) {
|
|
41
|
-
return getSetCookies(res).get(name)?.value;
|
|
42
|
-
}
|
|
43
|
-
// Raw Set-Cookie header for attribute assertions
|
|
44
|
-
// (`expect(raw).toMatch(/SameSite=Lax/)`). Returns undefined when missing.
|
|
45
|
-
export function getSetCookieRaw(res, name) {
|
|
46
|
-
return getSetCookies(res).get(name)?.raw;
|
|
47
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export type LateBoundHolder<T> = {
|
|
2
|
-
/** Store the concrete value. Must be called before any trampoline fires. */
|
|
3
|
-
set(value: T): void;
|
|
4
|
-
/** Fetch the concrete value or throw if set() hasn't been called yet. */
|
|
5
|
-
get(): T;
|
|
6
|
-
/** True after set() has been called. */
|
|
7
|
-
isReady(): boolean;
|
|
8
|
-
};
|
|
9
|
-
export declare function createLateBoundHolder<T>(label?: string): LateBoundHolder<T>;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Late-bound reference for values that only exist AFTER setupTestStack
|
|
2
|
-
// returns — typically the session-callbacks, which close over stack.db.
|
|
3
|
-
//
|
|
4
|
-
// Without this helper, integration tests repeat the same trampoline
|
|
5
|
-
// pattern (let real; async wrapper; null-check on each call). The holder
|
|
6
|
-
// inverts the dependency: the test passes the trampolines into setupTestStack
|
|
7
|
-
// first, then injects the concrete impl once db is available.
|
|
8
|
-
//
|
|
9
|
-
// Production wiring (runDevApp / runProdApp) doesn't reuse this — those
|
|
10
|
-
// wrappers inline an equivalent let-closure-throw pattern to avoid the
|
|
11
|
-
// runtime-isolation cross from dev → test packages. Same idea, different
|
|
12
|
-
// file.
|
|
13
|
-
export function createLateBoundHolder(label = "value") {
|
|
14
|
-
let value;
|
|
15
|
-
let isSet = false;
|
|
16
|
-
return {
|
|
17
|
-
set(v) {
|
|
18
|
-
value = v;
|
|
19
|
-
isSet = true;
|
|
20
|
-
},
|
|
21
|
-
get() {
|
|
22
|
-
if (!isSet) {
|
|
23
|
-
throw new Error(`late-bound ${label} accessed before set() was called`);
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
},
|
|
27
|
-
isReady() {
|
|
28
|
-
return isSet;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
-
import type { Clock } from "@cosmicdrift/kumiko-types/tz-context";
|
|
3
|
-
type ClockStep = {
|
|
4
|
-
readonly hours?: number;
|
|
5
|
-
readonly minutes?: number;
|
|
6
|
-
readonly seconds?: number;
|
|
7
|
-
readonly milliseconds?: number;
|
|
8
|
-
};
|
|
9
|
-
export type ManualClock = Clock & {
|
|
10
|
-
set(instant: Temporal.Instant): void;
|
|
11
|
-
advance(step: ClockStep): void;
|
|
12
|
-
};
|
|
13
|
-
export declare function createManualClock(start: Temporal.Instant): ManualClock;
|
|
14
|
-
export {};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Serializes a FormData instance to multipart/form-data bytes.
|
|
3
|
-
*
|
|
4
|
-
* Returns the encoded body and the Content-Type header value (including the
|
|
5
|
-
* generated boundary). Pass both directly to app.request or fetch.
|
|
6
|
-
*/
|
|
7
|
-
export declare function buildMultipartBody(fd: FormData): Promise<{
|
|
8
|
-
body: BodyInit;
|
|
9
|
-
contentType: string;
|
|
10
|
-
}>;
|
|
11
|
-
/**
|
|
12
|
-
* Patches File[Symbol.hasInstance] so that cross-realm Blob objects returned
|
|
13
|
-
* by Hono's parseBody() pass `instanceof File` checks in bun:test.
|
|
14
|
-
*
|
|
15
|
-
* In bun:test the multipart parser runs in a different JS realm than the test
|
|
16
|
-
* globals, so the Blob/File constructors differ. The patch replaces the
|
|
17
|
-
* prototype-chain check with a duck-type check: an object with string `.name`,
|
|
18
|
-
* number `.size`, and function `.arrayBuffer` is treated as a File.
|
|
19
|
-
*
|
|
20
|
-
* Safe to call multiple times (idempotent via the `_patched` marker).
|
|
21
|
-
*/
|
|
22
|
-
export declare function patchFileInstanceofForBunTest(): void;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
-
// Workarounds for Bun v1.3.x bun:test limitations with multipart/form-data.
|
|
3
|
-
//
|
|
4
|
-
// Two bugs combine to break file upload tests in bun:test:
|
|
5
|
-
//
|
|
6
|
-
// 1. Content-Type omission: both app.request({body: formData}) and
|
|
7
|
-
// fetch(url, {body: formData}) stringify FormData via .toString() instead
|
|
8
|
-
// of serializing it as multipart, so no Content-Type header is set.
|
|
9
|
-
// Fix: serialize FormData manually via buildMultipartBody().
|
|
10
|
-
//
|
|
11
|
-
// 2. Cross-realm instanceof: Hono's multipart parser creates Blob objects from
|
|
12
|
-
// a different JS realm than the test globals. In bun:test this means
|
|
13
|
-
// `parsedValue instanceof File` is always false even when the value has all
|
|
14
|
-
// File properties. Fix: patchFilInstanceofForBunTest().
|
|
15
|
-
//
|
|
16
|
-
// Both fixes are test-only — production code and real HTTP clients are unaffected.
|
|
17
|
-
/**
|
|
18
|
-
* Serializes a FormData instance to multipart/form-data bytes.
|
|
19
|
-
*
|
|
20
|
-
* Returns the encoded body and the Content-Type header value (including the
|
|
21
|
-
* generated boundary). Pass both directly to app.request or fetch.
|
|
22
|
-
*/
|
|
23
|
-
export async function buildMultipartBody(fd) {
|
|
24
|
-
const boundary = `KumikoBnd${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
|
|
25
|
-
const enc = new TextEncoder();
|
|
26
|
-
const parts = [];
|
|
27
|
-
for (const [name, value] of fd.entries()) {
|
|
28
|
-
const v = value;
|
|
29
|
-
if (typeof v === "object" && v !== null && typeof v.arrayBuffer === "function") {
|
|
30
|
-
parts.push(enc.encode(`--${boundary}\r\nContent-Disposition: form-data; name="${name}"; filename="${v.name ?? "blob"}"\r\nContent-Type: ${v.type || "application/octet-stream"}\r\n\r\n`));
|
|
31
|
-
parts.push(new Uint8Array(await v.arrayBuffer()));
|
|
32
|
-
parts.push(enc.encode("\r\n"));
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
parts.push(enc.encode(`--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${String(value)}\r\n`));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
parts.push(enc.encode(`--${boundary}--\r\n`));
|
|
39
|
-
const total = parts.reduce((s, p) => s + p.length, 0);
|
|
40
|
-
const buf = new Uint8Array(total);
|
|
41
|
-
let off = 0;
|
|
42
|
-
for (const p of parts) {
|
|
43
|
-
buf.set(p, off);
|
|
44
|
-
off += p.length;
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
body: buf,
|
|
48
|
-
contentType: `multipart/form-data; boundary=${boundary}`,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Patches File[Symbol.hasInstance] so that cross-realm Blob objects returned
|
|
53
|
-
* by Hono's parseBody() pass `instanceof File` checks in bun:test.
|
|
54
|
-
*
|
|
55
|
-
* In bun:test the multipart parser runs in a different JS realm than the test
|
|
56
|
-
* globals, so the Blob/File constructors differ. The patch replaces the
|
|
57
|
-
* prototype-chain check with a duck-type check: an object with string `.name`,
|
|
58
|
-
* number `.size`, and function `.arrayBuffer` is treated as a File.
|
|
59
|
-
*
|
|
60
|
-
* Safe to call multiple times (idempotent via the `_patched` marker).
|
|
61
|
-
*/
|
|
62
|
-
export function patchFileInstanceofForBunTest() {
|
|
63
|
-
// skip: idempotent re-patch — already installed, nothing to do
|
|
64
|
-
if (File._kumikoPatched)
|
|
65
|
-
return;
|
|
66
|
-
Object.defineProperty(File, Symbol.hasInstance, {
|
|
67
|
-
value(instance) {
|
|
68
|
-
if (!isPlainObject(instance))
|
|
69
|
-
return false;
|
|
70
|
-
return (typeof instance["name"] === "string" &&
|
|
71
|
-
typeof instance["size"] === "number" &&
|
|
72
|
-
typeof instance["arrayBuffer"] === "function");
|
|
73
|
-
},
|
|
74
|
-
configurable: true,
|
|
75
|
-
});
|
|
76
|
-
File._kumikoPatched = true;
|
|
77
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type EnvelopeCipher, type EnvelopeCipherOptions, type MasterKeyProvider } from "../secrets/index.js";
|
|
2
|
-
export type MutableMasterKeyProvider = MasterKeyProvider & {
|
|
3
|
-
replace(next: MasterKeyProvider): void;
|
|
4
|
-
};
|
|
5
|
-
export declare function createMutableMasterKeyProvider(initial: MasterKeyProvider): MutableMasterKeyProvider;
|
|
6
|
-
export declare function createTestMasterKeyProvider(keyBase64?: string): MasterKeyProvider;
|
|
7
|
-
export declare function createTestEnvelopeCipher(keyBase64?: string, opts?: EnvelopeCipherOptions): EnvelopeCipher;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// Test utility: a MasterKeyProvider wrapper whose backing provider can be
|
|
2
|
-
// swapped mid-test. Use-case: simulating KEK-rotation ("ops flipped
|
|
3
|
-
// CURRENT=2") without rebuilding the whole stack + SecretsContext.
|
|
4
|
-
//
|
|
5
|
-
// Prod analogue: ENV swap + process restart. This helper is purely for
|
|
6
|
-
// tests that want to exercise pre- and post-rotation behaviour in a
|
|
7
|
-
// single suite.
|
|
8
|
-
import { randomBytes } from "node:crypto";
|
|
9
|
-
import { createEnvelopeCipher, createEnvMasterKeyProvider, } from "../secrets/index.js";
|
|
10
|
-
export function createMutableMasterKeyProvider(initial) {
|
|
11
|
-
let current = initial;
|
|
12
|
-
return {
|
|
13
|
-
wrapDek: (dek, scope) => current.wrapDek(dek, scope),
|
|
14
|
-
unwrapDek: (e, v, scope) => current.unwrapDek(e, v, scope),
|
|
15
|
-
currentVersion: () => current.currentVersion(),
|
|
16
|
-
isAvailable: () => current.isAvailable(),
|
|
17
|
-
replace: (next) => {
|
|
18
|
-
current = next;
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
// Single-version env provider for tests — the shape every integration test
|
|
23
|
-
// needs to exercise encrypted config keys / entity fields without caring
|
|
24
|
-
// about keyring mechanics. Pass a fixed key to share it across stacks.
|
|
25
|
-
export function createTestMasterKeyProvider(keyBase64) {
|
|
26
|
-
return createEnvMasterKeyProvider({
|
|
27
|
-
env: {
|
|
28
|
-
KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "1",
|
|
29
|
-
KUMIKO_SECRETS_MASTER_KEY_V1: keyBase64 ?? randomBytes(32).toString("base64"),
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export function createTestEnvelopeCipher(keyBase64, opts) {
|
|
34
|
-
return createEnvelopeCipher(createTestMasterKeyProvider(keyBase64), opts ?? {});
|
|
35
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type MetricEvent, type ObservabilityOptions, type ObservabilityProvider, type RecordedSpan } from "../observability/index.js";
|
|
2
|
-
export type RecordingProvider = ObservabilityProvider & {
|
|
3
|
-
readonly spans: readonly RecordedSpan[];
|
|
4
|
-
readonly metricEvents: readonly MetricEvent[];
|
|
5
|
-
spansByName(name: string): readonly RecordedSpan[];
|
|
6
|
-
spansByTraceId(traceId: string): readonly RecordedSpan[];
|
|
7
|
-
reset(): void;
|
|
8
|
-
};
|
|
9
|
-
export declare function createRecordingProvider(options?: ObservabilityOptions): RecordingProvider;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_SENSITIVE_CONFIG, mergeSensitiveConfig, RecordingMeter, RecordingTracer, } from "../observability/index.js";
|
|
2
|
-
export function createRecordingProvider(options = {}) {
|
|
3
|
-
const sensitiveConfig = mergeSensitiveConfig(options.sensitiveFilter ?? DEFAULT_SENSITIVE_CONFIG);
|
|
4
|
-
const spans = [];
|
|
5
|
-
const metricEvents = [];
|
|
6
|
-
const tracer = new RecordingTracer({
|
|
7
|
-
sensitiveConfig,
|
|
8
|
-
onSpanEnd: (s) => spans.push(s),
|
|
9
|
-
});
|
|
10
|
-
const meter = new RecordingMeter((e) => metricEvents.push(e));
|
|
11
|
-
return {
|
|
12
|
-
name: "recording",
|
|
13
|
-
tracer,
|
|
14
|
-
meter,
|
|
15
|
-
spans,
|
|
16
|
-
metricEvents,
|
|
17
|
-
spansByName(name) {
|
|
18
|
-
return spans.filter((s) => s.name === name);
|
|
19
|
-
},
|
|
20
|
-
spansByTraceId(traceId) {
|
|
21
|
-
return spans.filter((s) => s.traceId === traceId);
|
|
22
|
-
},
|
|
23
|
-
reset() {
|
|
24
|
-
spans.length = 0;
|
|
25
|
-
metricEvents.length = 0;
|
|
26
|
-
},
|
|
27
|
-
async shutdown() { },
|
|
28
|
-
};
|
|
29
|
-
}
|
package/dist/testing/seed.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type AnyDb, type WhereObject } from "../bun-db/query.js";
|
|
2
|
-
import type { EntityTableMeta } from "../db/entity-table-meta.js";
|
|
3
|
-
import type { EntityTable } from "../db/table-builder.js";
|
|
4
|
-
type SeedTable = EntityTable | EntityTableMeta;
|
|
5
|
-
export declare function seedRow<TRow = unknown>(db: AnyDb, table: SeedTable, values: Record<string, unknown>): Promise<TRow | undefined>;
|
|
6
|
-
export declare function seedRows<TRow = unknown>(db: AnyDb, table: SeedTable, rows: ReadonlyArray<Record<string, unknown>>): Promise<readonly TRow[]>;
|
|
7
|
-
export declare function updateRows<TRow = unknown>(db: AnyDb, table: SeedTable, set: Record<string, unknown>, where: WhereObject): Promise<readonly TRow[]>;
|
|
8
|
-
export declare function deleteRows(db: AnyDb, table: SeedTable, where: WhereObject): Promise<void>;
|
|
9
|
-
export {};
|
package/dist/testing/seed.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Test-only ES-bypass: seed / mutate a managed projection directly, skipping
|
|
2
|
-
// the event log. Production code CANNOT do this — the write helpers reject a
|
|
3
|
-
// branded EntityTable at compile time (#742). Tests seed read-model state to
|
|
4
|
-
// arrange a scenario without driving the full event path; a projection rebuild
|
|
5
|
-
// is not part of the test lifecycle, so eventless writes are safe here. The
|
|
6
|
-
// `as EntityTableMeta` strips the executor-only brand at this single sanctioned
|
|
7
|
-
// seam — the signatures mirror the query helpers so a call-site migration is a
|
|
8
|
-
// plain identifier rename.
|
|
9
|
-
import { deleteMany, insertMany, insertOne, updateMany, } from "../bun-db/query.js";
|
|
10
|
-
export function seedRow(db, table, values) {
|
|
11
|
-
return insertOne(db, table, values);
|
|
12
|
-
}
|
|
13
|
-
export function seedRows(db, table, rows) {
|
|
14
|
-
return insertMany(db, table, rows);
|
|
15
|
-
}
|
|
16
|
-
export function updateRows(db, table, set, where) {
|
|
17
|
-
return updateMany(db, table, set, where);
|
|
18
|
-
}
|
|
19
|
-
export function deleteRows(db, table, where) {
|
|
20
|
-
return deleteMany(db, table, where);
|
|
21
|
-
}
|