Skip to content

JavaScript: Utilities

kizunasync exports a handful of standalone functions, types, and constants beside the client Initializing returns. No other reference page names them, because the composed client already exposes the behavior most of them implement: an application reaches for one of these directly only when it assembles its own ITransfer, IEngineTransport, or ILogger instead of the shipped kizunasync/supabase and kizunasync/web adapters.

Examples#

// kizunasync
import {
  contentKey,
  createLogger,
  createRequestSignal,
  createSignedUrlDownload,
  createTransferTimeoutError,
  INTERNAL_TABLES,
  isArrayRemove,
  isArrayUnion,
  isIncrement,
  isJsonObject,
  mimeForExt,
  noopLogger,
  parseFailureEnvelope,
  readEngineJson,
  reReadsRows,
  SCHEMA,
  SIGNED_URL_TTL_SECONDS,
  TRACKERS,
  type IEngineLeadership,
  type ILoggerOptions,
  type TLogLevel,
} from 'kizunasync'

Bound a fetch by its own deadline#

// src/storage-transfer.ts
import { createRequestSignal } from 'kizunasync'

export async function fetchWithDeadline(url: string): Promise<Response> {
  const { signal, timedOut, release } = createRequestSignal({ timeoutMs: 30_000 })

  try {
    return await fetch(url, { signal })
  } catch (error) {
    throw timedOut() ? new Error(`${url} did not answer within 30 seconds`) : error
  } finally {
    release()
  }
}

Recover a failure that crossed postMessage as text#

// src/engine-host.ts
import { parseFailureEnvelope } from 'kizunasync'

export function rethrowEngineFailures(worker: Worker): void {
  worker.addEventListener('message', (event) => {
    const failure = parseFailureEnvelope(event.data as string)

    if (failure !== null) throw failure
  })
}

Share one logger with the Storage transfer#

// src/storage-transfer.ts
import { createLogger } from 'kizunasync'
import { createSupabaseTransfer } from 'kizunasync/supabase'
import { createWebFileStore } from 'kizunasync/web'
import { supabase } from './supabase-client'

const logger = createLogger({ level: 'warn' })
const fileStore = createWebFileStore()

export const transfer = createSupabaseTransfer({ client: supabase, fileStore, logger })

File paths and MIME types#

Two pure functions behind the attachment pipeline's file naming.

NameTypeRequiredDescription
contentKey(sha)(sha: string) => string—The content-addressed sandbox path for a payload hashed to sha: `content/${sha}`. Every IFileStore implementation in this tree keys a downloaded or attached file by this path.
mimeForExt(ext)(ext: string) => string | null—The MIME type for a file extension such as 'png' or 'pdf', null for an extension the table does not carry. The reverse mapping, which names an upload from its content type, stays internal to the attachment queue.

Field transform guards#

Type guards for the three sentinels increment, arrayUnion, and arrayRemove build, for code that narrows an unknown update value before branching on its shape.

NameTypeRequiredDescription
isIncrement(value)(value: unknown) => value is TIncrementSentinel—true for the sentinel increment() returns.
isArrayUnion(value)(value: unknown) => value is TArrayUnionSentinel—true for the sentinel arrayUnion() returns.
isArrayRemove(value)(value: unknown) => value is TArrayRemoveSentinel—true for the sentinel arrayRemove() returns.

Engine call parsing#

The guarded parse every JSON text crossing the engine boundary goes through, and the parser built on it for every bridge's { ok: true, value } | { ok: false, error } call envelope, exported for a host that receives that envelope as text rather than as a return value.

NameTypeRequiredDescription
isJsonObject(value)(value: unknown) => value is Record<string, unknown>—true for a JSON object: not null, not an array.
readEngineJson(raw, isShape)<T>(raw: string, isShape: (value: unknown) => value is T) => T—Parses raw and narrows the result with isShape. Text that is not JSON, or does not match isShape, throws the same TEngineError the engine itself throws for an unreadable envelope, coded JSON.
parseFailureEnvelope(raw)(raw: string) => TEngineError | null—Parses a failure envelope JSON string into the same TEngineError every bridge throws, or null when raw does not parse as that shape, or its failure carries no catalog code. createWebWorkerDriver uses it to turn a failure that crossed postMessage as text back into a typed error.

Query re-read events#

The filter behind React: useQuery and Vue: useQuery: which TEngineEvent types can change what a row read would return, so a subscriber re-reads only on those.

