@cosmicdrift/kumiko-framework 0.362.0 → 0.364.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 +307 -0
- package/agents/examples/access-control.md +355 -0
- package/agents/examples/anonymous-access-multitenant.md +443 -0
- package/agents/examples/anonymous-access.md +300 -0
- package/agents/examples/apex-landing.md +528 -0
- package/agents/examples/apex-surface-auth.md +355 -0
- package/agents/examples/api-worker-split.md +434 -0
- package/agents/examples/auth-claims.md +351 -0
- package/agents/examples/auth-foundation-providers.md +155 -0
- package/agents/examples/auth-mfa.md +323 -0
- package/agents/examples/basic-entity.md +420 -0
- package/agents/examples/boot-check.md +122 -0
- package/agents/examples/chat-channels.md +254 -0
- package/agents/examples/compliance-profiles-demo.md +307 -0
- package/agents/examples/cross-feature-events.md +317 -0
- package/agents/examples/crypto-shredding-hr.md +314 -0
- package/agents/examples/currencies-global.md +609 -0
- package/agents/examples/currencies-per-tenant.md +453 -0
- package/agents/examples/custom-fields-basic.md +314 -0
- package/agents/examples/custom-handlers.md +397 -0
- package/agents/examples/delivery-notifications.md +432 -0
- package/agents/examples/designer-demo.md +86 -0
- package/agents/examples/e2e-generator.md +211 -0
- package/agents/examples/embedded-entity-form.md +598 -0
- package/agents/examples/embedded.md +649 -0
- package/agents/examples/encrypted-tenant-config.md +405 -0
- package/agents/examples/error-contract.md +539 -0
- package/agents/examples/event-sourcing.md +1287 -0
- package/agents/examples/feature-toggles.md +263 -0
- package/agents/examples/field-access.md +271 -0
- package/agents/examples/files-post-processing.md +433 -0
- package/agents/examples/i18n.md +166 -0
- package/agents/examples/idempotency.md +179 -0
- package/agents/examples/inbound-mail-triage.md +315 -0
- package/agents/examples/lane-routing.md +246 -0
- package/agents/examples/ledger-banking.md +214 -0
- package/agents/examples/ledger-invoicing.md +235 -0
- package/agents/examples/legal-pages.md +393 -0
- package/agents/examples/lifecycle-hooks.md +368 -0
- package/agents/examples/managed-config.md +428 -0
- package/agents/examples/managed-pages.md +353 -0
- package/agents/examples/notes-history-basic.md +345 -0
- package/agents/examples/ownership.md +396 -0
- package/agents/examples/pdf-audit-export.md +270 -0
- package/agents/examples/pipeline-basics.md +792 -0
- package/agents/examples/public-share-token.md +351 -0
- package/agents/examples/rate-limiting.md +211 -0
- package/agents/examples/realtime-sse.md +187 -0
- package/agents/examples/record-detail-layout.md +297 -0
- package/agents/examples/reference-data.md +151 -0
- package/agents/examples/relations.md +280 -0
- package/agents/examples/screens-nav.md +353 -0
- package/agents/examples/search.md +233 -0
- package/agents/examples/secrets-demo.md +417 -0
- package/agents/examples/security-baseline.md +187 -0
- package/agents/examples/seed-migration.md +99 -0
- package/agents/examples/session-revocation.md +259 -0
- package/agents/examples/state-machine.md +920 -0
- package/agents/examples/tags-basic.md +401 -0
- package/agents/examples/tenant-isolation.md +173 -0
- package/agents/examples/tenant-settings.md +275 -0
- package/agents/examples/tier-admin.md +347 -0
- package/agents/examples/timezones.md +366 -0
- package/agents/examples/user-data-rights.md +410 -0
- package/agents/examples/user-profile.md +226 -0
- package/agents/examples/webhook-step.md +597 -0
- package/agents/examples/wizard-form.md +379 -0
- package/agents/examples/workflow-engine.md +1731 -0
- package/agents/examples/writeform-section.md +300 -0
- package/agents/examples.md +74 -0
- package/agents/features.md +768 -0
- package/agents/widgets.md +181 -0
- package/dist/api/auth-middleware.d.ts +2 -0
- package/dist/api/auth-middleware.js +14 -5
- package/dist/api/auth-routes.js +5 -3
- package/dist/api/extra-route.d.ts +3 -0
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +1 -1
- package/dist/api/redis-sse-broker.js +10 -8
- package/dist/api/routes.js +48 -19
- package/dist/api/server.d.ts +2 -1
- package/dist/api/server.js +18 -6
- package/dist/api/session-cookies.d.ts +1 -0
- package/dist/api/session-cookies.js +20 -5
- package/dist/api/sse-route.js +16 -1
- package/dist/bun-db/index.d.ts +1 -1
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +12 -5
- package/dist/bun-db/query.js +88 -94
- 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/collect-table-metas.js +11 -7
- 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/eagerload.js +2 -2
- 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.d.ts +5 -6
- package/dist/db/event-store-executor-context.js +29 -27
- package/dist/db/event-store-executor-read.js +40 -25
- package/dist/db/event-store-executor-write.js +18 -20
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/lock-guard.d.ts +4 -0
- package/dist/db/lock-guard.js +14 -0
- package/dist/db/migrate-generator.js +6 -4
- package/dist/db/migrate-runner.js +32 -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.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.js +2 -2
- package/dist/db/queries/pii-backfill-state.js +2 -2
- 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-runner.d.ts +4 -0
- package/dist/db/tenant-db-runner.js +17 -0
- package/dist/db/tenant-db.d.ts +5 -2
- package/dist/db/tenant-db.js +34 -14
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/derivatives/variant-key.js +2 -1
- package/dist/engine/boot-validator/access-declarations.js +2 -2
- package/dist/engine/boot-validator/api-ext.js +3 -3
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +13 -13
- package/dist/engine/boot-validator/detail-screens.js +1 -1
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +84 -35
- package/dist/engine/boot-validator/entity-list-screens.js +3 -3
- package/dist/engine/boot-validator/handler-field-references.js +19 -9
- package/dist/engine/boot-validator/i18n-keys.js +2 -2
- package/dist/engine/boot-validator/nav.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +9 -9
- package/dist/engine/boot-validator/projection-list-screens.js +5 -5
- package/dist/engine/boot-validator/query-output-columns.js +7 -7
- package/dist/engine/boot-validator/screens.d.ts +2 -1
- package/dist/engine/boot-validator/screens.js +155 -96
- package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
- package/dist/engine/changeset-changes.js +3 -3
- package/dist/engine/define-handler.d.ts +2 -2
- package/dist/engine/define-handler.js +11 -7
- 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/handlers.js +2 -0
- 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 +10 -8
- package/dist/engine/feature-ast/pattern-change-schema.js +4 -3
- package/dist/engine/feature-ast/patterns.d.ts +3 -3
- package/dist/engine/feature-ast/render.js +16 -5
- 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-entity-handlers.d.ts +2 -2
- package/dist/engine/feature-entity-handlers.js +12 -3
- package/dist/engine/feature-ui-extensions.js +2 -3
- package/dist/engine/handler-field-references.d.ts +21 -2
- package/dist/engine/handler-field-references.js +55 -17
- package/dist/engine/index.d.ts +3 -2
- 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 +3 -4
- package/dist/engine/soft-delete-cleanup.js +12 -4
- package/dist/engine/steps/call-feature.js +1 -3
- package/dist/engine/steps/retry.js +2 -1
- package/dist/engine/steps/unsafe-projection-delete.js +2 -2
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -15
- package/dist/engine/template-variables.d.ts +32 -0
- package/dist/engine/template-variables.js +80 -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/env/_zod-introspect.d.ts +1 -0
- package/dist/errors/classes.js +1 -0
- package/dist/errors/kumiko-error.d.ts +2 -0
- package/dist/errors/kumiko-error.js +2 -0
- package/dist/errors/reasons.d.ts +1 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/errors/write-error-info.js +2 -0
- package/dist/event-store/tenant-db-append.js +2 -2
- package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
- package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
- package/dist/files/bind-pending-file-refs.d.ts +2 -0
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/bind-referenced-files.write.d.ts +1 -1
- package/dist/files/bind-referenced-files.write.js +22 -13
- package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
- package/dist/files/create-file-ref-from-bytes.js +67 -4
- package/dist/files/create-file-ref.d.ts +22 -0
- package/dist/files/create-file-ref.js +53 -25
- package/dist/files/file-handle.d.ts +1 -1
- package/dist/files/file-handle.js +8 -0
- package/dist/files/file-routes.js +2 -2
- package/dist/files/index.d.ts +2 -2
- package/dist/files/index.js +1 -1
- package/dist/files/load-save-file-refs.d.ts +10 -0
- package/dist/files/load-save-file-refs.js +47 -0
- package/dist/files/local-provider.js +2 -1
- package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
- package/dist/files/purge-expired-file-ref-bytes.js +45 -19
- 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/index.d.ts +1 -1
- package/dist/jobs/job-runner.d.ts +4 -1
- package/dist/jobs/job-runner.js +13 -9
- package/dist/observability/escape-hatch-report.d.ts +1 -1
- package/dist/observability/escape-hatch-report.js +17 -5
- package/dist/pipeline/cascade-handler.js +5 -2
- package/dist/pipeline/dispatch-batch.js +79 -31
- package/dist/pipeline/dispatch-query.js +2 -3
- package/dist/pipeline/dispatch-shared.d.ts +7 -2
- package/dist/pipeline/dispatch-shared.js +142 -34
- package/dist/pipeline/dispatch-write.js +34 -22
- package/dist/pipeline/event-dispatcher-delivery.js +2 -3
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +2 -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/pre-transaction-reservations.d.ts +3 -1
- package/dist/pipeline/pre-transaction-reservations.js +28 -9
- package/dist/pipeline/system-hooks.js +2 -1
- package/dist/pipeline/system-identity-switch.js +2 -2
- package/dist/rate-limit/bucket.d.ts +3 -3
- package/dist/rate-limit/bucket.js +14 -4
- package/dist/redis/cache-sync-bus.js +9 -3
- package/dist/search/meilisearch-adapter.js +39 -13
- 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/utils/is-plain-object.d.ts +1 -2
- package/dist/utils/is-plain-object.js +1 -4
- package/package.json +7 -5
- package/src/changes.json +251 -8
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
1
2
|
import { readNonEmptyStringArray, } from "../api/sse-broker.js";
|
|
2
3
|
import { collectSearchableSubjectFields, configuredPiiSubjectKms, decryptPiiFieldValues, isPiiCiphertext, PII_ERASED_SENTINEL, } from "../crypto/index.js";
|
|
3
4
|
import { createTenantDb, entityTableFromRegistry, rehydrateCompoundTypes } from "../db/index.js";
|
|
@@ -520,7 +521,7 @@ const TENANT_MEMBERSHIP_UPDATED_EVENT_TYPE = "tenant-membership.updated";
|
|
|
520
521
|
const TENANT_MEMBERSHIP_DELETED_EVENT_TYPE = "tenant-membership.deleted";
|
|
521
522
|
function readUserIdFromPreviousSnapshot(payload) {
|
|
522
523
|
const previous = payload["previous"];
|
|
523
|
-
if (
|
|
524
|
+
if (!isPlainObject(previous))
|
|
524
525
|
return undefined;
|
|
525
526
|
const userId = previous["userId"];
|
|
526
527
|
return typeof userId === "string" && userId.length > 0 ? userId : undefined;
|
|
@@ -124,14 +124,14 @@ export function createGatedProjectionReader(callerLabel, hasGrant, ungated, audi
|
|
|
124
124
|
function readIdentitySwitchFn(context, key) {
|
|
125
125
|
if (!(key in context))
|
|
126
126
|
return undefined;
|
|
127
|
-
const value = context
|
|
127
|
+
const value = Reflect.get(context, key);
|
|
128
128
|
return typeof value === "function" ? value : undefined; // @cast-boundary engine-bridge — checked via typeof above
|
|
129
129
|
}
|
|
130
130
|
// context's own keys only — never touch a property of the resolved value, which may be a Proxy that throws on any get.
|
|
131
131
|
function readDbLikeValue(context, key) {
|
|
132
132
|
if (!(key in context))
|
|
133
133
|
return undefined;
|
|
134
|
-
const value = context
|
|
134
|
+
const value = Reflect.get(context, key);
|
|
135
135
|
return typeof value === "object" && value !== null ? value : undefined;
|
|
136
136
|
}
|
|
137
137
|
function unavailableIdentitySwitchFn(callerLabel, kind) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RateLimitOption, SessionUser } from "../engine/types/index.js";
|
|
1
|
+
import type { PayloadBucketNormalization, RateLimitOption, SessionUser } from "../engine/types/index.js";
|
|
2
2
|
export type BucketContext = {
|
|
3
3
|
readonly handlerName: string;
|
|
4
4
|
readonly user: SessionUser;
|
|
@@ -12,7 +12,7 @@ export type BucketResult = {
|
|
|
12
12
|
readonly reason: string;
|
|
13
13
|
};
|
|
14
14
|
export declare function buildBucketKey(option: RateLimitOption, ctx: BucketContext): BucketResult;
|
|
15
|
-
export declare function normalizePayloadBucketValue(value: string): string;
|
|
16
|
-
export declare function buildPayloadBucketKey(handlerName: string, field: string, digest: string): string;
|
|
15
|
+
export declare function normalizePayloadBucketValue(value: string, normalize?: PayloadBucketNormalization): string;
|
|
16
|
+
export declare function buildPayloadBucketKey(tenantId: string, handlerName: string, field: string, digest: string): string;
|
|
17
17
|
export declare function toPublicBucketName(bucket: string): string;
|
|
18
18
|
export declare function createPayloadDigest(masterSecret: string): (value: string) => string;
|
|
@@ -23,11 +23,21 @@ export function buildBucketKey(option, ctx) {
|
|
|
23
23
|
// Payload-field buckets: the value (e.g. a recipient address) is normalized
|
|
24
24
|
// and HMAC-hashed so Redis never holds it in plaintext.
|
|
25
25
|
const PAYLOAD_BUCKET_PURPOSE = "kumiko:rate-limit:payload-bucket";
|
|
26
|
-
export function normalizePayloadBucketValue(value) {
|
|
27
|
-
|
|
26
|
+
export function normalizePayloadBucketValue(value, normalize) {
|
|
27
|
+
const base = value.normalize("NFKC").trim().toLowerCase();
|
|
28
|
+
return normalize === "email" ? stripEmailPlusTag(base) : base;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Only the `+tag` suffix is dropped; provider-specific dot rules are not applied.
|
|
31
|
+
function stripEmailPlusTag(address) {
|
|
32
|
+
const at = address.lastIndexOf("@");
|
|
33
|
+
if (at === -1)
|
|
34
|
+
return address;
|
|
35
|
+
const local = address.slice(0, at);
|
|
36
|
+
const plus = local.indexOf("+");
|
|
37
|
+
return plus <= 0 ? address : `${local.slice(0, plus)}${address.slice(at)}`;
|
|
38
|
+
}
|
|
39
|
+
export function buildPayloadBucketKey(tenantId, handlerName, field, digest) {
|
|
40
|
+
return `payload+handler:${tenantId}:${handlerName}:${field}:${digest}`;
|
|
31
41
|
}
|
|
32
42
|
// Everything after the scope tag identifies the caller (IP, user/tenant id,
|
|
33
43
|
// auth target, payload digest). IPv6 addresses and handler names contain ":",
|
|
@@ -64,7 +64,12 @@ function createLocalFanout() {
|
|
|
64
64
|
export function createLocalCacheSyncBus() {
|
|
65
65
|
const fanout = createLocalFanout();
|
|
66
66
|
return {
|
|
67
|
-
publish
|
|
67
|
+
publish(topic, message, options) {
|
|
68
|
+
// skip: a single-process bus has no remote subscribers to reach
|
|
69
|
+
if (options?.remoteOnly)
|
|
70
|
+
return;
|
|
71
|
+
fanout.deliver(topic, message);
|
|
72
|
+
},
|
|
68
73
|
subscribe: fanout.subscribe,
|
|
69
74
|
onResync: fanout.onResync,
|
|
70
75
|
async close() { },
|
|
@@ -108,8 +113,9 @@ export function createCacheSyncBusOverSignal(signal, opts) {
|
|
|
108
113
|
}
|
|
109
114
|
});
|
|
110
115
|
return {
|
|
111
|
-
publish(topic, message) {
|
|
112
|
-
|
|
116
|
+
publish(topic, message, options) {
|
|
117
|
+
if (!options?.remoteOnly)
|
|
118
|
+
fanout.deliver(topic, message);
|
|
113
119
|
const envelope = { origin, topic, message };
|
|
114
120
|
signal.publish(opts.channel, envelope);
|
|
115
121
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorCodeOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
1
2
|
import { ErrorStatusCode, Meilisearch } from "meilisearch";
|
|
2
3
|
// meilisearch's waitTask() RESOLVES (never rejects) once the server task
|
|
3
4
|
// reaches a terminal state, even "failed" — a rejected doc (e.g. an id over
|
|
@@ -22,11 +23,30 @@ const REMOVE_TOLERATED_ERROR_CODES = [ErrorStatusCode.INDEX_NOT_FOUND];
|
|
|
22
23
|
const INDEX_LIST_PAGE_SIZE = 100;
|
|
23
24
|
// What an unconfigured Meilisearch index reports as searchableAttributes.
|
|
24
25
|
const MEILI_ALL_ATTRIBUTES = "*";
|
|
26
|
+
const DEFAULT_FILTERABLE_ATTRIBUTES = ["_type", "_weight"];
|
|
27
|
+
const DEFAULT_SORTABLE_ATTRIBUTES = ["_weight"];
|
|
28
|
+
function defaultSearchableAttributes(config) {
|
|
29
|
+
return config.rankingFields ?? config.searchableFields;
|
|
30
|
+
}
|
|
31
|
+
// Meilisearch may return granular filterable objects; those are never part of our default.
|
|
32
|
+
function isSubsetOf(stored, wanted) {
|
|
33
|
+
return stored.every((attribute) => typeof attribute === "string" && wanted.includes(attribute));
|
|
34
|
+
}
|
|
35
|
+
// A stored list that lies inside the current default and misses attributes is an
|
|
36
|
+
// older default that gained fields since; anything with a foreign attribute is
|
|
37
|
+
// an explicit configure() and stays untouched.
|
|
38
|
+
function isOutdatedDefault(stored, wanted) {
|
|
39
|
+
const pairs = [
|
|
40
|
+
[stored.searchable, wanted.searchable],
|
|
41
|
+
[stored.filterable, wanted.filterable],
|
|
42
|
+
[stored.sortable, wanted.sortable],
|
|
43
|
+
];
|
|
44
|
+
return (pairs.every(([have, want]) => isSubsetOf(have, want)) &&
|
|
45
|
+
pairs.some(([have, want]) => want.some((attribute) => !have.includes(attribute))));
|
|
46
|
+
}
|
|
25
47
|
function isIndexNotFound(err) {
|
|
26
|
-
const cause = typeof err === "object" && err !== null ? err.cause : null;
|
|
27
|
-
return (
|
|
28
|
-
cause !== null &&
|
|
29
|
-
cause.code === ErrorStatusCode.INDEX_NOT_FOUND);
|
|
48
|
+
const cause = typeof err === "object" && err !== null && "cause" in err ? err.cause : null;
|
|
49
|
+
return errorCodeOf(cause) === ErrorStatusCode.INDEX_NOT_FOUND;
|
|
30
50
|
}
|
|
31
51
|
// Exported for unit tests (index-name / primary-key shape) without a live Meili.
|
|
32
52
|
export function meilisearchTenantIndex(prefix, tenantId) {
|
|
@@ -55,10 +75,9 @@ export function createMeilisearchAdapter(options) {
|
|
|
55
75
|
}
|
|
56
76
|
async function applyConfig(tenantId, config) {
|
|
57
77
|
const index = client.index(meilisearchTenantIndex(prefix, tenantId));
|
|
58
|
-
|
|
59
|
-
await awaitSucceededTask(index.
|
|
60
|
-
await awaitSucceededTask(index.
|
|
61
|
-
await awaitSucceededTask(index.updateSortableAttributes(["_weight"]));
|
|
78
|
+
await awaitSucceededTask(index.updateSearchableAttributes([...defaultSearchableAttributes(config)]));
|
|
79
|
+
await awaitSucceededTask(index.updateFilterableAttributes([...DEFAULT_FILTERABLE_ATTRIBUTES]));
|
|
80
|
+
await awaitSucceededTask(index.updateSortableAttributes([...DEFAULT_SORTABLE_ATTRIBUTES]));
|
|
62
81
|
}
|
|
63
82
|
// Lazily configures a tenant's index off the default config on its first
|
|
64
83
|
// access, so callers that never call `configure` still get a filterable/
|
|
@@ -68,15 +87,22 @@ export function createMeilisearchAdapter(options) {
|
|
|
68
87
|
// Meili outage doesn't permanently poison the tenant for the process.
|
|
69
88
|
// The configured-tenants map is process memory only: after a restart a stored explicit
|
|
70
89
|
// configure() must not be overwritten by the registry default, so the default is applied only
|
|
71
|
-
// to an index that is missing
|
|
72
|
-
|
|
90
|
+
// to an index that is missing, carries Meilisearch's factory settings, or holds an older
|
|
91
|
+
// default that is a strict subset of the current one (fields were added by a deploy).
|
|
92
|
+
async function indexNeedsDefaultConfig(tenantId, config) {
|
|
73
93
|
try {
|
|
74
94
|
const settings = await client.index(meilisearchTenantIndex(prefix, tenantId)).getSettings();
|
|
75
95
|
const searchable = settings.searchableAttributes ?? [MEILI_ALL_ATTRIBUTES];
|
|
76
96
|
const filterable = settings.filterableAttributes ?? [];
|
|
77
|
-
|
|
97
|
+
const factorySettings = searchable.length === 1 &&
|
|
78
98
|
searchable[0] === MEILI_ALL_ATTRIBUTES &&
|
|
79
|
-
!filterable.includes("_type")
|
|
99
|
+
!filterable.includes("_type");
|
|
100
|
+
return (factorySettings ||
|
|
101
|
+
isOutdatedDefault({ searchable, filterable, sortable: settings.sortableAttributes ?? [] }, {
|
|
102
|
+
searchable: defaultSearchableAttributes(config),
|
|
103
|
+
filterable: DEFAULT_FILTERABLE_ATTRIBUTES,
|
|
104
|
+
sortable: DEFAULT_SORTABLE_ATTRIBUTES,
|
|
105
|
+
}));
|
|
80
106
|
}
|
|
81
107
|
catch (err) {
|
|
82
108
|
if (isIndexNotFound(err))
|
|
@@ -96,7 +122,7 @@ export function createMeilisearchAdapter(options) {
|
|
|
96
122
|
}
|
|
97
123
|
const config = defaultConfig;
|
|
98
124
|
await trackConfigure(tenantId, async () => {
|
|
99
|
-
if (await indexNeedsDefaultConfig(tenantId))
|
|
125
|
+
if (await indexNeedsDefaultConfig(tenantId, config))
|
|
100
126
|
await applyConfig(tenantId, config);
|
|
101
127
|
});
|
|
102
128
|
}
|
|
@@ -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
|
}
|
|
@@ -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.364.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.364.0",
|
|
214
|
+
"@cosmicdrift/kumiko-types": "0.364.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.364.0",
|
|
231
231
|
"bun-types": "^1.3.13",
|
|
232
232
|
"pino-pretty": "^13.1.3"
|
|
233
233
|
},
|
|
@@ -419,6 +419,8 @@
|
|
|
419
419
|
"src/changes.json",
|
|
420
420
|
"src/scripts/codemod",
|
|
421
421
|
"README.md",
|
|
422
|
-
"LICENSE"
|
|
422
|
+
"LICENSE",
|
|
423
|
+
"AGENTS.md",
|
|
424
|
+
"agents"
|
|
423
425
|
]
|
|
424
426
|
}
|