@cosmicdrift/kumiko-framework 0.12.2 → 0.14.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @cosmicdrift/kumiko-framework
2
2
 
3
+ ## 0.14.0
4
+
5
+ ## 0.13.0
6
+
7
+ ### Minor Changes
8
+
9
+ - 7f56b2f: **Framework**: add `JsonbFieldDef` + `createJsonbField()` primitive. Schema-less jsonb-Spalte (default `{}`, NOT NULL) für tenant-defined extension-data, AI-inferred metadata, free-form config-blobs. Vs. `embedded` (typed sub-schema): jsonb akzeptiert beliebige keys. Table-builder + schema-builder + e2e-generator alle aktualisiert.
10
+
11
+ **custom-fields-Bundle (B2)**: ergänzt B1 um Custom-Field-VALUES:
12
+
13
+ - `customField.set` + `customField.cleared` Event-Types (auf host-aggregate stream)
14
+ - `set-custom-field` + `clear-custom-field` write-handlers (emit events)
15
+ - `r.extendsRegistrar("customFields")` für consumer opt-in via `useExtension`
16
+ - `customFieldsField()` helper für entity-fields-definition
17
+ - `wireCustomFieldsFor(r, entityName, entityTable)` consumer-side-API registriert:
18
+ - `r.useExtension("customFields", entity)` opt-in marker
19
+ - MultiStreamProjection: customField.set/.cleared/fieldDefinition.deleted → UPDATE entityTable.customFields jsonb (jsonb_set / minus-operator)
20
+ - `r.entityHook("postQuery", entity, ...)` — flatten row.customFields auf API-root (Spec-Promise "indistinguishable von Stammfeldern")
21
+ - `r.searchPayloadExtension(entity, ...)` — customFields-keys flach ins Meilisearch-Index (F3 wiring)
22
+
23
+ **Out-of-B2** (future iterations): cross-scope-conflict (tenant override system fieldKey), cap-counter quota, user-data-rights anonymization, value-validation gegen fieldDefinition.serializedField, system+tenant UNION-read.
24
+
25
+ Part of custom-fields-bundle Sprint Phase B2 (Plan-Doc: kumiko-platform/docs/plans/custom-fields-sprint.md).
26
+
3
27
  ## 0.12.2
4
28
 
5
29
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.12.2",
3
+ "version": "0.14.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>",
@@ -172,7 +172,7 @@
172
172
  "zod": "^4.4.3"
173
173
  },
