@cosmicdrift/kumiko-framework 0.388.0 → 0.389.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 +3 -0
- package/agents/examples/golden-path.md +13 -1
- package/agents/examples/lifecycle-hooks.md +55 -2
- package/agents/examples/list-table-options.md +466 -0
- package/agents/examples/pdf-audit-export.md +4 -2
- package/agents/examples/state-machine.md +3 -2
- package/agents/examples/tenant-isolation.md +73 -1
- package/agents/examples.md +1 -0
- package/agents/widgets.md +1 -0
- package/dist/api/api-constants.d.ts +1 -0
- package/dist/api/api-constants.js +2 -0
- package/dist/api/auth-routes-shared.d.ts +2 -0
- package/dist/api/auth-routes-shared.js +5 -0
- package/dist/api/client-error-route.js +2 -3
- package/dist/api/entity-read-access.d.ts +2 -1
- package/dist/api/entity-read-access.js +14 -0
- package/dist/api/export/export-cells-reference.d.ts +9 -0
- package/dist/api/export/export-cells-reference.js +21 -0
- package/dist/api/export/export-cells.d.ts +11 -0
- package/dist/api/export/export-cells.js +136 -0
- package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
- package/dist/api/export/export-ciphertext-guard.js +27 -0
- package/dist/api/export/export-columns.d.ts +29 -0
- package/dist/api/export/export-columns.js +85 -0
- package/dist/api/export/export-errors.d.ts +4 -0
- package/dist/api/export/export-errors.js +14 -0
- package/dist/api/export/export-route.d.ts +4 -0
- package/dist/api/export/export-route.js +74 -0
- package/dist/api/export/export-rows.d.ts +13 -0
- package/dist/api/export/export-rows.js +62 -0
- package/dist/api/export/export-screen.d.ts +12 -0
- package/dist/api/export/export-screen.js +47 -0
- package/dist/api/export/export-xlsx-writer.d.ts +4 -0
- package/dist/api/export/export-xlsx-writer.js +21 -0
- package/dist/api/extra-route.d.ts +4 -0
- package/dist/api/pii-leak-guard.d.ts +3 -0
- package/dist/api/pii-leak-guard.js +3 -2
- package/dist/api/routes.d.ts +16 -0
- package/dist/api/routes.js +2 -2
- package/dist/api/server-extra-routes.js +3 -0
- package/dist/api/server-routes.d.ts +1 -1
- package/dist/api/server-routes.js +8 -2
- package/dist/api/server-wiring.js +2 -0
- package/dist/api/sse-route.d.ts +2 -1
- package/dist/api/sse-route.js +11 -1
- package/dist/config/index.d.ts +3 -1
- package/dist/crypto/pg-kms-adapter.d.ts +1 -0
- package/dist/crypto/pg-kms-adapter.js +12 -18
- package/dist/db/aggregate-sql.d.ts +2 -0
- package/dist/db/aggregate-sql.js +10 -0
- package/dist/db/closed-connection.js +12 -19
- package/dist/db/connection.d.ts +4 -5
- package/dist/db/connection.js +29 -20
- package/dist/db/driver-row-coercion.js +4 -4
- package/dist/db/driver-value-preparation.js +6 -4
- package/dist/db/event-store-executor-read-aggregate-currencies.d.ts +17 -0
- package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
- package/dist/db/event-store-executor-read-aggregate.js +36 -5
- package/dist/db/event-store-executor-read-filter.js +6 -3
- package/dist/db/event-store-executor-write-atomic.d.ts +4 -0
- package/dist/db/event-store-executor-write-atomic.js +41 -0
- package/dist/db/event-store-executor-write-steps.d.ts +0 -3
- package/dist/db/event-store-executor-write-steps.js +0 -5
- package/dist/db/event-store-executor-write.js +15 -15
- package/dist/db/event-store-executor.js +2 -1
- package/dist/db/executor-managed-tables.js +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/migrate-runner.js +3 -3
- package/dist/db/pg-error.js +26 -21
- package/dist/db/queries/backfill-pii.js +3 -6
- package/dist/db/queries/database-now.js +2 -1
- package/dist/db/queries/shadow-swap.js +2 -3
- package/dist/db/query.d.ts +14 -0
- package/dist/db/query.js +68 -43
- package/dist/db/reference-data.js +14 -2
- package/dist/db/statement-timeout.js +2 -1
- package/dist/db/tenant-db-upsert.d.ts +5 -0
- package/dist/db/tenant-db-upsert.js +91 -0
- package/dist/db/tenant-db.js +11 -1
- package/dist/engine/boot-validator/bundled-lists-table.d.ts +2 -0
- package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
- package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +3 -0
- package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
- package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
- package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
- package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
- package/dist/engine/boot-validator/list-column-layout.js +41 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +10 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
- package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-export-options.js +27 -0
- package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
- package/dist/engine/boot-validator/list-table-options.js +77 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
- package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
- package/dist/engine/boot-validator/projection-list-screens.js +3 -13
- package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
- package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
- package/dist/engine/boot-validator/screens-kind-entity-list.js +7 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +4 -0
- package/dist/engine/boot-validator/screens-lists.js +12 -2
- package/dist/engine/build-app-schema.d.ts +2 -1
- package/dist/engine/build-app-schema.js +9 -1
- package/dist/engine/build-config-feature-schema.js +6 -1
- package/dist/engine/define-feature.js +4 -0
- package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
- package/dist/engine/feature-ast/extractors/index.js +1 -1
- package/dist/engine/feature-ast/extractors/round1.d.ts +2 -1
- package/dist/engine/feature-ast/extractors/round1.js +6 -0
- package/dist/engine/feature-ast/index.d.ts +1 -1
- package/dist/engine/feature-ast/parse.js +3 -1
- package/dist/engine/feature-ast/patch.d.ts +2 -0
- package/dist/engine/feature-ast/patch.js +1 -0
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +7 -245
- package/dist/engine/feature-ast/pattern-change-schema.js +9 -163
- package/dist/engine/feature-ast/pattern-id-matchers.js +1 -0
- package/dist/engine/feature-ast/pattern-id-schema.d.ts +251 -0
- package/dist/engine/feature-ast/pattern-id-schema.js +166 -0
- package/dist/engine/feature-ast/patterns.d.ts +5 -1
- package/dist/engine/feature-ast/patterns.js +1 -0
- package/dist/engine/feature-ast/render.js +4 -0
- package/dist/engine/feature-builder-state.d.ts +1 -0
- package/dist/engine/feature-builder-state.js +1 -0
- package/dist/engine/feature-manifest.d.ts +1 -0
- package/dist/engine/feature-manifest.js +1 -0
- package/dist/engine/field-access.d.ts +1 -0
- package/dist/engine/field-access.js +12 -0
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/engine/pattern-library/library.js +2 -1
- package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
- package/dist/engine/pattern-library/static-schemas.js +9 -0
- package/dist/engine/screen-filter-ops.js +2 -0
- package/dist/engine/strip-disabled-features-from-app-schema.js +7 -3
- package/dist/engine/types/feature.d.ts +1 -0
- package/dist/engine/types/index.d.ts +4 -3
- package/dist/engine/types/screen.d.ts +1 -1
- package/dist/engine/types/screen.js +1 -1
- package/dist/entrypoint/index.d.ts +2 -0
- package/dist/entrypoint/index.js +3 -0
- package/dist/event-store/event-store.d.ts +1 -0
- package/dist/event-store/event-store.js +7 -4
- package/dist/event-store/provenance-append.js +1 -1
- package/dist/files/create-file-ref-from-bytes.js +3 -0
- package/dist/files/create-file-ref.d.ts +3 -0
- package/dist/files/create-file-ref.js +11 -2
- package/dist/files/file-routes.d.ts +2 -0
- package/dist/files/file-routes.js +29 -2
- package/dist/files/provider-resolver.d.ts +1 -0
- package/dist/files/provider-resolver.js +1 -1
- package/dist/files/record-write-check.d.ts +6 -0
- package/dist/files/record-write-check.js +13 -0
- package/dist/files/upload-ledger.d.ts +2 -2
- package/dist/files/upload-ledger.js +4 -4
- package/dist/i18n/required-surface-keys.js +7 -2
- package/dist/internal/testing.js +1 -1
- package/dist/jobs/job-runner-scope.js +6 -6
- package/dist/jobs/job-runner.js +3 -0
- package/dist/observability/escape-hatch-report.js +9 -2
- package/dist/pipeline/after-rollback-sink.d.ts +6 -0
- package/dist/pipeline/after-rollback-sink.js +32 -0
- package/dist/pipeline/dispatch-batch.js +8 -1
- package/dist/pipeline/dispatch-shared.js +4 -0
- package/dist/pipeline/event-dispatcher-delivery.js +3 -1
- package/dist/pipeline/event-dispatcher.d.ts +1 -0
- package/dist/pipeline/event-dispatcher.js +6 -9
- package/dist/pipeline/handler-context-dispatch.d.ts +2 -0
- package/dist/pipeline/handler-context-dispatch.js +13 -1
- package/dist/pipeline/handler-context-events.d.ts +2 -4
- package/dist/pipeline/handler-context-events.js +6 -4
- package/dist/pipeline/system-hooks.d.ts +3 -1
- package/dist/pipeline/system-hooks.js +55 -6
- package/dist/pipeline/write-result-masking.js +13 -10
- package/dist/schema-cli/baseline-command.js +1 -1
- package/dist/schema-cli/status-command.js +1 -1
- package/dist/search/reindex-entity.js +15 -5
- package/dist/ui-types/index.d.ts +2 -2
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-changelog.d.ts +2 -0
- package/dist/upgrade-changelog.js +14 -0
- package/dist/upgrade-cli.d.ts +3 -12
- package/dist/upgrade-cli.js +22 -76
- package/dist/upgrade-marker-parse.d.ts +32 -0
- package/dist/upgrade-marker-parse.js +73 -0
- package/package.json +9 -6
- package/src/changes.json +245 -0
- package/src/scripts/codemod/codemod-support.ts +1 -1
- package/src/scripts/codemod/db-factory.ts +1 -1
- package/dist/engine/provider-plugin-context.d.ts +0 -3
- package/dist/engine/provider-plugin-context.js +0 -28
|
@@ -46,3 +46,17 @@ export function mergeUpgradeSection(existing, section) {
|
|
|
46
46
|
const others = sections.filter((candidate) => !candidate.startsWith(prefix));
|
|
47
47
|
return [header, renderUpgradeSection(section), ...others].join("\n");
|
|
48
48
|
}
|
|
49
|
+
/** Pure string match on the rendered `- [open] <id> · ` prefix, in every section. */
|
|
50
|
+
export function setManualStatusInChangelog(content, ids, resolution) {
|
|
51
|
+
return content
|
|
52
|
+
.split("\n")
|
|
53
|
+
.map((line) => {
|
|
54
|
+
for (const id of ids) {
|
|
55
|
+
const open = `- [open] ${id} · `;
|
|
56
|
+
if (line.startsWith(open))
|
|
57
|
+
return `- [${resolution}] ${id} · ${line.slice(open.length)}`;
|
|
58
|
+
}
|
|
59
|
+
return line;
|
|
60
|
+
})
|
|
61
|
+
.join("\n");
|
|
62
|
+
}
|
package/dist/upgrade-cli.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { type ManualResolution, manualEntryId, type UpgradeMarkerManual, type UpgradeMarkerResolvedManual } from "./upgrade-marker-parse.ts";
|
|
2
|
+
export type { ManualResolution, UpgradeMarkerManual, UpgradeMarkerResolvedManual };
|
|
3
|
+
export { manualEntryId };
|
|
1
4
|
export type UpgradeCliOut = {
|
|
2
5
|
readonly log: (line: string) => void;
|
|
3
6
|
readonly err: (line: string) => void;
|
|
@@ -7,18 +10,6 @@ export declare function findPackageChangelogFiles(cwd: string): string[];
|
|
|
7
10
|
export declare function findFeaturesDirs(cwd: string): string[];
|
|
8
11
|
export declare function findCodemodScriptsRoot(repoRoot: string): string | null;
|
|
9
12
|
export declare function resolveCodemodScript(repoRoot: string, codemodField: string | undefined): string | null;
|
|
10
|
-
export type UpgradeMarkerManual = {
|
|
11
|
-
readonly id: string;
|
|
12
|
-
readonly version: string;
|
|
13
|
-
readonly title: string;
|
|
14
|
-
};
|
|
15
|
-
export type ManualResolution = "migrated" | "not-applicable";
|
|
16
|
-
export type UpgradeMarkerResolvedManual = UpgradeMarkerManual & {
|
|
17
|
-
readonly resolution: ManualResolution;
|
|
18
|
-
readonly reason: string;
|
|
19
|
-
readonly resolvedAt: string;
|
|
20
|
-
};
|
|
21
|
-
export declare function manualEntryId(version: string, title: string): string;
|
|
22
13
|
export declare function runUpgradeCli(argv: readonly string[], cwd: string, out: UpgradeCliOut, options?: {
|
|
23
14
|
readonly repoRoot?: string;
|
|
24
15
|
}): Promise<number>;
|
package/dist/upgrade-cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
3
2
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
3
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
5
4
|
import { getFlag, getStringFlag, parseArgs } from "./arg-parser.js";
|
|
6
5
|
import { compareVersions, filterEntriesAfter, parseFeatureChangelog, sortEntries, } from "./engine/index.js";
|
|
7
6
|
import { ensureTemporalPolyfill } from "./time/index.js";
|
|
8
|
-
import { mergeUpgradeSection } from "./upgrade-changelog.js";
|
|
9
|
-
|
|
7
|
+
import { mergeUpgradeSection, setManualStatusInChangelog, } from "./upgrade-changelog.js";
|
|
8
|
+
import { dedupeManualById, manualEntryId, parseStoredMarker, SEMVER_RE, } from "./upgrade-marker-parse.js";
|
|
9
|
+
export { manualEntryId };
|
|
10
10
|
const SEMVER_CORE_RE = /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?$/;
|
|
11
11
|
const CODEMOD_SUBDIR = "scripts/codemod";
|
|
12
12
|
function readPackageJsonVersion(packageJsonPath) {
|
|
@@ -304,13 +304,7 @@ function hasCodemod(e) {
|
|
|
304
304
|
function needsManualMigration(e) {
|
|
305
305
|
return !hasCodemod(e) || e.manualAfterCodemod === true;
|
|
306
306
|
}
|
|
307
|
-
const MANUAL_ID_HASH_LENGTH = 8;
|
|
308
307
|
const MAX_RESOLVE_REASON_LENGTH = 500;
|
|
309
|
-
// The hash tells apart entries that share a version (studio has several at 0.341.0).
|
|
310
|
-
export function manualEntryId(version, title) {
|
|
311
|
-
const hash = createHash("sha256").update(title).digest("hex").slice(0, MANUAL_ID_HASH_LENGTH);
|
|
312
|
-
return `${version}:${hash}`;
|
|
313
|
-
}
|
|
314
308
|
// Breaking entries that need hand work carry the id `kumiko-upgrade --resolve` takes.
|
|
315
309
|
function manualIdOf(entry) {
|
|
316
310
|
return entry.type === "breaking" && needsManualMigration(entry)
|
|
@@ -324,15 +318,6 @@ function toMarkerManual(entry) {
|
|
|
324
318
|
title: entry.title,
|
|
325
319
|
};
|
|
326
320
|
}
|
|
327
|
-
function dedupeManualById(entries) {
|
|
328
|
-
const seen = new Set();
|
|
329
|
-
return entries.filter((entry) => {
|
|
330
|
-
if (seen.has(entry.id))
|
|
331
|
-
return false;
|
|
332
|
-
seen.add(entry.id);
|
|
333
|
-
return true;
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
321
|
// Open manual items survive every marker write until --resolve moves them.
|
|
337
322
|
function carryOverManual(previous, newManual) {
|
|
338
323
|
const resolvedIds = new Set(previous.resolvedManual.map((entry) => entry.id));
|
|
@@ -360,64 +345,6 @@ function writeUpgradeMarker(targetDir, marker) {
|
|
|
360
345
|
writeFileSync(join(dir, "upgrade-state.json"), `${JSON.stringify(marker, null, 2)}\n`, "utf-8");
|
|
361
346
|
}
|
|
362
347
|
const NO_CARRY = { codemods: [], pendingManual: [], resolvedManual: [] };
|
|
363
|
-
function isRecord(value) {
|
|
364
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
365
|
-
}
|
|
366
|
-
function parseManualEntry(value) {
|
|
367
|
-
if (!isRecord(value))
|
|
368
|
-
return null;
|
|
369
|
-
const { id, version, title } = value;
|
|
370
|
-
if (typeof version !== "string" || typeof title !== "string")
|
|
371
|
-
return null;
|
|
372
|
-
return {
|
|
373
|
-
id: typeof id === "string" && id.length > 0 ? id : manualEntryId(version, title),
|
|
374
|
-
version,
|
|
375
|
-
title,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
function parseResolvedEntry(value) {
|
|
379
|
-
const base = parseManualEntry(value);
|
|
380
|
-
if (base === null || !isRecord(value))
|
|
381
|
-
return null;
|
|
382
|
-
const { resolution, reason, resolvedAt } = value;
|
|
383
|
-
if (resolution !== "migrated" && resolution !== "not-applicable")
|
|
384
|
-
return null;
|
|
385
|
-
if (typeof reason !== "string" || typeof resolvedAt !== "string")
|
|
386
|
-
return null;
|
|
387
|
-
return { ...base, resolution, reason, resolvedAt };
|
|
388
|
-
}
|
|
389
|
-
function parseList(value, parseEntry) {
|
|
390
|
-
if (value === undefined)
|
|
391
|
-
return [];
|
|
392
|
-
if (!Array.isArray(value))
|
|
393
|
-
return null;
|
|
394
|
-
const parsed = value.map(parseEntry);
|
|
395
|
-
return parsed.every((entry) => entry !== null) ? parsed : null;
|
|
396
|
-
}
|
|
397
|
-
function isMarkerCodemod(value) {
|
|
398
|
-
return (isRecord(value) &&
|
|
399
|
-
typeof value["version"] === "string" &&
|
|
400
|
-
typeof value["codemod"] === "string" &&
|
|
401
|
-
typeof value["title"] === "string");
|
|
402
|
-
}
|
|
403
|
-
function parseStoredMarker(raw) {
|
|
404
|
-
if (!isRecord(raw))
|
|
405
|
-
return null;
|
|
406
|
-
const { version, appliedAt, codemods } = raw;
|
|
407
|
-
if (typeof version !== "string" || !SEMVER_RE.test(version))
|
|
408
|
-
return null;
|
|
409
|
-
const pendingManual = parseList(raw["pendingManual"], parseManualEntry);
|
|
410
|
-
const resolvedManual = parseList(raw["resolvedManual"], parseResolvedEntry);
|
|
411
|
-
if (pendingManual === null || resolvedManual === null)
|
|
412
|
-
return null;
|
|
413
|
-
return {
|
|
414
|
-
version,
|
|
415
|
-
appliedAt: typeof appliedAt === "string" ? appliedAt : Temporal.Now.instant().toString(),
|
|
416
|
-
codemods: Array.isArray(codemods) ? codemods.filter(isMarkerCodemod) : [],
|
|
417
|
-
pendingManual: dedupeManualById(pendingManual),
|
|
418
|
-
resolvedManual: dedupeManualById(resolvedManual),
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
348
|
// A missing marker is a bootstrap app that never ran `--apply`; an
|
|
422
349
|
// existing but unreadable one must not silently fall back to the installed
|
|
423
350
|
// version, or a bare `--apply` would skip every codemod in between.
|
|
@@ -697,6 +624,11 @@ function resolveManualMigrations(out, args, targetDir) {
|
|
|
697
624
|
...(remaining.length > 0 && { pendingManual: remaining }),
|
|
698
625
|
resolvedManual,
|
|
699
626
|
});
|
|
627
|
+
const changelogPath = join(targetDir, "CHANGELOG.md");
|
|
628
|
+
if (existsSync(changelogPath)) {
|
|
629
|
+
const content = readFileSync(changelogPath, "utf-8");
|
|
630
|
+
writeFileSync(changelogPath, setManualStatusInChangelog(content, resolvedIds, resolution), "utf-8");
|
|
631
|
+
}
|
|
700
632
|
out.log("");
|
|
701
633
|
out.log(` ✓ Marked ${newlyResolved.length} manual migration(s) as ${resolution}:`);
|
|
702
634
|
for (const entry of newlyResolved) {
|
|
@@ -862,6 +794,18 @@ function logNonBreakingSection(out, heading, entries, verbose, showCodemod) {
|
|
|
862
794
|
}
|
|
863
795
|
out.log("");
|
|
864
796
|
}
|
|
797
|
+
function logApplyHint(out, plan) {
|
|
798
|
+
const { markerRead, targetInstalledVersion } = plan;
|
|
799
|
+
const markerBehindInstalled = markerRead.kind === "ok" &&
|
|
800
|
+
targetInstalledVersion !== null &&
|
|
801
|
+
compareVersions(markerRead.version, targetInstalledVersion) < 0;
|
|
802
|
+
// skip: nothing differs between marker and installed version, so there is nothing to record.
|
|
803
|
+
if (plan.pending.length === 0 && !markerBehindInstalled)
|
|
804
|
+
return;
|
|
805
|
+
out.log(" Nothing is recorded yet: the upgrade CHANGELOG and the marker are only");
|
|
806
|
+
out.log(" written by --apply (add --dry-run to preview without writing).");
|
|
807
|
+
out.log("");
|
|
808
|
+
}
|
|
865
809
|
function reportUpgradeText(out, plan, verbose) {
|
|
866
810
|
const { pending } = plan;
|
|
867
811
|
const breaking = pending.filter((e) => e.type === "breaking");
|
|
@@ -873,6 +817,7 @@ function reportUpgradeText(out, plan, verbose) {
|
|
|
873
817
|
if (pending.length === 0) {
|
|
874
818
|
out.log(" ✓ Nothing new since your version.");
|
|
875
819
|
out.log("");
|
|
820
|
+
logApplyHint(out, plan);
|
|
876
821
|
logOpenManual(out, openManual, manualDetails);
|
|
877
822
|
return 0;
|
|
878
823
|
}
|
|
@@ -884,6 +829,7 @@ function reportUpgradeText(out, plan, verbose) {
|
|
|
884
829
|
out.log(" Run with --verbose for full migration details.");
|
|
885
830
|
out.log("");
|
|
886
831
|
}
|
|
832
|
+
logApplyHint(out, plan);
|
|
887
833
|
logOpenManual(out, openManual, manualDetails);
|
|
888
834
|
return 0;
|
|
889
835
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const SEMVER_RE: RegExp;
|
|
2
|
+
export type UpgradeMarkerCodemod = {
|
|
3
|
+
readonly version: string;
|
|
4
|
+
readonly codemod: string;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
};
|
|
7
|
+
export type UpgradeMarkerManual = {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly version: string;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
};
|
|
12
|
+
export type ManualResolution = "migrated" | "not-applicable";
|
|
13
|
+
export type UpgradeMarkerResolvedManual = UpgradeMarkerManual & {
|
|
14
|
+
readonly resolution: ManualResolution;
|
|
15
|
+
readonly reason: string;
|
|
16
|
+
readonly resolvedAt: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function manualEntryId(version: string, title: string): string;
|
|
19
|
+
export declare function dedupeManualById<T extends {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
}>(entries: readonly T[]): readonly T[];
|
|
22
|
+
export type MarkerCarry = {
|
|
23
|
+
readonly codemods: readonly UpgradeMarkerCodemod[];
|
|
24
|
+
readonly pendingManual: readonly UpgradeMarkerManual[];
|
|
25
|
+
readonly resolvedManual: readonly UpgradeMarkerResolvedManual[];
|
|
26
|
+
};
|
|
27
|
+
export type StoredMarker = MarkerCarry & {
|
|
28
|
+
readonly version: string;
|
|
29
|
+
readonly appliedAt: string;
|
|
30
|
+
readonly codemods: readonly UpgradeMarkerCodemod[];
|
|
31
|
+
};
|
|
32
|
+
export declare function parseStoredMarker(raw: unknown): StoredMarker | null;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
|
+
export const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
4
|
+
const MANUAL_ID_HASH_LENGTH = 8;
|
|
5
|
+
// The hash tells apart entries that share a version (studio has several at 0.341.0).
|
|
6
|
+
export function manualEntryId(version, title) {
|
|
7
|
+
const hash = createHash("sha256").update(title).digest("hex").slice(0, MANUAL_ID_HASH_LENGTH);
|
|
8
|
+
return `${version}:${hash}`;
|
|
9
|
+
}
|
|
10
|
+
export function dedupeManualById(entries) {
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
return entries.filter((entry) => {
|
|
13
|
+
if (seen.has(entry.id))
|
|
14
|
+
return false;
|
|
15
|
+
seen.add(entry.id);
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function parseManualEntry(value) {
|
|
20
|
+
if (!isPlainObject(value))
|
|
21
|
+
return null;
|
|
22
|
+
const { id, version, title } = value;
|
|
23
|
+
if (typeof version !== "string" || typeof title !== "string")
|
|
24
|
+
return null;
|
|
25
|
+
return {
|
|
26
|
+
id: typeof id === "string" && id.length > 0 ? id : manualEntryId(version, title),
|
|
27
|
+
version,
|
|
28
|
+
title,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function parseResolvedEntry(value) {
|
|
32
|
+
const base = parseManualEntry(value);
|
|
33
|
+
if (base === null || !isPlainObject(value))
|
|
34
|
+
return null;
|
|
35
|
+
const { resolution, reason, resolvedAt } = value;
|
|
36
|
+
if (resolution !== "migrated" && resolution !== "not-applicable")
|
|
37
|
+
return null;
|
|
38
|
+
if (typeof reason !== "string" || typeof resolvedAt !== "string")
|
|
39
|
+
return null;
|
|
40
|
+
return { ...base, resolution, reason, resolvedAt };
|
|
41
|
+
}
|
|
42
|
+
function parseList(value, parseEntry) {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return [];
|
|
45
|
+
if (!Array.isArray(value))
|
|
46
|
+
return null;
|
|
47
|
+
const parsed = value.map(parseEntry);
|
|
48
|
+
return parsed.every((entry) => entry !== null) ? parsed : null;
|
|
49
|
+
}
|
|
50
|
+
function isMarkerCodemod(value) {
|
|
51
|
+
return (isPlainObject(value) &&
|
|
52
|
+
typeof value["version"] === "string" &&
|
|
53
|
+
typeof value["codemod"] === "string" &&
|
|
54
|
+
typeof value["title"] === "string");
|
|
55
|
+
}
|
|
56
|
+
export function parseStoredMarker(raw) {
|
|
57
|
+
if (!isPlainObject(raw))
|
|
58
|
+
return null;
|
|
59
|
+
const { version, appliedAt, codemods } = raw;
|
|
60
|
+
if (typeof version !== "string" || !SEMVER_RE.test(version))
|
|
61
|
+
return null;
|
|
62
|
+
const pendingManual = parseList(raw["pendingManual"], parseManualEntry);
|
|
63
|
+
const resolvedManual = parseList(raw["resolvedManual"], parseResolvedEntry);
|
|
64
|
+
if (pendingManual === null || resolvedManual === null)
|
|
65
|
+
return null;
|
|
66
|
+
return {
|
|
67
|
+
version,
|
|
68
|
+
appliedAt: typeof appliedAt === "string" ? appliedAt : Temporal.Now.instant().toString(),
|
|
69
|
+
codemods: Array.isArray(codemods) ? codemods.filter(isMarkerCodemod) : [],
|
|
70
|
+
pendingManual: dedupeManualById(pendingManual),
|
|
71
|
+
resolvedManual: dedupeManualById(resolvedManual),
|
|
72
|
+
};
|
|
73
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.389.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -199,24 +199,27 @@
|
|
|
199
199
|
"./package.json": "./package.json"
|
|
200
200
|
},
|
|
201
201
|
"dependencies": {
|
|
202
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
202
|
+
"@cosmicdrift/kumiko-types": "0.389.0",
|
|
203
203
|
"bullmq": "^5.76.7",
|
|
204
|
-
"bun-types": "^1.3.13",
|
|
205
204
|
"hono": "^4.13.1",
|
|
206
205
|
"i18next": "^26.1.0",
|
|
207
206
|
"ioredis": "^5.10.1",
|
|
208
207
|
"jose": "^6.2.3",
|
|
209
208
|
"meilisearch": "^0.58.0",
|
|
210
209
|
"pino": "^10.3.1",
|
|
211
|
-
"postgres": "npm:@bender0oo0/postgres@3.4.9-kumiko.2",
|
|
212
210
|
"temporal-polyfill": "^0.3.2",
|
|
213
211
|
"ts-morph": "^28.0.0",
|
|
214
212
|
"uuid": "^14.0.0",
|
|
213
|
+
"write-excel-file": "^4.1.1",
|
|
215
214
|
"zod": "^4.4.3"
|
|
216
215
|
},
|
|
216
|
+
"peerDependencies": {
|
|
217
|
+
"bun-types": "^1.4.4"
|
|
218
|
+
},
|
|
217
219
|
"devDependencies": {
|
|
218
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
219
|
-
"bun-types": "^1.
|
|
220
|
+
"@cosmicdrift/kumiko-testing": "0.389.0",
|
|
221
|
+
"bun-types": "^1.4.4",
|
|
222
|
+
"fflate": "^0.8.2",
|
|
220
223
|
"pino-pretty": "^13.1.3"
|
|
221
224
|
},
|
|
222
225
|
"publishConfig": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,249 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.389.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "Bun.SQL replaces postgres-js as the only Postgres driver",
|
|
6
|
+
"detail": "createDbConnection opens a Bun.SQL pool and needs Bun 1.4.4 or newer, the first release that drops a cached statement plan after DDL on the same connection. The handle gains listenClient for LISTEN/NOTIFY. Server errors carry the code ERR_POSTGRES_SERVER_ERROR with the SQLSTATE in errno and the constraint name in constraint; connection failures carry ERR_POSTGRES_CONNECTION_CLOSED, ERR_POSTGRES_CONNECTION_REFUSED, ERR_POSTGRES_CONNECTION_TIMEOUT or ERR_POSTGRES_CONNECTION_FAILED. extractPgError, isUniqueViolation, isTableAlreadyExists, constraintOf and the newly exported isUndefinedTable read that shape. The onNotice option is removed, because Bun.SQL has no notice hook and drops server notices. The ssl option takes Bun's tls value (a boolean, \"disable\", \"prefer\", \"require\", \"verify-ca\", \"verify-full\" or a TLS options object); ssl true and a TLS object without rejectUnauthorized still verify the server certificate, as they did with postgres-js. DB_PROVIDER is refused at boot for every non-empty value, \"postgres\" included.",
|
|
7
|
+
"migration": "Read SQLSTATE and constraint only through extractPgError or the is* helpers from @cosmicdrift/kumiko-framework/db; a direct err.code === \"23505\" check or an err.constraint_name read no longer matches. Replace checks for CONNECTION_CLOSED, CONNECTION_ENDED, CONNECTION_DESTROYED or CONNECT_TIMEOUT with the ERR_POSTGRES_CONNECTION_* codes. Drop onNotice from createDbConnection calls. Remove DB_PROVIDER from every environment. Run on Bun 1.4.4 or newer in engines, CI and images. Code that imports postgres itself must declare its own dependency or move to Bun.SQL, since the framework no longer installs it. Manual migration, there is no codemod."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.389.0",
|
|
11
|
+
"type": "improvement",
|
|
12
|
+
"title": "schema status and baseline use one connection, bun-types is a peer dependency",
|
|
13
|
+
"detail": "kumiko schema status and baseline open a pool of one connection instead of the default size, because Bun.SQL dials every pool connection on the first query. bun-types is now a peer dependency (^1.4.4) of framework, types, server-runtime and dev-server instead of a regular dependency, so install it next to the framework."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"version": "0.389.0",
|
|
17
|
+
"type": "improvement",
|
|
18
|
+
"title": "validateBundledListsOptions and the BundledListsOptions type",
|
|
19
|
+
"detail": "BundledListsOptions is exported from the types package and the engine barrel. validateBundledListsOptions rejects unknown keys, grouping, totals and pinFirstColumn without table.pinning with a message that names the key, and checks the remaining keys like a screen's own table block."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"version": "0.389.0",
|
|
23
|
+
"type": "improvement",
|
|
24
|
+
"title": "r.adoptsBundledLists() marks features that take the bundledLists block",
|
|
25
|
+
"detail": "The registrar method r.adoptsBundledLists() marks a feature whose list screens take the app-wide bundledLists block; the flag appears as FeatureDefinition.adoptsBundledLists and in feature-manifest.json, and the feature-ast parser knows the call as an opaque singleton pattern."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"version": "0.389.0",
|
|
29
|
+
"type": "improvement",
|
|
30
|
+
"title": "table block on entityList, projectionList and relatedList",
|
|
31
|
+
"detail": "The new optional table block (rememberState, resetAll, columnPicker, density with default and userChoice) is validated at boot; unknown keys, hidden columns without columnPicker and invalid density values are rejected. Columns accept hidden: true. Remembered state lives in sessionStorage per tenant and user and is cleared on logout or identity change."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "0.389.0",
|
|
35
|
+
"type": "improvement",
|
|
36
|
+
"title": "table.pinning, table.resizing and table.keyboardNavigation, column pinned, width and minWidth",
|
|
37
|
+
"detail": "The boot validator rejects pinned without table.pinning, a pinned value other than left or right, width or minWidth without table.resizing, values that are not finite numbers above 0, and a minWidth above the width."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "0.389.0",
|
|
41
|
+
"type": "breaking",
|
|
42
|
+
"title": "Money list aggregates answer one value per currency",
|
|
43
|
+
"detail": "A sum, avg, min or max measure over a money field in a list aggregate now answers [{ currency, value }] with value in minor units, one entry per currency in the matching rows, and never adds amounts of different currencies. Before, it answered one plain number over all currencies.",
|
|
44
|
+
"migration": "code reading measures of a money field from a list aggregate answer (executor.aggregate, answerListAggregate or the list query with aggregate) reads an array of { currency, value } instead of a number; take the entry of the wanted currency, value stays in minor units"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"version": "0.389.0",
|
|
48
|
+
"type": "improvement",
|
|
49
|
+
"title": "table.grouping, table.totals and range facets validated at boot",
|
|
50
|
+
"detail": "The table block takes grouping ({ field, label?, labels?, collapsedWhen?, userChoice?, fields? }) and totals ([{ field, kind }]). The boot validator checks the fields against the entity or the columns, rejects measures a field type cannot carry and requires the list query to accept aggregate (entityListSchema does; hand-written queries answer it with answerListAggregate). facets.<field>.display \"range\" needs a filterable number, decimal, money or bigInt field."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"version": "0.389.0",
|
|
54
|
+
"type": "improvement",
|
|
55
|
+
"title": "bulkActions on entityList, projectionList and relatedList",
|
|
56
|
+
"detail": "Each entry is { id, rowAction, confirm? } and points at a writeHandler entry of the same screen's rowActions. The boot validator rejects unknown keys, a non-kebab or duplicate id, an unknown rowAction and a rowAction of kind clientAction, navigate or drawer."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"version": "0.389.0",
|
|
60
|
+
"type": "improvement",
|
|
61
|
+
"title": "XLSX export for entityList and projectionList via table.export",
|
|
62
|
+
"detail": "The new table.export block ({ xlsx: true, maxRows? }) is validated at boot and is only accepted on entityList and projectionList. POST /api/export/xlsx runs the list query again with the caller's session, pages through the whole filtered list and answers 413 above maxRows (default 10000, hard cap 100000). Encrypted, sensitive, write-only and unreadable fields and virtual columns are left out of the sheet. Reference columns export the label of the target (joined with commas for multiple references), not the id. Timestamp cells use the browser time zone sent with the request, else the profile zone, else UTC. Personal access tokens are rejected. A projectionList query must honor offset or return nextCursor. Adds the write-excel-file dependency."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"version": "0.389.0",
|
|
66
|
+
"type": "fix",
|
|
67
|
+
"title": "Executor writes on a pool runner are atomic",
|
|
68
|
+
"detail": "executor.create/update/delete/forget/restore on a TenantDb that is not inside a transaction used to run append, projection and custom projections as separate autocommit statements, so a failing projection left an event without its row. They now run in their own transaction, and a returned failure such as version_conflict rolls it back too. Calls inside a handler transaction keep their savepoint behavior. Callers that wrapped executor writes in runInOwnTransaction as a workaround can drop it."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"version": "0.389.0",
|
|
72
|
+
"type": "breaking",
|
|
73
|
+
"title": "FeatureRegistrar types moved to @cosmicdrift/kumiko-types/feature-registrar",
|
|
74
|
+
"migration": "Import FeatureRegistrar, HookDefinition, EntityObjectDefinition and RequiresApi from @cosmicdrift/kumiko-types/feature-registrar or from the engine barrel of @cosmicdrift/kumiko-framework; @cosmicdrift/kumiko-types/feature no longer exports them."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"version": "0.389.0",
|
|
78
|
+
"type": "fix",
|
|
79
|
+
"title": "maxPerTenant counts active and waiting jobs in one snapshot",
|
|
80
|
+
"detail": "The guard read the active and the waiting list with separate Redis calls, so a job that moved from waiting to active between them was missing from both and a dispatch over the cap got through. It now reads both lists with one getJobs snapshot per queue. The integration tests hold the cap jobs on a release gate instead of sleeping."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"version": "0.389.0",
|
|
84
|
+
"type": "breaking",
|
|
85
|
+
"title": "relatedList groupBy replaced by table.grouping",
|
|
86
|
+
"detail": "The groupBy key on a relatedList section and an entityList expandableRow is removed together with the RelatedListGroupBy type. Both group through table.grouping ({ field, label?, labels?, collapsedWhen? }): groups, counts and header measures come from one aggregate query over all matching rows instead of the loaded page, and each open group loads its own rows. An expandableRow takes only table.grouping with a fixed field; userChoice, fields and every other table option stay boot errors there. The boot validator reports a leftover groupBy with a pointer to table.grouping. ExpandableRowGrouping is the new type of an expandableRow's grouping.",
|
|
87
|
+
"migration": "move groupBy { field, collapsedWhen, label, labels } to table: { grouping: { field, collapsedWhen, label, labels } }; the bound query must accept aggregate (entityListSchema does, a hand-written query answers it with answerListAggregate). dateField and the {lastDate} placeholder have no replacement on an expandableRow, where totals stay off; on a relatedList a max measure in table.totals shows the latest date. Every group now gets a header: a group without label or labels entry shows its value instead of rendering headerless."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"version": "0.389.0",
|
|
91
|
+
"type": "improvement",
|
|
92
|
+
"title": "ctx.loadAggregate can read a single event by version",
|
|
93
|
+
"detail": "The new atVersion option returns the event at that version of the stream (or nothing), with upcasting and the tenant filter. It cannot be combined with asOf. Workflow resume uses it for event references."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"version": "0.389.0",
|
|
97
|
+
"type": "fix",
|
|
98
|
+
"title": "Access invalidation survives a Redis outage",
|
|
99
|
+
"detail": "The consumer that pushes session, token and membership revocations to open streams does not count failed publishes towards the attempt limit and backs off between them, instead of dying after 10 failed deliveries and silently dropping later revocations. Other failures keep the limit. New EventConsumerErrorPolicy.isTransientError."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"version": "0.389.0",
|
|
103
|
+
"type": "fix",
|
|
104
|
+
"title": "file upload rechecks the record lock when inserting the row",
|
|
105
|
+
"detail": "POST /files with an entityId reads the stored row again through the inserting transaction and refuses with 409 record_locked when lockedWhen holds. The window between that read and the commit stays open, like for any other write that checks the lock before its own commit."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"version": "0.389.0",
|
|
109
|
+
"type": "fix",
|
|
110
|
+
"title": "Meilisearch default upgrade cannot tell a narrowed configure() from an older default",
|
|
111
|
+
"detail": "A tenant that called configure() with a strict subset of the default attributes gets the full default back after the next process restart, because Meilisearch stores no marker for an explicit configure(). Configure the full attribute set, or a set that contains an attribute outside the default, to keep it."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"version": "0.389.0",
|
|
115
|
+
"type": "fix",
|
|
116
|
+
"title": "Cross-tenant-read crons audit once per day, not once per foreign row",
|
|
117
|
+
"detail": "The process dedup key of a declared system cron hatch includes the target only for global-write (a table). Row targets of cross-tenant-read no longer grow the in-memory map or write one audit event per row."
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"version": "0.389.0",
|
|
121
|
+
"type": "fix",
|
|
122
|
+
"title": "Failed job-runner start waits for the timed-out boot gate",
|
|
123
|
+
"detail": "start() now waits a bounded time for an abandoned boot gate before throwing, instead of leaving that to stop(). The scheduler-seen timer is only started after every step that can fail, so no timer outlives a failed start."
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"version": "0.389.0",
|
|
127
|
+
"type": "fix",
|
|
128
|
+
"title": "disabled feature no longer removes another feature's workspace with the same id",
|
|
129
|
+
"detail": "buildAppSchema records which feature owns each workspace and the feature strip removes only the workspaces of the disabled features. Two features that use the same short workspace id keep their own entries."
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"version": "0.389.0",
|
|
133
|
+
"type": "fix",
|
|
134
|
+
"title": "upload ledger uses the injected clock",
|
|
135
|
+
"detail": "createFileUploadLedger and withUploadLedger take an optional now function, and the file provider resolver wires AppContext.clock into it. Without a clock the system time is used as before."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"version": "0.389.0",
|
|
139
|
+
"type": "fix",
|
|
140
|
+
"title": "anonymous save leaves foreign pending uploads unbound",
|
|
141
|
+
"detail": "Integration test through the real write route with anonymous access, covering the save pipeline path of the pending-upload ownership check."
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"version": "0.389.0",
|
|
145
|
+
"type": "breaking",
|
|
146
|
+
"title": "Unused ProviderPluginContext helpers removed",
|
|
147
|
+
"detail": "ProviderPluginContext, providerPluginContextFromHandler and providerPluginContextFromJob are gone. No plugin type or resolver used them, and the type exposed db handles without an audit user. Provider contexts stay the narrow FileProviderContext, MailTransportContext and InboundMailContext.",
|
|
148
|
+
"migration": "no known callers; use FileProviderContext, MailTransportContext or InboundMailContext"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"version": "0.389.0",
|
|
152
|
+
"type": "breaking",
|
|
153
|
+
"title": "provider selector option labels are unique per owner",
|
|
154
|
+
"detail": "The selector field id of a provider form is qualified with its owner feature, so the translation keys of its option labels change.",
|
|
155
|
+
"migration": "move app translation overrides from config:entity:__config-edit__:field:provider:option:<value> to the owner-qualified field id, field:mail-foundation-provider:option:<value> or field:file-foundation-provider:option:<value>"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"version": "0.389.0",
|
|
159
|
+
"type": "fix",
|
|
160
|
+
"title": "live signals hide row ids from row-filtered roles",
|
|
161
|
+
"detail": "createSseRoute takes an optional entityRowReadMaps option, built from entity access.read by the server routes. A signed-in connection receives id and version only if the entity has no read ownership map or one of its roles is mapped to all; otherwise the signal carries just the entity and event type so the client refetches."
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"version": "0.389.0",
|
|
165
|
+
"type": "fix",
|
|
166
|
+
"title": "write handlers that spread the executor result keep projections and postSave hooks",
|
|
167
|
+
"detail": "The event is stripped from lifecycle results when the dispatcher masks the response, no longer made non-enumerable on the result, so copying the result does not lose it. Responses still carry no event."
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"version": "0.389.0",
|
|
171
|
+
"type": "improvement",
|
|
172
|
+
"title": "event-free write results proven for non-HTTP entry points",
|
|
173
|
+
"detail": "Integration tests assert that dispatcher.write, dispatcher.batch and the agent tool dispatch return no event. No runtime change: all of them already pass through the dispatcher masking."
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"version": "0.389.0",
|
|
177
|
+
"type": "fix",
|
|
178
|
+
"title": "reference-data seed adopts rows that have no event stream",
|
|
179
|
+
"detail": "Rows from installs that seeded reference data by direct insert are re-created under the same id through the executor on the next boot, so updates apply and the rows survive a rebuild. No action is needed; the row id is unchanged, only its version restarts at 1."
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"version": "0.389.0",
|
|
183
|
+
"type": "fix",
|
|
184
|
+
"title": "client-errors route keeps an in-memory floor limit",
|
|
185
|
+
"detail": "The in-memory per-IP limiter now runs in front of the Redis-backed limiter instead of only when no resolver is wired, so a failing resolver can no longer lift the limit on the public client-errors endpoint."
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"version": "0.389.0",
|
|
189
|
+
"type": "fix",
|
|
190
|
+
"title": "reindex job batches are no longer cut off by the server statement timeout",
|
|
191
|
+
"detail": "reindexEntity reads each batch in its own transaction with statement_timeout lifted for that transaction, on the caller's own runner, so the job path through ctx.systemDb.unsafeRaw() keeps its gates."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"version": "0.389.0",
|
|
195
|
+
"type": "fix",
|
|
196
|
+
"title": "files meta batch route is rate limited by id count",
|
|
197
|
+
"detail": "createFileRoutes takes an optional metaBatchRateLimitResolver, wired by the server from the rate-limit resolver or Redis. A user over 1000 requested ids per minute gets 429 rate_limited; a failing limiter fails open and is reported like the other limiters."
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"version": "0.389.0",
|
|
201
|
+
"type": "fix",
|
|
202
|
+
"title": "kumiko-upgrade explains that --apply writes the CHANGELOG",
|
|
203
|
+
"detail": "The preview run never wrote the upgrade CHANGELOG or the marker, and consumers running it without --apply got no entry and no explanation. It now prints a hint; the exit code is unchanged."
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"version": "0.389.0",
|
|
207
|
+
"type": "fix",
|
|
208
|
+
"title": "Codemod imports no longer land inside header comments",
|
|
209
|
+
"detail": "ensureNamedImport appends the new import after the last existing import, so a file with a header comment block keeps the comment intact."
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"version": "0.389.0",
|
|
213
|
+
"type": "fix",
|
|
214
|
+
"title": "Database now is rounded to milliseconds",
|
|
215
|
+
"detail": "The Postgres clock read behind readDatabaseNow is cast to timestamptz(3), the precision of events.created_at, so a comparison with a stored event time is exact."
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"version": "0.389.0",
|
|
219
|
+
"type": "improvement",
|
|
220
|
+
"title": "E2E seeders and signature routes can write with explicit roles",
|
|
221
|
+
"detail": "dispatchWriteAs on signature route deps and writeAs on the E2E seeder context run a write as the tenant's system user with the \"system\" role plus the given roles, without SystemAdmin. write() keeps running as SystemAdmin."
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"version": "0.389.0",
|
|
225
|
+
"type": "fix",
|
|
226
|
+
"title": "List filter on multiple references matches array contents",
|
|
227
|
+
"detail": "eq, ne and in on a multi-reference field now check jsonb containment instead of scalar equality, and screen filters on such fields allow the equality operators. Consumers no longer need a full scan."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"version": "0.389.0",
|
|
231
|
+
"type": "fix",
|
|
232
|
+
"title": "kumiko-upgrade --resolve updates CHANGELOG.md",
|
|
233
|
+
"detail": "The Manual migrations lines of every CHANGELOG.md section change from [open] to the chosen resolution for the resolved ids; other entries and files without a CHANGELOG.md are left alone."
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"version": "0.389.0",
|
|
237
|
+
"type": "improvement",
|
|
238
|
+
"title": "ctx.scheduleAfterRollback for outside-transaction compensation",
|
|
239
|
+
"detail": "ctx.scheduleAfterRollback(fn) runs fn when the handler transaction does not commit, or when the enclosing savepoint of a nested ctx.write rolls back (after the outer transaction ends). It never runs on commit, errors are logged and do not change the write result, and it is absent outside a transaction scope (perCommand, post-commit, beforeTransaction, member resolution). A failed COMMIT also triggers it, so callbacks must be idempotent and outcome-safe, for example releasing a reservation row that may already be gone."
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"version": "0.389.0",
|
|
243
|
+
"type": "improvement",
|
|
244
|
+
"title": "Tenant-safe upsert on ctx.db",
|
|
245
|
+
"detail": "ctx.db.upsert(table, values, { conflictTarget, update? }) requires tenantId in conflictTarget and the target to match a declared unique index or primary key. The update only touches the row of the inserted tenant, update may not set tenantId, and the default update leaves conflict, primary-key and tenant columns alone. Event-fed tables are rejected. Replaces unsafeRaw plus upsertOnConflict for tenant-scoped upserts."
|
|
246
|
+
},
|
|
2
247
|
{
|
|
3
248
|
"version": "0.388.0",
|
|
4
249
|
"type": "fix",
|
|
@@ -155,7 +155,7 @@ export function ensureNamedImport(
|
|
|
155
155
|
declaration.getNamedImports().length > 0,
|
|
156
156
|
);
|
|
157
157
|
if (target === undefined) {
|
|
158
|
-
sourceFile.
|
|
158
|
+
sourceFile.addImportDeclaration({
|
|
159
159
|
moduleSpecifier: specifier,
|
|
160
160
|
namedImports: [name],
|
|
161
161
|
isTypeOnly,
|
|
@@ -623,7 +623,7 @@ function reportRemovedProviderEnv(
|
|
|
623
623
|
file: filePath,
|
|
624
624
|
line: node.getStartLineNumber(),
|
|
625
625
|
kind: "call",
|
|
626
|
-
description: `${REMOVED_ENV_NAME}:
|
|
626
|
+
description: `${REMOVED_ENV_NAME}: DB_PROVIDER is gone, Bun.SQL is the only driver`,
|
|
627
627
|
});
|
|
628
628
|
};
|
|
629
629
|
for (const literal of sourceFile.getDescendantsOfKind(SyntaxKind.StringLiteral)) {
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { HandlerContext, JobContext, ProviderPluginContext } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
-
export declare function providerPluginContextFromHandler(ctx: HandlerContext): ProviderPluginContext;
|
|
3
|
-
export declare function providerPluginContextFromJob(ctx: JobContext): ProviderPluginContext;
|