@columnus/shared 0.1.61 → 0.1.62

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 +1 @@
1
- {"version":3,"sources":["../src/fieldValueEqual.ts","../src/shareProposalDiff.ts"],"names":[],"mappings":"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","file":"shareProposalDiff.js","sourcesContent":["// Compare Convex field values for pending-edit diffing. Uses the same\n// JSON normalization as csv export so bigint/bytes compare consistently.\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s);\n}\n\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === \"bigint\") return `${value.toString()}n`;\n if (value instanceof Uint8Array) return { __bytes__: bytesToBase64(value) };\n return value;\n}\n\nfunction normalizeForCompare(value: unknown): unknown {\n if (value === undefined || value === null) return undefined;\n return value;\n}\n\nfunction stableJsonEqual(a: unknown, b: unknown): boolean {\n try {\n return JSON.stringify(a, jsonReplacer) === JSON.stringify(b, jsonReplacer);\n } catch {\n return false;\n }\n}\n\n/** True when two stored field values are equivalent (missing ≡ undefined). */\nexport function fieldValuesEqual(a: unknown, b: unknown): boolean {\n const na = normalizeForCompare(a);\n const nb = normalizeForCompare(b);\n if (Object.is(na, nb)) return true;\n if (na === undefined && nb === undefined) return true;\n\n const ta = typeof na;\n const tb = typeof nb;\n if (ta !== tb) {\n // bigint stored as string in some editors vs bigint on server\n if (ta === \"bigint\" && tb === \"string\") {\n const naBig = na as bigint;\n const nbStr = nb as string;\n return `${naBig.toString()}n` === nbStr || naBig.toString() === nbStr.replace(/n$/, \"\");\n }\n if (ta === \"string\" && tb === \"bigint\") {\n const naStr = na as string;\n const nbBig = nb as bigint;\n return `${nbBig.toString()}n` === naStr || nbBig.toString() === naStr.replace(/n$/, \"\");\n }\n return false;\n }\n\n if (ta === \"object\" && na !== null && nb !== null) {\n return stableJsonEqual(na, nb);\n }\n\n return false;\n}\n\n/** Drop patch keys whose values match the server row baseline. */\nexport function filterChangedPatchFields(\n patch: Record<string, unknown>,\n baseline: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(patch)) {\n if (!fieldValuesEqual(value, baseline?.[key])) out[key] = value;\n }\n return out;\n}\n","import { fieldValuesEqual } from \"./fieldValueEqual.ts\";\nimport type { ProposalFieldDiff, ProposalRecordFieldDiff } from \"./shareTypes.ts\";\n\nexport function computeProposalFieldDiff(\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n): ProposalFieldDiff[] {\n return Object.entries(fields)\n .filter(([field, after]) => !fieldValuesEqual(baseline?.[field], after))\n .map(([field, after]) => ({ field, before: baseline?.[field], after }));\n}\n\n/** Merge baseline + patch into the record a reviewer would see after accept. */\nexport function mergeProposedRecord(\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n op: \"insert\" | \"update\" | \"delete\",\n): Record<string, unknown> {\n if (op === \"delete\") return { ...(baseline ?? {}) };\n if (op === \"insert\") return { ...fields };\n return { ...(baseline ?? {}), ...fields };\n}\n\n/** Field names for full-record review: schema order when available, else baseline ∪ patch. */\nexport function proposalDiffFieldNames(\n tableFields: readonly { name: string }[] | undefined,\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n): string[] {\n if (tableFields && tableFields.length > 0) {\n return tableFields.map((field) => field.name);\n }\n const names = new Set<string>();\n for (const key of Object.keys(baseline ?? {})) {\n if (!key.startsWith(\"_\")) names.add(key);\n }\n for (const key of Object.keys(fields)) {\n if (!key.startsWith(\"_\")) names.add(key);\n }\n return [...names];\n}\n\n/**\n * Full-record field diffs in schema (or key) order, including unchanged fields.\n * `changed` mirrors patch membership / equality — same rules as computeProposalFieldDiff.\n */\nexport function computeProposalRecordDiff(\n fieldNames: readonly string[],\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n op: \"insert\" | \"update\" | \"delete\",\n): ProposalRecordFieldDiff[] {\n return fieldNames.map((field) => {\n const before = baseline?.[field];\n if (op === \"delete\") {\n return {\n field,\n before,\n after: undefined,\n changed: before !== undefined,\n };\n }\n const inPatch = Object.hasOwn(fields, field);\n const after = inPatch ? fields[field] : op === \"insert\" ? undefined : before;\n const changed = op === \"insert\" ? inPatch : inPatch && !fieldValuesEqual(before, fields[field]);\n return { field, before, after, changed };\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/fieldValueEqual.ts","../src/shareProposalDiff.ts"],"names":[],"mappings":"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","file":"shareProposalDiff.js","sourcesContent":["// Compare Convex field values for pending-edit diffing. Uses the same\n// JSON normalization as csv export so bigint/bytes compare consistently.\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let s = \"\";\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s);\n}\n\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === \"bigint\") return `${value.toString()}n`;\n if (value instanceof Uint8Array) return { __bytes__: bytesToBase64(value) };\n return value;\n}\n\nfunction normalizeForCompare(value: unknown): unknown {\n if (value === undefined || value === null) return undefined;\n return value;\n}\n\nfunction stableJsonEqual(a: unknown, b: unknown): boolean {\n try {\n return JSON.stringify(a, jsonReplacer) === JSON.stringify(b, jsonReplacer);\n } catch {\n return false;\n }\n}\n\n/** True when two stored field values are equivalent (missing ≡ undefined). */\nexport function fieldValuesEqual(a: unknown, b: unknown): boolean {\n const na = normalizeForCompare(a);\n const nb = normalizeForCompare(b);\n if (Object.is(na, nb)) return true;\n if (na === undefined && nb === undefined) return true;\n\n const ta = typeof na;\n const tb = typeof nb;\n if (ta !== tb) {\n // bigint stored as string in some editors vs bigint on server\n if (ta === \"bigint\" && tb === \"string\") {\n const naBig = na as bigint;\n const nbStr = nb as string;\n return `${naBig.toString()}n` === nbStr || naBig.toString() === nbStr.replace(/n$/, \"\");\n }\n if (ta === \"string\" && tb === \"bigint\") {\n const naStr = na as string;\n const nbBig = nb as bigint;\n return `${nbBig.toString()}n` === naStr || nbBig.toString() === naStr.replace(/n$/, \"\");\n }\n return false;\n }\n\n if (ta === \"object\" && na !== null && nb !== null) {\n return stableJsonEqual(na, nb);\n }\n\n return false;\n}\n\n/** Drop patch keys whose values match the server row baseline. */\nexport function filterChangedPatchFields(\n patch: Record<string, unknown>,\n baseline: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(patch)) {\n if (!fieldValuesEqual(value, baseline?.[key])) out[key] = value;\n }\n return out;\n}\n\n/**\n * Strict structural equality for stored Convex values (Media metadata drafts, defaults,\n * automated writes). Unlike `fieldValuesEqual`, an explicit `null` differs from a missing\n * value. bigint and bytes compare by value, and object key order is ignored.\n */\nexport function convexValuesEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (a instanceof ArrayBuffer || b instanceof ArrayBuffer) {\n if (!(a instanceof ArrayBuffer && b instanceof ArrayBuffer)) return false;\n const left = new Uint8Array(a);\n const right = new Uint8Array(b);\n return left.length === right.length && left.every((byte, index) => byte === right[index]);\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false;\n return a.every((item, index) => convexValuesEqual(item, b[index]));\n }\n // Convex drops `undefined` fields, so they count as absent.\n const left = Object.entries(a).filter(([, value]) => value !== undefined);\n const right = b as Record<string, unknown>;\n return (\n left.length === Object.values(right).filter((value) => value !== undefined).length &&\n left.every(([key, value]) => convexValuesEqual(value, right[key]))\n );\n}\n","import { fieldValuesEqual } from \"./fieldValueEqual.ts\";\nimport type { ProposalFieldDiff, ProposalRecordFieldDiff } from \"./shareTypes.ts\";\n\nexport function computeProposalFieldDiff(\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n): ProposalFieldDiff[] {\n return Object.entries(fields)\n .filter(([field, after]) => !fieldValuesEqual(baseline?.[field], after))\n .map(([field, after]) => ({ field, before: baseline?.[field], after }));\n}\n\n/** Merge baseline + patch into the record a reviewer would see after accept. */\nexport function mergeProposedRecord(\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n op: \"insert\" | \"update\" | \"delete\",\n): Record<string, unknown> {\n if (op === \"delete\") return { ...(baseline ?? {}) };\n if (op === \"insert\") return { ...fields };\n return { ...(baseline ?? {}), ...fields };\n}\n\n/** Field names for full-record review: schema order when available, else baseline ∪ patch. */\nexport function proposalDiffFieldNames(\n tableFields: readonly { name: string }[] | undefined,\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n): string[] {\n if (tableFields && tableFields.length > 0) {\n return tableFields.map((field) => field.name);\n }\n const names = new Set<string>();\n for (const key of Object.keys(baseline ?? {})) {\n if (!key.startsWith(\"_\")) names.add(key);\n }\n for (const key of Object.keys(fields)) {\n if (!key.startsWith(\"_\")) names.add(key);\n }\n return [...names];\n}\n\n/**\n * Full-record field diffs in schema (or key) order, including unchanged fields.\n * `changed` mirrors patch membership / equality — same rules as computeProposalFieldDiff.\n */\nexport function computeProposalRecordDiff(\n fieldNames: readonly string[],\n baseline: Record<string, unknown> | null | undefined,\n fields: Record<string, unknown>,\n op: \"insert\" | \"update\" | \"delete\",\n): ProposalRecordFieldDiff[] {\n return fieldNames.map((field) => {\n const before = baseline?.[field];\n if (op === \"delete\") {\n return {\n field,\n before,\n after: undefined,\n changed: before !== undefined,\n };\n }\n const inPatch = Object.hasOwn(fields, field);\n const after = inPatch ? fields[field] : op === \"insert\" ? undefined : before;\n const changed = op === \"insert\" ? inPatch : inPatch && !fieldValuesEqual(before, fields[field]);\n return { field, before, after, changed };\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columnus/shared",
3
- "version": "0.1.61",
3
+ "version": "0.1.62",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Columnus shared package (CMS install family)",