174
174
  "devDependencies": {
175
- "@cosmicdrift/kumiko-dispatcher-live": "0.12.2",
175
+ "@cosmicdrift/kumiko-dispatcher-live": "0.14.0",
176
176
  "@types/uuid": "^11.0.0",
177
177
  "bun-types": "^1.3.13",
178
178
  "drizzle-kit": "^0.31.10",
@@ -132,6 +132,11 @@ function fieldToColumns(
132
132
  // "Feld komplett weglassen können") modelliert das über ein
133
133
  // wrapper-feld mit boolean-flag oder discriminierte-union.
134
134
  return { [name]: jsonb(snakeName).default({}).notNull() };
135
+ case "jsonb":
136
+ // Free-form jsonb — keys nicht schema-validated. Default `{}`, NOT NULL
137
+ // (analog zu embedded). Use-case: custom-fields-Bundle's host-entity-
138
+ // `customFields`-Spalte (tenant-definierte dynamische keys).
139
+ return { [name]: jsonb(snakeName).default({}).notNull() };
135
140
  case "date": {
136
141
  // `type:"date"` aliased auf instant() = TIMESTAMPTZ. Echte
137
142
  // PlainDate-Migration (PG `date` Spalte, kein TZ) kommt später.
@@ -10,6 +10,7 @@ import type {
10
10
  FilesFieldDef,
11
11
  ImageFieldDef,
12
12
  ImagesFieldDef,
13
+ JsonbFieldDef,
13
14
  LocatedTimestampFieldDef,
14
15
  LongTextFieldDef,
15
16
  MoneyFieldDef,
@@ -159,6 +160,17 @@ export function createEmbeddedField(
159
160
  };
160
161
  }
161
162
 
163
+ // Free-form jsonb-Spalte — siehe `JsonbFieldDef`-Doku. Schema-less, default
164
+ // `{}`, NOT NULL. Hauptnutzer: custom-fields-Bundle (host-entity's
165
+ // `customFields`-Spalte). Andere valid uses: tenant-config-blobs, AI-
166
+ // inferred-metadata, future tags-arrays.
167
+ export function createJsonbField(overrides?: Partial<Omit<JsonbFieldDef, "type">>): JsonbFieldDef {
168
+ return {
169
+ type: "jsonb",
170
+ ...overrides,
171
+ };
172
+ }
173
+
162
174
  export function createDateField<R extends true | false = false>(
163
175
  overrides?: Partial<Omit<DateFieldDef, "type" | "required">> & { required?: R },
164
176
  ): DateFieldDef & { required: R } {
@@ -85,6 +85,7 @@ export {
85
85
  createFilesField,
86
86
  createImageField,
87
87
  createImagesField,
88
+ createJsonbField,
88
89
  createLocatedTimestampField,
89
90
  createLongTextField,
90
91
  createMoneyField,
@@ -252,6 +253,7 @@ export type {
252
253
  JobDefinition,
253
254
  JobHandlerFn,
254
255
  JobTrigger,
256
+ JsonbFieldDef,
255
257
  KumikoEntityTypeMap,
256
258
  KumikoEventTypeMap,
257
259
  KumikoHandlerPayloadMap,
@@ -84,6 +84,11 @@ function fieldToZod(field: FieldDefinition, currencies: readonly string[]): z.Zo
84
84
  }
85
85
  return z.object(shape);
86
86
  }
87
+ case "jsonb": {
88
+ // Free-form jsonb — keys sind tenant-/runtime-defined. Validation
89
+ // passthrough: any plain object passt durch.
90
+ return z.record(z.string(), z.unknown());
91
+ }
87
92
  case "date": {
88
93
  return z.string().date();
89
94
  }
@@ -302,6 +302,21 @@ export type EmbeddedFieldDef = {
302
302
  readonly access?: FieldAccess;
303
303
  } & PiiAnnotations;
304
304
 
305
+ // Free-form jsonb — keys/shape NOT validated at write-time. Use for:
306
+ // - Tenant-defined extension data (custom-fields-bundle uses this for
307
+ // `customFields` on host-entities — keys are dynamic per fieldDefinition)
308
+ // - Configuration-blobs with shape that evolves outside Stammfeld-schema
309
+ // - AI-inferred metadata where shape is provider-dependent
310
+ //
311
+ // Vs. embedded: embedded enforces a typed sub-schema; jsonb accepts any
312
+ // JSON-shaped object. Read-side both map to Postgres `jsonb`. Default `{}`
313
+ // + NOT NULL, identisch zu embedded.
314
+ export type JsonbFieldDef = {
315
+ readonly type: "jsonb";
316
+ readonly sensitive?: boolean;
317
+ readonly access?: FieldAccess;
318
+ } & PiiAnnotations;
319
+
305
320
  // Legacy "date" — JS-Date-Object, semantisch unklar (Wall-Clock vs Instant).
306
321
  // Für neue Felder bevorzuge:
307
322
  // - `timestamp` für UTC-Instant ("wann ist das passiert")
@@ -429,6 +444,7 @@ export type FieldDefinition =
429
444
  | MoneyFieldDef
430
445
  | ReferenceFieldDef
431
446
  | EmbeddedFieldDef
447
+ | JsonbFieldDef
432
448
  | DateFieldDef
433
449
  | TimestampFieldDef
434
450
  | TzFieldDef
@@ -88,6 +88,7 @@ export type {
88
88
  FilesFieldDef,
89
89
  ImageFieldDef,
90
90
  ImagesFieldDef,
91
+ JsonbFieldDef,
91
92
  LocatedTimestampFieldDef,
92
93
  LongTextFieldDef,
93
94
  MoneyFieldDef,
@@ -450,6 +450,9 @@ function fieldToFixture(name: string, field: FieldDefinition): unknown {
450
450
  }
451
451
  return sub;
452
452
  }
453
+ case "jsonb":
454
+ // Free-form jsonb — e2e-generator returns empty-object.
455
+ return {};
453
456
  case "file":
454
457
  case "image":
455
458
  case "files":