Skip to content

✨ Launch the ingestion feature - #208

Merged
KeKs0r merged 12 commits into
mainfrom
feat/ingestion-runtime
Sep 20, 2026
Merged

KeKs0r merged 12 commits into
mainfrom
feat/ingestion-runtime

Conversation

@KeKs0r

@KeKs0r KeKs0r commented Sep 19, 2026 •

Copy link
Copy Markdown
Member

Add scheduled API-to-ClickHouse ingestion as the optional TypeScript @chkit/plugin-ingest package. Readers yield destination-shaped rows, writes land before checkpoints advance, and failed runs resume from journaled progress with stable deduplication tokens. A later successful full sync starts a distinct batch identity cycle, so an A → B → A source update is not suppressed as a retry.

Changes

  • Add defineStream and exported definePipeline definitions, discovered through an opt-in project entry module. Only exported pipelines participate; there is no process-wide registry.
  • Add ingest run, list, and status, exact AND tag selection, isolated backfill namespaces, timestamp-window and provider-cursor strategies, and full syncs.
  • Add append-only checkpoint projection, p-retry with provider classification and Retry-After handling, p-limit for separate stream/fetch/load concurrency, bounded buffering, execution budgets, cancellation, and OpenTelemetry spans.
  • Reuse p-retry in the migration journal and Node promise timers in migration polling, backfills, auth polling, and CLI test helpers. Keep checkpoint sequencing and bounded streaming backpressure explicit instead of adding a general-purpose utils package.
  • Add rawTable/rawRows for native JSON landing tables, runtime ingestion metadata, and optional per-insert ClickHouse settings.
  • Cover replay, successful sync cycles, hung readers/writes/journal requests, late writes, retry exhaustion and vetoes, Retry-After, concurrency release during backoff, queued cancellation, graph discovery, and config layering with regression tests.
  • Document the API and its TypeScript-only scope alongside the current Python documentation from main.

Compatibility

  • Existing schema-glob configs and CLI runtime behavior require no migration. entry is optional and mutually exclusive with schema globs; resolved config.schema remains a string array. Existing insert calls do not need the new optional settings field.

  • TypeScript source caveat: ChxUserConfig.schema is now optional. External code reading that field from a value annotated as ChxUserConfig may need to normalize with resolveConfig(config).schema or handle undefined. This is not a blanket source-compatibility guarantee for consumers of the raw config type.

  • Ingestion requires a direct ClickHouse connection. Host-provided executors are rejected before writes because their contract does not guarantee JSON encoding and deduplication settings. This restriction applies to the new ingestion commands.

  • The new ingestion package and entry option are TypeScript-only; Python behavior is unchanged. The ingestion journal uses its existing table shape and checkpoint envelope; successful-cycle identity is projected from existing completion events.

  • The new ingestion retry callbacks use p-retry's native context plus classified FetchFailure errors. The unreleased custom retryDelay context field and ExecutionEnv.sleep/random test hooks are removed; p-retry owns timers and jitter. Existing released feature APIs are unchanged by this simplification.

Validation

  • 187 affected tests passed across ingestion, CLI runtime/config/migrations, backfill, and auth, including 13 new retry/concurrency cases.
  • 3 live tests passed against isolated ClickHouse 25.3: lost-acknowledgement replay, A → B → A raw-table updates across successful full syncs, and CLI migration journal naming.
  • All 10 workspace typecheck tasks and all 10 workspace lint tasks passed.
  • Full workspace build passed (11 tasks), including package declarations, example-manifest checks, and the documentation site.
  • git diff --check passed.
  • Hosted-service and multi-node test suites were not run locally.

Interval-based batch identity, validated journal history, enforced
execution deadline, abandonable readers, load permit per attempt,
serialized journal appends, stateless executor for concurrent work.
One write unit per declared source interval, poisoned append chain after
an unconfirmed fact, no progress commits from abandoned work, and
envelope-change validation in the journal projection.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-19T22:33:35.633836Z 69324c8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread packages/plugin-ingest/src/errors.ts Fixed
Comment thread packages/plugin-ingest/src/journal.ts Fixed
Comment thread packages/plugin-ingest/src/plugin.ts Fixed
Comment thread packages/plugin-ingest/src/registry.ts Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69324c84af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/plugin-ingest/src/executor.ts Outdated
// that changed between attempts.
let sinceBoundary = 0
for (let batch = await queue.pop(signal); batch !== undefined; batch = await queue.pop(signal)) {
const discriminator = batch.intervalIds ? `interval:${canonicalJson(batch.intervalIds)}` : `content:${canonicalJson(batch.rows)}`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support bigint values when deriving batch IDs

When a source yields rows containing JavaScript bigint values—for example for UInt64/Int64 columns—and does not declare an interval id, this call passes the rows to canonicalJson, whose JSON.stringify throws on bigint. The stream therefore fails before invoking its loader even though Row permits these values; serialize bigint deterministically or validate/narrow the supported row values.

Useful? React with 👍 / 👎.

Comment thread packages/plugin-ingest/src/errors.ts Outdated
signal: AbortSignal,
classifier: ErrorClassifier | undefined
): FailureClass {
if (signal.aborted || isAbortError(cause)) return { kind: 'cancelled' }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require the execution signal to classify cancellation

When a provider operation throws an AbortError from its own request-local timeout while the execution signal remains active, this unconditional check classifies it as executor cancellation before the provider classifier can handle it. executeStream then reports budget_exhausted and skips retries even though the execution budget was not exhausted; only treat the error as execution cancellation when the supplied signal is actually aborted.

Useful? React with 👍 / 👎.

Comment thread packages/plugin-ingest/src/executor.ts Outdated
await queue.push(pending, signal)
pending = emptyBatch()
}
if (stream.budget?.maxChunks !== undefined && progress.chunks >= stream.budget.maxChunks) budgetExhausted = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce maxChunks before recreating the reader

If loading a chunk fails after progress.chunks reaches maxChunks, the outer retry recreates the reader with a fresh budgetExhausted = false, and this limit is checked only after another chunk has already been pulled. Each reader retry can therefore fetch an additional chunk beyond the documented execution-wide maximum; check the accumulated count before calling iterator.next() or stop recreating the reader once the limit is reached.

Useful? React with 👍 / 👎.

Comment thread packages/plugin-ingest/src/retry.ts Fixed
@KeKs0r KeKs0r changed the title ✨ Add journaled pull ingestion for TypeScript ✨ Launch the ingestion feature Sep 19, 2026
@KeKs0r
KeKs0r merged commit f8238db into main Sep 20, 2026
7 checks passed
@KeKs0r
KeKs0r deleted the feat/ingestion-runtime branch September 20, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants