chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import { toError } from '@sim/utils/errors'
|
||||
import { z } from 'zod'
|
||||
|
||||
/**
|
||||
* Sleep for `ms` milliseconds, resolving early if `signal` aborts. Used by
|
||||
* destination retry/poll loops so cancelled drain runs do not hang waiting on
|
||||
* a `setTimeout` that ignores the abort signal.
|
||||
*/
|
||||
export function sleepUntilAborted(ms: number, signal: AbortSignal): Promise<void> {
|
||||
if (signal.aborted) return Promise.resolve()
|
||||
return new Promise((resolve) => {
|
||||
const onAbort = () => {
|
||||
clearTimeout(timeoutId)
|
||||
resolve()
|
||||
}
|
||||
const timeoutId = setTimeout(() => {
|
||||
signal.removeEventListener('abort', onAbort)
|
||||
resolve()
|
||||
}, ms)
|
||||
signal.addEventListener('abort', onAbort, { once: true })
|
||||
})
|
||||
}
|
||||
|
||||
export function normalizePrefix(raw: string | undefined): string {
|
||||
if (!raw) return ''
|
||||
const trimmed = raw.replace(/^\/+/, '').replace(/\/+$/, '')
|
||||
return trimmed.length === 0 ? '' : `${trimmed}/`
|
||||
}
|
||||
|
||||
export interface ObjectKeyMetadata {
|
||||
drainId: string
|
||||
runId: string
|
||||
source: string
|
||||
sequence: number
|
||||
runStartedAt: Date
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a date-partitioned NDJSON object key for blob-store destinations.
|
||||
* Layout: `<prefix><source>/<drainId>/<YYYY>/<MM>/<DD>/<runId>-<seq>.ndjson`.
|
||||
* Partition uses the run's start time so all chunks from a run share one
|
||||
* date prefix even if delivery crosses a UTC midnight boundary.
|
||||
*/
|
||||
export function buildObjectKey(prefix: string | undefined, metadata: ObjectKeyMetadata): string {
|
||||
const partition = metadata.runStartedAt
|
||||
const yyyy = partition.getUTCFullYear().toString().padStart(4, '0')
|
||||
const mm = (partition.getUTCMonth() + 1).toString().padStart(2, '0')
|
||||
const dd = partition.getUTCDate().toString().padStart(2, '0')
|
||||
const seq = metadata.sequence.toString().padStart(5, '0')
|
||||
return `${normalizePrefix(prefix)}${metadata.source}/${metadata.drainId}/${yyyy}/${mm}/${dd}/${metadata.runId}-${seq}.ndjson`
|
||||
}
|
||||
|
||||
export interface ParsedServiceAccount {
|
||||
clientEmail: string
|
||||
privateKey: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Google service-account JSON key, returning the only two fields
|
||||
* that destinations need (client email + private key). Shared by GCS and
|
||||
* BigQuery so a fix in one place applies to both.
|
||||
*/
|
||||
export function parseServiceAccount(json: string): ParsedServiceAccount {
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(json)
|
||||
} catch (error) {
|
||||
throw new Error(`serviceAccountJson is not valid JSON: ${toError(error).message}`)
|
||||
}
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
throw new Error('serviceAccountJson must be a JSON object')
|
||||
}
|
||||
const obj = parsed as Record<string, unknown>
|
||||
const clientEmail = obj.client_email
|
||||
const privateKey = obj.private_key
|
||||
if (typeof clientEmail !== 'string' || clientEmail.length === 0) {
|
||||
throw new Error('serviceAccountJson is missing client_email')
|
||||
}
|
||||
if (typeof privateKey !== 'string' || privateKey.length === 0) {
|
||||
throw new Error('serviceAccountJson is missing private_key')
|
||||
}
|
||||
return { clientEmail, privateKey }
|
||||
}
|
||||
|
||||
/**
|
||||
* Zod `superRefine` helper that validates a service-account JSON key string
|
||||
* is parseable and carries `client_email` + `private_key`. Used by both
|
||||
* `gcsCredentialsSchema` and `bigqueryCredentialsSchema`.
|
||||
*/
|
||||
export interface ParseNdjsonObjectsOptions {
|
||||
/** When true, throw if a parsed value is not a plain object. */
|
||||
requireObject?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an NDJSON buffer into per-row JSON values. Error messages use
|
||||
* 1-indexed line numbers so they line up with how editors and `Content-Range`
|
||||
* headers reference NDJSON payloads.
|
||||
*/
|
||||
export function parseNdjsonObjects(
|
||||
body: Buffer,
|
||||
options: ParseNdjsonObjectsOptions = {}
|
||||
): unknown[] {
|
||||
const text = body.toString('utf8')
|
||||
const rows: unknown[] = []
|
||||
const lines = text.split(/\r?\n/)
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
if (line.length === 0) continue
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(line)
|
||||
} catch (error) {
|
||||
throw new Error(`NDJSON parse failed at line ${i + 1}: ${toError(error).message}`)
|
||||
}
|
||||
if (options.requireObject) {
|
||||
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
||||
throw new Error(`NDJSON row at line ${i + 1} is not an object`)
|
||||
}
|
||||
}
|
||||
rows.push(parsed)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
export function refineServiceAccountJson(
|
||||
value: { serviceAccountJson: string },
|
||||
ctx: z.RefinementCtx
|
||||
): void {
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(value.serviceAccountJson)
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['serviceAccountJson'],
|
||||
message: 'serviceAccountJson must be valid JSON',
|
||||
})
|
||||
return
|
||||
}
|
||||
if (typeof parsed !== 'object' || parsed === null) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['serviceAccountJson'],
|
||||
message: 'serviceAccountJson must be a JSON object',
|
||||
})
|
||||
return
|
||||
}
|
||||
const obj = parsed as Record<string, unknown>
|
||||
if (typeof obj.client_email !== 'string' || obj.client_email.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['serviceAccountJson'],
|
||||
message: 'serviceAccountJson is missing client_email',
|
||||
})
|
||||
}
|
||||
if (typeof obj.private_key !== 'string' || obj.private_key.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['serviceAccountJson'],
|
||||
message: 'serviceAccountJson is missing private_key',
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user