@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.
Files changed (259) hide show
  1. package/AGENTS.md +307 -0
  2. package/agents/examples/access-control.md +355 -0
  3. package/agents/examples/anonymous-access-multitenant.md +443 -0
  4. package/agents/examples/anonymous-access.md +300 -0
  5. package/agents/examples/apex-landing.md +528 -0
  6. package/agents/examples/apex-surface-auth.md +355 -0
  7. package/agents/examples/api-worker-split.md +434 -0
  8. package/agents/examples/auth-claims.md +351 -0
  9. package/agents/examples/auth-foundation-providers.md +155 -0
  10. package/agents/examples/auth-mfa.md +323 -0
  11. package/agents/examples/basic-entity.md +420 -0
  12. package/agents/examples/boot-check.md +122 -0
  13. package/agents/examples/chat-channels.md +254 -0
  14. package/agents/examples/compliance-profiles-demo.md +307 -0
  15. package/agents/examples/cross-feature-events.md +317 -0
  16. package/agents/examples/crypto-shredding-hr.md +314 -0
  17. package/agents/examples/currencies-global.md +609 -0
  18. package/agents/examples/currencies-per-tenant.md +453 -0
  19. package/agents/examples/custom-fields-basic.md +314 -0
  20. package/agents/examples/custom-handlers.md +397 -0
  21. package/agents/examples/delivery-notifications.md +432 -0
  22. package/agents/examples/designer-demo.md +86 -0
  23. package/agents/examples/e2e-generator.md +211 -0
  24. package/agents/examples/embedded-entity-form.md +598 -0
  25. package/agents/examples/embedded.md +649 -0
  26. package/agents/examples/encrypted-tenant-config.md +405 -0
  27. package/agents/examples/error-contract.md +539 -0
  28. package/agents/examples/event-sourcing.md +1287 -0
  29. package/agents/examples/feature-toggles.md +263 -0
  30. package/agents/examples/field-access.md +271 -0
  31. package/agents/examples/files-post-processing.md +433 -0
  32. package/agents/examples/i18n.md +166 -0
  33. package/agents/examples/idempotency.md +179 -0
  34. package/agents/examples/inbound-mail-triage.md +315 -0
  35. package/agents/examples/lane-routing.md +246 -0
  36. package/agents/examples/ledger-banking.md +214 -0
  37. package/agents/examples/ledger-invoicing.md +235 -0
  38. package/agents/examples/legal-pages.md +393 -0
  39. package/agents/examples/lifecycle-hooks.md +368 -0
  40. package/agents/examples/managed-config.md +428 -0
  41. package/agents/examples/managed-pages.md +353 -0
  42. package/agents/examples/notes-history-basic.md +345 -0
  43. package/agents/examples/ownership.md +396 -0
  44. package/agents/examples/pdf-audit-export.md +270 -0
  45. package/agents/examples/pipeline-basics.md +792 -0
  46. package/agents/examples/public-share-token.md +351 -0
  47. package/agents/examples/rate-limiting.md +211 -0
  48. package/agents/examples/realtime-sse.md +187 -0
  49. package/agents/examples/record-detail-layout.md +297 -0
  50. package/agents/examples/reference-data.md +151 -0
  51. package/agents/examples/relations.md +280 -0
  52. package/agents/examples/screens-nav.md +353 -0
  53. package/agents/examples/search.md +233 -0
  54. package/agents/examples/secrets-demo.md +417 -0
  55. package/agents/examples/security-baseline.md +187 -0
  56. package/agents/examples/seed-migration.md +99 -0
  57. package/agents/examples/session-revocation.md +259 -0
  58. package/agents/examples/state-machine.md +920 -0
  59. package/agents/examples/tags-basic.md +401 -0
  60. package/agents/examples/tenant-isolation.md +173 -0
  61. package/agents/examples/tenant-settings.md +275 -0
  62. package/agents/examples/tier-admin.md +347 -0
  63. package/agents/examples/timezones.md +366 -0
  64. package/agents/examples/user-data-rights.md +410 -0
  65. package/agents/examples/user-profile.md +226 -0
  66. package/agents/examples/webhook-step.md +597 -0
  67. package/agents/examples/wizard-form.md +379 -0
  68. package/agents/examples/workflow-engine.md +1731 -0
  69. package/agents/examples/writeform-section.md +300 -0
  70. package/agents/examples.md +74 -0
  71. package/agents/features.md +768 -0
  72. package/agents/widgets.md +181 -0
  73. package/dist/api/auth-middleware.d.ts +2 -0
  74. package/dist/api/auth-middleware.js +14 -5
  75. package/dist/api/auth-routes.js +5 -3
  76. package/dist/api/extra-route.d.ts +3 -0
  77. package/dist/api/index.d.ts +2 -2
  78. package/dist/api/index.js +1 -1
  79. package/dist/api/redis-sse-broker.js +10 -8
  80. package/dist/api/routes.js +48 -19
  81. package/dist/api/server.d.ts +2 -1
  82. package/dist/api/server.js +18 -6
  83. package/dist/api/session-cookies.d.ts +1 -0
  84. package/dist/api/session-cookies.js +20 -5
  85. package/dist/api/sse-route.js +16 -1
  86. package/dist/bun-db/index.d.ts +1 -1
  87. package/dist/bun-db/index.js +1 -1
  88. package/dist/bun-db/query.d.ts +12 -5
  89. package/dist/bun-db/query.js +88 -94
  90. package/dist/crypto/kek-source.js +5 -3
  91. package/dist/crypto/pii-field-encryption.d.ts +4 -1
  92. package/dist/crypto/pii-field-encryption.js +2 -2
  93. package/dist/db/apply-entity-event.js +2 -2
  94. package/dist/db/collect-table-metas.js +11 -7
  95. package/dist/db/cursor.js +2 -1
  96. package/dist/db/dialect.d.ts +1 -0
  97. package/dist/db/dialect.js +28 -13
  98. package/dist/db/eagerload.js +2 -2
  99. package/dist/db/entity-table-from-registry.js +1 -1
  100. package/dist/db/entity-table-meta.js +5 -13
  101. package/dist/db/event-store-executor-context.d.ts +5 -6
  102. package/dist/db/event-store-executor-context.js +29 -27
  103. package/dist/db/event-store-executor-read.js +40 -25
  104. package/dist/db/event-store-executor-write.js +18 -20
  105. package/dist/db/index.d.ts +1 -1
  106. package/dist/db/index.js +1 -1
  107. package/dist/db/lock-guard.d.ts +4 -0
  108. package/dist/db/lock-guard.js +14 -0
  109. package/dist/db/migrate-generator.js +6 -4
  110. package/dist/db/migrate-runner.js +32 -35
  111. package/dist/db/parent-ref-clause.js +2 -2
  112. package/dist/db/queries/backfill-pii.js +11 -9
  113. package/dist/db/queries/es-ops.js +1 -1
  114. package/dist/db/queries/event-consumer.js +17 -17
  115. package/dist/db/queries/event-store-admin.js +6 -6
  116. package/dist/db/queries/event-store-transfer.js +2 -2
  117. package/dist/db/queries/event-store.js +2 -2
  118. package/dist/db/queries/pii-backfill-state.js +2 -2
  119. package/dist/db/queries/raw-sql.js +1 -1
  120. package/dist/db/queries/test-stack.js +1 -1
  121. package/dist/db/reference-data.js +3 -5
  122. package/dist/db/table-builder.js +6 -4
  123. package/dist/db/table-introspection.d.ts +16 -0
  124. package/dist/db/table-introspection.js +76 -0
  125. package/dist/db/tenant-db-runner.d.ts +4 -0
  126. package/dist/db/tenant-db-runner.js +17 -0
  127. package/dist/db/tenant-db.d.ts +5 -2
  128. package/dist/db/tenant-db.js +34 -14
  129. package/dist/derivatives/derivatives-context.js +1 -1
  130. package/dist/derivatives/variant-key.js +2 -1
  131. package/dist/engine/boot-validator/access-declarations.js +2 -2
  132. package/dist/engine/boot-validator/api-ext.js +3 -3
  133. package/dist/engine/boot-validator/boot-check.js +1 -1
  134. package/dist/engine/boot-validator/config-deps.js +13 -13
  135. package/dist/engine/boot-validator/detail-screens.js +1 -1
  136. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  137. package/dist/engine/boot-validator/entity-handler.js +84 -35
  138. package/dist/engine/boot-validator/entity-list-screens.js +3 -3
  139. package/dist/engine/boot-validator/handler-field-references.js +19 -9
  140. package/dist/engine/boot-validator/i18n-keys.js +2 -2
  141. package/dist/engine/boot-validator/nav.js +1 -1
  142. package/dist/engine/boot-validator/pii-retention.js +9 -9
  143. package/dist/engine/boot-validator/projection-list-screens.js +5 -5
  144. package/dist/engine/boot-validator/query-output-columns.js +7 -7
  145. package/dist/engine/boot-validator/screens.d.ts +2 -1
  146. package/dist/engine/boot-validator/screens.js +155 -96
  147. package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
  148. package/dist/engine/changeset-changes.js +3 -3
  149. package/dist/engine/define-handler.d.ts +2 -2
  150. package/dist/engine/define-handler.js +11 -7
  151. package/dist/engine/embedded-derived.js +10 -6
  152. package/dist/engine/extensions/escape-hatch-usage.js +2 -1
  153. package/dist/engine/factories.js +5 -6
  154. package/dist/engine/feature-ast/extractors/events.js +43 -33
  155. package/dist/engine/feature-ast/extractors/handlers.js +2 -0
  156. package/dist/engine/feature-ast/patcher.d.ts +3 -3
  157. package/dist/engine/feature-ast/patcher.js +3 -3
  158. package/dist/engine/feature-ast/pattern-change-schema.d.ts +10 -8
  159. package/dist/engine/feature-ast/pattern-change-schema.js +4 -3
  160. package/dist/engine/feature-ast/patterns.d.ts +3 -3
  161. package/dist/engine/feature-ast/render.js +16 -5
  162. package/dist/engine/feature-changelog.d.ts +2 -1
  163. package/dist/engine/feature-changelog.js +8 -6
  164. package/dist/engine/feature-config-events-jobs.d.ts +17 -7
  165. package/dist/engine/feature-config-events-jobs.js +20 -5
  166. package/dist/engine/feature-entity-handlers.d.ts +2 -2
  167. package/dist/engine/feature-entity-handlers.js +12 -3
  168. package/dist/engine/feature-ui-extensions.js +2 -3
  169. package/dist/engine/handler-field-references.d.ts +21 -2
  170. package/dist/engine/handler-field-references.js +55 -17
  171. package/dist/engine/index.d.ts +3 -2
  172. package/dist/engine/index.js +1 -0
  173. package/dist/engine/ownership.js +7 -20
  174. package/dist/engine/pattern-library/mixed-schemas.js +0 -1
  175. package/dist/engine/registry-ingest.d.ts +2 -1
  176. package/dist/engine/registry-ingest.js +85 -2
  177. package/dist/engine/registry-validate.d.ts +1 -0
  178. package/dist/engine/registry-validate.js +38 -8
  179. package/dist/engine/registry.js +2 -1
  180. package/dist/engine/schema-builder.js +3 -4
  181. package/dist/engine/soft-delete-cleanup.js +12 -4
  182. package/dist/engine/steps/call-feature.js +1 -3
  183. package/dist/engine/steps/retry.js +2 -1
  184. package/dist/engine/steps/unsafe-projection-delete.js +2 -2
  185. package/dist/engine/steps/unsafe-projection-upsert.js +4 -15
  186. package/dist/engine/template-variables.d.ts +32 -0
  187. package/dist/engine/template-variables.js +80 -0
  188. package/dist/engine/tier-resolver-extension.js +2 -3
  189. package/dist/engine/types/index.d.ts +2 -2
  190. package/dist/engine/validate-projection-allowlist.js +12 -13
  191. package/dist/engine/where-rule-lint.js +8 -7
  192. package/dist/env/_zod-introspect.d.ts +1 -0
  193. package/dist/errors/classes.js +1 -0
  194. package/dist/errors/kumiko-error.d.ts +2 -0
  195. package/dist/errors/kumiko-error.js +2 -0
  196. package/dist/errors/reasons.d.ts +1 -0
  197. package/dist/errors/reasons.js +2 -0
  198. package/dist/errors/write-error-info.js +2 -0
  199. package/dist/event-store/tenant-db-append.js +2 -2
  200. package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
  201. package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
  202. package/dist/files/bind-pending-file-refs.d.ts +2 -0
  203. package/dist/files/bind-pending-file-refs.js +3 -3
  204. package/dist/files/bind-referenced-files.write.d.ts +1 -1
  205. package/dist/files/bind-referenced-files.write.js +22 -13
  206. package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
  207. package/dist/files/create-file-ref-from-bytes.js +67 -4
  208. package/dist/files/create-file-ref.d.ts +22 -0
  209. package/dist/files/create-file-ref.js +53 -25
  210. package/dist/files/file-handle.d.ts +1 -1
  211. package/dist/files/file-handle.js +8 -0
  212. package/dist/files/file-routes.js +2 -2
  213. package/dist/files/index.d.ts +2 -2
  214. package/dist/files/index.js +1 -1
  215. package/dist/files/load-save-file-refs.d.ts +10 -0
  216. package/dist/files/load-save-file-refs.js +47 -0
  217. package/dist/files/local-provider.js +2 -1
  218. package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
  219. package/dist/files/purge-expired-file-ref-bytes.js +45 -19
  220. package/dist/i18n/index.d.ts +1 -1
  221. package/dist/i18n/index.js +1 -1
  222. package/dist/i18n/mail-registry.d.ts +2 -0
  223. package/dist/i18n/mail-registry.js +7 -0
  224. package/dist/jobs/index.d.ts +1 -1
  225. package/dist/jobs/job-runner.d.ts +4 -1
  226. package/dist/jobs/job-runner.js +13 -9
  227. package/dist/observability/escape-hatch-report.d.ts +1 -1
  228. package/dist/observability/escape-hatch-report.js +17 -5
  229. package/dist/pipeline/cascade-handler.js +5 -2
  230. package/dist/pipeline/dispatch-batch.js +79 -31
  231. package/dist/pipeline/dispatch-query.js +2 -3
  232. package/dist/pipeline/dispatch-shared.d.ts +7 -2
  233. package/dist/pipeline/dispatch-shared.js +142 -34
  234. package/dist/pipeline/dispatch-write.js +34 -22
  235. package/dist/pipeline/event-dispatcher-delivery.js +2 -3
  236. package/dist/pipeline/index.d.ts +2 -0
  237. package/dist/pipeline/index.js +2 -0
  238. package/dist/pipeline/member-read-only-transaction.js +1 -1
  239. package/dist/pipeline/notify-scope.d.ts +13 -0
  240. package/dist/pipeline/notify-scope.js +32 -0
  241. package/dist/pipeline/pre-transaction-reservations.d.ts +3 -1
  242. package/dist/pipeline/pre-transaction-reservations.js +28 -9
  243. package/dist/pipeline/system-hooks.js +2 -1
  244. package/dist/pipeline/system-identity-switch.js +2 -2
  245. package/dist/rate-limit/bucket.d.ts +3 -3
  246. package/dist/rate-limit/bucket.js +14 -4
  247. package/dist/redis/cache-sync-bus.js +9 -3
  248. package/dist/search/meilisearch-adapter.js +39 -13
  249. package/dist/secrets/stored-envelope.js +7 -7
  250. package/dist/stack/push-entity-projection-tables.js +2 -3
  251. package/dist/stack/table-helpers.js +10 -26
  252. package/dist/stack/test-stack.js +2 -5
  253. package/dist/testing/multipart-helper.d.ts +0 -6
  254. package/dist/testing/multipart-helper.js +5 -5
  255. package/dist/testing/without-ambient-temporal.js +4 -4
  256. package/dist/utils/is-plain-object.d.ts +1 -2
  257. package/dist/utils/is-plain-object.js +1 -4
  258. package/package.json +7 -5
  259. 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 (typeof previous !== "object" || previous === null)
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[key];
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[key];
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
- return value.trim().toLowerCase();
26
+ export function normalizePayloadBucketValue(value, normalize) {
27
+ const base = value.normalize("NFKC").trim().toLowerCase();
28
+ return normalize === "email" ? stripEmailPlusTag(base) : base;
28
29
  }
