Showing the changes to scripts/migration/service-split-postgres-cli.ts (1 of 2 files). Show all
Unified · Split
| @@ -1,5 +1,6 @@ | |||
| 1 | /* oxlint-disable executor/no-error-constructor, executor/no-try-catch-or-throw -- boundary: one-shot migration CLI */ | 1 | /* oxlint-disable executor/no-error-constructor, executor/no-try-catch-or-throw -- boundary: one-shot migration CLI */ |
| 2 | 2 | ||
| 3 | import { createHash } from "node:crypto"; | ||
| 3 | import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; | 4 | import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; |
| 4 | import { resolve } from "node:path"; | 5 | import { resolve } from "node:path"; |
| 5 | import { pathToFileURL } from "node:url"; | 6 | import { pathToFileURL } from "node:url"; |
| @@ -220,14 +221,10 @@ const scrubJson = (value: unknown): unknown => { · 214 unmodified lines | |||
| 220 | return value; | 221 | return value; |
| 221 | }; | 222 | }; |
| 222 | 223 | ||
| 223 | const stableId = (...parts: readonly string[]): string => { | 224 | // sha256-based: a 31-bit hash collides around ~50k rows (observed in prod); |
| 224 | const text = parts.join("\u0000"); | 225 | // 96 bits keeps re-run determinism with no realistic collision risk. |
| 225 | let hash = 0; | 226 | const stableId = (...parts: readonly string[]): string => |
| 226 | for (let index = 0; index < text.length; index += 1) { | 227 | `service_split_${createHash("sha256").update(parts.join("\u0000")).digest("hex").slice(0, 24)}`; |
| 227 | hash = (hash * 31 + text.charCodeAt(index)) >>> 0; | ||
| 228 | } | ||
| 229 | return `service_split_${hash.toString(36)}`; | ||
| 230 | }; | ||
| 231 | 228 | ||
| 232 | const operationKeyPrefix = (integration: string): string => | 229 | const operationKeyPrefix = (integration: string): string => |
| 233 | `${operationStorageKey(integration, "").split(".").slice(0, 2).join(".")}.%`; | 230 | `${operationStorageKey(integration, "").split(".").slice(0, 2).join(".")}.%`; |
| @@ -280,8 +277,8 @@ export const applyOrg = async (sql: SqlClient, org: OrgPlan): Promise<void> => { · 46 unmodified lines | |||
| 280 | row.target.pluginId, | 277 | row.target.pluginId, |
| 281 | row.target.name, | 278 | row.target.name, |
| 282 | row.target.description, | 279 | row.target.description, |
| 283 | JSON.stringify(scrubJson(row.config)), | 280 | scrubJson(row.config), |
| 284 | row.healthCheck ? JSON.stringify(scrubJson(row.healthCheck)) : null, | 281 | row.healthCheck ? scrubJson(row.healthCheck) : null, |
| 285 | now, | 282 | now, |
| 286 | stableId("integration", org.tenant, row.target.slug), | 283 | stableId("integration", org.tenant, row.target.slug), |
| 287 | org.tenant, | 284 | org.tenant, |
| @@ -391,13 +388,11 @@ export const applyOrg = async (sql: SqlClient, org: OrgPlan): Promise<void> => { · 103 unmodified lines | |||
| 391 | params.push( | 388 | params.push( |
| 392 | integration.target.pluginId, | 389 | integration.target.pluginId, |
| 393 | operationStorageKey(integration.target.slug, toolName), | 390 | operationStorageKey(integration.target.slug, toolName), |
| 394 | JSON.stringify( | 391 | scrubJson({ |
| 395 | scrubJson({ | 392 | ...storageDataRecord(operation), |
| 396 | ...storageDataRecord(operation), | 393 | integration: integration.target.slug, |
| 397 | integration: integration.target.slug, | 394 | toolName, |
| 398 | toolName, | 395 | }), |
| 399 | }), | ||
| 400 | ), | ||
| 401 | operation.created_at, | 396 | operation.created_at, |
| 402 | now, | 397 | now, |
| 403 | stableId("operation", org.tenant, integration.target.slug, toolName), | 398 | stableId("operation", org.tenant, integration.target.slug, toolName), |