@cosmicdrift/kumiko-framework 0.361.0 → 0.363.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/dist/api/auth-middleware.js +7 -5
- package/dist/api/redis-sse-broker.js +10 -8
- package/dist/api/routes.js +2 -2
- package/dist/api/server.js +3 -0
- package/dist/bun-db/query.d.ts +6 -4
- package/dist/bun-db/query.js +66 -92
- package/dist/crypto/kek-source.js +5 -3
- package/dist/crypto/pii-field-encryption.d.ts +4 -1
- package/dist/crypto/pii-field-encryption.js +2 -2
- package/dist/db/apply-entity-event.js +2 -2
- package/dist/db/cursor.js +2 -1
- package/dist/db/dialect.d.ts +1 -0
- package/dist/db/dialect.js +28 -13
- package/dist/db/entity-table-from-registry.js +1 -1
- package/dist/db/entity-table-meta.js +5 -13
- package/dist/db/event-store-executor-context.js +6 -6
- package/dist/db/event-store-executor-read.js +9 -9
- package/dist/db/migrate-generator.js +5 -3
- package/dist/db/migrate-runner.js +31 -35
- package/dist/db/parent-ref-clause.js +2 -2
- package/dist/db/queries/backfill-pii.js +11 -9
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.d.ts +6 -6
- package/dist/db/queries/event-consumer.js +17 -17
- package/dist/db/queries/event-store-admin.js +6 -6
- package/dist/db/queries/event-store-transfer.js +2 -2
- package/dist/db/queries/event-store.d.ts +1 -1
- package/dist/db/queries/event-store.js +2 -2
- package/dist/db/queries/pii-backfill-state.js +2 -2
- package/dist/db/queries/projection-rebuild.d.ts +1 -1
- package/dist/db/queries/raw-sql.js +1 -1
- package/dist/db/queries/test-stack.js +1 -1
- package/dist/db/reference-data.js +3 -5
- package/dist/db/table-builder.js +6 -4
- package/dist/db/table-introspection.d.ts +16 -0
- package/dist/db/table-introspection.js +76 -0
- package/dist/db/tenant-db.d.ts +3 -2
- package/dist/db/tenant-db.js +4 -10
- package/dist/derivatives/variant-key.js +2 -1
- package/dist/engine/boot-validator/access-declarations.js +29 -18
- package/dist/engine/boot-validator/card-meta-overflow.js +5 -2
- package/dist/engine/boot-validator/entity-list-screens.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +1 -1
- package/dist/engine/boot-validator/screens.js +2 -1
- package/dist/engine/build-app-schema.js +1 -0
- package/dist/engine/changeset-changes.js +3 -3
- package/dist/engine/define-handler.js +3 -6
- package/dist/engine/embedded-derived.js +10 -6
- package/dist/engine/extensions/escape-hatch-usage.js +2 -1
- package/dist/engine/factories.js +5 -6
- package/dist/engine/feature-ast/extractors/events.js +43 -33
- package/dist/engine/feature-ast/extractors/round3.d.ts +2 -2
- package/dist/engine/feature-ast/extractors/round3.js +4 -4
- package/dist/engine/feature-ast/patcher.d.ts +3 -3
- package/dist/engine/feature-ast/patcher.js +3 -3
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +8 -8
- package/dist/engine/feature-ast/pattern-change-schema.js +3 -3
- package/dist/engine/feature-ast/patterns.d.ts +3 -3
- package/dist/engine/feature-ast/render.js +6 -3
- package/dist/engine/feature-changelog.d.ts +2 -1
- package/dist/engine/feature-changelog.js +8 -6
- package/dist/engine/feature-config-events-jobs.d.ts +17 -7
- package/dist/engine/feature-config-events-jobs.js +20 -5
- package/dist/engine/feature-ui-extensions.js +2 -3
- package/dist/engine/index.d.ts +2 -1
- package/dist/engine/index.js +1 -0
- package/dist/engine/ownership.js +7 -20
- package/dist/engine/pattern-library/mixed-schemas.js +0 -1
- package/dist/engine/registry-ingest.d.ts +2 -1
- package/dist/engine/registry-ingest.js +85 -2
- package/dist/engine/registry-validate.d.ts +1 -0
- package/dist/engine/registry-validate.js +38 -8
- package/dist/engine/registry.js +2 -1
- package/dist/engine/schema-builder.js +1 -3
- package/dist/engine/soft-delete-cleanup.js +2 -1
- package/dist/engine/steps/call-feature.js +1 -3
- package/dist/engine/steps/unsafe-projection-upsert.js +2 -13
- package/dist/engine/template-variables.d.ts +29 -0
- package/dist/engine/template-variables.js +77 -0
- package/dist/engine/tier-resolver-extension.js +2 -3
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/engine/validate-projection-allowlist.js +12 -13
- package/dist/engine/where-rule-lint.js +8 -7
- package/dist/errors/reasons.d.ts +1 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/files/bind-referenced-files.write.d.ts +16 -0
- package/dist/files/bind-referenced-files.write.js +109 -0
- package/dist/files/feature.d.ts +1 -0
- package/dist/files/feature.js +2 -0
- package/dist/files/file-routes.d.ts +2 -1
- package/dist/files/file-routes.js +10 -3
- package/dist/files/local-provider.js +2 -1
- package/dist/files/record-write-check.d.ts +1 -0
- package/dist/files/record-write-check.js +9 -9
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/mail-registry.d.ts +2 -0
- package/dist/i18n/mail-registry.js +7 -0
- package/dist/jobs/job-runner.js +5 -5
- package/dist/pipeline/dispatch-query.js +2 -3
- package/dist/pipeline/dispatch-shared.js +45 -14
- package/dist/pipeline/event-dispatcher-delivery.js +2 -3
- package/dist/pipeline/index.d.ts +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/member-read-only-transaction.js +1 -1
- package/dist/pipeline/notify-scope.d.ts +13 -0
- package/dist/pipeline/notify-scope.js +32 -0
- package/dist/pipeline/system-hooks.js +2 -1
- package/dist/pipeline/system-identity-switch.js +2 -2
- package/dist/pipeline/write-origin.js +2 -2
- package/dist/search/meilisearch-adapter.js +3 -4
- package/dist/secrets/stored-envelope.js +7 -7
- package/dist/stack/push-entity-projection-tables.js +2 -3
- package/dist/stack/table-helpers.js +10 -26
- package/dist/stack/test-stack.js +2 -5
- package/dist/testing/multipart-helper.d.ts +0 -6
- package/dist/testing/multipart-helper.js +5 -5
- package/dist/testing/without-ambient-temporal.js +4 -4
- package/dist/ui-types/card-column-roles.d.ts +5 -0
- package/dist/ui-types/card-column-roles.js +22 -4
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/ui-types/index.js +1 -1
- package/dist/utils/is-plain-object.d.ts +1 -2
- package/dist/utils/is-plain-object.js +1 -4
- package/package.json +4 -4
- package/src/changes.json +88 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// jsonb columns (secrets feature) or JSON-stringified TEXT columns
|
|
3
3
|
// (EnvelopeCipher for config values / entity fields). One wire shape for
|
|
4
4
|
// every envelope-encrypted store in the framework.
|
|
5
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
5
6
|
export function encodeStoredEnvelope(envelope) {
|
|
6
7
|
return {
|
|
7
8
|
ciphertext: envelope.ciphertext.toString("base64"),
|
|
@@ -21,12 +22,11 @@ export function decodeStoredEnvelope(stored) {
|
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
export function isStoredEnvelope(value) {
|
|
24
|
-
if (!value
|
|
25
|
+
if (!isPlainObject(value))
|
|
25
26
|
return false;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
typeof
|
|
29
|
-
typeof
|
|
30
|
-
typeof
|
|
31
|
-
typeof v["kekVersion"] === "number");
|
|
27
|
+
return (typeof value["ciphertext"] === "string" &&
|
|
28
|
+
typeof value["iv"] === "string" &&
|
|
29
|
+
typeof value["authTag"] === "string" &&
|
|
30
|
+
typeof value["encryptedDek"] === "string" &&
|
|
31
|
+
typeof value["kekVersion"] === "number");
|
|
32
32
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { KUMIKO_NAME_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
2
1
|
import { columnNamesOf, tableExists } from "../db/schema-inspection.js";
|
|
2
|
+
import { schemaTableName } from "../db/table-introspection.js";
|
|
3
3
|
import { addMissingColumns, addMissingIndexes, tableToMeta, unsafePushTables, } from "./table-helpers.js";
|
|
4
4
|
// biome-ignore lint/suspicious/noConsole: stack-internal status logging
|
|
5
5
|
const logInfo = (msg) => console.log(msg);
|
|
@@ -29,8 +29,7 @@ export async function pushEntityProjectionTables(stack, registry) {
|
|
|
29
29
|
if (seen.has(proj.table))
|
|
30
30
|
continue;
|
|
31
31
|
seen.add(proj.table);
|
|
32
|
-
const
|
|
33
|
-
const physical = tableRec[KUMIKO_NAME_SYMBOL];
|
|
32
|
+
const physical = schemaTableName(proj.table) ?? tableToMeta(proj.table).tableName;
|
|
34
33
|
if (!(await tableExists(stack.db, `public.${physical}`))) {
|
|
35
34
|
missing[projName] = proj.table;
|
|
36
35
|
continue;
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import { KUMIKO_META_SYMBOL, KUMIKO_NAME_SYMBOL, } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
2
1
|
import { pgTypeToSqlType } from "../db/dialect.js";
|
|
3
2
|
import { alterTableAddColumn, createIndexIfNotExists, executeDdlStatement, } from "../db/queries/ddl.js";
|
|
4
3
|
import { truncateTablesRestartIdentity } from "../db/queries/test-stack.js";
|
|
5
4
|
import { renderTableDdl } from "../db/render-ddl.js";
|
|
6
5
|
import { tableExists } from "../db/schema-inspection.js";
|
|
7
6
|
import { buildEntityTable, toTableName } from "../db/table-builder.js";
|
|
7
|
+
import { asEntityTableMeta, schemaTableName } from "../db/table-introspection.js";
|
|
8
8
|
function tableNameOf(table) {
|
|
9
9
|
if (typeof table !== "object" || table === null) {
|
|
10
10
|
throw new Error("table-helpers: table is not a SchemaTable object");
|
|
11
11
|
}
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
return
|
|
15
|
-
if (typeof
|
|
16
|
-
return
|
|
17
|
-
}
|
|
12
|
+
const symbolName = schemaTableName(table);
|
|
13
|
+
if (symbolName !== undefined)
|
|
14
|
+
return symbolName;
|
|
15
|
+
if ("tableName" in table && typeof table.tableName === "string")
|
|
16
|
+
return table.tableName;
|
|
18
17
|
throw new Error("table-helpers: table has no name");
|
|
19
18
|
}
|
|
20
19
|
/**
|
|
@@ -36,26 +35,11 @@ export async function unsafeEnsureEntityTable(db, entity, entityName) {
|
|
|
36
35
|
// Tables produced by the native dialect already carry EntityTableMeta-shape
|
|
37
36
|
// (source/columns/indexes). renderTableDdl converts that to CREATE TABLE +
|
|
38
37
|
// CREATE INDEX statements executed via db/queries/test-stack.
|
|
39
|
-
function isMetaShape(v) {
|
|
40
|
-
return (typeof v === "object" &&
|
|
41
|
-
v !== null &&
|
|
42
|
-
typeof v.tableName === "string" &&
|
|
43
|
-
Array.isArray(v.columns) &&
|
|
44
|
-
Array.isArray(v.indexes) &&
|
|
45
|
-
(v.source === "managed" || v.source === "unmanaged"));
|
|
46
|
-
}
|
|
47
38
|
export function tableToMeta(table) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const fromSymbol = table[KUMIKO_META_SYMBOL];
|
|
53
|
-
if (isMetaShape(fromSymbol))
|
|
54
|
-
return fromSymbol;
|
|
55
|
-
}
|
|
56
|
-
if (isMetaShape(table))
|
|
57
|
-
return table;
|
|
58
|
-
throw new Error("unsafePushTables: argument is not a SchemaTable / EntityTableMeta");
|
|
39
|
+
const meta = asEntityTableMeta(table);
|
|
40
|
+
if (!meta)
|
|
41
|
+
throw new Error("unsafePushTables: argument is not a SchemaTable / EntityTableMeta");
|
|
42
|
+
return meta;
|
|
59
43
|
}
|
|
60
44
|
/** Applies ALTER TABLE ADD COLUMN for exactly the given columns. */
|
|
61
45
|
export async function addMissingColumns(db, tableName, columns) {
|
package/dist/stack/test-stack.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
1
2
|
import { buildServer } from "../api/server.js";
|
|
2
3
|
import { createSseBroker } from "../api/sse-broker.js";
|
|
3
4
|
import { validateBoot, validateNavBootSubset } from "../engine/boot-validator/index.js";
|
|
@@ -269,11 +270,7 @@ export async function setupTestStack(options) {
|
|
|
269
270
|
// always-real `testRedis.redis`, independent of whatever extraContext
|
|
270
271
|
// did to `appContext.redis` — an explicit `context.rateLimit` from
|
|
271
272
|
// extraContext still wins (same precedence buildServer documents).
|
|
272
|
-
|
|
273
|
-
// dynamic extension point) — appContext's inferred type doesn't carry a
|
|
274
|
-
// `rateLimit` key, so reading it back needs the same relaxed boundary
|
|
275
|
-
// extraContext itself is typed with.
|
|
276
|
-
const explicitRateLimit = appContext.rateLimit;
|
|
273
|
+
const explicitRateLimit = propertyOf(appContext, "rateLimit");
|
|
277
274
|
const needsOwnRateLimitResolver = explicitRateLimit === undefined &&
|
|
278
275
|
(appContext.redis === undefined || options.rateLimitNowMs !== undefined);
|
|
279
276
|
const rateLimitFallback = needsOwnRateLimitResolver
|
|
@@ -1,9 +1,3 @@
|
|
|
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
1
|
export declare function buildMultipartBody(fd: FormData): Promise<{
|
|
8
2
|
body: BodyInit;
|
|
9
3
|
contentType: string;
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* Returns the encoded body and the Content-Type header value (including the
|
|
20
20
|
* generated boundary). Pass both directly to app.request or fetch.
|
|
21
21
|
*/
|
|
22
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
22
23
|
export async function buildMultipartBody(fd) {
|
|
23
24
|
const boundary = `KumikoBnd${Date.now()}${Math.random().toString(36).slice(2, 8)}`;
|
|
24
25
|
const enc = new TextEncoder();
|
|
@@ -64,12 +65,11 @@ export function patchFileInstanceofForBunTest() {
|
|
|
64
65
|
return;
|
|
65
66
|
Object.defineProperty(File, Symbol.hasInstance, {
|
|
66
67
|
value(instance) {
|
|
67
|
-
if (
|
|
68
|
+
if (!isPlainObject(instance))
|
|
68
69
|
return false;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
typeof
|
|
72
|
-
typeof f["arrayBuffer"] === "function");
|
|
70
|
+
return (typeof instance["name"] === "string" &&
|
|
71
|
+
typeof instance["size"] === "number" &&
|
|
72
|
+
typeof instance["arrayBuffer"] === "function");
|
|
73
73
|
},
|
|
74
74
|
configurable: true,
|
|
75
75
|
});
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
* Used by ambient-independence regression tests (kumiko-framework#1525/#1550).
|
|
4
4
|
*/
|
|
5
5
|
export async function withoutAmbientTemporal(fn) {
|
|
6
|
-
const savedGlobal = globalThis
|
|
7
|
-
|
|
6
|
+
const savedGlobal = Reflect.get(globalThis, "Temporal");
|
|
7
|
+
Reflect.deleteProperty(globalThis, "Temporal");
|
|
8
8
|
try {
|
|
9
9
|
return await fn();
|
|
10
10
|
}
|
|
11
11
|
finally {
|
|
12
12
|
if (savedGlobal === undefined)
|
|
13
|
-
|
|
13
|
+
Reflect.deleteProperty(globalThis, "Temporal");
|
|
14
14
|
else
|
|
15
|
-
globalThis
|
|
15
|
+
Reflect.set(globalThis, "Temporal", savedGlobal);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -5,10 +5,15 @@ export type CardRoleColumn = {
|
|
|
5
5
|
readonly type?: string;
|
|
6
6
|
readonly renderer?: unknown;
|
|
7
7
|
readonly hideOnNarrow?: boolean;
|
|
8
|
+
readonly fileDisplay?: {
|
|
9
|
+
readonly kind: string;
|
|
10
|
+
};
|
|
8
11
|
};
|
|
9
12
|
export type CardColumnRoles<C extends CardRoleColumn> = {
|
|
13
|
+
readonly image: C | undefined;
|
|
10
14
|
readonly title: C | undefined;
|
|
11
15
|
readonly status: C | undefined;
|
|
12
16
|
readonly meta: readonly C[];
|
|
13
17
|
};
|
|
18
|
+
export declare function cardFileDisplayKind(type: string | undefined, renderer: unknown): "image" | "file" | undefined;
|
|
14
19
|
export declare function cardColumnRoles<C extends CardRoleColumn>(columns: readonly C[]): CardColumnRoles<C>;
|
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
export const CARD_META_MAX = 3;
|
|
2
|
+
// An explicit `{ format: "image" | "file" }` renderer wins over the field type, the
|
|
3
|
+
// same precedence as the list view model's file display.
|
|
4
|
+
export function cardFileDisplayKind(type, renderer) {
|
|
5
|
+
if (typeof renderer === "object" && renderer !== null && "format" in renderer) {
|
|
6
|
+
if (renderer.format === "image")
|
|
7
|
+
return "image";
|
|
8
|
+
if (renderer.format === "file")
|
|
9
|
+
return "file";
|
|
10
|
+
}
|
|
11
|
+
if (type === "image" || type === "images")
|
|
12
|
+
return "image";
|
|
13
|
+
if (type === "file" || type === "files")
|
|
14
|
+
return "file";
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
2
17
|
// One rule for the narrow-layout list card, shared by the renderer and the
|
|
3
18
|
// boot validator so the validator counts exactly what the card would draw.
|
|
19
|
+
// The first image column is the card's leading preview, neither title nor meta.
|
|
4
20
|
export function cardColumnRoles(columns) {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
21
|
+
const image = columns.find((col) => col.fileDisplay?.kind === "image");
|
|
22
|
+
const textColumns = columns.filter((col) => col !== image);
|
|
23
|
+
const title = textColumns.find((col) => col.highlighted === true) ?? textColumns[0];
|
|
24
|
+
const status = textColumns.find((col) => col !== title && col.type === "select" && col.renderer === undefined);
|
|
25
|
+
const meta = textColumns.filter((col) => col !== title && col !== status && col.hideOnNarrow !== true);
|
|
26
|
+
return { image, title, status, meta };
|
|
9
27
|
}
|
package/dist/ui-types/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type { WorkspaceDefinition } from "../engine/types/workspace.js";
|
|
|
17
17
|
export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
|
|
18
18
|
export type { AppSchema, FeatureSchema, QualifiedContentCollection, WorkspaceSchema, } from "./app-schema.js";
|
|
19
19
|
export type { CardColumnRoles, CardRoleColumn } from "./card-column-roles.js";
|
|
20
|
-
export { CARD_META_MAX, cardColumnRoles } from "./card-column-roles.js";
|
|
20
|
+
export { CARD_META_MAX, cardColumnRoles, cardFileDisplayKind } from "./card-column-roles.js";
|
|
21
21
|
export { type Formality, formalLocaleTag } from "./formality.js";
|
|
22
22
|
export type { ListRowMetaColumnType, ListRowMetaReference, ReferenceLookupSource, SystemReferenceLabel, } from "./list-row-meta.js";
|
|
23
23
|
export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
|
package/dist/ui-types/index.js
CHANGED
|
@@ -26,7 +26,7 @@ export { availableSelectOptions, evalFieldCondition, explicitListScreenId, findU
|
|
|
26
26
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
27
27
|
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
|
|
28
28
|
export { ACTION_FORM_ENTITY, handlerFieldLabelKey, handlerTitleKey, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
|
|
29
|
-
export { CARD_META_MAX, cardColumnRoles } from "./card-column-roles.js";
|
|
29
|
+
export { CARD_META_MAX, cardColumnRoles, cardFileDisplayKind } from "./card-column-roles.js";
|
|
30
30
|
export { formalLocaleTag } from "./formality.js";
|
|
31
31
|
export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
|
|
32
32
|
export { resolveTranslationValue, translationValueOtherText, } from "./plural.js";
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
1
|
+
export { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
@@ -1,4 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export function isPlainObject(value) {
|
|
3
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
-
}
|
|
1
|
+
export { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.363.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -210,8 +210,8 @@
|
|
|
210
210
|
"./package.json": "./package.json"
|
|
211
211
|
},
|
|
212
212
|
"dependencies": {
|
|
213
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
214
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
213
|
+
"@cosmicdrift/kumiko-http": "0.363.0",
|
|
214
|
+
"@cosmicdrift/kumiko-types": "0.363.0",
|
|
215
215
|
"bullmq": "^5.76.7",
|
|
216
216
|
"bun-types": "^1.3.13",
|
|
217
217
|
"hono": "^4.13.1",
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"zod": "^4.4.3"
|
|
228
228
|
},
|
|
229
229
|
"devDependencies": {
|
|
230
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
230
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.363.0",
|
|
231
231
|
"bun-types": "^1.3.13",
|
|
232
232
|
"pino-pretty": "^13.1.3"
|
|
233
233
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,92 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.363.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Fewer unchecked casts in bundled features, dev-server, server-runtime, testing and cli",
|
|
6
|
+
"detail": "Casts are replaced by shared type guards, typed query rows and direct Bun typings. Behavior stays the same, except that a tier-resolver registration without a build() factory now fails with a clear error at boot, the kumiko CLI prints the thrown value of non-Error failures instead of \"undefined\"."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.363.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Remove unknown cast-boundary reasons and share propertyOf",
|
|
12
|
+
"detail": "Framework internals read untyped properties through the shared propertyOf from kumiko-types instead of private copies and casts. The as-casts guard knows four more boundary reasons (phantom-brand, third-party-type-gap, dom-event-target, feature-exports) and normalizes whitespace in cast targets so re-indenting a multi-line cast no longer counts as a regression. A stored inbound-mail cursor that is not a JSON object is now treated like a missing cursor."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.363.0",
|
|
16
|
+
"type": "breaking",
|
|
17
|
+
"title": "ctx.notify in write handlers delivers after commit",
|
|
18
|
+
"detail": "Inside a write handler's transaction ctx.notify only records the notification and delivers it once the write has committed, so a rolled-back write sends nothing and leaves no attempt event. Delivery no longer takes a connection from the app pool while the write holds one, which deadlocked at pool max N with N parallel writes. With immediate: true the call stays synchronous and runs its DB work on the outside-transaction pool.",
|
|
19
|
+
"migration": "NotifyResult is now a union, NotifyDispatched ({ kind: \"dispatched\", deliveries }) or NotifyDeferred ({ kind: \"deferred\" }). In a write handler without immediate: true ctx.notify returns deferred and delivers after commit (a rollback means no notification). Code that reads the result (for example a \"send test message\" handler showing sent/failed) sets immediate: true, which delivers synchronously on the outside-transaction pool and returns dispatched; narrow on kind before reading deliveries. Errors such as a missing idempotency Redis, a tenant broadcast without the tenant feature or an unknown notification type no longer throw in a deferred write handler, they land in the afterCommit log. Notifications from jobs, from seeding and from handlers without a transaction stay synchronous. The boot default wires tenantUserIdsQuery for to: { tenant } when the tenant feature is mounted, so an app-side tenantUserIdsQuery wiring can go. Custom NotifyFactory implementations receive an optional 4th parameter deliveryDb, and DeliveryService.notify an optional 6th."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"version": "0.363.0",
|
|
23
|
+
"type": "improvement",
|
|
24
|
+
"title": "Shared type guards and fewer unchecked casts in framework internals",
|
|
25
|
+
"detail": "\"@cosmicdrift/kumiko-types/type-guards\" exports isPlainObject, isOneOf and errorCodeOf. The framework replaces many unchecked as-casts with narrowing, generic query rows and table-introspection helpers. encryptPiiFieldValues is generic over the row and returns PiiEncryptedRow<TRow>, so callers read encrypted fields without casts. Behavior stays the same."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"version": "0.363.0",
|
|
29
|
+
"type": "improvement",
|
|
30
|
+
"title": "r.notification declares variables, template and meta; applyBootSeeds seeds them",
|
|
31
|
+
"detail": "r.notification accepts variables (typed with templateVariable.text, link, money, date and list), template (per locale body, document html) and meta (per locale subject and more). Boot validation requires a kebab-case name, a meta.<locale>.subject for every template locale and valid placeholder names. templateVariablesToZod turns the variables into a Zod schema. applyBootSeeds calls seedNotificationTemplates, which writes the text as system templates of kind mail and fails loudly when notifications declare a template but the template-resolver feature is not mounted."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "0.363.0",
|
|
35
|
+
"type": "improvement",
|
|
36
|
+
"title": "r.notification works without trigger and ships localized templates",
|
|
37
|
+
"detail": "trigger, recipient and data are optional and must be declared together, a notification without trigger is sent explicitly through ctx.notify. localizedTemplate groups body and meta per locale and is mutually exclusive with template and meta. The variable names link, money and date are reserved for template helpers. ctx.notify validates the data against the declared variables and fails the write on a missing required variable. New exports are listMailLocales, hasMailTranslationKey, assertNotificationVariables and NotificationLocalizedTemplate."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "0.363.0",
|
|
41
|
+
"type": "breaking",
|
|
42
|
+
"title": "ctx.notify rejects a broadcast to another tenant",
|
|
43
|
+
"detail": "A notify with to: { tenant } now throws an AccessDeniedError (reason notify_foreign_tenant_broadcast) unless the tenant is the caller's own, before any delivery work. In a write handler it fails synchronously instead of landing in the afterCommit log. Notifications fired from seed hooks during self-signup no longer use the app pool inside the signup transaction, which deadlocked at pool max N with N parallel signups; they deliver after commit. ctx.scheduleAfterCommit is now absent in query bridges and post-commit contexts, where hooks pushed to it never ran; callers that check for it (cache-sync announce, set-tenant-tier onAssigned, form-draft discard, seed hooks) now run inline there instead of being dropped.",
|
|
44
|
+
"migration": "to: { tenant } must be the tenant of the caller (user.tenantId of the handler or job context), otherwise notify throws; in a write handler the throw is synchronous. Seed-hook notifications in the signup transaction run after commit and return { kind: \"deferred\" } (immediate: true runs synchronously on the outside-transaction pool, which cannot see rows the signup transaction has not committed yet, so a tenant broadcast there finds no new member). No known consumer broadcasts to a foreign tenant."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"version": "0.362.0",
|
|
48
|
+
"type": "fix",
|
|
49
|
+
"title": "Client schema carries entity lockedWhen",
|
|
50
|
+
"detail": "buildAppSchema projects lockedWhen, so an entityEdit screen shows a locked record read-only without a screen-level readOnly."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"version": "0.362.0",
|
|
54
|
+
"type": "fix",
|
|
55
|
+
"title": "r.secret options with function values (redact) no longer fail to parse",
|
|
56
|
+
"detail": "r.secret(...) options that contain arrow or function expressions (for example redact: () => \"••••••••\") were reported as \"options could not be read as a plain object\", which turned the whole feature into a legacy corpus entry. The functions are now kept verbatim as raw references, option keys stay visible and a parse to render round-trip keeps them (multi-line function bodies may be re-indented). Function values in other patterns (entity, nav, metric, job) are still rejected on purpose.",
|
|
57
|
+
"migration": "Consumers that ship a checked-in few-shot corpus (Enterprise) should regenerate it on the next framework bump; its drift test enforces that."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"version": "0.362.0",
|
|
61
|
+
"type": "improvement",
|
|
62
|
+
"title": "Binding of referenced files only for the record creator's own uploads",
|
|
63
|
+
"detail": "files:write:bind-referenced-files now binds a file only if the record creator uploaded it (or the system generated it), so foreign references cannot expose private uploads."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"version": "0.362.0",
|
|
67
|
+
"type": "improvement",
|
|
68
|
+
"title": "files:write:bind-referenced-files binds a record's referenced files",
|
|
69
|
+
"detail": "System-only handler that binds the files a record references through its declared file fields (legacy uploads with entityId null, records created without a save lifecycle) so record readers can open them. Skips files bound elsewhere, of another tenant or missing. Callable from es-ops seeds via ctx.systemWriteAs; pages with limit and nextAfterId."
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"version": "0.362.0",
|
|
73
|
+
"type": "breaking",
|
|
74
|
+
"title": "Optional number fields in entity forms start empty instead of 0",
|
|
75
|
+
"migration": "An optional number field without default no longer starts at 0 in entityEdit create/edit, and an untouched one is not sent. If a screen relied on the implicit 0, declare default: 0 on the field."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"version": "0.362.0",
|
|
79
|
+
"type": "improvement",
|
|
80
|
+
"title": "unclaimedUploadReaders option for createFilesFeature",
|
|
81
|
+
"detail": "Roles listed in unclaimedUploadReaders may read (not delete) uploads that have no entityType and entityId, for documents referenced without a file field."
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"version": "0.362.0",
|
|
85
|
+
"type": "fix",
|
|
86
|
+
"title": "Query handlers may declare personalData public-intake",
|
|
87
|
+
"detail": "The personal-data write gate computed the public-intake exemption only for write handlers, so an anonymous query handler that wrote personal data (directly or through a nested write) was always rejected, and the boot validator refused access.personalData on query handlers. Query handlers now follow the same rules as write handlers: access.personalData \"public-intake\" requires the anonymous role and an active rateLimit (not disabled). openToAll.personalData stays write-only, stream handlers still cannot declare personalData.",
|
|
88
|
+
"migration": "No consumer change; anonymous query handlers that write personal data now declare access.personalData: \"public-intake\" instead of failing at the write gate."
|
|
89
|
+
},
|
|
2
90
|
{
|
|
3
91
|
"version": "0.361.0",
|
|
4
92
|
"type": "improvement",
|