29
- export function buildPayloadBucketKey(handlerName, field, digest) {
30
- return `payload+handler:${handlerName}:${field}:${digest}`;
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: fanout.deliver,
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
- fanout.deliver(topic, message);
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 (typeof cause === "object" &&
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
- const fields = config.rankingFields ?? config.searchableFields;
59
- await awaitSucceededTask(index.updateSearchableAttributes([...fields]));
60
- await awaitSucceededTask(index.updateFilterableAttributes(["_type", "_weight"]));
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 or still carries Meilisearch's factory settings.
72
- async function indexNeedsDefaultConfig(tenantId) {
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
- return (searchable.length === 1 &&
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 || typeof value !== "object")
25
+ if (!isPlainObject(value))
25
26
  return false;
26
- const v = value; // @cast-boundary parse-boundary after typeof check
27
- return (typeof v["ciphertext"] === "string" &&
28
- typeof v["iv"] === "string" &&
29
- typeof v["authTag"] === "string" &&
30
- typeof v["encryptedDek"] === "string" &&
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 tableRec = proj.table;
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 rec = table;
13
- if (typeof rec[KUMIKO_NAME_SYMBOL] === "string")
14
- return rec[KUMIKO_NAME_SYMBOL];
15
- if (typeof rec.tableName === "string") {
16
- return rec.tableName;
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
- // table() spreads column handles as enumerable props, so a field named
49
- // `columns`/`tableName`/`source`/… shadows the matching meta key — read the
50
- // canonical meta from the unshadowable symbol when present.
51
- if (table !== null && typeof table === "object") {
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) {
@@ -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
- // @cast-boundary extraContext is declared Record<string, unknown> (late,
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 (typeof instance !== "object" || instance === null)
68
+ if (!isPlainObject(instance))
68
69
  return false;
69
- const f = instance;
70
- return (typeof f["name"] === "string" &&
71
- typeof f["size"] === "number" &&
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.Temporal;
7
- delete globalThis.Temporal;
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
- delete globalThis.Temporal;
13
+ Reflect.deleteProperty(globalThis, "Temporal");
14
14
  else
15
- globalThis.Temporal = savedGlobal;
15
+ Reflect.set(globalThis, "Temporal", savedGlobal);
16
16
  }
17
17
  }
@@ -1,2 +1 @@
1
- /** Non-null object that is not an array — shared guard for deep-merge and AST extractors. */
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
- /** Non-null object that is not an array — shared guard for deep-merge and AST extractors. */
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.362.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.362.0",
214
- "@cosmicdrift/kumiko-types": "0.362.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.362.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
  }