@cosmicdrift/kumiko-framework 0.16.0 → 0.19.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.
@@ -4,6 +4,44 @@ import type { SessionUser } from "../engine/types";
4
4
 
5
5
  export type BatchCommand = { type: string; payload: unknown };
6
6
 
7
+ type WireErrorBody = {
8
+ readonly code?: string;
9
+ readonly details?: {
10
+ readonly causeName?: string;
11
+ readonly causeMessage?: string;
12
+ readonly causeStack?: string;
13
+ };
14
+ };
15
+
16
+ function formatWriteFailure(type: string, body: unknown): string {
17
+ const parsed = body as {
18
+ isSuccess?: boolean;
19
+ error?: WireErrorBody | string;
20
+ };
21
+ const code =
22
+ (typeof parsed.error === "object" ? parsed.error?.code : undefined) ??
23
+ (typeof parsed.error === "string" ? parsed.error : "unknown");
24
+ const details =
25
+ typeof parsed.error === "object" && parsed.error?.details !== undefined
26
+ ? parsed.error.details
27
+ : undefined;
28
+ const causeMessage =
29
+ details && typeof details === "object" && "causeMessage" in details
30
+ ? String((details as { causeMessage?: unknown }).causeMessage ?? "")
31
+ : "";
32
+ const causeName =
33
+ details && typeof details === "object" && "causeName" in details
34
+ ? String((details as { causeName?: unknown }).causeName ?? "")
35
+ : "";
36
+ if (code === "internal_error" && (causeMessage || causeName)) {
37
+ return `Expected write "${type}" to succeed but got error: ${code} (${causeName}: ${causeMessage})`;
38
+ }
39
+ if (details !== undefined) {
40
+ return `Expected write "${type}" to succeed but got error: ${code} — ${JSON.stringify(details)}`;
41
+ }
42
+ return `Expected write "${type}" to succeed but got error: ${code}`;
43
+ }
44
+
7
45
  export type RequestHelper = {
8
46
  write: (
9
47
  type: string,
@@ -123,10 +161,7 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
123
161
  // follow the error-contract shape { error: { code, i18nKey, ... } } with
124
162
  // a 4xx/5xx status — no isSuccess flag. Detect either.
125
163
  if (body.isSuccess !== true) {
126
- const code =
127
- (typeof body.error === "object" ? body.error?.code : undefined) ??
128
- (typeof body.error === "string" ? body.error : "unknown");
129
- throw new Error(`Expected write "${type}" to succeed but got error: ${code}`);
164
+ throw new Error(formatWriteFailure(type, body));
130
165
  }
131
166
  return body.data as T; // @cast-boundary engine-bridge
132
167
  },
@@ -107,7 +107,7 @@ export async function unsafePushTables(
107
107
  if (!prevIdxNames.has(idx.name)) {
108
108
  const kind = idx.unique ? "UNIQUE INDEX" : "INDEX";
109
109
  const colList = idx.columns.map((c) => `"${c}"`).join(", ");
110
- await createIndexIfNotExists(db, kind, idx.name, meta.tableName, colList);
110
+ await createIndexIfNotExists(db, kind, idx.name, meta.tableName, colList, idx.whereSql);
111
111
  }
112
112
  }
113
113
  } else {
@@ -4,5 +4,5 @@ export { readPositiveIntEnv } from "./env-parse";
4
4
  export { generateId } from "./ids";
5
5
  export { isPlainObject } from "./is-plain-object";
6
6
  export { parseStringArrayJson } from "./parse-string-array-json";
7
- export { parseJsonOrThrow, parseJsonSafe } from "./safe-json";
7
+ export { parseJsonOrThrow, parseJsonSafe, stringifyJson } from "./safe-json";
8
8
  export { parseRoles } from "./serialization";
@@ -28,3 +28,22 @@ export function parseJsonOrThrow<T>(raw: string, context: string): T {
28
28
  throw new Error(`Invalid JSON in ${context}: ${msg}`);
29
29
  }
30
30
  }
31
+
32
+ /** JSON.stringify that survives BigInt / Temporal values from DB rows. */
33
+ export function stringifyJson(value: unknown): string {
34
+ return JSON.stringify(value, (_key, v) => {
35
+ if (typeof v === "bigint") {
36
+ const asNumber = Number(v);
37
+ if (
38
+ asNumber <= Number.MAX_SAFE_INTEGER &&
39
+ asNumber >= Number.MIN_SAFE_INTEGER &&
40
+ BigInt(asNumber) === v
41
+ ) {
42
+ return asNumber;
43
+ }
44
+ return v.toString();
45
+ }
46
+ if (v instanceof Temporal.Instant) return v.toString();
47
+ return v;
48
+ });
49
+ }