@cosmicdrift/kumiko-framework 0.339.0 → 0.340.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.
@@ -1,4 +1,4 @@
1
1
  export type { BunDbConnection, BunDbConnectionOptions, BunDbRunner, BunDbTx, PgListenClient, } from "./connection.js";
2
2
  export { bunDbConnectionOptionsFromEnv, createBunDbConnection } from "./connection.js";
3
- export type { SelectOptions, TableInfo, WhereObject, WhereOperator, WhereValue } from "./query.js";
4
- export { aggregateWhere, asEntityTableMeta, asRawClient, countWhere, type DeleteManyBatchedOptions, type DeleteManyBatchedResult, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, type IncrementCounterOptions, incrementCounter, insertMany, insertOne, isTimestamptzType, requireEntityTableMeta, runInSavepoint, runInSavepointIfSupported, selectMany, transaction, type UpsertOnConflictOptions, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, } from "./query.js";
3
+ export type { InnerJoinRow, InnerJoinSpec, InsertOnConflictDoNothingOptions, JoinColumnPair, JsonTextMatch, SelectOptions, TableInfo, WhereObject, WhereOperator, WhereValue, } from "./query.js";
4
+ export { aggregateWhere, asEntityTableMeta, asRawClient, countWhere, type DeleteManyBatchedOptions, type DeleteManyBatchedResult, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, type IncrementCounterOptions, incrementCounter, insertMany, insertOnConflictDoNothing, insertOne, isTimestamptzType, requireEntityTableMeta, runInSavepoint, runInSavepointIfSupported, selectInnerJoin, selectMany, transaction, type UpsertOnConflictOptions, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, } from "./query.js";
@@ -1,4 +1,4 @@
1
1
  // bun-db: Bun.sql-basierte DB-API ohne drizzle.
2
2
  // Production-Pfad nach drizzle-removal.
3
3
  export { bunDbConnectionOptionsFromEnv, createBunDbConnection } from "./connection.js";
4
- export { aggregateWhere, asEntityTableMeta, asRawClient, countWhere, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, incrementCounter, insertMany, insertOne, isTimestamptzType, requireEntityTableMeta, runInSavepoint, runInSavepointIfSupported, selectMany, transaction, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, } from "./query.js";
4
+ export { aggregateWhere, asEntityTableMeta, asRawClient, countWhere, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, incrementCounter, insertMany, insertOnConflictDoNothing, insertOne, isTimestamptzType, requireEntityTableMeta, runInSavepoint, runInSavepointIfSupported, selectInnerJoin, selectMany, transaction, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, } from "./query.js";
@@ -14,7 +14,7 @@ export declare function asRawClient(db: unknown): RawClient;
14
14
  export declare function runInSavepoint<T>(tx: unknown, fn: (sp: unknown) => Promise<T>): Promise<T>;
15
15
  export declare function runInSavepointIfSupported<T>(db: unknown, fn: (sp: DbRunner) => Promise<T>): Promise<T>;
16
16
  export type AnyDb = BunDbRunner | unknown;
17
- export type { OrderByClause, SelectOptions, WhereObject, WhereOperator, WhereValue, } from "@cosmicdrift/kumiko-types/where-clause-types";
17
+ export type { JsonTextMatch, OrderByClause, SelectOptions, WhereObject, WhereOperator, WhereValue, } from "@cosmicdrift/kumiko-types/where-clause-types";
18
18
  type TableLike = EntityTableMeta | any;
19
19
  type WritableTable = EntityTableMeta & NotExecutorOnly;