NameTypeRequiredDescription
reReadsRows(event)(event: TEngineEvent) => boolean—true for LOCAL_CHANGED, RESET_REQUIRED, and CHECKPOINT_EXPIRED, the events that carry a row change of their own or gate/rehydrate the local snapshot. Every other event either carries no row change (QUEUE_DEPTH, a verdict) or rides a LOCAL_CHANGED from the same commit (COLUMN_OVERWRITTEN).

Logging#

The factory behind the client's logging option, documented on Initializing, for an application that builds one logger and shares it across more than one adapter.

NameTypeRequiredDescription
createLogger(options?)(options?: ILoggerOptions) => ILogger—Returns options.logger verbatim when supplied, the built-in adze-backed logger for any level but 'silent', and noopLogger for 'silent' or when options is omitted.
noopLoggerILogger—The sink every method on it drops silently, .child() included, which returns itself. What createLogger() returns by default.
ILoggerOptionstype—{ level?: TLogLevel; logger?: ILogger }, the shape createLogger and the client's logging option share.
TLogLeveltype—'debug' | 'info' | 'warn' | 'error' | 'silent', also what createConsoleLogger takes.

Request and transfer deadlines#

Two pieces Wait with a deadline is built from, for an adapter that bounds one fetch call directly instead of racing a whole promise.

NameTypeRequiredDescription
createRequestSignal(options?)(options?: ICreateRequestSignalOptions) => IRequestSignal—Builds an AbortSignal that aborts on an options.timeoutMs deadline, on a caller's own options.parentSignal, or on both, so a catch block can read timedOut() to tell the two apart before calling release().
createTransferTimeoutError(options)(options: ICreateTransferTimeoutErrorOptions) => Error—Builds the retryable error a blown transfer deadline rejects with, coded ETransferError.timedOut. options.operation names the request ('upload', 'sign', 'download'); options.detail appends context in parentheses.

Signed URL downloads#

The download half of an ITransfer adapter, built once and reused: Create the Storage transfer calls it, and a host with its own Storage layer can call it directly.

NameTypeRequiredDescription
createSignedUrlDownload(options)(options: ICreateSignedUrlDownloadOptions) => ITransfer['download']—Signs the object's URL under options.controlTimeoutMs, fetches it under options.bytesTimeoutMs through the caller's own options.fetchBytes, and writes the bytes through options.fileStore. options.client types only the one call the function makes (storage.from(bucket).createSignedUrl), so this function carries no supabase-js type dependency. Rejects with ATTACHMENT_HASH_MISMATCH before writing when the download's own options named a sha256 and the fetched bytes hash to something else.

Multi-tab engine leadership#

The optional member on IEngineTransport a transport implements when it shares one engine across more than one host, such as the browser worker driver's tabs.

NameTypeRequiredDescription
IEngineLeadershipinterface—isLeader() reads whether this host currently drives the shared engine's automatic sync loop; every host's own calls still reach the engine regardless. subscribe(onChange) calls onChange(leader) on every change and returns the unsubscribe function. A transport with no leadership member always leads its own engine.

Server schema constants#

The Postgres schema name and object names kizunasync init provisions, exported so client code names them without repeating the literals the SQL pack installs.

NameTypeRequiredDescription
SCHEMAconst—'kizunasync', the Postgres schema every server-side Kizuna object lives under. createRpcRemote and createSupabaseTransfer pass it to client.schema(...) to reach that schema through PostgREST.
INTERNAL_TABLESconst—{ config: '_config', provisions: '_provisions', settings: '_settings' }, the bookkeeping table names, unqualified. Schema-qualify with SCHEMA.
TRACKERSconst—{ change: 'track_change', delete: 'track_delete' }, the change-capture routine base names: the SQL function is `${SCHEMA}.<name>()` and the per-table trigger is `${SCHEMA}_<name>`.
SIGNED_URL_TTL_SECONDSconst—60, the TTL in seconds createSignedUrlDownload requests for every signed download URL it mints.

Notes#

INTERNAL_TABLES, SCHEMA, and TRACKERS name what the SQL migration in packages/supabase-pack installs. Both sides are kept in lockstep by hand, because the migration is plain SQL and cannot import a TypeScript constant; a rename on one side needs the matching edit on the other.

Found a problem? Edit this page on GitHub