diff --git a/.github/workflows/.release-please-manifest.json b/.github/workflows/.release-please-manifest.json index 0ea4572..a024d7e 100644 --- a/.github/workflows/.release-please-manifest.json +++ b/.github/workflows/.release-please-manifest.json @@ -1 +1 @@ -{".":"0.21.4"} +{ ".": "0.21.4" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb96167..60630da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,10 @@ -name: Build and Publish to jsr +name: Build and Publish to npm on: release: types: [published] permissions: - id-token: write contents: read jobs: @@ -17,31 +16,29 @@ jobs: with: token: ${{ secrets.FLOWCORE_MACHINE_GITHUB_TOKEN }} submodules: true - - name: Extract version from deno.json + - name: Extract version from package.json uses: sergeysova/jq-action@v2 id: version with: - cmd: "jq .version deno.json -r" + cmd: "jq .version package.json -r" - name: Show my version run: 'echo "version ${{ steps.version.outputs.value }}"' - - name: Setup Deno2 environment - uses: denoland/setup-deno@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - deno-version: v2.x - - name: Deno install - run: deno install --frozen-lockfile + bun-version: 1.3.14 + - name: Install dependencies + run: bun install --frozen-lockfile - name: Lint - run: deno lint - - name: Publish to JSR - run: deno publish + run: bun run lint - name: Build for NPM - run: deno run -A bin/build-npm.ts - - uses: actions/setup-node@v4 + run: bun run build + - uses: useblacksmith/setup-node@v5 with: - node-version: '20.x' - registry-url: 'https://registry.npmjs.org' + node-version: "20.x" + registry-url: "https://registry.npmjs.org" - name: Update npm run: npm install -g npm@latest - name: Publish to NPM - run: ( cd npm && npm publish --access public --provenance=false ) + run: npm publish --access public --provenance=false diff --git a/.github/workflows/release-please-config.json b/.github/workflows/release-please-config.json index 4802e18..8dfa220 100644 --- a/.github/workflows/release-please-config.json +++ b/.github/workflows/release-please-config.json @@ -5,7 +5,7 @@ "extra-files": [ { "type": "json", - "path": "deno.json", + "path": "package.json", "jsonpath": "$.version" } ] diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index c46d890..3b83b15 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -4,7 +4,7 @@ on: - main env: - NODE_VERSION: ">=18.12.1" + BUN_VERSION: "1.3.14" name: release-please jobs: @@ -12,17 +12,17 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v3 - - name: Extract package name from deno.json + - name: Extract package name from package.json uses: sergeysova/jq-action@v2 id: package with: - cmd: "jq .name deno.json -r" - - name: Setup Deno2 environment - uses: denoland/setup-deno@v2 + cmd: "jq .name package.json -r" + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - deno-version: v2.x - - name: Deno install - run: deno install --frozen-lockfile + bun-version: ${{ env.BUN_VERSION }} + - name: Install dependencies + run: bun install --frozen-lockfile - name: Show package name run: 'echo "version ${{ steps.package.outputs.value }}"' - uses: googleapis/release-please-action@v4 @@ -30,4 +30,3 @@ jobs: token: ${{ secrets.FLOWCORE_MACHINE_GITHUB_TOKEN }} config-file: .github/workflows/release-please-config.json manifest-file: .github/workflows/.release-please-manifest.json - diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 91ec314..3e16201 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -5,10 +5,9 @@ on: branches-ignore: ["main"] env: - NODE_VERSION: ">=18.12.1" + BUN_VERSION: "1.3.14" permissions: - id-token: write contents: read jobs: @@ -20,29 +19,31 @@ jobs: with: token: ${{ secrets.FLOWCORE_MACHINE_GITHUB_TOKEN }} submodules: true - - name: Extract version from deno.json + - name: Extract version from package.json uses: sergeysova/jq-action@v2 id: version with: - cmd: "jq .version deno.json -r" + cmd: "jq .version package.json -r" - name: Show my version run: 'echo "version ${{ steps.version.outputs.value }}"' - - name: Setup Deno2 environment - uses: denoland/setup-deno@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - deno-version: v2.x - - name: Deno install - run: deno install --frozen-lockfile + bun-version: ${{ env.BUN_VERSION }} + - name: Install dependencies + run: bun install --frozen-lockfile - name: Lint - run: deno lint - - name: Format - run: deno fmt + run: bun run lint + - name: Format check + run: bun run format:check + - name: Type check + run: bun run typecheck - name: Test - run: deno test -A - - name: Validate NPM build - run: deno run -A bin/build-npm.ts + run: bun test + - name: Build + run: bun run build integration-test: runs-on: blacksmith-4vcpu-ubuntu-2204 @@ -52,29 +53,26 @@ jobs: with: token: ${{ secrets.FLOWCORE_MACHINE_GITHUB_TOKEN }} submodules: true - - name: Setup Deno2 - uses: denoland/setup-deno@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - deno-version: v2.x + bun-version: ${{ env.BUN_VERSION }} + - name: Install dependencies + run: bun install --frozen-lockfile - name: Install Docker run: | if ! command -v docker &>/dev/null; then curl -fsSL https://get.docker.com | sh fi docker version - - name: Install k3d - run: curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash - - name: Install kubectl - run: | - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" - chmod +x kubectl && sudo mv kubectl /usr/local/bin/ - name: Run integration tests run: bash integration/run-integration.sh - name: Collect logs on failure if: failure() run: | + export PATH="$PWD/.cache/bin:$PATH" kubectl logs -n data-pump-integration-test -l app=data-pump-test --tail=200 || true kubectl logs -n data-pump-integration-test -l app=postgres --tail=100 || true - name: Teardown if: always() - run: bash integration/scripts/teardown.sh || true + run: PATH="$PWD/.cache/bin:$PATH" bash integration/scripts/teardown.sh || true diff --git a/.gitignore b/.gitignore index 4c4a077..6adbdee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ tmp/ .env .DS_Store +.cache/ src/local/ npm/ +node_modules/ +dist/ coverage/ -src/test/ \ No newline at end of file +src/test/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9a0c4ef --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules +dist +CHANGELOG.md +handoff.md +bun.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..993f278 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "semi": false, + "trailingComma": "all" +} diff --git a/README.md b/README.md index ab17af5..d6d5bf0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ A reliable, high-performance TypeScript client for streaming and processing events from the Flowcore platform. Built for real-time event processing with automatic retry, buffering, and state management. -[![JSR](https://jsr.io/badges/@flowcore/data-pump)](https://jsr.io/@flowcore/data-pump) [![NPM Version](https://img.shields.io/npm/v/@flowcore/data-pump)](https://www.npmjs.com/package/@flowcore/data-pump) ## Simple Example Setup @@ -29,7 +28,8 @@ const dataPump = FlowcoreDataPump.create({ getState: () => null, // Start in live mode setState: (state) => console.log("Position:", state), }, - processor: { // use this for automatic event lifecycle management + processor: { + // use this for automatic event lifecycle management handler: async (events) => { console.log(`Processing ${events.length} events`) // Your event processing logic here @@ -70,7 +70,7 @@ Events are organized in **hourly time buckets** using the format `yyyyMMddHH0000 ``` 20240315140000 = March 15, 2024, 14:00 (2 PM) -20240315150000 = March 15, 2024, 15:00 (3 PM) +20240315150000 = March 15, 2024, 15:00 (3 PM) 20240315160000 = March 15, 2024, 16:00 (4 PM) ``` @@ -125,7 +125,7 @@ Fetch → Buffer → YOU Reserve → YOU Process → YOU Acknowledge/Fail Local **in-memory event queue** between fetching and processing: ```typescript -Buffer: [Event1, Event2, Event3, Event4, Event5] +Buffer: [Event1, Event2, Event3, Event4, Event5] ↑ ↑ Processing these Fetching more ``` @@ -201,9 +201,15 @@ Event fails → Retry 1 → Retry 2 → Retry 3 → Permanent failure ```typescript const dataPump = FlowcoreDataPump.create({ - auth: {/* auth config */}, - dataSource: {/* data source config */}, - stateManager: {/* state management */}, + auth: { + /* auth config */ + }, + dataSource: { + /* data source config */ + }, + stateManager: { + /* state management */ + }, processor: { concurrency: 5, handler: async (events) => { @@ -236,9 +242,15 @@ await dataPump.start() // Just start and it runs automatically! ```typescript const dataPump = FlowcoreDataPump.create({ - auth: {/* auth config */}, - dataSource: {/* data source config */}, - stateManager: {/* state management */}, + auth: { + /* auth config */ + }, + dataSource: { + /* data source config */ + }, + stateManager: { + /* state management */ + }, // ❌ No processor = manual mode }) @@ -256,9 +268,7 @@ while (dataPump.isRunning) { } // 2️⃣ YOU handle business logic with custom error handling - const results = await Promise.allSettled( - events.map((event) => processEvent(event)), - ) + const results = await Promise.allSettled(events.map((event) => processEvent(event))) // 3️⃣ YOU decide what succeeded vs failed const successfulIds = [] @@ -325,8 +335,8 @@ const oidc = oidcClient({ }) auth: { - getBearerToken: ; - ;(() => oidc.getToken().then((token) => token.accessToken)) + getBearerToken:; + ;() => oidc.getToken().then((token) => token.accessToken) } ``` @@ -399,14 +409,14 @@ const timestamp = date.getTime() // Unix timestamp let currentState = null; // Start in live mode // Or start from specific time: -// let currentState = { +// let currentState = { // timeBucket: "20240101000000", // January 1, 2024 00:00 // eventId: undefined // Start from first event in that hour // }; stateManager: { getState: () => currentState, - setState: (state) => { + setState: (state) => { currentState = state; console.log(`Processed up to: ${state.timeBucket} - ${state.eventId}`); } @@ -481,22 +491,22 @@ stateManager: { getState: async () => { try { const result = await db.query( - 'SELECT time_bucket, event_id FROM flowcore_pump_state WHERE id = ?', + 'SELECT time_bucket, event_id FROM flowcore_pump_state WHERE id = ?', ['main'] ); - + if (result.length === 0) { console.log('No previous state found, starting in live mode'); return null; } - + const state = { timeBucket: result[0].time_bucket, eventId: result[0].event_id }; console.log('Resuming from database state:', state); return state; - + } catch (error) { console.error('Failed to load state from database:', error); // Critical decision: start fresh or fail fast? @@ -504,18 +514,18 @@ stateManager: { // throw error; // Or fail fast if state is critical } }, - + setState: async (state) => { try { await db.query(` INSERT INTO flowcore_pump_state (id, time_bucket, event_id, updated_at) VALUES (?, ?, ?, NOW()) - ON DUPLICATE KEY UPDATE + ON DUPLICATE KEY UPDATE time_bucket = VALUES(time_bucket), event_id = VALUES(event_id), updated_at = NOW() `, ['main', state.timeBucket, state.eventId]); - + } catch (error) { console.error('CRITICAL: Failed to save state to database:', error); throw error; // Stop processing if we can't save progress @@ -612,9 +622,9 @@ notifier: { For distributed systems with message queues: ```typescript -notifier: { - type: "nats", - servers: ["nats://localhost:4222", "nats://backup:4222"] +notifier: { + type: "nats", + servers: ["nats://localhost:4222", "nats://backup:4222"] } ``` @@ -623,8 +633,8 @@ notifier: { Simple polling mechanism: ```typescript -notifier: { - type: "poller", +notifier: { + type: "poller", intervalMs: 5000 // Poll every 5 seconds } ``` diff --git a/bin/build-npm.ts b/bin/build-npm.ts deleted file mode 100644 index 853eb81..0000000 --- a/bin/build-npm.ts +++ /dev/null @@ -1,40 +0,0 @@ -// ex. scripts/build_npm.ts -import { build, emptyDir } from "@deno/dnt" -import denoJson from "../deno.json" with { type: "json" } -await emptyDir("./npm") - -await build({ - entryPoints: ["./src/mod.ts"], - test: false, - outDir: "./npm", - importMap: "./deno.json", - shims: { - // see JS docs for overview and more options - deno: true, - webSocket: true, - }, - package: { - name: denoJson.name, - description: denoJson.description, - version: denoJson.version, - license: denoJson.license, - homepage: "https://github.com/flowcore-io/flowcore-data-pump#readme", - repository: { - type: "git", - url: "git+https://github.com/flowcore-io/flowcore-data-pump.git", - }, - bugs: { - url: "https://github.com/flowcore-io/flowcore-data-pump/issues", - }, - devDependencies: { - "@types/ws": "^8.5.10", - "@types/long": "^5.0.0", - }, - }, - postBuild() { - // steps to run after building and before running the tests - // Deno.copyFileSync("LICENSE", "npm/LICENSE") - Deno.copyFileSync("README.md", "npm/README.md") - Deno.copyFileSync("CHANGELOG.md", "npm/CHANGELOG.md") - }, -}) diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..468884e --- /dev/null +++ b/bun.lock @@ -0,0 +1,302 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@flowcore/data-pump", + "dependencies": { + "@date-fns/utc": "^2.1.0", + "@flowcore/sdk": "^4.2.3", + "@flowcore/sdk-oidc-client": "^1.3.1", + "@flowcore/time-uuid": "^0.2.0", + "@sinclair/typebox": "0.32.15", + "date-fns": "^4.1.0", + "nats": "^2.29.1", + "prom-client": "^15.1.3", + "rxjs": "^7.8.1", + }, + "devDependencies": { + "@types/bun": "^1.2.19", + "@types/pg": "^8.11.10", + "@types/ws": "^8.5.10", + "pg": "^8.13.0", + "prettier": "^3.3.3", + "tsup": "^8.3.0", + "typescript": "^5.6.3", + }, + }, + }, + "packages": { + "@date-fns/utc": ["@date-fns/utc@2.1.1", "", {}, "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@flowcore/sdk": ["@flowcore/sdk@4.2.3", "", { "dependencies": { "@sinclair/typebox": "0.32.15", "rxjs": "^7.8.1", "ws": "^8.18.0" } }, "sha512-pB7FRGD4oBQeJWFQGFzIXQ+50ydTlWmin0xiJZs6n+BCRwcPGyQAE/co3zOHLjYD5QCFJ7rhqYph3ZRYZGH3YA=="], + + "@flowcore/sdk-oidc-client": ["@flowcore/sdk-oidc-client@1.3.1", "", {}, "sha512-fUHOmKDQYMqMDlh/KucJiDZ9he12QUkQ2KOJKCVy6GD7qsJs9qiJ8jV9th1Xeuz9kAca4TRXt+spMUhZ4CfUDw=="], + + "@flowcore/time-uuid": ["@flowcore/time-uuid@0.2.0", "", { "dependencies": { "long": "^5.3.1" } }, "sha512-48+3o2EAsJa0lZbVEb5GRYbGoq5FSBgis4LFvhWspdtrSI6LBHTkhKNJKMkLVw9GzYxvIKyjRfFgEQ9aqbkUSQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.61.1", "", { "os": "android", "cpu": "arm" }, "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.61.1", "", { "os": "android", "cpu": "arm64" }, "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.61.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.61.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.61.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.61.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.61.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.61.1", "", { "os": "linux", "cpu": "arm" }, "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.61.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.61.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.61.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.61.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.61.1", "", { "os": "linux", "cpu": "none" }, "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.61.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.61.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.61.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.61.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.61.1", "", { "os": "none", "cpu": "arm64" }, "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.61.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.61.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.61.1", "", { "os": "win32", "cpu": "x64" }, "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.61.1", "", { "os": "win32", "cpu": "x64" }, "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.32.15", "", {}, "sha512-5Lrwo7VOiWEBJBhHmqNmf3TPB9ll8gcEshvYJyAIJyCZ2PF48MFOtiDHJNj8+FsNcqImaQYmxVkKBCBlyAa/wg=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], + + "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "bintrees": ["bintrees@1.0.2", "", {}, "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "nats": ["nats@2.29.3", "", { "dependencies": { "nkeys.js": "1.1.0" } }, "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA=="], + + "nkeys.js": ["nkeys.js@1.1.0", "", { "dependencies": { "tweetnacl": "1.0.3" } }, "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pg": ["pg@8.21.0", "", { "dependencies": { "pg-connection-string": "^2.13.0", "pg-pool": "^3.14.0", "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA=="], + + "pg-cloudflare": ["pg-cloudflare@1.4.0", "", {}, "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A=="], + + "pg-connection-string": ["pg-connection-string@2.13.0", "", {}, "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.14.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw=="], + + "pg-protocol": ["pg-protocol@1.14.0", "", {}, "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "prettier": ["prettier@3.8.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="], + + "prom-client": ["prom-client@15.1.3", "", { "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" } }, "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rollup": ["rollup@4.61.1", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.61.1", "@rollup/rollup-android-arm64": "4.61.1", "@rollup/rollup-darwin-arm64": "4.61.1", "@rollup/rollup-darwin-x64": "4.61.1", "@rollup/rollup-freebsd-arm64": "4.61.1", "@rollup/rollup-freebsd-x64": "4.61.1", "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", "@rollup/rollup-linux-arm-musleabihf": "4.61.1", "@rollup/rollup-linux-arm64-gnu": "4.61.1", "@rollup/rollup-linux-arm64-musl": "4.61.1", "@rollup/rollup-linux-loong64-gnu": "4.61.1", "@rollup/rollup-linux-loong64-musl": "4.61.1", "@rollup/rollup-linux-ppc64-gnu": "4.61.1", "@rollup/rollup-linux-ppc64-musl": "4.61.1", "@rollup/rollup-linux-riscv64-gnu": "4.61.1", "@rollup/rollup-linux-riscv64-musl": "4.61.1", "@rollup/rollup-linux-s390x-gnu": "4.61.1", "@rollup/rollup-linux-x64-gnu": "4.61.1", "@rollup/rollup-linux-x64-musl": "4.61.1", "@rollup/rollup-openbsd-x64": "4.61.1", "@rollup/rollup-openharmony-arm64": "4.61.1", "@rollup/rollup-win32-arm64-msvc": "4.61.1", "@rollup/rollup-win32-ia32-msvc": "4.61.1", "@rollup/rollup-win32-x64-gnu": "4.61.1", "@rollup/rollup-win32-x64-msvc": "4.61.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA=="], + + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "tdigest": ["tdigest@0.1.2", "", { "dependencies": { "bintrees": "1.0.2" } }, "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + } +} diff --git a/deno.json b/deno.json deleted file mode 100644 index 08b8e02..0000000 --- a/deno.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", - "name": "@flowcore/data-pump", - "description": "Flowcore Data Pump", - "version": "0.21.4", - "license": "MIT", - "exports": "./src/mod.ts", - "publish": { - "include": [ - "src/**/*.ts", - "README.md", - "CHANGELOG.md" - ] - }, - "tasks": {}, - "imports": { - "@date-fns/utc": "npm:@date-fns/utc@^2.1.0", - "@deno/dnt": "jsr:@deno/dnt@^0.41.3", - "@flowcore/sdk": "jsr:@flowcore/sdk@^4.2.2", - "@flowcore/sdk-oidc-client": "npm:@flowcore/sdk-oidc-client@^1.3.1", - "@flowcore/time-uuid": "jsr:@flowcore/time-uuid@^0.1.1", - "@sinclair/typebox": "npm:@sinclair/typebox@0.32.15", - "@std/assert": "jsr:@std/assert@^1.0.6", - "@std/testing": "jsr:@std/testing@^1.0.6", - "date-fns": "npm:date-fns@^4.1.0", - "nats": "npm:nats@^2.29.1", - "prom-client": "npm:prom-client@^15.1.3", - "rxjs": "npm:rxjs@^7.8.1" - }, - "fmt": { - "exclude": [ - "node_modules", - "npm", - ".github", - "CHANGELOG.md", - "integration" - ], - "lineWidth": 120, - "indentWidth": 2, - "useTabs": false, - "semiColons": false - }, - "lint": { - "exclude": [ - "node_modules", - "npm", - "integration" - ] - } -} diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 8fb25be..0000000 --- a/deno.lock +++ /dev/null @@ -1,260 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@david/code-block-writer@^13.0.2": "13.0.3", - "jsr:@deno/cache-dir@~0.10.3": "0.10.3", - "jsr:@deno/dnt@~0.41.3": "0.41.3", - "jsr:@deno/graph@~0.73.1": "0.73.1", - "jsr:@flowcore/sdk@4.2.2": "4.2.2", - "jsr:@flowcore/sdk@^4.2.2": "4.2.2", - "jsr:@flowcore/time-uuid@~0.1.1": "0.1.2", - "jsr:@std/assert@0.223": "0.223.0", - "jsr:@std/assert@0.226": "0.226.0", - "jsr:@std/assert@^1.0.17": "1.0.19", - "jsr:@std/assert@^1.0.6": "1.0.19", - "jsr:@std/async@^1.1.0": "1.2.0", - "jsr:@std/bytes@0.223": "0.223.0", - "jsr:@std/data-structures@^1.0.10": "1.0.10", - "jsr:@std/fmt@0.223": "0.223.0", - "jsr:@std/fmt@1": "1.0.9", - "jsr:@std/fs@0.223": "0.223.0", - "jsr:@std/fs@1": "1.0.23", - "jsr:@std/fs@^1.0.22": "1.0.23", - "jsr:@std/fs@~0.229.3": "0.229.3", - "jsr:@std/internal@^1.0.12": "1.0.12", - "jsr:@std/io@0.223": "0.223.0", - "jsr:@std/path@0.223": "0.223.0", - "jsr:@std/path@1": "1.1.4", - "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", - "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/path@~0.225.2": "0.225.2", - "jsr:@std/testing@^1.0.6": "1.0.17", - "jsr:@ts-morph/bootstrap@0.24": "0.24.0", - "jsr:@ts-morph/common@0.24": "0.24.0", - "npm:@date-fns/utc@^2.1.0": "2.1.1", - "npm:@flowcore/sdk-oidc-client@^1.3.1": "1.3.1", - "npm:@sinclair/typebox@0.32.15": "0.32.15", - "npm:date-fns@^4.1.0": "4.1.0", - "npm:long@^5.3.1": "5.3.2", - "npm:nats@^2.29.1": "2.29.3", - "npm:prom-client@^15.1.3": "15.1.3", - "npm:rxjs@^7.8.1": "7.8.2" - }, - "jsr": { - "@david/code-block-writer@13.0.3": { - "integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f" - }, - "@deno/cache-dir@0.10.3": { - "integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776", - "dependencies": [ - "jsr:@deno/graph", - "jsr:@std/fmt@0.223", - "jsr:@std/fs@0.223", - "jsr:@std/io", - "jsr:@std/path@0.223" - ] - }, - "@deno/dnt@0.41.3": { - "integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2", - "dependencies": [ - "jsr:@david/code-block-writer", - "jsr:@deno/cache-dir", - "jsr:@std/fmt@1", - "jsr:@std/fs@1", - "jsr:@std/path@1", - "jsr:@ts-morph/bootstrap" - ] - }, - "@deno/graph@0.73.1": { - "integrity": "cd69639d2709d479037d5ce191a422eabe8d71bb68b0098344f6b07411c84d41" - }, - "@flowcore/sdk@4.2.2": { - "integrity": "1f62166e11376d6e7aafa572e072665ea2e7591497254abce4e8b13b7b5d166c", - "dependencies": [ - "npm:@sinclair/typebox", - "npm:rxjs" - ] - }, - "@flowcore/time-uuid@0.1.2": { - "integrity": "3d531115cd5fef4ef9d72dbaec174aec827d8186965f92aec04359853722a705", - "dependencies": [ - "npm:long" - ] - }, - "@std/assert@0.223.0": { - "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" - }, - "@std/assert@0.226.0": { - "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" - }, - "@std/assert@1.0.19": { - "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/async@1.2.0": { - "integrity": "c059c6f6d95ca7cc012ae8e8d7164d1697113d54b0b679e4372b354b11c2dee5" - }, - "@std/bytes@0.223.0": { - "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" - }, - "@std/data-structures@1.0.10": { - "integrity": "f574f86b0e07c69b9edc555fcc814b57d29258bad39fd5a34ba8a80ecf033cfe" - }, - "@std/fmt@0.223.0": { - "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" - }, - "@std/fmt@1.0.9": { - "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" - }, - "@std/fs@0.223.0": { - "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" - }, - "@std/fs@0.229.3": { - "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", - "dependencies": [ - "jsr:@std/path@1.0.0-rc.1" - ] - }, - "@std/fs@1.0.23": { - "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", - "dependencies": [ - "jsr:@std/internal", - "jsr:@std/path@^1.1.4" - ] - }, - "@std/internal@1.0.12": { - "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" - }, - "@std/io@0.223.0": { - "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", - "dependencies": [ - "jsr:@std/assert@0.223", - "jsr:@std/bytes" - ] - }, - "@std/path@0.223.0": { - "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", - "dependencies": [ - "jsr:@std/assert@0.223" - ] - }, - "@std/path@0.225.2": { - "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", - "dependencies": [ - "jsr:@std/assert@0.226" - ] - }, - "@std/path@1.0.0-rc.1": { - "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" - }, - "@std/path@1.1.4": { - "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/testing@1.0.17": { - "integrity": "87bdc2700fa98249d48a17cd72413352d3d3680dcfbdb64947fd0982d6bbf681", - "dependencies": [ - "jsr:@std/assert@^1.0.17", - "jsr:@std/async", - "jsr:@std/data-structures", - "jsr:@std/fs@^1.0.22", - "jsr:@std/internal", - "jsr:@std/path@^1.1.4" - ] - }, - "@ts-morph/bootstrap@0.24.0": { - "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", - "dependencies": [ - "jsr:@ts-morph/common" - ] - }, - "@ts-morph/common@0.24.0": { - "integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3", - "dependencies": [ - "jsr:@std/fs@~0.229.3", - "jsr:@std/path@~0.225.2" - ] - } - }, - "npm": { - "@date-fns/utc@2.1.1": { - "integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA==" - }, - "@flowcore/sdk-oidc-client@1.3.1": { - "integrity": "sha512-fUHOmKDQYMqMDlh/KucJiDZ9he12QUkQ2KOJKCVy6GD7qsJs9qiJ8jV9th1Xeuz9kAca4TRXt+spMUhZ4CfUDw==" - }, - "@opentelemetry/api@1.9.0": { - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" - }, - "@sinclair/typebox@0.32.15": { - "integrity": "sha512-5Lrwo7VOiWEBJBhHmqNmf3TPB9ll8gcEshvYJyAIJyCZ2PF48MFOtiDHJNj8+FsNcqImaQYmxVkKBCBlyAa/wg==" - }, - "bintrees@1.0.2": { - "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" - }, - "date-fns@4.1.0": { - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==" - }, - "long@5.3.2": { - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" - }, - "nats@2.29.3": { - "integrity": "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA==", - "dependencies": [ - "nkeys.js" - ], - "deprecated": true - }, - "nkeys.js@1.1.0": { - "integrity": "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg==", - "dependencies": [ - "tweetnacl" - ] - }, - "prom-client@15.1.3": { - "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", - "dependencies": [ - "@opentelemetry/api", - "tdigest" - ] - }, - "rxjs@7.8.2": { - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dependencies": [ - "tslib" - ] - }, - "tdigest@0.1.2": { - "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", - "dependencies": [ - "bintrees" - ] - }, - "tslib@2.8.1": { - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "tweetnacl@1.0.3": { - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - } - }, - "workspace": { - "dependencies": [ - "jsr:@deno/dnt@~0.41.3", - "jsr:@flowcore/sdk@^4.2.2", - "jsr:@flowcore/time-uuid@~0.1.1", - "jsr:@std/assert@^1.0.6", - "jsr:@std/testing@^1.0.6", - "npm:@date-fns/utc@^2.1.0", - "npm:@flowcore/sdk-oidc-client@^1.3.1", - "npm:@sinclair/typebox@0.32.15", - "npm:date-fns@^4.1.0", - "npm:nats@^2.29.1", - "npm:prom-client@^15.1.3", - "npm:rxjs@^7.8.1" - ] - } -} diff --git a/integration/app/Dockerfile b/integration/app/Dockerfile index ff2630c..7dc27b5 100644 --- a/integration/app/Dockerfile +++ b/integration/app/Dockerfile @@ -1,8 +1,8 @@ -FROM denoland/deno:latest +FROM oven/bun:1 WORKDIR /app -COPY deno.json deno.lock ./ +COPY package.json bun.lock ./ COPY src/ ./src/ COPY integration/app/ ./integration/app/ -RUN deno install +RUN bun install --frozen-lockfile EXPOSE 8080 -CMD ["deno", "run", "-A", "integration/app/main.ts"] +CMD ["bun", "run", "integration/app/main.ts"] diff --git a/integration/app/deps.ts b/integration/app/deps.ts index 150de5b..c79394a 100644 --- a/integration/app/deps.ts +++ b/integration/app/deps.ts @@ -1,3 +1,3 @@ -export { default as pg } from "npm:pg@^8.13.0" -export const { Client } = await import("npm:pg@^8.13.0") -export type { Client as PgClient } from "npm:pg@^8.13.0" +export { default as pg } from "pg" +export const { Client } = await import("pg") +export type { Client as PgClient } from "pg" diff --git a/integration/app/fake-data-source.ts b/integration/app/fake-data-source.ts index a685356..f66b570 100644 --- a/integration/app/fake-data-source.ts +++ b/integration/app/fake-data-source.ts @@ -34,9 +34,11 @@ export class FakeDataSource extends FlowcoreDataSource { this.events.push({ eventId: timeUuid.toString(), eventType: "test-event", - aggregator: `agg-${i}`, + tenant: "integration-test", + dataCoreId: "test-data-core", + flowType: "test-flow-type", payload: { index: i, data: `test-payload-${i}` }, - metadata: {}, + metadata: { aggregator: `agg-${i}` }, timeBucket: this.timeBucket, validTime: new Date().toISOString(), }) diff --git a/integration/app/main.ts b/integration/app/main.ts index af98e18..bd25150 100644 --- a/integration/app/main.ts +++ b/integration/app/main.ts @@ -1,14 +1,14 @@ -import { Client } from "npm:pg@^8.13.0" +import { Client } from "pg" import { FlowcoreDataPumpCluster } from "../../src/data-pump/data-pump-cluster.ts" import { PostgresCoordinator } from "./postgres-coordinator.ts" import { PostgresStateManager } from "./postgres-state-manager.ts" import { FakeDataSource } from "./fake-data-source.ts" -const DATABASE_URL = Deno.env.get("DATABASE_URL") ?? "postgres://postgres:postgres@localhost:5432/datapump_test" -const POD_NAME = Deno.env.get("POD_NAME") ?? "local-pod" -const NATS_URL = Deno.env.get("NATS_URL") -const TOTAL_EVENTS = parseInt(Deno.env.get("TOTAL_EVENTS") ?? "100", 10) -const WS_PORT = parseInt(Deno.env.get("WS_PORT") ?? "8080", 10) +const DATABASE_URL = Bun.env.DATABASE_URL ?? "postgres://postgres:postgres@localhost:5432/datapump_test" +const POD_NAME = Bun.env.POD_NAME ?? "local-pod" +const NATS_URL = Bun.env.NATS_URL +const TOTAL_EVENTS = parseInt(Bun.env.TOTAL_EVENTS ?? "100", 10) +const WS_PORT = parseInt(Bun.env.WS_PORT ?? "8080", 10) const log = { debug: (msg: string, meta?: Record) => console.log(`[DEBUG] [${POD_NAME}] ${msg}`, meta ?? ""), @@ -62,7 +62,7 @@ const useNats = !!NATS_URL log.info(`Distribution mode: ${useNats ? "NATS" : "WS"}`, { natsUrl: NATS_URL }) // create cluster -const POD_IP = Deno.env.get("POD_IP") ?? "127.0.0.1" +const POD_IP = Bun.env.POD_IP ?? "127.0.0.1" const cluster = new FlowcoreDataPumpCluster({ auth: { getBearerToken: () => Promise.resolve("fake") }, dataSource: { @@ -76,21 +76,18 @@ const cluster = new FlowcoreDataPumpCluster({ dataSourceOverride: fakeDataSource, ...(useNats ? { - notifier: { type: "nats" as const, servers: [NATS_URL!] }, - } + notifier: { type: "nats" as const, servers: [NATS_URL!] }, + } : { - advertisedAddress: `ws://${POD_IP}:${WS_PORT}`, - notifier: { type: "poller" as const, intervalMs: 2000 }, - }), + advertisedAddress: `ws://${POD_IP}:${WS_PORT}`, + notifier: { type: "poller" as const, intervalMs: 2000 }, + }), noTranslation: true, processor: { concurrency: 5, handler: async (events) => { for (const event of events) { - await db.query(`INSERT INTO processed_events (pod_name, event_id) VALUES ($1, $2)`, [ - POD_NAME, - event.eventId, - ]) + await db.query(`INSERT INTO processed_events (pod_name, event_id) VALUES ($1, $2)`, [POD_NAME, event.eventId]) } log.info(`Processed ${events.length} events`) }, @@ -103,30 +100,66 @@ const cluster = new FlowcoreDataPumpCluster({ }) // start HTTP server (health endpoint only in NATS mode, health + WS in WS mode) -Deno.serve({ port: WS_PORT }, (req) => { - const url = new URL(req.url) - - if (url.pathname === "/health") { - return new Response( - JSON.stringify({ - instanceId: cluster.id, - isLeader: cluster.isLeaderInstance, - workerCount: cluster.activeWorkerCount, - isRunning: cluster.isRunning, - podName: POD_NAME, - distributionMode: useNats ? "nats" : "ws", - }), - { headers: { "content-type": "application/json" } }, - ) - } +interface BunWebSocketAdapter { + onmessage: ((event: MessageEvent) => void) | null + onclose: ((event: CloseEvent) => void) | null + onerror: ((event: Event) => void) | null + readonly readyState: number + send(data: string | BufferSource): void + close(code?: number, reason?: string): void +} + +const server = Bun.serve<{ adapter?: BunWebSocketAdapter }>({ + port: WS_PORT, + fetch(req, server) { + const url = new URL(req.url) + + if (url.pathname === "/health") { + return new Response( + JSON.stringify({ + instanceId: cluster.id, + isLeader: cluster.isLeaderInstance, + workerCount: cluster.activeWorkerCount, + isRunning: cluster.isRunning, + podName: POD_NAME, + distributionMode: useNats ? "nats" : "ws", + }), + { headers: { "content-type": "application/json" } }, + ) + } - if (!useNats && req.headers.get("upgrade")?.toLowerCase() === "websocket") { - const { socket, response } = Deno.upgradeWebSocket(req) - cluster.handleConnection(socket) - return response - } + if (!useNats && server.upgrade(req, { data: {} })) { + return undefined + } - return new Response("Not found", { status: 404 }) + return new Response("Not found", { status: 404 }) + }, + websocket: { + open(socket) { + const adapter: BunWebSocketAdapter = { + onmessage: null, + onclose: null, + onerror: null, + get readyState() { + return socket.readyState + }, + send(data) { + socket.send(data as string) + }, + close(code, reason) { + socket.close(code, reason) + }, + } + socket.data.adapter = adapter + cluster.handleConnection(adapter as WebSocket) + }, + message(socket, message) { + socket.data.adapter?.onmessage?.(new MessageEvent("message", { data: message })) + }, + close(socket, code, reason) { + socket.data.adapter?.onclose?.(new CloseEvent("close", { code, reason })) + }, + }, }) log.info(`HTTP server listening on :${WS_PORT}`) @@ -136,9 +169,10 @@ await cluster.start() log.info("Cluster started") // graceful shutdown -Deno.addSignalListener("SIGTERM", async () => { +process.on("SIGTERM", async () => { log.info("SIGTERM received, shutting down...") await cluster.stop() await db.end() - Deno.exit(0) + server.stop() + process.exit(0) }) diff --git a/integration/app/postgres-coordinator.ts b/integration/app/postgres-coordinator.ts index 29c363a..36f4fad 100644 --- a/integration/app/postgres-coordinator.ts +++ b/integration/app/postgres-coordinator.ts @@ -1,4 +1,4 @@ -import type { Client as PgClient } from "npm:pg@^8.13.0" +import type { Client as PgClient } from "pg" import type { FlowcoreDataPumpCoordinator } from "../../src/data-pump/types.ts" export class PostgresCoordinator implements FlowcoreDataPumpCoordinator { @@ -15,7 +15,7 @@ export class PostgresCoordinator implements FlowcoreDataPumpCoordinator { RETURNING holder`, [key, instanceId, expiresAt], ) - return result.rowCount > 0 && result.rows[0].holder === instanceId + return (result.rowCount ?? 0) > 0 && result.rows[0].holder === instanceId } async renewLease(instanceId: string, key: string, ttlMs: number): Promise { diff --git a/integration/app/postgres-state-manager.ts b/integration/app/postgres-state-manager.ts index 2f90519..c52b4f9 100644 --- a/integration/app/postgres-state-manager.ts +++ b/integration/app/postgres-state-manager.ts @@ -1,13 +1,11 @@ -import type { Client as PgClient } from "npm:pg@^8.13.0" +import type { Client as PgClient } from "pg" import type { FlowcoreDataPumpState, FlowcoreDataPumpStateManager } from "../../src/data-pump/types.ts" export class PostgresStateManager implements FlowcoreDataPumpStateManager { constructor(private readonly db: PgClient) {} async getState(): Promise { - const result = await this.db.query( - `SELECT time_bucket, event_id FROM pump_state ORDER BY updated_at DESC LIMIT 1`, - ) + const result = await this.db.query(`SELECT time_bucket, event_id FROM pump_state ORDER BY updated_at DESC LIMIT 1`) if (result.rows.length === 0) return null return { timeBucket: result.rows[0].time_bucket, diff --git a/integration/run-integration.sh b/integration/run-integration.sh index ab1a0a8..6dec102 100755 --- a/integration/run-integration.sh +++ b/integration/run-integration.sh @@ -2,6 +2,11 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +BIN_DIR="${DATA_PUMP_INTEGRATION_BIN_DIR:-${REPO_DIR}/.cache/bin}" +mkdir -p "${BIN_DIR}" +export DATA_PUMP_INTEGRATION_BIN_DIR="${BIN_DIR}" +export PATH="${BIN_DIR}:${PATH}" echo "==========================================" echo " Data Pump K8s Integration Test" diff --git a/integration/scripts/setup-k3d.sh b/integration/scripts/setup-k3d.sh index a58baf2..bddfee8 100755 --- a/integration/scripts/setup-k3d.sh +++ b/integration/scripts/setup-k3d.sh @@ -2,25 +2,29 @@ set -euo pipefail CLUSTER_NAME="data-pump-integration" +BIN_DIR="${DATA_PUMP_INTEGRATION_BIN_DIR:-${PWD}/.cache/bin}" +mkdir -p "${BIN_DIR}" +export PATH="${BIN_DIR}:${PATH}" echo "==> Setting up k3d cluster: ${CLUSTER_NAME}" # install k3d if missing if ! command -v k3d &>/dev/null; then echo "==> Installing k3d..." - curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash + curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | K3D_INSTALL_DIR="${BIN_DIR}" USE_SUDO=false bash fi # install kubectl if missing if ! command -v kubectl &>/dev/null; then echo "==> Installing kubectl..." if [[ "$(uname -s)" == "Linux" ]]; then - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" - chmod +x kubectl && sudo mv kubectl /usr/local/bin/ + curl -L -o "${BIN_DIR}/kubectl" \ + "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" elif [[ "$(uname -s)" == "Darwin" ]]; then - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl" - chmod +x kubectl && sudo mv kubectl /usr/local/bin/ + curl -L -o "${BIN_DIR}/kubectl" \ + "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/arm64/kubectl" fi + chmod +x "${BIN_DIR}/kubectl" fi # delete existing cluster if present (clean slate) diff --git a/package.json b/package.json new file mode 100644 index 0000000..91ec750 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "@flowcore/data-pump", + "description": "Flowcore Data Pump", + "version": "0.21.4", + "license": "MIT", + "type": "module", + "exports": { + ".": { + "types": "./dist/mod.d.ts", + "import": "./dist/mod.js" + } + }, + "types": "./dist/mod.d.ts", + "files": [ + "dist", + "README.md", + "CHANGELOG.md" + ], + "sideEffects": false, + "homepage": "https://github.com/flowcore-io/flowcore-data-pump#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/flowcore-io/flowcore-data-pump.git" + }, + "bugs": { + "url": "https://github.com/flowcore-io/flowcore-data-pump/issues" + }, + "scripts": { + "build": "tsup src/mod.ts --format esm --dts --clean --out-dir dist", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint": "tsc --noEmit", + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@date-fns/utc": "^2.1.0", + "@flowcore/sdk": "^4.2.3", + "@flowcore/sdk-oidc-client": "^1.3.1", + "@flowcore/time-uuid": "^0.2.0", + "@sinclair/typebox": "0.32.15", + "date-fns": "^4.1.0", + "nats": "^2.29.1", + "prom-client": "^15.1.3", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@types/bun": "^1.2.19", + "@types/pg": "^8.11.10", + "@types/ws": "^8.5.10", + "pg": "^8.13.0", + "prettier": "^3.3.3", + "tsup": "^8.3.0", + "typescript": "^5.6.3" + } +} diff --git a/src/data-pump/data-pump-cluster.ts b/src/data-pump/data-pump-cluster.ts index 3cf6e17..1ff627e 100644 --- a/src/data-pump/data-pump-cluster.ts +++ b/src/data-pump/data-pump-cluster.ts @@ -96,7 +96,8 @@ export class FlowcoreDataPumpCluster { } private get natsDistributionSubject(): string { - const key = this.options.clusterKey ?? + const key = + this.options.clusterKey ?? `${this.options.dataSource.tenant}.${this.options.dataSource.dataCore}.${this.options.dataSource.flowType}` return `data-pump.distribute.${key}` } @@ -121,15 +122,20 @@ export class FlowcoreDataPumpCluster { * Handle an incoming WebSocket connection from a peer. * Call this from your HTTP server's WebSocket upgrade handler. * - * Example (Deno): + * Example (Bun): * ```typescript - * Deno.serve({ port: 8080 }, (req) => { - * if (req.headers.get("upgrade") === "websocket") { - * const { socket, response } = Deno.upgradeWebSocket(req) - * cluster.handleConnection(socket) - * return response - * } - * return new Response("Not found", { status: 404 }) + * Bun.serve({ + * port: 8080, + * fetch(req, server) { + * if (server.upgrade(req)) return undefined + * return new Response("Not found", { status: 404 }) + * }, + * websocket: { + * open(socket) { + * cluster.handleConnection(socket as unknown as WebSocket) + * }, + * message() {}, + * }, * }) * ``` * @@ -315,11 +321,7 @@ export class FlowcoreDataPumpCluster { if (this.useNatsDistribution) { // NATS mode: create leader distributor, no WS discovery needed const conn = await this.natsConnectionManager!.connect() - this.natsDistLeader = new NatsDistributionLeader( - conn, - this.natsDistributionSubject, - this.logger, - ) + this.natsDistLeader = new NatsDistributionLeader(conn, this.natsDistributionSubject, this.logger) } else { // WS mode: start worker discovery this.startWorkerDiscovery() diff --git a/src/data-pump/data-pump.ts b/src/data-pump/data-pump.ts index 69f13ec..0c6f761 100644 --- a/src/data-pump/data-pump.ts +++ b/src/data-pump/data-pump.ts @@ -145,13 +145,15 @@ export class FlowcoreDataPump { options.auth.apiKeyId = parts[1] } - const dataSource = dataSourceOverride ?? new FlowcoreDataSource({ - auth: options.auth, - dataSource: options.dataSource, - baseUrlOverride: options.baseUrlOverride, - noTranslation: options.noTranslation, - directMode: options.directMode, - }) + const dataSource = + dataSourceOverride ?? + new FlowcoreDataSource({ + auth: options.auth, + dataSource: options.dataSource, + baseUrlOverride: options.baseUrlOverride, + noTranslation: options.noTranslation, + directMode: options.directMode, + }) const notifier = new FlowcoreNotifier({ auth: options.auth, dataSource: options.dataSource, @@ -224,10 +226,11 @@ export class FlowcoreDataPump { if (this.options.stopAt) { this.stopAtState = { - timeBucket: (await this.dataSource.getClosestTimeBucket( - format(startOfHour(utc(this.options.stopAt)), "yyyyMMddHH0000"), - true, - )) ?? format(startOfHour(utc(new Date())), "yyyyMMddHH0000"), + timeBucket: + (await this.dataSource.getClosestTimeBucket( + format(startOfHour(utc(this.options.stopAt)), "yyyyMMddHH0000"), + true, + )) ?? format(startOfHour(utc(new Date())), "yyyyMMddHH0000"), eventId: TimeUuid.fromDate(this.options.stopAt).toString(), } } @@ -316,9 +319,7 @@ export class FlowcoreDataPump { continue } - this.logger?.debug( - `fetching ${amountToFetch} events from ${this.bufferState.timeBucket}(${this.nextCursor})`, - ) + this.logger?.debug(`fetching ${amountToFetch} events from ${this.bufferState.timeBucket}(${this.nextCursor})`) const { events, nextCursor } = await this.dataSource.getEvents( this.bufferState, @@ -345,7 +346,9 @@ export class FlowcoreDataPump { this.bufferState.eventId = events[events.length - 1]?.eventId ?? this.bufferState.eventId if ( - this.stopAtState?.timeBucket && this.bufferState.timeBucket >= this.stopAtState.timeBucket && !events.length + this.stopAtState?.timeBucket && + this.bufferState.timeBucket >= this.stopAtState.timeBucket && + !events.length ) { this.logger?.info("Stopping at stopAt state") await this.waitForBufferEmpty() @@ -378,7 +381,8 @@ export class FlowcoreDataPump { if (this.restartTo) { try { await this.dataSource.getTimeBuckets(true) - this.restartTo.timeBucket = (await this.dataSource.getClosestTimeBucket(this.restartTo.timeBucket)) ?? + this.restartTo.timeBucket = + (await this.dataSource.getClosestTimeBucket(this.restartTo.timeBucket)) ?? format(startOfHour(utc(new Date())), "yyyyMMddHH0000") this.nextCursor = undefined this.bufferState = this.restartTo diff --git a/src/data-pump/data-source.ts b/src/data-pump/data-source.ts index d1e2ad5..d50347f 100644 --- a/src/data-pump/data-source.ts +++ b/src/data-pump/data-source.ts @@ -248,12 +248,12 @@ export class FlowcoreDataSource { if (getBefore) { return ( timeBuckets.findLast((t) => Number.parseFloat(t) <= Number.parseFloat(timeBucket)) ?? - timeBuckets[timeBuckets.length - 1] + timeBuckets[timeBuckets.length - 1] ) } return ( timeBuckets.find((t) => Number.parseFloat(t) >= Number.parseFloat(timeBucket)) ?? - timeBuckets[timeBuckets.length - 1] + timeBuckets[timeBuckets.length - 1] ) } diff --git a/src/data-pump/flowcore-client.ts b/src/data-pump/flowcore-client.ts index f3e31eb..0103745 100644 --- a/src/data-pump/flowcore-client.ts +++ b/src/data-pump/flowcore-client.ts @@ -10,13 +10,14 @@ class FlowcoreClientWithMetrics extends FlowcoreClient { } export function getFlowcoreClient(auth: FlowcoreDataPumpAuth, baseUrlOverride?: string) { - const client = "apiKey" in auth - ? new FlowcoreClientWithMetrics({ - apiKey: auth.apiKey, - // apiKeyId is guaranteed to be set — FlowcoreDataPump.create() parses it from fc_ keys - apiKeyId: auth.apiKeyId!, - }) - : new FlowcoreClientWithMetrics(auth) + const client = + "apiKey" in auth + ? new FlowcoreClientWithMetrics({ + apiKey: auth.apiKey, + // apiKeyId is guaranteed to be set — FlowcoreDataPump.create() parses it from fc_ keys + apiKeyId: auth.apiKeyId!, + }) + : new FlowcoreClientWithMetrics(auth) if (baseUrlOverride) { client.setBaseUrl(baseUrlOverride) diff --git a/src/data-pump/metrics.ts b/src/data-pump/metrics.ts index 0146c02..0d93121 100644 --- a/src/data-pump/metrics.ts +++ b/src/data-pump/metrics.ts @@ -1,7 +1,7 @@ import PromClient, { Counter, Gauge } from "prom-client" -export const dataPumpPromRegistry: PromClient.Registry<"text/plain; version=0.0.4; charset=utf-8"> = new PromClient - .Registry() +export const dataPumpPromRegistry: PromClient.Registry<"text/plain; version=0.0.4; charset=utf-8"> = + new PromClient.Registry() const bufferEventCountGauge: Gauge<"tenant" | "data_core" | "flow_type" | "event_type"> = new Gauge({ name: "flowcore_data_pump_buffer_events_gauge", diff --git a/src/data-pump/nats-distribution.ts b/src/data-pump/nats-distribution.ts index 9014f53..66957a9 100644 --- a/src/data-pump/nats-distribution.ts +++ b/src/data-pump/nats-distribution.ts @@ -30,11 +30,9 @@ export class NatsDistributionLeader { this.logger?.debug("Distributing events via NATS", { deliveryId, count: events.length, subject: this.subject }) - const response = await this.connection.request( - this.subject, - this.codec.encode(request), - { timeout: this.timeoutMs }, - ) + const response = await this.connection.request(this.subject, this.codec.encode(request), { + timeout: this.timeoutMs, + }) const reply = this.codec.decode(response.data) as NatsDistributionReply if (reply.status === "fail") { diff --git a/src/data-pump/notifier.ts b/src/data-pump/notifier.ts index c1274eb..8e892e2 100644 --- a/src/data-pump/notifier.ts +++ b/src/data-pump/notifier.ts @@ -25,8 +25,7 @@ export class FlowcoreNotifier { private subject?: Subject private notificationClient?: NotificationClient private eventResolver?: () => void - // deno-lint-ignore no-explicit-any - private timer?: any + private timer?: ReturnType constructor(private readonly options: FlowcoreNotifierOptions) { this.dataSource = new FlowcoreDataSource({ @@ -205,7 +204,6 @@ function isNormalNotificationLifecycleInfo(message: string): boolean { * property with a fake factory to drive the subject manually. Not part of the * public API. */ -// deno-lint-ignore no-explicit-any type NotificationClientFactory = (...args: any[]) => NotificationClient export const _internals: { createNotificationClient: NotificationClientFactory } = { createNotificationClient: (...args) => diff --git a/src/data-pump/pulse.ts b/src/data-pump/pulse.ts index 53dd3db..3fe04f0 100644 --- a/src/data-pump/pulse.ts +++ b/src/data-pump/pulse.ts @@ -117,9 +117,7 @@ export class PulseEmitter { }, uptimeMs: snapshot.uptimeMs, } - await client.execute( - new SendPumpPulseCommand(input as ConstructorParameters[0]), - ) + await client.execute(new SendPumpPulseCommand(input as ConstructorParameters[0])) this.logger?.[this.successLogLevel]?.("Pulse sent", { pathwayId: snapshot.pathwayId, diff --git a/src/data-pump/types.ts b/src/data-pump/types.ts index 0911cc3..9077a17 100644 --- a/src/data-pump/types.ts +++ b/src/data-pump/types.ts @@ -2,16 +2,16 @@ import type { FlowcoreEvent } from "@flowcore/sdk" export type FlowcoreDataPumpAuth = | { - apiKey: string - /** - * The ID of the API key. - * @deprecated Use apiKey only instead (apiKeyId is only used for old api keys). - */ - apiKeyId?: string - } + apiKey: string + /** + * The ID of the API key. + * @deprecated Use apiKey only instead (apiKeyId is only used for old api keys). + */ + apiKeyId?: string + } | { - getBearerToken: () => Promise - } + getBearerToken: () => Promise + } export interface FlowcoreDataPumpState { timeBucket: string diff --git a/test/fixtures/fetch.fixture.ts b/test/fixtures/fetch.fixture.ts index 12b6f02..e64fdd0 100644 --- a/test/fixtures/fetch.fixture.ts +++ b/test/fixtures/fetch.fixture.ts @@ -1,6 +1,3 @@ -import { type Stub, stub } from "@std/testing/mock" -import { assertArrayIncludes, assertObjectMatch } from "@std/assert" - type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS" | "HEAD" | "TRACE" | "CONNECT" interface MockedRequest { @@ -21,12 +18,11 @@ interface FetchMockerOptions { export class FetchMocker { private originalFetch: typeof globalThis.fetch - private fetchStub: Stub private mocks: Map = new Map() constructor(private options: FetchMockerOptions = {}) { this.originalFetch = globalThis.fetch - this.fetchStub = stub(globalThis, "fetch", this.mockedFetch.bind(this)) + globalThis.fetch = this.mockedFetch.bind(this) as typeof globalThis.fetch } public clear() { @@ -51,7 +47,7 @@ export class FetchMocker { } public restore() { - this.fetchStub.restore() + globalThis.fetch = this.originalFetch } private mockedFetch(input: RequestInfo | URL, init?: RequestInit): Promise { @@ -153,17 +149,13 @@ export class FetchMockBuilder { } if (mockedRequest.searchParams) { - try { - assertObjectMatch(mockedRequest.searchParams, searchParams) - } catch (_e) { + if (!objectIncludes(searchParams, mockedRequest.searchParams)) { return false } } if (mockedRequest.headers) { - try { - assertObjectMatch(mockedRequest.headers, headers) - } catch (_e) { + if (!objectIncludes(headers, mockedRequest.headers)) { console.log("headers mismatch", { expected: mockedRequest.headers, actual: headers }) return false } @@ -178,18 +170,14 @@ export class FetchMockBuilder { if (!Array.isArray(body)) { return false } - try { - assertArrayIncludes(mockedRequest.body, body) - } catch (_e) { + if (!mockedRequest.body.every((expected) => body.some((actual) => deepCompare(expected, actual)))) { return false } } else if (typeof mockedRequest.body === "object") { if (Array.isArray(body) || typeof body === "string") { return false } - try { - assertObjectMatch(mockedRequest.body, body) - } catch (_e) { + if (!objectIncludes(body, mockedRequest.body)) { return false } } @@ -236,9 +224,7 @@ class FetchMockBuilderPath { } public matchHeaders(headers: Record) { - this.headers = Object.fromEntries( - Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]), - ) + this.headers = Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])) return this } @@ -287,6 +273,10 @@ const urlParamsToObject = (urlParams: URLSearchParams) => { return obj } +function objectIncludes(actual: Record, expected: Record): boolean { + return Object.entries(expected).every(([key, value]) => deepCompare(actual[key], value)) +} + function deepCompare(a: unknown, b: unknown): boolean { // Handle null/undefined if (a === b) { diff --git a/test/tests/data-pump-cluster.test.ts b/test/tests/data-pump-cluster.test.ts index 33a8a6c..13e0ae0 100644 --- a/test/tests/data-pump-cluster.test.ts +++ b/test/tests/data-pump-cluster.test.ts @@ -1,5 +1,4 @@ -import { assertEquals, assertRejects, assertThrows } from "@std/assert" -import { beforeEach, describe, it } from "@std/testing/bdd" +import { beforeEach, describe, expect, it } from "bun:test" import type { FlowcoreEvent } from "@flowcore/sdk" import { FlowcoreDataPumpCluster } from "../../src/data-pump/data-pump-cluster.ts" import type { FlowcoreDataPumpCoordinator, FlowcoreLogger } from "../../src/data-pump/types.ts" @@ -13,6 +12,18 @@ import { } from "../../src/data-pump/ws-protocol.ts" import type { NatsDistributionReply, NatsDistributionRequest } from "../../src/data-pump/nats-distribution.ts" +function assertEquals(actual: T, expected: T, message?: string) { + expect(actual, message).toEqual(expected) +} + +function assertThrows(fn: () => unknown, _errorClass?: typeof Error, message?: string) { + expect(fn).toThrow(message) +} + +async function assertRejects(fn: () => Promise, _errorClass?: typeof Error, message?: string) { + await expect(fn()).rejects.toThrow(message) +} + // #region Mock Coordinator class MockCoordinator implements FlowcoreDataPumpCoordinator { diff --git a/test/tests/data-pump-restart.test.ts b/test/tests/data-pump-restart.test.ts index 77ca253..1c4356e 100644 --- a/test/tests/data-pump-restart.test.ts +++ b/test/tests/data-pump-restart.test.ts @@ -1,6 +1,4 @@ -import { assert, assertEquals, assertThrows } from "@std/assert" -import { afterEach, beforeEach, describe, it } from "@std/testing/bdd" -import { FakeTime } from "@std/testing/time" +import { afterEach, beforeEach, describe, expect, it, jest } from "bun:test" import type { EventListOutput, FlowcoreEvent } from "@flowcore/sdk" import { FlowcoreDataPump } from "../../src/data-pump/data-pump.ts" import { FlowcoreDataSource } from "../../src/data-pump/data-source.ts" @@ -10,6 +8,30 @@ import type { FlowcoreDataPumpState, FlowcoreDataPumpStateManager } from "../../ const FAKE_API_KEY = "fc_testid_testsecret" +function assert(condition: unknown, message?: string): asserts condition { + expect(Boolean(condition), message).toBe(true) +} + +function assertEquals(actual: T, expected: T, message?: string) { + expect(actual, message).toEqual(expected) +} + +function assertThrows(fn: () => unknown, _errorClass?: typeof Error, message?: string) { + expect(fn).toThrow(message) +} + +async function tickAsync(ms: number) { + await flushMicrotasks() + jest.advanceTimersByTime(ms) + await flushMicrotasks() +} + +async function flushMicrotasks() { + for (let i = 0; i < 10; i++) { + await Promise.resolve() + } +} + function createMockStateManager(): FlowcoreDataPumpStateManager { return { getState: () => Promise.resolve({ timeBucket: "20260331120000" }), @@ -49,22 +71,20 @@ function createPump( }, notifier: { type: "poller", intervalMs: 60_000 }, logger, - baseUrlOverride: "http://localhost:99999", + baseUrlOverride: "http://localhost:9999", noTranslation: true, }) } // #endregion -describe("processLoop restart", { sanitizeOps: false, sanitizeResources: false }, () => { - let fakeTime: FakeTime - +describe("processLoop restart", () => { beforeEach(() => { - fakeTime = new FakeTime() + jest.useFakeTimers() }) afterEach(() => { - fakeTime.restore() + jest.useRealTimers() }) it("should not emit restart warnings after stop", async () => { @@ -79,7 +99,7 @@ describe("processLoop restart", { sanitizeOps: false, sanitizeResources: false } pump.stop() // Advance past any potential restart delays - await fakeTime.tickAsync(60_000) + await tickAsync(60_000) const restartLogs = logs.filter((l) => l.level === "warn" && l.message.includes("Restarting process loop")) assertEquals(restartLogs.length, 0) @@ -107,7 +127,7 @@ class FakeDataSource extends FlowcoreDataSource { flowType: "test.0", eventTypes: ["test.created.0"], }, - baseUrlOverride: "http://localhost:99999", + baseUrlOverride: "http://localhost:9999", noTranslation: true, }) this.timeBucketsValue = opts.timeBuckets ?? ["20260331120000", "20260331130000"] @@ -125,12 +145,14 @@ class FakeDataSource extends FlowcoreDataSource { if (getBefore) { return Promise.resolve( this.timeBucketsValue.findLast((t) => Number.parseFloat(t) <= Number.parseFloat(timeBucket)) ?? - this.timeBucketsValue[this.timeBucketsValue.length - 1] ?? null, + this.timeBucketsValue[this.timeBucketsValue.length - 1] ?? + null, ) } return Promise.resolve( this.timeBucketsValue.find((t) => Number.parseFloat(t) >= Number.parseFloat(timeBucket)) ?? - this.timeBucketsValue[this.timeBucketsValue.length - 1] ?? null, + this.timeBucketsValue[this.timeBucketsValue.length - 1] ?? + null, ) } @@ -169,7 +191,7 @@ function createPumpWithFakeDataSource( stateManager: stateManager ?? createMockStateManager(), notifier: { type: "poller", intervalMs: 60_000 }, logger, - baseUrlOverride: "http://localhost:99999", + baseUrlOverride: "http://localhost:9999", noTranslation: true, }, fakeDataSource, @@ -178,15 +200,13 @@ function createPumpWithFakeDataSource( // #endregion -describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: false }, () => { - let fakeTime: FakeTime - +describe("startMainLoop self-heal", () => { beforeEach(() => { - fakeTime = new FakeTime() + jest.useFakeTimers() }) afterEach(() => { - fakeTime.restore() + jest.useRealTimers() }) it("(a) loop throws once then self-restarts and resets attempts on next successful fetch", async () => { @@ -210,11 +230,11 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal }) // First call should have happened and thrown - await fakeTime.tickAsync(0) + await tickAsync(0) // Backoff: attempt 1 → 1s - await fakeTime.tickAsync(1_000) + await tickAsync(1_000) // Drain microtasks for the restarted loop to make a second call - await fakeTime.tickAsync(0) + await tickAsync(0) assert(fakeDataSource.getEventsCalls >= 2, `expected >=2 calls, got ${fakeDataSource.getEventsCalls}`) assertEquals(pump.isRunning, true) @@ -228,7 +248,7 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal assert(restartLogs[0].message.includes("attempt 1")) pump.stop() - await fakeTime.tickAsync(60_000) + await tickAsync(60_000) // After graceful stop, callback fires without error assertEquals(callbackFired, true) assertEquals(callbackError, undefined) @@ -244,42 +264,42 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal void pump.start(() => {}) // First call (attempt 1 scheduled after 1s) - await fakeTime.tickAsync(0) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 1) // 1s → 2nd call (attempt 1 scheduled, then attempt 2 after 2s) - await fakeTime.tickAsync(1_000) - await fakeTime.tickAsync(0) + await tickAsync(1_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 2) // 2s → 3rd call (4s next) - await fakeTime.tickAsync(2_000) - await fakeTime.tickAsync(0) + await tickAsync(2_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 3) // 4s → 4th call (8s next) - await fakeTime.tickAsync(4_000) - await fakeTime.tickAsync(0) + await tickAsync(4_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 4) // 8s → 5th call (16s next) - await fakeTime.tickAsync(8_000) - await fakeTime.tickAsync(0) + await tickAsync(8_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 5) // 16s → 6th call (30s capped next) - await fakeTime.tickAsync(16_000) - await fakeTime.tickAsync(0) + await tickAsync(16_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 6) // 30s → 7th call (30s capped next) - await fakeTime.tickAsync(30_000) - await fakeTime.tickAsync(0) + await tickAsync(30_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 7) // 30s → 8th call (cap held) - await fakeTime.tickAsync(30_000) - await fakeTime.tickAsync(0) + await tickAsync(30_000) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 8) // Verify exact backoff sequence was logged @@ -292,7 +312,7 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal assertEquals(restartDelays.slice(0, 7), [1_000, 2_000, 4_000, 8_000, 16_000, 30_000, 30_000]) pump.stop() - await fakeTime.tickAsync(60_000) + await tickAsync(60_000) }) it("(c) stop() during pending retry → no further retry fires", async () => { @@ -305,14 +325,14 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal void pump.start(() => {}) // First call fires → throws → schedules 1s retry - await fakeTime.tickAsync(0) + await tickAsync(0) assertEquals(fakeDataSource.getEventsCalls, 1) // Stop BEFORE the 1s retry fires pump.stop() // Advance past the retry window - await fakeTime.tickAsync(60_000) + await tickAsync(60_000) // No second call should have happened assertEquals(fakeDataSource.getEventsCalls, 1) @@ -327,7 +347,7 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal const pump = createPumpWithFakeDataSource(fakeDataSource, logger) void pump.start(() => {}) - await fakeTime.tickAsync(0) + await tickAsync(0) assertThrows( () => pump.restart({ timeBucket: "2026-05-12T13:13" } as FlowcoreDataPumpState), @@ -339,7 +359,7 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal assertEquals(pump.isRunning, true) pump.stop() - await fakeTime.tickAsync(60_000) + await tickAsync(60_000) }) it("(e) poisoned restartTo causes loop to log and exit cleanly", async () => { @@ -369,7 +389,7 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal internals.abortController?.abort() // Let loop iterations + microtasks settle - await fakeTime.tickAsync(2_000) + await tickAsync(2_000) // Loop should have exited cleanly without throwing assertEquals(callbackFired, true) @@ -390,21 +410,21 @@ describe("startMainLoop self-heal", { sanitizeOps: false, sanitizeResources: fal const pump = createPumpWithFakeDataSource(fakeDataSource, logger) void pump.start(() => {}) - await fakeTime.tickAsync(0) + await tickAsync(0) assert(fakeDataSource.getEventsCalls >= 1) // Valid 14-digit timebucket — should not throw pump.restart({ timeBucket: "20260101000000" }) // Loop processes the restart and continues pulling - await fakeTime.tickAsync(1_000) - await fakeTime.tickAsync(0) + await tickAsync(1_000) + await tickAsync(0) assert(pump.isRunning) assert(fakeDataSource.getEventsCalls >= 2) pump.stop() - await fakeTime.tickAsync(60_000) + await tickAsync(60_000) }) }) diff --git a/test/tests/example.test.ts b/test/tests/example.test.ts index 57bcba0..a98def5 100644 --- a/test/tests/example.test.ts +++ b/test/tests/example.test.ts @@ -1,8 +1,7 @@ -import { assertEquals } from "@std/assert/equals" -import { describe, it } from "@std/testing/bdd" +import { describe, expect, it } from "bun:test" describe("Example", () => { it("should be successful", () => { - assertEquals(true, true) + expect(true).toEqual(true) }) }) diff --git a/test/tests/notifier.test.ts b/test/tests/notifier.test.ts index 8137bf0..d19bc2e 100644 --- a/test/tests/notifier.test.ts +++ b/test/tests/notifier.test.ts @@ -1,6 +1,4 @@ -import { assert, assertEquals, assertNotStrictEquals, assertStrictEquals } from "@std/assert" -import { afterEach, describe, it } from "@std/testing/bdd" -import { stub } from "@std/testing/mock" +import { afterEach, describe, expect, it } from "bun:test" import type { NotificationClient, NotificationEvent } from "@flowcore/sdk" import type { Subject } from "rxjs" import { _internals, FlowcoreNotifier } from "../../src/data-pump/notifier.ts" @@ -10,6 +8,32 @@ import type { FlowcoreLogger } from "../../src/data-pump/types.ts" const FAKE_API_KEY = "fc_testid_testsecret" +function assert(condition: unknown, message?: string): asserts condition { + expect(Boolean(condition), message).toBe(true) +} + +function assertEquals(actual: T, expected: T, message?: string) { + expect(actual, message).toEqual(expected) +} + +function assertNotStrictEquals(actual: T, expected: T, message?: string) { + expect(actual, message).not.toBe(expected) +} + +function assertStrictEquals(actual: T, expected: T, message?: string) { + expect(actual, message).toBe(expected) +} + +function stubProperty(target: T, key: K, value: T[K]) { + const original = target[key] + target[key] = value + return { + restore() { + target[key] = original + }, + } +} + interface FakeClientHandle { client: NotificationClient subject: Subject @@ -120,8 +144,7 @@ function createNotifier(timeoutMs?: number, logger?: FlowcoreLogger): FlowcoreNo // #endregion describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { - // deno-lint-ignore no-explicit-any - let factoryStub: any + let factoryStub: { restore: () => void } | undefined afterEach(() => { factoryStub?.restore() @@ -138,7 +161,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { handles.push(h) }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000) // Safety: if the bug is present, wait() will hang 20 s. We assert resolution @@ -171,7 +194,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { h.pendingPostConnect.push((handle) => handle.subject.error(new Error("close"))) }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000) await notifier.wait() @@ -216,7 +239,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { } }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000) await notifier.wait() // cycle 1 — WS error path @@ -234,7 +257,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { const { factory } = createFakeFactory({ onCreate: (h) => handles.push(h), }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(30) @@ -252,7 +275,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { const { factory } = createFakeFactory({ onCreate: (h) => handles.push(h), }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000) const controller = new AbortController() @@ -277,7 +300,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { h.pendingPostConnect.push((handle) => handle.subject.error(new Error("ws down"))) }, }) - const s = stub(_internals, "createNotificationClient", factory) + const s = stubProperty(_internals, "createNotificationClient", factory) const start = Date.now() try { const notifier = createNotifier(20_000) @@ -295,7 +318,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { const { factory } = createFakeFactory({ onCreate: (h) => handles.push(h), }) - const s = stub(_internals, "createNotificationClient", factory) + const s = stubProperty(_internals, "createNotificationClient", factory) try { const notifier = createNotifier(20) await notifier.wait() @@ -325,7 +348,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { }) }, }) - const s = stub(_internals, "createNotificationClient", factory) + const s = stubProperty(_internals, "createNotificationClient", factory) try { const notifier = createNotifier(20_000) await notifier.wait() @@ -340,7 +363,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { const { factory } = createFakeFactory({ onCreate: (h) => handles.push(h), }) - const s = stub(_internals, "createNotificationClient", factory) + const s = stubProperty(_internals, "createNotificationClient", factory) try { const notifier = createNotifier(20_000) const controller = new AbortController() @@ -382,7 +405,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { }) }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000) // Wrap the eventResolver to count how many times it would have fired. @@ -434,7 +457,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { h.connectError = new Error("connect failed") }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000) const start = Date.now() @@ -471,7 +494,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { handles.push(h) }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(30) // Safety: if the bug is present, wait() hangs. Force-resolve at 250ms so the @@ -521,7 +544,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { handles.push(h) }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(40) const safety = setTimeout(() => { @@ -562,7 +585,7 @@ describe("FlowcoreNotifier.waitWebSocket — bug fix (v0.20.1)", () => { }) }, }) - factoryStub = stub(_internals, "createNotificationClient", factory) + factoryStub = stubProperty(_internals, "createNotificationClient", factory) const notifier = createNotifier(20_000, logger) await notifier.wait() diff --git a/test/tests/pulse.test.ts b/test/tests/pulse.test.ts index 3c2c833..04be471 100644 --- a/test/tests/pulse.test.ts +++ b/test/tests/pulse.test.ts @@ -1,5 +1,4 @@ -import { assertEquals } from "@std/assert/equals" -import { describe, it } from "@std/testing/bdd" +import { describe, expect, it } from "bun:test" import { PulseEmitter, type PulseSnapshot } from "../../src/data-pump/pulse.ts" // The PulseEmitter posts via a Flowcore SDK Command, which routes through @@ -23,7 +22,7 @@ describe("PulseEmitter snapshot shape", () => { pulledTotal: 0, uptimeMs: 0, } - assertEquals(snapshot.sourceId, "source-1") + expect(snapshot.sourceId).toEqual("source-1") }) it("PulseSnapshot.sourceId is optional for back-compat", () => { @@ -41,7 +40,7 @@ describe("PulseEmitter snapshot shape", () => { pulledTotal: 0, uptimeMs: 0, } - assertEquals(snapshot.sourceId, undefined) + expect(snapshot.sourceId).toEqual(undefined) }) it("emitter accepts sourceId in options (compile-time check)", () => { @@ -55,7 +54,7 @@ describe("PulseEmitter snapshot shape", () => { }, () => null, ) - assertEquals(typeof emitter.start, "function") - assertEquals(typeof emitter.stop, "function") + expect(typeof emitter.start).toEqual("function") + expect(typeof emitter.stop).toEqual("function") }) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3727d6b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2023", "DOM"], + "types": ["bun"], + "strict": true, + "skipLibCheck": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["src/**/*.ts", "test/**/*.ts", "integration/app/**/*.ts"] +}