20
20
  export type TableInfo = {
@@ -32,6 +32,26 @@ export declare function describeClosedConnectionFailure(raw: unknown, err: unkno
32
32
  export declare function unsafeReadRetrying<TRow>(db: AnyDb, sqlText: string, params: readonly unknown[]): Promise<readonly TRow[]>;
33
33
  export declare function selectMany<TRow = any>(db: AnyDb, table: TableLike, where?: WhereObject, options?: SelectOptions): Promise<readonly TRow[]>;
34
34
  export declare function fetchOne<TRow = any>(db: AnyDb, table: TableLike, where: WhereObject): Promise<TRow | undefined>;
35
+ export type JoinColumnPair = {
36
+ readonly left: string;
37
+ readonly right: string;
38
+ };
39
+ export type InnerJoinSpec = {
40
+ readonly left: TableLike;
41
+ readonly right: TableLike;
42
+ readonly on: readonly [JoinColumnPair, ...JoinColumnPair[]];
43
+ readonly leftWhere?: WhereObject;
44
+ readonly rightWhere?: WhereObject;
45
+ /** Fields to return per side; default: all columns of `left`, none of `right`. */
46
+ readonly leftFields?: readonly string[];
47
+ readonly rightFields?: readonly string[];
48
+ readonly limit?: number;
49
+ };
50
+ export type InnerJoinRow<TLeft, TRight> = {
51
+ readonly left: TLeft;
52
+ readonly right: TRight;
53
+ };
54
+ export declare function selectInnerJoin<TLeft = Record<string, unknown>, TRight = Record<string, unknown>>(db: AnyDb, spec: InnerJoinSpec): Promise<readonly InnerJoinRow<TLeft, TRight>[]>;
35
55
  export declare function insertMany<TRow = any>(db: AnyDb, table: WritableTable, rows: ReadonlyArray<Record<string, unknown>>): Promise<readonly TRow[]>;
36
56
  export declare function insertOne<TRow = any>(db: AnyDb, table: WritableTable, values: Record<string, unknown>): Promise<TRow | undefined>;
37
57
  export declare function updateMany<TRow = any>(db: AnyDb, table: WritableTable, set: Record<string, unknown>, where: WhereObject): Promise<readonly TRow[]>;
@@ -57,6 +77,10 @@ export declare function isTimestamptzType(pgType: string | undefined): boolean;
57
77
  export declare function aggregateWhere(db: AnyDb, table: TableLike, spec: AggregateSpec, where?: WhereObject): Promise<readonly AggregateRow[]>;
58
78
  export declare function upsertOnConflict<TRow = any>(db: AnyDb, table: WritableTable, values: Record<string, unknown>, options: UpsertOnConflictOptions): Promise<TRow | undefined>;
59
79
  export declare function upsertByPk<TRow = any>(db: AnyDb, table: WritableTable, values: Record<string, unknown>, updateOnConflict?: Record<string, unknown>): Promise<TRow | undefined>;
80
+ export type InsertOnConflictDoNothingOptions = {
81
+ readonly conflictKeys?: readonly string[];
82
+ };
83
+ export declare function insertOnConflictDoNothing<TRow = any>(db: AnyDb, table: WritableTable, values: Record<string, unknown>, options?: InsertOnConflictDoNothingOptions): Promise<TRow | undefined>;
60
84
  export type IncrementCounterOptions = {
61
85
  readonly conflictKeys?: readonly string[];
62
86
  /** Extra SET clauses on conflict (e.g. lastUpdatedAt: sql\`NOW()\`). */
@@ -179,7 +179,7 @@ function assertNotTenantScoped(db, fnName) {
179
179
  "explicit cross-tenant op, or use a scoped TenantDb method.");
180
180
  }
181
181
  }
182
- const WHERE_OPERATOR_KEYS = ["gt", "gte", "lt", "lte", "ne", "in", "like"];
182
+ const WHERE_OPERATOR_KEYS = ["gt", "gte", "lt", "lte", "ne", "in", "like", "jsonText"];
183
183
  const _whereOperatorKeysExhaustive = true;
184
184
  void _whereOperatorKeysExhaustive;
185
185
  function isWhereOperator(v) {
@@ -483,10 +483,13 @@ function prepareValue(value, pgType) {
483
483
  }
484
484
  return { kind: "param", sql: "", bound: value };
485
485
  }
486
- function buildWhereClause(info, where, startIndex) {
486
+ function buildWhereClause(info, where, startIndex, qualifier) {
487
487
  const conditions = [];
488
488
  const values = [];
489
489
  let idx = startIndex;
490
+ // Single place that renders a column reference, so a join qualifier reaches
491
+ // every arm (blind index, jsonb containment, IS NULL, jsonText) alike.
492
+ const ref = (column) => qualifier === undefined ? quoteIdent(column) : `${quoteIdent(qualifier)}.${quoteIdent(column)}`;
490
493
  // multiSelect (and any other jsonb-array-of-scalars column) stores an
491
494
  // array — a filter value is one option, not the whole array, so eq/ne/in
492
495
  // must check array containment (`@>`) instead of scalar `=`/`<>`/`IN`
@@ -501,7 +504,7 @@ function buildWhereClause(info, where, startIndex) {
501
504
  // prepareJsonbValue never returns kind:"literal" (only prepareValue's
502
505
  // isSqlExpression branch does) — narrow anyway, PreparedValue is a union.
503
506
  if (p && p.kind === "param") {
504
- parts.push(`${quoteIdent(col)} @> $${idx++}${p.sql}`);
507
+ parts.push(`${ref(col)} @> $${idx++}${p.sql}`);
505
508
  values.push(p.bound);
506
509
  }
507
510
  }
@@ -514,7 +517,7 @@ function buildWhereClause(info, where, startIndex) {
514
517
  const col = info.columnOf(field);
515
518
  const pgType = info.pgTypeOf(col);
516
519
  if (value === null) {
517
- conditions.push(`${quoteIdent(col)} IS NULL`);
520
+ conditions.push(`${ref(col)} IS NULL`);
518
521
  }
519
522
  else if (Array.isArray(value)) {
520
523
  if (value.length === 0) {
@@ -535,12 +538,37 @@ function buildWhereClause(info, where, startIndex) {
535
538
  values.push(p.bound);
536
539
  }
537
540
  }
538
- conditions.push(`${quoteIdent(col)} IN (${parts.join(", ")})`);
541
+ conditions.push(`${ref(col)} IN (${parts.join(", ")})`);
539
542
  }
540
543
  }
541
544
  else if (isWhereOperator(value)) {
545
+ if (value.jsonText !== undefined) {
546
+ if (Object.keys(value).length > 1) {
547
+ throw new Error(`where: jsonText on column "${col}" cannot be combined with other operators`);
548
+ }
549
+ if (pgType !== "jsonb") {
550
+ throw new Error(`where: jsonText requires a jsonb column, "${col}" is ${pgType}`);
551
+ }
552
+ const matches = Array.isArray(value.jsonText) ? value.jsonText : [value.jsonText];
553
+ for (const match of matches) {
554
+ if (!Array.isArray(match.keys) ||
555
+ match.keys.length === 0 ||
556
+ !match.keys.every((k) => typeof k === "string") ||
557
+ typeof match.eq !== "string") {
558
+ throw new Error(`where: jsonText on column "${col}" needs non-empty string keys and a string eq`);
559
+ }
560
+ const lookups = match.keys.map((key) => {
561
+ values.push(key);
562
+ return `${ref(col)}->>$${idx++}::text`;
563
+ });
564
+ const lhs = lookups.length === 1 ? lookups[0] : `COALESCE(${lookups.join(", ")})`;
565
+ values.push(match.eq);
566
+ conditions.push(`(${lhs} = $${idx++})`);
567
+ }
568
+ continue;
569
+ }
542
570
  if (value.ne === null && Object.keys(value).length === 1) {
543
- conditions.push(`${quoteIdent(col)} IS NOT NULL`);
571
+ conditions.push(`${ref(col)} IS NOT NULL`);
544
572
  continue;
545
573
  }
546
574
  const opMap = {
@@ -564,17 +592,17 @@ function buildWhereClause(info, where, startIndex) {
564
592
  if (opKey === "ne" && pgType === "jsonb" && isJsonbScalar(opVal)) {
565
593
  const p = prepareJsonbValue([opVal]);
566
594
  if (p && p.kind === "param") {
567
- conditions.push(`(${quoteIdent(col)} IS NULL OR NOT (${quoteIdent(col)} @> $${idx++}${p.sql}))`);
595
+ conditions.push(`(${ref(col)} IS NULL OR NOT (${ref(col)} @> $${idx++}${p.sql}))`);
568
596
  values.push(p.bound);
569
597
  continue;
570
598
  }
571
599
  }
572
600
  const p = prepareValue(opVal, pgType);
573
601
  if (p.kind === "literal") {
574
- conditions.push(`${quoteIdent(col)} ${opSym} ${p.literal}`);
602
+ conditions.push(`${ref(col)} ${opSym} ${p.literal}`);
575
603
  }
576
604
  else {
577
- conditions.push(`${quoteIdent(col)} ${opSym} $${idx++}${p.sql}`);
605
+ conditions.push(`${ref(col)} ${opSym} $${idx++}${p.sql}`);
578
606
  values.push(p.bound);
579
607
  }
580
608
  }
@@ -598,7 +626,7 @@ function buildWhereClause(info, where, startIndex) {
598
626
  values.push(p.bound);
599
627
  }
600
628
  }
601
- conditions.push(`${quoteIdent(col)} IN (${parts.join(", ")})`);
629
+ conditions.push(`${ref(col)} IN (${parts.join(", ")})`);
602
630
  }
603
631
  }
604
632
  }
@@ -608,7 +636,7 @@ function buildWhereClause(info, where, startIndex) {
608
636
  else {
609
637
  const p = prepareValue(value, pgType);
610
638
  if (p.kind === "literal") {
611
- conditions.push(`${quoteIdent(col)} = ${p.literal}`);
639
+ conditions.push(`${ref(col)} = ${p.literal}`);
612
640
  }
613
641
  else {
614
642
  // Blind-index OR rewrite (#818): if the column has a bidx
@@ -623,11 +651,11 @@ function buildWhereClause(info, where, startIndex) {
623
651
  typeof value === "string" &&
624
652
  !col.endsWith("_bidx") &&
625
653
  info.hasColumn(bidxCol)) {
626
- conditions.push(`(${quoteIdent(col)} = $${idx++} OR ${quoteIdent(bidxCol)} = $${idx++})`);
654
+ conditions.push(`(${ref(col)} = $${idx++} OR ${ref(bidxCol)} = $${idx++})`);
627
655
  values.push(p.bound, computeBlindIndex(bidxKey, value));
628
656
  }
629
657
  else {
630
- conditions.push(`${quoteIdent(col)} = $${idx++}${p.sql}`);
658
+ conditions.push(`${ref(col)} = $${idx++}${p.sql}`);
631
659
  values.push(p.bound);
632
660
  }
633
661
  }
@@ -820,6 +848,76 @@ export async function fetchOne(db, table, where) {
820
848
  const rows = await selectMany(db, table, where, { limit: 1 });
821
849
  return rows[0];
822
850
  }
851
+ const JOIN_LEFT_ALIAS = "l";
852
+ const JOIN_RIGHT_ALIAS = "r";
853
+ function requireJoinColumn(info, field, role) {
854
+ if (!info.hasColumn(field)) {
855
+ throw new Error(`selectInnerJoin: ${role} "${field}" is not a column of "${info.name}"`);
856
+ }
857
+ return info.columnOf(field);
858
+ }
859
+ export async function selectInnerJoin(db, spec) {
860
+ assertNotTenantScoped(db, "selectInnerJoin");
861
+ const leftInfo = extractTableInfo(spec.left);
862
+ const rightInfo = extractTableInfo(spec.right);
863
+ if (spec.limit !== undefined && (!Number.isInteger(spec.limit) || spec.limit < 0)) {
864
+ throw new Error(`selectInnerJoin: limit must be a non-negative integer, got ${spec.limit}`);
865
+ }
866
+ const leftColumns = (spec.leftFields ??
867
+ requireEntityTableMeta(spec.left, "selectInnerJoin").columns.map((c) => c.name)).map((field) => requireJoinColumn(leftInfo, field, "leftFields entry"));
868
+ const rightColumns = (spec.rightFields ?? []).map((field) => requireJoinColumn(rightInfo, field, "rightFields entry"));
869
+ // Positional aliases: a `<side>__<column>` scheme would hit Postgres' 63-byte
870
+ // identifier truncation for long column names.
871
+ const projected = [
872
+ ...leftColumns.map((column) => ({ side: "left", column })),
873
+ ...rightColumns.map((column) => ({ side: "right", column })),
874
+ ];
875
+ if (projected.length === 0) {
876
+ throw new Error("selectInnerJoin: no columns selected");
877
+ }
878
+ const selectList = projected
879
+ .map(({ side, column }, index) => `${quoteIdent(side === "left" ? JOIN_LEFT_ALIAS : JOIN_RIGHT_ALIAS)}.${quoteIdent(column)} AS "c${index}"`)
880
+ .join(", ");
881
+ const onPairs = spec.on.map((pair) => {
882
+ const leftColumn = requireJoinColumn(leftInfo, pair.left, "on.left");
883
+ const rightColumn = requireJoinColumn(rightInfo, pair.right, "on.right");
884
+ const leftRef = `${quoteIdent(JOIN_LEFT_ALIAS)}.${quoteIdent(leftColumn)}`;
885
+ const rightRef = `${quoteIdent(JOIN_RIGHT_ALIAS)}.${quoteIdent(rightColumn)}`;
886
+ return leftInfo.pgTypeOf(leftColumn) === rightInfo.pgTypeOf(rightColumn)
887
+ ? `${leftRef} = ${rightRef}`
888
+ : `${leftRef}::text = ${rightRef}::text`;
889
+ });
890
+ let sqlText = `SELECT ${selectList} FROM ${quoteIdent(leftInfo.name)} AS ${quoteIdent(JOIN_LEFT_ALIAS)} ` +
891
+ `INNER JOIN ${quoteIdent(rightInfo.name)} AS ${quoteIdent(JOIN_RIGHT_ALIAS)} ON ${onPairs.join(" AND ")}`;
892
+ const conditions = [];
893
+ const values = [];
894
+ if (spec.leftWhere && Object.keys(spec.leftWhere).length > 0) {
895
+ const w = buildWhereClause(leftInfo, spec.leftWhere, values.length + 1, JOIN_LEFT_ALIAS);
896
+ conditions.push(w.sqlText);
897
+ values.push(...w.values);
898
+ }
899
+ if (spec.rightWhere && Object.keys(spec.rightWhere).length > 0) {
900
+ const w = buildWhereClause(rightInfo, spec.rightWhere, values.length + 1, JOIN_RIGHT_ALIAS);
901
+ conditions.push(w.sqlText);
902
+ values.push(...w.values);
903
+ }
904
+ if (conditions.length > 0)
905
+ sqlText += ` WHERE ${conditions.join(" AND ")}`;
906
+ if (spec.limit !== undefined)
907
+ sqlText += ` LIMIT ${spec.limit}`;
908
+ const raw = (await unsafeReadRetrying(db, sqlText, values));
909
+ return raw.map((row) => {
910
+ const leftRow = {};
911
+ const rightRow = {};
912
+ projected.forEach(({ side, column }, index) => {
913
+ (side === "left" ? leftRow : rightRow)[column] = row[`c${index}`];
914
+ });
915
+ return {
916
+ left: coerceRow(leftRow, leftInfo),
917
+ right: coerceRow(rightRow, rightInfo),
918
+ };
919
+ });
920
+ }
823
921
  // Bulk INSERT — same shape as insertOne but takes an array of rows and
824
922
  // produces one multi-VALUES statement. Mirrors drizzle's
825
923
  // `db.insert(t).values(rows[])`. Empty input is a no-op.
@@ -1064,15 +1162,20 @@ function assertValidAggregateLimits(spec) {
1064
1162
  if (spec.orderByValue !== undefined && !AGGREGATE_ORDER_DIRECTIONS.includes(spec.orderByValue)) {
1065
1163
  throw new Error(`aggregateWhere: unknown orderByValue "${String(spec.orderByValue)}"`);
1066
1164
  }
1165
+ if (spec.orderByKeys !== undefined && !AGGREGATE_ORDER_DIRECTIONS.includes(spec.orderByKeys)) {
1166
+ throw new Error(`aggregateWhere: unknown orderByKeys "${String(spec.orderByKeys)}"`);
1167
+ }
1067
1168
  }
1068
1169
  function aggregateGroupOrderLimitSql(spec, dimensionCount) {
1069
1170
  let sqlText = "";
1070
1171
  if (dimensionCount > 0) {
1071
1172
  const positions = Array.from({ length: dimensionCount }, (_, index) => String(index + 1));
1072
1173
  sqlText += ` GROUP BY ${positions.join(", ")}`;
1174
+ const keyDirection = spec.orderByKeys === "desc" ? "DESC" : "ASC";
1175
+ const keyParts = positions.map((position) => `${position} ${keyDirection}`);
1073
1176
  const orderParts = spec.orderByValue === undefined
1074
- ? positions
1075
- : [`${dimensionCount + 1} ${spec.orderByValue === "desc" ? "DESC" : "ASC"}`, ...positions];
1177
+ ? keyParts
1178
+ : [`${dimensionCount + 1} ${spec.orderByValue === "desc" ? "DESC" : "ASC"}`, ...keyParts];
1076
1179
  sqlText += ` ORDER BY ${orderParts.join(", ")}`;
1077
1180
  }
1078
1181
  if (spec.limit !== undefined)
@@ -1162,6 +1265,24 @@ export async function upsertByPk(db, table, values, updateOnConflict) {
1162
1265
  });
1163
1266
  }
1164
1267
  // biome-ignore lint/suspicious/noExplicitAny: see selectMany default
1268
+ export async function insertOnConflictDoNothing(db, table, values, options) {
1269
+ assertNotTenantScoped(db, "insertOnConflictDoNothing");
1270
+ const info = extractTableInfo(table);
1271
+ const entries = insertEntries(info, values);
1272
+ const conflictCols = resolveConflictColumns(table, info, options?.conflictKeys);
1273
+ const { sqlPrefix, params } = buildInsertSql(info, entries);
1274
+ const conflictList = conflictCols.map((c) => quoteIdent(c)).join(", ");
1275
+ const sqlText = `${sqlPrefix} ON CONFLICT (${conflictList}) DO NOTHING RETURNING *`;
1276
+ const rows = (await unsafeLoggingClosedConnection(db, sqlText, params, {
1277
+ operation: "insertOnConflictDoNothing",
1278
+ table: info.name,
1279
+ }));
1280
+ const first = rows[0];
1281
+ if (!first)
1282
+ return undefined;
1283
+ return coerceRow(first, info);
1284
+ }
1285
+ // biome-ignore lint/suspicious/noExplicitAny: see selectMany default
1165
1286
  export async function incrementCounter(db, table, values, increments, options = {}) {
1166
1287
  assertNotTenantScoped(db, "incrementCounter");
1167
1288
  const info = extractTableInfo(table);
@@ -1,2 +1,2 @@
1
- export type { SelectOptions, WhereObject, WhereOperator, WhereValue, } from "../db/query.js";
1
+ export type { JsonTextMatch, SelectOptions, WhereObject, WhereOperator, WhereValue, } from "../db/query.js";
2
2
  export { aggregateWhere, asRawClient, countWhere, deleteMany, fetchOne, insertMany, insertOne, runInSavepoint, selectMany, transaction, updateMany, } from "../db/query.js";
@@ -1 +1 @@
1
- export { type AnyDb, aggregateWhere, asEntityTableMeta, asRawClient, coerceRow, countWhere, type DeleteManyBatchedOptions, type DeleteManyBatchedResult, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, type IncrementCounterOptions, incrementCounter, insertMany, insertOne, type OrderByClause, runInNewTransaction, runInSavepoint, runInSavepointIfSupported, type SelectOptions, selectMany, type TableInfo, transaction, type UpsertOnConflictOptions, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, type WhereObject, type WhereOperator, type WhereValue, } from "../bun-db/query.js";
1
+ export { type AnyDb, aggregateWhere, asEntityTableMeta, asRawClient, coerceRow, countWhere, type DeleteManyBatchedOptions, type DeleteManyBatchedResult, deleteMany, deleteManyBatched, extractTableInfo, fetchOne, type IncrementCounterOptions, incrementCounter, insertMany, insertOne, type JsonTextMatch, type OrderByClause, runInNewTransaction, runInSavepoint, runInSavepointIfSupported, type SelectOptions, selectMany, type TableInfo, transaction, type UpsertOnConflictOptions, unsafeReadRetrying, updateMany, upsertByPk, upsertOnConflict, type WhereObject, type WhereOperator, type WhereValue, } from "../bun-db/query.js";
@@ -4,7 +4,7 @@
4
4
  // natural key + tenantId). Skips lifecycle hooks, field-access,
5
5
  // crypto-shredding, schema-versioning, audit-trail, read-access-log.
6
6
  // See "Was unsafeProjection.* überspringt" in
7
- // docs/plans/architecture/intern/step-vocabulary.md.
7
+ // kumiko-platform/docs/archive/plans/architecture/intern/step-vocabulary.md.
8
8
  //
9
9
  // Use only on tables explicitly declared via r.requires.projection in
10
10
  // the owning feature. Aggregate-tables (registered via r.entity) are
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.339.0",
3
+ "version": "0.340.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>",
@@ -206,8 +206,8 @@
206
206
  "./package.json": "./package.json"
207
207
  },
208
208
  "dependencies": {
209
- "@cosmicdrift/kumiko-http": "0.339.0",
210
- "@cosmicdrift/kumiko-types": "0.339.0",
209
+ "@cosmicdrift/kumiko-http": "0.340.0",
210
+ "@cosmicdrift/kumiko-types": "0.340.0",
211
211
  "bullmq": "^5.76.7",
212
212
  "bun-types": "^1.3.13",
213
213
  "hono": "^4.13.1",
@@ -223,7 +223,7 @@
223
223
  "zod": "^4.4.3"
224
224
  },
225
225
  "devDependencies": {
226
- "@cosmicdrift/kumiko-dispatcher-live": "0.339.0",
226
+ "@cosmicdrift/kumiko-dispatcher-live": "0.340.0",
227
227
  "bun-types": "^1.3.13",
228
228
  "pino-pretty": "^13.1.3"
229
229
  },
package/src/changes.json CHANGED
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.340.0",
4
+ "type": "improvement",
5
+ "title": "Typed bun-db helpers for insert-ignore, inner joins, jsonb text filters and key-ordered aggregates",
6
+ "detail": "`insertOnConflictDoNothing(db, table, values, { conflictKeys? })` returns the inserted row, or `undefined` when the conflict target already exists. `selectInnerJoin(db, { left, right, on, leftWhere?, rightWhere?, leftFields?, rightFields?, limit? })` joins two tables on one or more column pairs (casting to text only when the column types differ) and returns `{ left, right }` rows; `rightFields` defaults to none, so secrets on the joined table are only read when named. The where operator `jsonText: { keys, eq }` compares `col->>key` on jsonb columns, with several keys combined through `COALESCE`. `aggregateWhere` accepts `orderByKeys: \"asc\" | \"desc\"` to sort groups by their key before `limit`. All identifiers come from the table metadata and all values are bound parameters. The helpers refuse tenant-scoped tables like the other raw-connection helpers; use the `TenantDb` methods there.",
7
+ "migration": "keine"
8
+ },
2
9
  {
3
10
  "version": "0.339.0",
4
11
  "type": "improvement",