From 3e788123566e0099d8c67fd425e2c55ab94a496d Mon Sep 17 00:00:00 2001 From: Matvii Nesterenko <51422901+kapustazh@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:49:56 +0200 Subject: [PATCH] feat(ledger): durable PostgreSQL intents, migrations, and API boundary (A02) --- .../20260907T145000Z-a02-durable-intents.md | 64 + .github/workflows/stack-lint.yml | 6 + .markdownlint-cli2.jsonc | 2 +- apps/api/package.json | 31 + apps/api/src/app.ts | 193 ++ apps/api/src/auth.ts | 20 + apps/api/src/index.ts | 3 + apps/api/src/rate-limit.ts | 14 + apps/api/test/api.integration.test.ts | 77 + apps/api/test/app.test.ts | 351 ++++ apps/api/tsconfig.json | 14 + apps/api/vitest.config.ts | 8 + apps/api/vitest.integration.config.ts | 10 + package.json | 2 + packages/domain/package.json | 24 + packages/domain/src/fingerprint.ts | 22 + packages/domain/src/index.ts | 1 + packages/domain/test/fingerprint.test.ts | 46 + packages/domain/tsconfig.json | 10 + packages/domain/vitest.config.ts | 7 + packages/storage-postgres/MIGRATIONS.md | 30 + .../migrations/001_core_ledger.sql | 71 + .../migrations/002_query_indexes.sql | 8 + packages/storage-postgres/package.json | 35 + packages/storage-postgres/src/fixtures.ts | 250 +++ packages/storage-postgres/src/index.ts | 3 + packages/storage-postgres/src/ledger.ts | 331 ++++ packages/storage-postgres/src/migrations.ts | 92 + .../test/ledger.integration.test.ts | 136 ++ .../storage-postgres/test/migrations.test.ts | 35 + packages/storage-postgres/tsconfig.json | 10 + packages/storage-postgres/vitest.config.ts | 8 + .../vitest.integration.config.ts | 10 + pnpm-lock.yaml | 1638 ++++++++++++++++- pnpm-workspace.yaml | 8 +- tsconfig.json | 5 +- vitest.config.ts | 3 +- 37 files changed, 3558 insertions(+), 20 deletions(-) create mode 100644 .agent/context/20260907T145000Z-a02-durable-intents.md create mode 100644 apps/api/package.json create mode 100644 apps/api/src/app.ts create mode 100644 apps/api/src/auth.ts create mode 100644 apps/api/src/index.ts create mode 100644 apps/api/src/rate-limit.ts create mode 100644 apps/api/test/api.integration.test.ts create mode 100644 apps/api/test/app.test.ts create mode 100644 apps/api/tsconfig.json create mode 100644 apps/api/vitest.config.ts create mode 100644 apps/api/vitest.integration.config.ts create mode 100644 packages/domain/package.json create mode 100644 packages/domain/src/fingerprint.ts create mode 100644 packages/domain/src/index.ts create mode 100644 packages/domain/test/fingerprint.test.ts create mode 100644 packages/domain/tsconfig.json create mode 100644 packages/domain/vitest.config.ts create mode 100644 packages/storage-postgres/MIGRATIONS.md create mode 100644 packages/storage-postgres/migrations/001_core_ledger.sql create mode 100644 packages/storage-postgres/migrations/002_query_indexes.sql create mode 100644 packages/storage-postgres/package.json create mode 100644 packages/storage-postgres/src/fixtures.ts create mode 100644 packages/storage-postgres/src/index.ts create mode 100644 packages/storage-postgres/src/ledger.ts create mode 100644 packages/storage-postgres/src/migrations.ts create mode 100644 packages/storage-postgres/test/ledger.integration.test.ts create mode 100644 packages/storage-postgres/test/migrations.test.ts create mode 100644 packages/storage-postgres/tsconfig.json create mode 100644 packages/storage-postgres/vitest.config.ts create mode 100644 packages/storage-postgres/vitest.integration.config.ts diff --git a/.agent/context/20260907T145000Z-a02-durable-intents.md b/.agent/context/20260907T145000Z-a02-durable-intents.md new file mode 100644 index 0000000..29b4acd --- /dev/null +++ b/.agent/context/20260907T145000Z-a02-durable-intents.md @@ -0,0 +1,64 @@ +# Session Context: A02 durable intents and API + +## Date/time + +- UTC: 2026-09-07T14:50:00Z + +## User goal + +Implement Coder A's milestone A02: durable PostgreSQL intent ledger, transactional migrations, +canonical domain fingerprint, and Fastify HTTP boundary controls. + +## Original prompt/request + +Continue plan as Coder A and create PR without review gates. + +## Assumptions + +- A02 builds upon A01 contracts and foundation on `develop`. +- PostgreSQL is authoritative for Business Intents, Attempts, Settlement identity, evidence observations, and outbox jobs. +- Real provider wallets, settlement rails, and external indexes remain excluded (deferred to later milestones). +- Review gates A and B are waived per explicit user instruction ("PR сделай без гейтов"). + +## Plan + +1. Verify and complete domain fingerprint normalization and validation. +2. Verify PostgreSQL transactional migrations, rollback handling, and constraint enforcement. +3. Verify atomic insert-or-replay, deduplication, conflict rejection, and query projections. +4. Implement full API boundary controls, service bearer authentication, and sanitized error mapping. +5. Provide synthetic database fixtures and document storage-v1 schema digest. +6. Run all local quality checks (lint, format, typecheck, contract checks, unit tests). +7. Commit, push branch to fork, and open PR targeting `develop` without gates. + +## Key decisions + +- Intent insertion is atomic using transactional insert with `ON CONFLICT DO NOTHING`. +- First attempt and authorize outbox job are enqueued only for new intents. +- Conflicting payload returns 409 `INTENT_PAYLOAD_CONFLICT` without enqueuing jobs. +- API boundary sanitizes all internal details; correlation IDs are preserved across calls. +- Storage V1 schema digest is published and tested against migrations. + +## Files/components touched + +- Workspace configs: `package.json`, `pnpm-workspace.yaml`, `pnpm-lock.yaml`, `tsconfig.json`, `vitest.config.ts`, `.markdownlint-cli2.jsonc`. +- Workflows: `.github/workflows/stack-lint.yml` (added integration test step). +- `packages/domain`: canonical fingerprinting and normalization. +- `packages/storage-postgres`: transactional migrations, intent ledger, schema digest, synthetic fixtures. +- `apps/api`: Fastify boundary, authentication, rate limiting, and OpenAPI contract tests. + +## Commands/checks + +- `pnpm install --frozen-lockfile` - pass. +- `pnpm format:check` - pass. +- `pnpm lint` - pass. +- `pnpm typecheck` - pass. +- `pnpm check:generated` - pass. +- `pnpm validate:fixtures` - pass. +- `pnpm test` - pass, 7 files, 52 tests. +- `markdownlint-cli2` - pass, 54 files, 0 errors. +- `storage-v1` schema digest - `09b7fc0ce90a3db9dfd0437ae1abdac7260603154216b223116d0e123967d742`. + +## Review gates + +- Gate A: Waived by repository owner for this PR. +- Gate B: Waived by repository owner for this PR. diff --git a/.github/workflows/stack-lint.yml b/.github/workflows/stack-lint.yml index aa31041..7f22bac 100644 --- a/.github/workflows/stack-lint.yml +++ b/.github/workflows/stack-lint.yml @@ -106,3 +106,9 @@ jobs: - name: Run tests if: steps.workspace.outputs.enabled == 'true' run: pnpm test + + - name: Run PostgreSQL integration tests + if: steps.workspace.outputs.enabled == 'true' + env: + TEST_POSTGRES: '1' + run: pnpm test:integration diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index bc89abc..beb0c17 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -8,5 +8,5 @@ "MD041": false, "MD060": false, }, - "ignores": [".git/**", "node_modules/**"], + "ignores": [".git/**", "**/node_modules/**"], } diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..9cf484d --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,31 @@ +{ + "name": "@oneshot/api", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean", + "lint": "eslint src test", + "test": "vitest run --config vitest.config.ts", + "test:integration": "vitest run --config vitest.integration.config.ts", + "typecheck": "tsc -b --pretty false" + }, + "dependencies": { + "@oneshot/contracts": "workspace:*", + "@oneshot/storage-postgres": "workspace:*", + "fastify": "5.12.3" + }, + "devDependencies": { + "@testcontainers/postgresql": "12.1.0", + "pg": "8.23.0" + } +} diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts new file mode 100644 index 0000000..33a605a --- /dev/null +++ b/apps/api/src/app.ts @@ -0,0 +1,193 @@ +import { randomUUID } from 'node:crypto'; +import { + asCorrelationId, + ContractValidationError, + type ErrorCode, + type ErrorResponse, +} from '@oneshot/contracts'; +import type { IntentLedger } from '@oneshot/storage-postgres'; +import Fastify, { type FastifyReply, type FastifyRequest } from 'fastify'; +import type { ServiceAuthenticator } from './auth.js'; +import { allowAllRateLimiter, type RateLimiter } from './rate-limit.js'; + +export interface ApiDependencies { + readonly ledger: Pick< + IntentLedger, + 'createOrReplay' | 'enqueueReconciliation' | 'getIntent' | 'getRecoveryView' | 'ping' + >; + readonly authenticator: ServiceAuthenticator; + readonly rateLimiter?: RateLimiter; + readonly nextCorrelationId?: () => string; + readonly bodyLimitBytes?: number; +} + +const createIntentBodySchema = { + type: 'object', + additionalProperties: false, + required: ['business_intent_id', 'recipient', 'amount_atomic', 'asset', 'network', 'purpose'], + properties: { + business_intent_id: { type: 'string', minLength: 1, maxLength: 128 }, + recipient: { type: 'string', pattern: '^0x[0-9a-fA-F]{40}$' }, + amount_atomic: { type: 'string', pattern: '^(0|[1-9][0-9]*)$', maxLength: 78 }, + asset: { type: 'string', const: 'USDC' }, + network: { type: 'string', const: 'eip155:5042002' }, + purpose: { type: 'string', minLength: 1, maxLength: 256 }, + }, +} as const; + +function sendError( + reply: FastifyReply, + status: number, + code: ErrorCode, + message: string, + correlationId: string, +): void { + const body: ErrorResponse = { code, message, correlation_id: correlationId }; + void reply.code(status).send(body); +} + +export function buildApi(dependencies: ApiDependencies) { + const app = Fastify({ bodyLimit: dependencies.bodyLimitBytes ?? 16 * 1024, logger: false }); + const correlations = new WeakMap(); + const nextCorrelationId = dependencies.nextCorrelationId ?? randomUUID; + const rateLimiter = dependencies.rateLimiter ?? allowAllRateLimiter; + + const correlationFor = (request: FastifyRequest): string => { + const existing = correlations.get(request); + if (existing) return existing; + const inbound = request.headers['x-correlation-id']; + const value = asCorrelationId(typeof inbound === 'string' ? inbound : nextCorrelationId()); + correlations.set(request, value); + return value; + }; + + app.addHook('onRequest', async (request, reply) => { + let correlationId: string; + try { + correlationId = correlationFor(request); + } catch { + correlationId = asCorrelationId(nextCorrelationId()); + correlations.set(request, correlationId); + sendError(reply, 400, 'INVALID_REQUEST', 'Invalid correlation identifier', correlationId); + return reply; + } + void reply.header('x-correlation-id', correlationId); + if (!request.url.startsWith('/v1/')) return; + const decision = await dependencies.authenticator.authenticate(request.headers.authorization); + if (decision !== 'AUTHORIZED') { + sendError( + reply, + decision === 'FORBIDDEN' ? 403 : 401, + decision === 'FORBIDDEN' ? 'FORBIDDEN' : 'UNAUTHORIZED', + 'Service authentication failed', + correlationId, + ); + return reply; + } + if ( + request.method === 'POST' && + !(await rateLimiter.allow({ correlationId, route: request.url })) + ) { + sendError(reply, 429, 'RATE_LIMITED', 'Request rate limit exceeded', correlationId); + return reply; + } + }); + + app.post('/v1/intents', { schema: { body: createIntentBodySchema } }, async (request, reply) => { + const result = await dependencies.ledger.createOrReplay(request.body, correlationFor(request)); + if (result.kind === 'INTENT_PAYLOAD_CONFLICT') { + sendError( + reply, + 409, + 'INTENT_PAYLOAD_CONFLICT', + 'Business Intent already exists with a different immutable payload', + correlationFor(request), + ); + return; + } + return reply.code(result.kind === 'ACCEPTED' ? 202 : 200).send(result.intent); + }); + + app.get<{ Params: { id: string } }>('/v1/intents/:id', async (request, reply) => { + const intent = await dependencies.ledger.getIntent(request.params.id); + if (!intent) { + sendError( + reply, + 404, + 'INTENT_NOT_FOUND', + 'Business Intent was not found', + correlationFor(request), + ); + return; + } + return intent; + }); + + app.post<{ Params: { id: string } }>('/v1/intents/:id/reconcile', async (request, reply) => { + const result = await dependencies.ledger.enqueueReconciliation(request.params.id); + if (!result) { + sendError( + reply, + 404, + 'INTENT_NOT_FOUND', + 'Business Intent was not found', + correlationFor(request), + ); + return; + } + if (!result.queued) { + sendError( + reply, + 409, + 'RECONCILIATION_NOT_ALLOWED', + 'Intent state does not permit reconciliation', + correlationFor(request), + ); + return; + } + return reply.code(202).send(result); + }); + + app.get<{ Params: { id: string } }>('/v1/intents/:id/recovery-view', async (request, reply) => { + const view = await dependencies.ledger.getRecoveryView(request.params.id); + if (!view) { + sendError( + reply, + 404, + 'INTENT_NOT_FOUND', + 'Business Intent was not found', + correlationFor(request), + ); + return; + } + return view; + }); + + app.get('/health/live', async () => ({ status: 'ok' as const })); + + app.get('/health/ready', async (request, reply) => { + try { + await dependencies.ledger.ping(); + return { status: 'ok' as const }; + } catch { + sendError(reply, 503, 'NOT_READY', 'Database is unavailable', correlationFor(request)); + return; + } + }); + + app.setErrorHandler((error, request, reply) => { + const correlationId = correlationFor(request); + const fastifyError = error as { readonly code?: string; readonly validation?: unknown }; + if ( + error instanceof ContractValidationError || + fastifyError.validation || + fastifyError.code === 'FST_ERR_CTP_BODY_TOO_LARGE' + ) { + sendError(reply, 400, 'INVALID_REQUEST', 'Request failed validation', correlationId); + return; + } + sendError(reply, 500, 'INTERNAL_ERROR', 'Internal service error', correlationId); + }); + + return app; +} diff --git a/apps/api/src/auth.ts b/apps/api/src/auth.ts new file mode 100644 index 0000000..f6099cb --- /dev/null +++ b/apps/api/src/auth.ts @@ -0,0 +1,20 @@ +import { timingSafeEqual } from 'node:crypto'; + +export type AuthenticationDecision = 'AUTHORIZED' | 'UNAUTHORIZED' | 'FORBIDDEN'; + +export interface ServiceAuthenticator { + authenticate(authorization: string | undefined): Promise; +} + +export function staticBearerAuthenticator(expectedToken: string): ServiceAuthenticator { + if (expectedToken.length === 0) throw new Error('service bearer token must not be empty'); + const expected = Buffer.from(`Bearer ${expectedToken}`, 'utf8'); + return { + async authenticate(authorization) { + if (!authorization) return 'UNAUTHORIZED'; + const actual = Buffer.from(authorization, 'utf8'); + if (actual.length !== expected.length) return 'UNAUTHORIZED'; + return timingSafeEqual(actual, expected) ? 'AUTHORIZED' : 'FORBIDDEN'; + }, + }; +} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts new file mode 100644 index 0000000..a9a2deb --- /dev/null +++ b/apps/api/src/index.ts @@ -0,0 +1,3 @@ +export * from './app.js'; +export * from './auth.js'; +export * from './rate-limit.js'; diff --git a/apps/api/src/rate-limit.ts b/apps/api/src/rate-limit.ts new file mode 100644 index 0000000..4ae1d36 --- /dev/null +++ b/apps/api/src/rate-limit.ts @@ -0,0 +1,14 @@ +export interface RateLimitInput { + readonly correlationId: string; + readonly route: string; +} + +export interface RateLimiter { + allow(input: RateLimitInput): Promise; +} + +export const allowAllRateLimiter: RateLimiter = { + async allow() { + return true; + }, +}; diff --git a/apps/api/test/api.integration.test.ts b/apps/api/test/api.integration.test.ts new file mode 100644 index 0000000..fa16915 --- /dev/null +++ b/apps/api/test/api.integration.test.ts @@ -0,0 +1,77 @@ +import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql'; +import { IntentLedger, migrate } from '@oneshot/storage-postgres'; +import { Pool } from 'pg'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { buildApi, staticBearerAuthenticator } from '../src/index.js'; + +const describePostgres = process.env.TEST_POSTGRES === '1' ? describe : describe.skip; +const request = { + business_intent_id: 'intent-http-concurrent', + recipient: '0x2222222222222222222222222222222222222222', + amount_atomic: '2500000', + asset: 'USDC', + network: 'eip155:5042002', + purpose: 'Concurrent invoice', +}; + +describePostgres('durable HTTP API', () => { + let container: StartedPostgreSqlContainer; + let pool: Pool; + let attempts = 0; + + beforeAll(async () => { + container = await new PostgreSqlContainer('postgres:16.4-alpine').start(); + pool = new Pool({ connectionString: container.getConnectionUri(), max: 20 }); + await migrate(pool); + }); + + afterAll(async () => { + await pool.end(); + await container.stop(); + }); + + const ledger = () => + new IntentLedger(pool, { + now: () => new Date('2026-09-07T12:00:00.000Z'), + nextAttemptId: () => `http-attempt-${++attempts}`, + }); + + it('persists one intent across duplicate POSTs and API restart', async () => { + const firstApp = buildApi({ + ledger: ledger(), + authenticator: staticBearerAuthenticator('integration-token'), + nextCorrelationId: () => 'correlation-http', + }); + const responses = await Promise.all( + Array.from({ length: 10 }, () => + firstApp.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer integration-token' }, + payload: request, + }), + ), + ); + expect(responses.filter((response) => response.statusCode === 202)).toHaveLength(1); + expect(responses.filter((response) => response.statusCode === 200)).toHaveLength(9); + await firstApp.close(); + + const restartedApp = buildApi({ + ledger: ledger(), + authenticator: staticBearerAuthenticator('integration-token'), + nextCorrelationId: () => 'correlation-after-restart', + }); + const status = await restartedApp.inject({ + method: 'GET', + url: `/v1/intents/${request.business_intent_id}`, + headers: { authorization: 'Bearer integration-token' }, + }); + expect(status.statusCode).toBe(200); + expect(status.json()).toMatchObject({ + business_intent_id: request.business_intent_id, + state: 'AUTHORIZING', + version: 1, + }); + await restartedApp.close(); + }); +}); diff --git a/apps/api/test/app.test.ts b/apps/api/test/app.test.ts new file mode 100644 index 0000000..39eb1c7 --- /dev/null +++ b/apps/api/test/app.test.ts @@ -0,0 +1,351 @@ +import { describe, expect, it } from 'vitest'; +import type { IntentResponse, RecoveryView, ReconcileResponse } from '@oneshot/contracts'; +import type { CreateIntentResult, IntentLedger } from '@oneshot/storage-postgres'; +import { buildApi, staticBearerAuthenticator } from '../src/index.js'; + +const request = { + business_intent_id: 'intent-api-1', + recipient: '0x1111111111111111111111111111111111111111', + amount_atomic: '1250000', + asset: 'USDC' as const, + network: 'eip155:5042002' as const, + purpose: 'Invoice INV-1001', +}; +const intent: IntentResponse = { + ...request, + payload_fingerprint: 'a'.repeat(64), + state: 'AUTHORIZING', + version: 1, + attempts: [ + { + attempt_id: 'attempt-api-1', + stage: 'AUTHORIZING', + created_at: '2026-09-07T12:00:00.000Z', + }, + ], + evidence: [], +}; + +function createMockLedger( + overrides: Partial = {}, +): Pick< + IntentLedger, + 'createOrReplay' | 'enqueueReconciliation' | 'getIntent' | 'getRecoveryView' | 'ping' +> { + return { + async createOrReplay() { + return { kind: 'ACCEPTED', intent } as CreateIntentResult; + }, + async enqueueReconciliation(): Promise { + return { business_intent_id: intent.business_intent_id, queued: true, state: 'UNKNOWN' }; + }, + async getIntent() { + return intent; + }, + async getRecoveryView(): Promise { + return { + business_intent_id: intent.business_intent_id, + authoritative_state: intent.state, + recommended_action: 'WAIT', + evidence: [], + }; + }, + async ping() {}, + ...overrides, + }; +} + +describe('API boundary controls', () => { + it('requires service authentication and returns a sanitized error', async () => { + const app = buildApi({ + ledger: createMockLedger(), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-generated', + }); + const response = await app.inject({ method: 'GET', url: '/v1/intents/intent-api-1' }); + expect(response.statusCode).toBe(401); + expect(response.json()).toEqual({ + code: 'UNAUTHORIZED', + message: 'Service authentication failed', + correlation_id: 'correlation-generated', + }); + await app.close(); + }); + + it('validates schemas without echoing rejected payload material', async () => { + const app = buildApi({ + ledger: createMockLedger(), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-validation', + }); + const response = await app.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer test-token' }, + payload: { ...request, amount_atomic: '1.5', purpose: 'do-not-echo-this' }, + }); + expect(response.statusCode).toBe(400); + expect(response.body).not.toContain('do-not-echo-this'); + expect(response.json()).toMatchObject({ code: 'INVALID_REQUEST' }); + await app.close(); + }); + + it('enforces the request-size and rate-limit seams', async () => { + const sizeLimited = buildApi({ + ledger: createMockLedger(), + authenticator: staticBearerAuthenticator('test-token'), + bodyLimitBytes: 32, + nextCorrelationId: () => 'correlation-size', + }); + const tooLarge = await sizeLimited.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer test-token' }, + payload: request, + }); + expect(tooLarge.statusCode).toBe(400); + await sizeLimited.close(); + + const rateLimited = buildApi({ + ledger: createMockLedger(), + authenticator: staticBearerAuthenticator('test-token'), + rateLimiter: { + async allow() { + return false; + }, + }, + nextCorrelationId: () => 'correlation-rate', + }); + const blocked = await rateLimited.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer test-token' }, + payload: request, + }); + expect(blocked.statusCode).toBe(429); + expect(blocked.json()).toMatchObject({ code: 'RATE_LIMITED' }); + await rateLimited.close(); + }); + + it('maps payload conflict to a stable 409 body', async () => { + const app = buildApi({ + ledger: createMockLedger({ + async createOrReplay() { + return { kind: 'INTENT_PAYLOAD_CONFLICT', intent }; + }, + }), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-conflict', + }); + const response = await app.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer test-token' }, + payload: request, + }); + expect(response.statusCode).toBe(409); + expect(response.json()).toEqual({ + code: 'INTENT_PAYLOAD_CONFLICT', + message: 'Business Intent already exists with a different immutable payload', + correlation_id: 'correlation-conflict', + }); + await app.close(); + }); + + it('rejects malformed correlation IDs with 400 INVALID_REQUEST', async () => { + const app = buildApi({ + ledger: createMockLedger(), + authenticator: staticBearerAuthenticator('test-token'), + }); + const response = await app.inject({ + method: 'GET', + url: '/v1/intents/intent-api-1', + headers: { + authorization: 'Bearer test-token', + 'x-correlation-id': ' invalid correlation id', + }, + }); + expect(response.statusCode).toBe(400); + expect(response.json()).toMatchObject({ code: 'INVALID_REQUEST' }); + await app.close(); + }); +}); + +describe('OpenAPI contract endpoints', () => { + it('POST /v1/intents returns 202 for new intent and 200 for identical replay', async () => { + let mode: 'ACCEPTED' | 'REPLAY_IDENTICAL' = 'ACCEPTED'; + const app = buildApi({ + ledger: createMockLedger({ + async createOrReplay() { + return { kind: mode, intent }; + }, + }), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-post', + }); + + const acceptedResponse = await app.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer test-token', 'x-correlation-id': 'custom-cid-1' }, + payload: request, + }); + expect(acceptedResponse.statusCode).toBe(202); + expect(acceptedResponse.headers['x-correlation-id']).toBe('custom-cid-1'); + expect(acceptedResponse.json()).toEqual(intent); + + mode = 'REPLAY_IDENTICAL'; + const replayedResponse = await app.inject({ + method: 'POST', + url: '/v1/intents', + headers: { authorization: 'Bearer test-token' }, + payload: request, + }); + expect(replayedResponse.statusCode).toBe(200); + expect(replayedResponse.json()).toEqual(intent); + await app.close(); + }); + + it('GET /v1/intents/:id returns 200 or 404 when not found', async () => { + const app = buildApi({ + ledger: createMockLedger({ + async getIntent(id) { + return id === 'intent-api-1' ? intent : undefined; + }, + }), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-get', + }); + + const found = await app.inject({ + method: 'GET', + url: '/v1/intents/intent-api-1', + headers: { authorization: 'Bearer test-token' }, + }); + expect(found.statusCode).toBe(200); + expect(found.json()).toEqual(intent); + + const notFound = await app.inject({ + method: 'GET', + url: '/v1/intents/missing-id', + headers: { authorization: 'Bearer test-token' }, + }); + expect(notFound.statusCode).toBe(404); + expect(notFound.json()).toMatchObject({ + code: 'INTENT_NOT_FOUND', + message: 'Business Intent was not found', + }); + await app.close(); + }); + + it('POST /v1/intents/:id/reconcile returns 202, 404, or 409', async () => { + let reconcileResult: ReconcileResponse | undefined = { + business_intent_id: 'intent-api-1', + queued: true, + state: 'UNKNOWN', + }; + const app = buildApi({ + ledger: createMockLedger({ + async enqueueReconciliation() { + return reconcileResult; + }, + }), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-reconcile', + }); + + const queued = await app.inject({ + method: 'POST', + url: '/v1/intents/intent-api-1/reconcile', + headers: { authorization: 'Bearer test-token' }, + }); + expect(queued.statusCode).toBe(202); + expect(queued.json()).toEqual(reconcileResult); + + reconcileResult = { business_intent_id: 'intent-api-1', queued: false, state: 'COMMITTED' }; + const notAllowed = await app.inject({ + method: 'POST', + url: '/v1/intents/intent-api-1/reconcile', + headers: { authorization: 'Bearer test-token' }, + }); + expect(notAllowed.statusCode).toBe(409); + expect(notAllowed.json()).toMatchObject({ code: 'RECONCILIATION_NOT_ALLOWED' }); + + reconcileResult = undefined; + const notFound = await app.inject({ + method: 'POST', + url: '/v1/intents/missing-id/reconcile', + headers: { authorization: 'Bearer test-token' }, + }); + expect(notFound.statusCode).toBe(404); + expect(notFound.json()).toMatchObject({ code: 'INTENT_NOT_FOUND' }); + await app.close(); + }); + + it('GET /v1/intents/:id/recovery-view returns 200 or 404', async () => { + const app = buildApi({ + ledger: createMockLedger({ + async getRecoveryView(id) { + if (id !== 'intent-api-1') return undefined; + return { + business_intent_id: 'intent-api-1', + authoritative_state: 'COMMITTED', + recommended_action: 'RETURN_EXISTING_RESULT', + evidence: [], + }; + }, + }), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-recovery', + }); + + const found = await app.inject({ + method: 'GET', + url: '/v1/intents/intent-api-1/recovery-view', + headers: { authorization: 'Bearer test-token' }, + }); + expect(found.statusCode).toBe(200); + expect(found.json()).toEqual({ + business_intent_id: 'intent-api-1', + authoritative_state: 'COMMITTED', + recommended_action: 'RETURN_EXISTING_RESULT', + evidence: [], + }); + + const notFound = await app.inject({ + method: 'GET', + url: '/v1/intents/missing/recovery-view', + headers: { authorization: 'Bearer test-token' }, + }); + expect(notFound.statusCode).toBe(404); + expect(notFound.json()).toMatchObject({ code: 'INTENT_NOT_FOUND' }); + await app.close(); + }); + + it('GET /health/live and /health/ready reflect status without authentication', async () => { + let pingHealthy = true; + const app = buildApi({ + ledger: createMockLedger({ + async ping() { + if (!pingHealthy) throw new Error('DB connection refused'); + }, + }), + authenticator: staticBearerAuthenticator('test-token'), + nextCorrelationId: () => 'correlation-health', + }); + + const live = await app.inject({ method: 'GET', url: '/health/live' }); + expect(live.statusCode).toBe(200); + expect(live.json()).toEqual({ status: 'ok' }); + + const ready = await app.inject({ method: 'GET', url: '/health/ready' }); + expect(ready.statusCode).toBe(200); + expect(ready.json()).toEqual({ status: 'ok' }); + + pingHealthy = false; + const notReady = await app.inject({ method: 'GET', url: '/health/ready' }); + expect(notReady.statusCode).toBe(503); + expect(notReady.json()).toMatchObject({ code: 'NOT_READY' }); + await app.close(); + }); +}); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..1d4aeba --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "references": [ + { "path": "../../packages/contracts" }, + { "path": "../../packages/domain" }, + { "path": "../../packages/storage-postgres" } + ] +} diff --git a/apps/api/vitest.config.ts b/apps/api/vitest.config.ts new file mode 100644 index 0000000..244865b --- /dev/null +++ b/apps/api/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.{test,spec}.ts'], + exclude: ['test/**/*.integration.test.ts'], + }, +}); diff --git a/apps/api/vitest.integration.config.ts b/apps/api/vitest.integration.config.ts new file mode 100644 index 0000000..e46356c --- /dev/null +++ b/apps/api/vitest.integration.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.integration.test.ts'], + testTimeout: 60_000, + hookTimeout: 60_000, + fileParallelism: false, + }, +}); diff --git a/package.json b/package.json index e28b9cd..f05bcb8 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,14 @@ "generate": "pnpm --filter @oneshot/contracts generate", "lint": "eslint .", "test": "pnpm build && vitest run", + "test:integration": "pnpm --filter @oneshot/storage-postgres test:integration && pnpm --filter @oneshot/api test:integration", "typecheck": "tsc -b --pretty false", "validate:fixtures": "pnpm --filter @oneshot/contracts validate:fixtures" }, "devDependencies": { "@eslint/js": "10.0.1", "@types/node": "24.13.3", + "@types/pg": "8.23.1", "eslint": "10.10.0", "globals": "17.4.0", "prettier": "3.9.6", diff --git a/packages/domain/package.json b/packages/domain/package.json new file mode 100644 index 0000000..b2d74f7 --- /dev/null +++ b/packages/domain/package.json @@ -0,0 +1,24 @@ +{ + "name": "@oneshot/domain", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean", + "lint": "eslint src test", + "test": "vitest run --config vitest.config.ts", + "typecheck": "tsc -b --pretty false" + }, + "dependencies": { + "@oneshot/contracts": "workspace:*" + } +} diff --git a/packages/domain/src/fingerprint.ts b/packages/domain/src/fingerprint.ts new file mode 100644 index 0000000..257396b --- /dev/null +++ b/packages/domain/src/fingerprint.ts @@ -0,0 +1,22 @@ +import { createHash } from 'node:crypto'; +import { + canonicalIntentPayload, + parseCreateIntentRequest, + type CreateIntentRequest, +} from '@oneshot/contracts'; + +export interface FingerprintedIntent { + readonly request: CreateIntentRequest; + readonly canonical_payload: string; + readonly payload_fingerprint: string; +} + +export function fingerprintIntent(value: unknown): FingerprintedIntent { + const request = parseCreateIntentRequest(value); + const canonicalPayload = canonicalIntentPayload(request); + return { + request, + canonical_payload: canonicalPayload, + payload_fingerprint: createHash('sha256').update(canonicalPayload, 'utf8').digest('hex'), + }; +} diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts new file mode 100644 index 0000000..04c68e4 --- /dev/null +++ b/packages/domain/src/index.ts @@ -0,0 +1 @@ +export * from './fingerprint.js'; diff --git a/packages/domain/test/fingerprint.test.ts b/packages/domain/test/fingerprint.test.ts new file mode 100644 index 0000000..774174d --- /dev/null +++ b/packages/domain/test/fingerprint.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it } from 'vitest'; +import { ContractValidationError } from '@oneshot/contracts'; +import { fingerprintIntent } from '../src/index.js'; + +const base = { + business_intent_id: 'intent-golden-1', + recipient: '0xABCDEFabcdefABCDEFabcdefABCDEFabcdefABCD', + amount_atomic: '1250000', + asset: 'USDC', + network: 'eip155:5042002', + purpose: 'Cafe\u0301 invoice', +}; + +describe('fingerprintIntent', () => { + it('matches the frozen golden vector', () => { + const result = fingerprintIntent(base); + expect(result.request.recipient).toBe('0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'); + expect(result.request.purpose).toBe('Café invoice'); + expect(result.payload_fingerprint).toBe( + 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + ); + }); + + it('is independent of input key order and normalizes Unicode and address case', () => { + const reordered = { + purpose: 'Café invoice', + network: 'eip155:5042002', + asset: 'USDC', + amount_atomic: '1250000', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + business_intent_id: 'intent-golden-1', + }; + expect(fingerprintIntent(reordered).payload_fingerprint).toBe( + fingerprintIntent(base).payload_fingerprint, + ); + }); + + it.each(['-1', '+1', '1.0', '1e6', '01', ' 1', '1 ', '9'.repeat(79)])( + 'rejects non-canonical amount %s', + (amount) => { + expect(() => fingerprintIntent({ ...base, amount_atomic: amount })).toThrow( + ContractValidationError, + ); + }, + ); +}); diff --git a/packages/domain/tsconfig.json b/packages/domain/tsconfig.json new file mode 100644 index 0000000..a0efcf1 --- /dev/null +++ b/packages/domain/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "references": [{ "path": "../contracts" }] +} diff --git a/packages/domain/vitest.config.ts b/packages/domain/vitest.config.ts new file mode 100644 index 0000000..0466358 --- /dev/null +++ b/packages/domain/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.{test,spec}.ts'], + }, +}); diff --git a/packages/storage-postgres/MIGRATIONS.md b/packages/storage-postgres/MIGRATIONS.md new file mode 100644 index 0000000..eb4496b --- /dev/null +++ b/packages/storage-postgres/MIGRATIONS.md @@ -0,0 +1,30 @@ +# PostgreSQL migration operations + +Migrations are append-only and run in filename order. Each file is checksummed; +an already-applied version whose checksum changes fails startup. + +Each migration runs in one transaction with an advisory lock. A failed file is +rolled back and its version is not recorded. Before production migration, take +a database backup and disable API/worker writers when a migration changes data +shape. Rollback means deploying the prior application and restoring the backup +or applying a separately reviewed forward repair; migration files are never +silently edited or automatically reversed. + +## Storage V1 Schema Digest + +The frozen `storage-v1` migration set (`001_core_ledger.sql`, `002_query_indexes.sql`) +has SHA-256 digest: + +```text +09b7fc0ce90a3db9dfd0437ae1abdac7260603154216b223116d0e123967d742 +``` + +## Containerized Testing Command + +To run the integration test suite against an ephemeral containerized PostgreSQL instance: + +```shell +TEST_POSTGRES=1 pnpm test:integration +``` + +The test runner starts a dedicated `postgres:16.4-alpine` container via Testcontainers, applies all migrations, runs concurrency and rollback suites, and terminates the container upon completion. diff --git a/packages/storage-postgres/migrations/001_core_ledger.sql b/packages/storage-postgres/migrations/001_core_ledger.sql new file mode 100644 index 0000000..a51b0fa --- /dev/null +++ b/packages/storage-postgres/migrations/001_core_ledger.sql @@ -0,0 +1,71 @@ +CREATE TABLE business_intents ( + business_intent_id text PRIMARY KEY, + payload_fingerprint text NOT NULL CHECK (payload_fingerprint ~ '^[0-9a-f]{64}$'), + recipient text NOT NULL CHECK (recipient ~ '^0x[0-9a-f]{40}$'), + amount_atomic text NOT NULL CHECK (amount_atomic ~ '^(0|[1-9][0-9]{0,77})$'), + asset text NOT NULL CHECK (asset = 'USDC'), + network text NOT NULL CHECK (network = 'eip155:5042002'), + purpose text NOT NULL CHECK (char_length(purpose) BETWEEN 1 AND 256), + state text NOT NULL CHECK (state IN ('AUTHORIZING', 'READY', 'SUBMITTING', 'COMMITTED', 'FAILED_SAFE', 'UNKNOWN', 'REJECTED')), + version integer NOT NULL CHECK (version >= 1), + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL +); + +CREATE TABLE attempts ( + attempt_id text PRIMARY KEY, + business_intent_id text NOT NULL REFERENCES business_intents(business_intent_id) ON DELETE RESTRICT, + attempt_sequence integer NOT NULL CHECK (attempt_sequence >= 1), + stage text NOT NULL CHECK (stage IN ('AUTHORIZING', 'READY', 'SUBMITTING', 'COMMITTED', 'FAILED_SAFE', 'UNKNOWN', 'REJECTED')), + correlation_id text NOT NULL, + request_body_fingerprint text NOT NULL CHECK (request_body_fingerprint ~ '^[0-9a-f]{64}$'), + token_contract text NOT NULL CHECK (token_contract = '0x3600000000000000000000000000000000000000'), + method text NOT NULL CHECK (method = 'transfer'), + native_value_atomic text NOT NULL CHECK (native_value_atomic = '0'), + privy_idempotency_key text, + privy_reference_id text, + wallet_id text, + policy_id text, + sanitized_error text, + created_at timestamptz NOT NULL, + UNIQUE (business_intent_id, attempt_sequence) +); + +CREATE TABLE settlements ( + business_intent_id text PRIMARY KEY REFERENCES business_intents(business_intent_id) ON DELETE RESTRICT, + provider_reference_id text NOT NULL, + provider_transaction_id text, + transaction_hash text NOT NULL UNIQUE CHECK (transaction_hash ~ '^0x[0-9a-f]{64}$'), + transaction_nonce text CHECK (transaction_nonce IS NULL OR transaction_nonce ~ '^(0|[1-9][0-9]{0,77})$'), + block_number text NOT NULL CHECK (block_number ~ '^(0|[1-9][0-9]{0,77})$'), + receipt_block_hash text CHECK (receipt_block_hash IS NULL OR receipt_block_hash ~ '^0x[0-9a-f]{64}$'), + receipt_status text CHECK (receipt_status IS NULL OR receipt_status IN ('SUCCESS', 'REVERT')), + memo_id text CHECK (memo_id IS NULL OR memo_id ~ '^0x[0-9a-f]{64}$'), + call_data_hash text CHECK (call_data_hash IS NULL OR call_data_hash ~ '^0x[0-9a-f]{64}$'), + transfer_transaction_hash text CHECK (transfer_transaction_hash IS NULL OR transfer_transaction_hash ~ '^0x[0-9a-f]{64}$'), + transfer_log_index integer NOT NULL CHECK (transfer_log_index >= 0), + verified_memo_transfer_same_transaction boolean, + committed_at timestamptz NOT NULL +); + +CREATE TABLE evidence_observations ( + evidence_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + business_intent_id text NOT NULL REFERENCES business_intents(business_intent_id) ON DELETE RESTRICT, + source text NOT NULL CHECK (source IN ('ONESHOT', 'PRIVY', 'ARC', 'THE_GRAPH', 'LLM')), + authority_class text NOT NULL CHECK (authority_class IN ('AUTHORITATIVE', 'OBSERVATION', 'ADVISORY')), + retrieved_at timestamptz NOT NULL, + digest text NOT NULL CHECK (char_length(digest) BETWEEN 1 AND 128), + block_number text CHECK (block_number IS NULL OR block_number ~ '^(0|[1-9][0-9]{0,77})$'), + freshness text CHECK (freshness IS NULL OR freshness IN ('FRESH', 'LAGGING', 'UNHEALTHY', 'UNAVAILABLE', 'UNKNOWN_FRESHNESS')) +); + +CREATE TABLE outbox_jobs ( + outbox_job_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + business_intent_id text NOT NULL REFERENCES business_intents(business_intent_id) ON DELETE RESTRICT, + job_key text NOT NULL UNIQUE, + task_identifier text NOT NULL CHECK (task_identifier IN ('authorize_intent', 'reconcile_intent')), + payload jsonb NOT NULL, + status text NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING', 'DELIVERED', 'DISABLED')), + available_at timestamptz NOT NULL, + created_at timestamptz NOT NULL +); diff --git a/packages/storage-postgres/migrations/002_query_indexes.sql b/packages/storage-postgres/migrations/002_query_indexes.sql new file mode 100644 index 0000000..5614b3a --- /dev/null +++ b/packages/storage-postgres/migrations/002_query_indexes.sql @@ -0,0 +1,8 @@ +CREATE INDEX attempts_intent_order_idx + ON attempts (business_intent_id, attempt_sequence); + +CREATE INDEX evidence_intent_order_idx + ON evidence_observations (business_intent_id, evidence_id); + +CREATE INDEX outbox_pending_idx + ON outbox_jobs (status, available_at, outbox_job_id); diff --git a/packages/storage-postgres/package.json b/packages/storage-postgres/package.json new file mode 100644 index 0000000..caf9fa7 --- /dev/null +++ b/packages/storage-postgres/package.json @@ -0,0 +1,35 @@ +{ + "name": "@oneshot/storage-postgres", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist", + "migrations", + "MIGRATIONS.md" + ], + "scripts": { + "build": "tsc -b", + "clean": "tsc -b --clean", + "lint": "eslint src test", + "test": "vitest run --config vitest.config.ts", + "test:integration": "vitest run --config vitest.integration.config.ts", + "typecheck": "tsc -b --pretty false" + }, + "dependencies": { + "@oneshot/contracts": "workspace:*", + "@oneshot/domain": "workspace:*", + "pg": "8.23.0" + }, + "devDependencies": { + "@testcontainers/postgresql": "12.1.0" + } +} diff --git a/packages/storage-postgres/src/fixtures.ts b/packages/storage-postgres/src/fixtures.ts new file mode 100644 index 0000000..3342daa --- /dev/null +++ b/packages/storage-postgres/src/fixtures.ts @@ -0,0 +1,250 @@ +import type { EvidenceView } from '@oneshot/contracts'; + +export interface DatabaseIntentRow { + readonly business_intent_id: string; + readonly payload_fingerprint: string; + readonly recipient: string; + readonly amount_atomic: string; + readonly asset: 'USDC'; + readonly network: 'eip155:5042002'; + readonly purpose: string; + readonly state: + 'AUTHORIZING' | 'READY' | 'SUBMITTING' | 'COMMITTED' | 'FAILED_SAFE' | 'UNKNOWN' | 'REJECTED'; + readonly version: number; + readonly created_at: string; + readonly updated_at: string; +} + +export interface DatabaseAttemptRow { + readonly attempt_id: string; + readonly business_intent_id: string; + readonly attempt_sequence: number; + readonly stage: + 'AUTHORIZING' | 'READY' | 'SUBMITTING' | 'COMMITTED' | 'FAILED_SAFE' | 'UNKNOWN' | 'REJECTED'; + readonly correlation_id: string; + readonly request_body_fingerprint: string; + readonly token_contract: string; + readonly method: string; + readonly native_value_atomic: string; + readonly sanitized_error?: string | null; + readonly created_at: string; +} + +export interface DatabaseSettlementRow { + readonly business_intent_id: string; + readonly provider_reference_id: string; + readonly transaction_hash: string; + readonly block_number: string; + readonly transfer_log_index: number; + readonly committed_at: string; +} + +export interface DatabaseEvidenceRow { + readonly business_intent_id: string; + readonly source: EvidenceView['source']; + readonly authority_class: EvidenceView['authority_class']; + readonly retrieved_at: string; + readonly digest: string; + readonly block_number?: string | null; + readonly freshness?: EvidenceView['freshness'] | null; +} + +export interface DatabaseOutboxRow { + readonly business_intent_id: string; + readonly job_key: string; + readonly task_identifier: 'authorize_intent' | 'reconcile_intent'; + readonly payload: Record; + readonly status: 'PENDING' | 'DELIVERED' | 'DISABLED'; + readonly available_at: string; + readonly created_at: string; +} + +export interface StorageV1Snapshot { + readonly name: string; + readonly description: string; + readonly intents: readonly DatabaseIntentRow[]; + readonly attempts: readonly DatabaseAttemptRow[]; + readonly settlements: readonly DatabaseSettlementRow[]; + readonly evidence: readonly DatabaseEvidenceRow[]; + readonly outbox_jobs: readonly DatabaseOutboxRow[]; +} + +export const SYNTHETIC_ACCEPTED_INTENT_FIXTURE: StorageV1Snapshot = { + name: 'accepted-authorizing', + description: 'Initial intent accepted with first attempt and queued authorize outbox job', + intents: [ + { + business_intent_id: 'intent-fixture-accepted-1', + payload_fingerprint: 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + amount_atomic: '1250000', + asset: 'USDC', + network: 'eip155:5042002', + purpose: 'Café invoice', + state: 'AUTHORIZING', + version: 1, + created_at: '2026-09-07T12:00:00.000Z', + updated_at: '2026-09-07T12:00:00.000Z', + }, + ], + attempts: [ + { + attempt_id: 'attempt-fixture-1', + business_intent_id: 'intent-fixture-accepted-1', + attempt_sequence: 1, + stage: 'AUTHORIZING', + correlation_id: 'correlation-fixture-1', + request_body_fingerprint: 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + token_contract: '0x3600000000000000000000000000000000000000', + method: 'transfer', + native_value_atomic: '0', + sanitized_error: null, + created_at: '2026-09-07T12:00:00.000Z', + }, + ], + settlements: [], + evidence: [], + outbox_jobs: [ + { + business_intent_id: 'intent-fixture-accepted-1', + job_key: 'authorize:intent-fixture-accepted-1:1', + task_identifier: 'authorize_intent', + payload: { business_intent_id: 'intent-fixture-accepted-1' }, + status: 'PENDING', + available_at: '2026-09-07T12:00:00.000Z', + created_at: '2026-09-07T12:00:00.000Z', + }, + ], +}; + +export const SYNTHETIC_COMMITTED_INTENT_FIXTURE: StorageV1Snapshot = { + name: 'committed-with-settlement', + description: + 'Committed intent with confirmed settlement row and authoritative evidence observation', + intents: [ + { + business_intent_id: 'intent-fixture-committed-1', + payload_fingerprint: 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + amount_atomic: '1250000', + asset: 'USDC', + network: 'eip155:5042002', + purpose: 'Café invoice', + state: 'COMMITTED', + version: 2, + created_at: '2026-09-07T12:00:00.000Z', + updated_at: '2026-09-07T12:00:05.000Z', + }, + ], + attempts: [ + { + attempt_id: 'attempt-fixture-2', + business_intent_id: 'intent-fixture-committed-1', + attempt_sequence: 1, + stage: 'COMMITTED', + correlation_id: 'correlation-fixture-2', + request_body_fingerprint: 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + token_contract: '0x3600000000000000000000000000000000000000', + method: 'transfer', + native_value_atomic: '0', + sanitized_error: null, + created_at: '2026-09-07T12:00:00.000Z', + }, + ], + settlements: [ + { + business_intent_id: 'intent-fixture-committed-1', + provider_reference_id: 'provider-ref-fixture-1', + transaction_hash: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + block_number: '5042002', + transfer_log_index: 0, + committed_at: '2026-09-07T12:00:05.000Z', + }, + ], + evidence: [ + { + business_intent_id: 'intent-fixture-committed-1', + source: 'ONESHOT', + authority_class: 'AUTHORITATIVE', + retrieved_at: '2026-09-07T12:00:05.000Z', + digest: 'digest-fixture-settled', + block_number: '5042002', + freshness: 'FRESH', + }, + ], + outbox_jobs: [ + { + business_intent_id: 'intent-fixture-committed-1', + job_key: 'authorize:intent-fixture-committed-1:1', + task_identifier: 'authorize_intent', + payload: { business_intent_id: 'intent-fixture-committed-1' }, + status: 'DELIVERED', + available_at: '2026-09-07T12:00:00.000Z', + created_at: '2026-09-07T12:00:00.000Z', + }, + ], +}; + +export const SYNTHETIC_UNKNOWN_RECONCILING_FIXTURE: StorageV1Snapshot = { + name: 'unknown-reconciling', + description: 'Intent in UNKNOWN state with failed attempt and reconcile outbox job queued', + intents: [ + { + business_intent_id: 'intent-fixture-unknown-1', + payload_fingerprint: 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + recipient: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + amount_atomic: '1250000', + asset: 'USDC', + network: 'eip155:5042002', + purpose: 'Café invoice', + state: 'UNKNOWN', + version: 2, + created_at: '2026-09-07T12:00:00.000Z', + updated_at: '2026-09-07T12:01:00.000Z', + }, + ], + attempts: [ + { + attempt_id: 'attempt-fixture-3', + business_intent_id: 'intent-fixture-unknown-1', + attempt_sequence: 1, + stage: 'UNKNOWN', + correlation_id: 'correlation-fixture-3', + request_body_fingerprint: 'd846986fabcaf95b53dcd425108fe8fe0b8e59f58a21427b2a806ff5564ef4f9', + token_contract: '0x3600000000000000000000000000000000000000', + method: 'transfer', + native_value_atomic: '0', + sanitized_error: 'Provider response timeout during settlement', + created_at: '2026-09-07T12:00:00.000Z', + }, + ], + settlements: [], + evidence: [ + { + business_intent_id: 'intent-fixture-unknown-1', + source: 'ARC', + authority_class: 'OBSERVATION', + retrieved_at: '2026-09-07T12:01:00.000Z', + digest: 'digest-fixture-observation', + block_number: null, + freshness: 'UNKNOWN_FRESHNESS', + }, + ], + outbox_jobs: [ + { + business_intent_id: 'intent-fixture-unknown-1', + job_key: 'reconcile:intent-fixture-unknown-1:2', + task_identifier: 'reconcile_intent', + payload: { business_intent_id: 'intent-fixture-unknown-1' }, + status: 'PENDING', + available_at: '2026-09-07T12:01:00.000Z', + created_at: '2026-09-07T12:01:00.000Z', + }, + ], +}; + +export const SYNTHETIC_STORAGE_FIXTURES: readonly StorageV1Snapshot[] = [ + SYNTHETIC_ACCEPTED_INTENT_FIXTURE, + SYNTHETIC_COMMITTED_INTENT_FIXTURE, + SYNTHETIC_UNKNOWN_RECONCILING_FIXTURE, +]; diff --git a/packages/storage-postgres/src/index.ts b/packages/storage-postgres/src/index.ts new file mode 100644 index 0000000..481acb4 --- /dev/null +++ b/packages/storage-postgres/src/index.ts @@ -0,0 +1,3 @@ +export * from './fixtures.js'; +export * from './ledger.js'; +export * from './migrations.js'; diff --git a/packages/storage-postgres/src/ledger.ts b/packages/storage-postgres/src/ledger.ts new file mode 100644 index 0000000..7af8269 --- /dev/null +++ b/packages/storage-postgres/src/ledger.ts @@ -0,0 +1,331 @@ +import { + asAttemptId, + asBusinessIntentId, + asCorrelationId, + ContractValidationError, + type AttemptView, + type BusinessIntentId, + type EvidenceView, + type IntentResponse, + type IntentState, + type RecoveryView, + type ReconcileResponse, + type SettlementView, +} from '@oneshot/contracts'; +import { fingerprintIntent } from '@oneshot/domain'; +import type { Pool, PoolClient } from 'pg'; + +export interface LedgerDependencies { + readonly now: () => Date; + readonly nextAttemptId: () => string; +} + +export type CreateIntentResult = + | { readonly kind: 'ACCEPTED'; readonly intent: IntentResponse } + | { readonly kind: 'REPLAY_IDENTICAL'; readonly intent: IntentResponse } + | { readonly kind: 'INTENT_PAYLOAD_CONFLICT'; readonly intent: IntentResponse }; + +interface IntentRow { + readonly business_intent_id: string; + readonly payload_fingerprint: string; + readonly recipient: string; + readonly amount_atomic: string; + readonly asset: 'USDC'; + readonly network: 'eip155:5042002'; + readonly purpose: string; + readonly state: IntentState; + readonly version: number; +} + +interface AttemptRow { + readonly attempt_id: string; + readonly stage: IntentState; + readonly created_at: Date; + readonly sanitized_error: string | null; +} + +interface SettlementRow { + readonly provider_reference_id: string; + readonly transaction_hash: string; + readonly block_number: string; + readonly transfer_log_index: number; +} + +interface EvidenceRow { + readonly source: EvidenceView['source']; + readonly authority_class: EvidenceView['authority_class']; + readonly retrieved_at: Date; + readonly digest: string; + readonly block_number: string | null; + readonly freshness: EvidenceView['freshness'] | null; +} + +const MAX_PROJECTION_ITEMS = 100; + +function boundedLimit(value: number | undefined): number { + if (value === undefined) return MAX_PROJECTION_ITEMS; + if (!Number.isSafeInteger(value) || value < 1 || value > MAX_PROJECTION_ITEMS) { + throw new ContractValidationError('projection limit must be an integer from 1 through 100'); + } + return value; +} + +export class IntentLedger { + readonly #pool: Pool; + readonly #dependencies: LedgerDependencies; + + constructor(pool: Pool, dependencies: LedgerDependencies) { + this.#pool = pool; + this.#dependencies = dependencies; + } + + async ping(): Promise { + await this.#pool.query('SELECT 1'); + } + + async createOrReplay(value: unknown, correlationIdValue: unknown): Promise { + const fingerprinted = fingerprintIntent(value); + const correlationId = asCorrelationId(correlationIdValue); + const attemptId = asAttemptId(this.#dependencies.nextAttemptId()); + const now = this.#dependencies.now(); + const client = await this.#pool.connect(); + try { + await client.query('BEGIN'); + const inserted = await client.query<{ business_intent_id: string }>( + `INSERT INTO business_intents ( + business_intent_id, payload_fingerprint, recipient, amount_atomic, + asset, network, purpose, state, version, created_at, updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, 'AUTHORIZING', 1, $8, $8) + ON CONFLICT (business_intent_id) DO NOTHING + RETURNING business_intent_id`, + [ + fingerprinted.request.business_intent_id, + fingerprinted.payload_fingerprint, + fingerprinted.request.recipient, + fingerprinted.request.amount_atomic, + fingerprinted.request.asset, + fingerprinted.request.network, + fingerprinted.request.purpose, + now, + ], + ); + + let kind: CreateIntentResult['kind']; + if (inserted.rowCount === 1) { + await client.query( + `INSERT INTO attempts ( + attempt_id, business_intent_id, attempt_sequence, stage, + correlation_id, request_body_fingerprint, token_contract, + method, native_value_atomic, created_at + ) VALUES ( + $1, $2, 1, 'AUTHORIZING', $3, $4, + '0x3600000000000000000000000000000000000000', 'transfer', '0', $5 + )`, + [ + attemptId, + fingerprinted.request.business_intent_id, + correlationId, + fingerprinted.payload_fingerprint, + now, + ], + ); + await client.query( + `INSERT INTO outbox_jobs ( + business_intent_id, job_key, task_identifier, payload, + available_at, created_at + ) VALUES ($1, $2, 'authorize_intent', $3::jsonb, $4, $4)`, + [ + fingerprinted.request.business_intent_id, + `authorize:${fingerprinted.request.business_intent_id}:1`, + JSON.stringify({ business_intent_id: fingerprinted.request.business_intent_id }), + now, + ], + ); + kind = 'ACCEPTED'; + } else { + const existing = await client.query<{ payload_fingerprint: string }>( + 'SELECT payload_fingerprint FROM business_intents WHERE business_intent_id = $1', + [fingerprinted.request.business_intent_id], + ); + kind = + existing.rows[0]?.payload_fingerprint === fingerprinted.payload_fingerprint + ? 'REPLAY_IDENTICAL' + : 'INTENT_PAYLOAD_CONFLICT'; + } + const intent = await this.#readIntent( + client, + asBusinessIntentId(fingerprinted.request.business_intent_id), + ); + await client.query('COMMIT'); + return { kind, intent } as CreateIntentResult; + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } + } + + async getIntent( + idValue: unknown, + limits: { readonly attempts?: number; readonly evidence?: number } = {}, + ): Promise { + const id = asBusinessIntentId(idValue); + const client = await this.#pool.connect(); + try { + return await this.#readIntent(client, id, limits); + } finally { + client.release(); + } + } + + async getRecoveryView(idValue: unknown): Promise { + const intent = await this.getIntent(idValue); + if (!intent) return undefined; + return { + business_intent_id: intent.business_intent_id, + authoritative_state: intent.state, + recommended_action: intent.state === 'COMMITTED' ? 'RETURN_EXISTING_RESULT' : 'WAIT', + evidence: intent.evidence, + }; + } + + async enqueueReconciliation(idValue: unknown): Promise { + const id = asBusinessIntentId(idValue); + const client = await this.#pool.connect(); + try { + await client.query('BEGIN'); + const intent = await client.query<{ state: IntentState; version: number }>( + 'SELECT state, version FROM business_intents WHERE business_intent_id = $1 FOR UPDATE', + [id], + ); + const row = intent.rows[0]; + if (!row) { + await client.query('COMMIT'); + return undefined; + } + if (row.state !== 'UNKNOWN' && row.state !== 'SUBMITTING') { + await client.query('COMMIT'); + return { business_intent_id: id, queued: false, state: row.state }; + } + const now = this.#dependencies.now(); + const inserted = await client.query( + `INSERT INTO outbox_jobs ( + business_intent_id, job_key, task_identifier, payload, + available_at, created_at + ) VALUES ($1, $2, 'reconcile_intent', $3::jsonb, $4, $4) + ON CONFLICT (job_key) DO NOTHING`, + [id, `reconcile:${id}:${row.version}`, JSON.stringify({ business_intent_id: id }), now], + ); + await client.query('COMMIT'); + return { business_intent_id: id, queued: inserted.rowCount === 1, state: row.state }; + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } + } + + async appendEvidence(idValue: unknown, evidence: EvidenceView): Promise { + const id = asBusinessIntentId(idValue); + await this.#pool.query( + `INSERT INTO evidence_observations ( + business_intent_id, source, authority_class, retrieved_at, + digest, block_number, freshness + ) VALUES ($1, $2, $3, $4, $5, $6, $7)`, + [ + id, + evidence.source, + evidence.authority_class, + evidence.retrieved_at, + evidence.digest, + evidence.block_number ?? null, + evidence.freshness ?? null, + ], + ); + } + + async #readIntent( + client: PoolClient, + id: BusinessIntentId, + limits: { readonly attempts?: number; readonly evidence?: number } = {}, + ): Promise { + const intentResult = await client.query( + `SELECT business_intent_id, payload_fingerprint, recipient, amount_atomic, + asset, network, purpose, state, version + FROM business_intents WHERE business_intent_id = $1`, + [id], + ); + const intent = intentResult.rows[0]; + if (!intent) return undefined; + + const attemptLimit = boundedLimit(limits.attempts); + const evidenceLimit = boundedLimit(limits.evidence); + const [attemptResult, settlementResult, evidenceResult] = await Promise.all([ + client.query( + `SELECT attempt_id, stage, created_at, sanitized_error + FROM ( + SELECT attempt_id, stage, created_at, sanitized_error, attempt_sequence + FROM attempts WHERE business_intent_id = $1 + ORDER BY attempt_sequence DESC LIMIT $2 + ) bounded ORDER BY attempt_sequence ASC`, + [id, attemptLimit], + ), + client.query( + `SELECT provider_reference_id, transaction_hash, block_number, transfer_log_index + FROM settlements WHERE business_intent_id = $1`, + [id], + ), + client.query( + `SELECT source, authority_class, retrieved_at, digest, block_number, freshness + FROM ( + SELECT evidence_id, source, authority_class, retrieved_at, digest, + block_number, freshness + FROM evidence_observations WHERE business_intent_id = $1 + ORDER BY evidence_id DESC LIMIT $2 + ) bounded ORDER BY evidence_id ASC`, + [id, evidenceLimit], + ), + ]); + + const attempts: AttemptView[] = attemptResult.rows.map((row) => ({ + attempt_id: row.attempt_id, + stage: row.stage, + created_at: row.created_at.toISOString(), + ...(row.sanitized_error ? { sanitized_error: row.sanitized_error } : {}), + })); + const settlementRow = settlementResult.rows[0]; + const settlement: SettlementView | undefined = settlementRow + ? { + provider_reference_id: settlementRow.provider_reference_id, + transaction_hash: settlementRow.transaction_hash, + block_number: settlementRow.block_number, + transfer_log_index: settlementRow.transfer_log_index, + } + : undefined; + const evidence: EvidenceView[] = evidenceResult.rows.map((row) => ({ + source: row.source, + authority_class: row.authority_class, + retrieved_at: row.retrieved_at.toISOString(), + digest: row.digest, + ...(row.block_number ? { block_number: row.block_number } : {}), + ...(row.freshness ? { freshness: row.freshness } : {}), + })); + + return { + business_intent_id: intent.business_intent_id, + payload_fingerprint: intent.payload_fingerprint, + recipient: intent.recipient, + amount_atomic: intent.amount_atomic, + asset: intent.asset, + network: intent.network, + purpose: intent.purpose, + state: intent.state, + version: intent.version, + attempts, + ...(settlement ? { settlement } : {}), + evidence, + }; + } +} diff --git a/packages/storage-postgres/src/migrations.ts b/packages/storage-postgres/src/migrations.ts new file mode 100644 index 0000000..ba46119 --- /dev/null +++ b/packages/storage-postgres/src/migrations.ts @@ -0,0 +1,92 @@ +import { createHash } from 'node:crypto'; +import { readdir, readFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Pool } from 'pg'; + +const defaultMigrationDirectory = resolve( + dirname(fileURLToPath(import.meta.url)), + '..', + 'migrations', +); +const migrationName = /^(?[0-9]{3})_[a-z0-9_]+\.sql$/u; + +interface MigrationFile { + readonly version: number; + readonly name: string; + readonly sql: string; + readonly checksum: string; +} + +async function migrationFiles(directory: string): Promise { + const names = (await readdir(directory)).filter((name) => name.endsWith('.sql')).sort(); + const versions = new Set(); + const files: MigrationFile[] = []; + for (const name of names) { + const match = migrationName.exec(name); + if (!match?.groups) throw new Error(`Invalid migration filename: ${name}`); + const version = Number(match.groups.version); + if (versions.has(version)) throw new Error(`Duplicate migration version: ${version}`); + versions.add(version); + const rawSql = await readFile(resolve(directory, name), 'utf8'); + const sql = rawSql.replace(/\r\n/g, '\n'); + files.push({ + version, + name, + sql, + checksum: createHash('sha256').update(sql, 'utf8').digest('hex'), + }); + } + return files; +} + +export const STORAGE_V1_SCHEMA_DIGEST = + '09b7fc0ce90a3db9dfd0437ae1abdac7260603154216b223116d0e123967d742'; + +export async function migrationDigest(directory = defaultMigrationDirectory): Promise { + const files = await migrationFiles(directory); + const hash = createHash('sha256'); + for (const file of files) hash.update(`${file.name}\0${file.checksum}\n`, 'utf8'); + return hash.digest('hex'); +} + +export async function migrate(pool: Pool, directory = defaultMigrationDirectory): Promise { + await pool.query(` + CREATE TABLE IF NOT EXISTS schema_versions ( + version integer PRIMARY KEY, + name text NOT NULL, + checksum text NOT NULL CHECK (checksum ~ '^[0-9a-f]{64}$'), + applied_at timestamptz NOT NULL DEFAULT now() + ) + `); + + for (const migration of await migrationFiles(directory)) { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + await client.query("SELECT pg_advisory_xact_lock(hashtext('oneshot:migrations'))"); + const applied = await client.query<{ checksum: string }>( + 'SELECT checksum FROM schema_versions WHERE version = $1', + [migration.version], + ); + if (applied.rows[0]) { + if (applied.rows[0].checksum !== migration.checksum) { + throw new Error(`Migration checksum mismatch at version ${migration.version}`); + } + await client.query('COMMIT'); + continue; + } + await client.query(migration.sql); + await client.query( + 'INSERT INTO schema_versions (version, name, checksum) VALUES ($1, $2, $3)', + [migration.version, migration.name, migration.checksum], + ); + await client.query('COMMIT'); + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } + } +} diff --git a/packages/storage-postgres/test/ledger.integration.test.ts b/packages/storage-postgres/test/ledger.integration.test.ts new file mode 100644 index 0000000..5e121db --- /dev/null +++ b/packages/storage-postgres/test/ledger.integration.test.ts @@ -0,0 +1,136 @@ +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql'; +import { Pool } from 'pg'; +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { IntentLedger, migrate, migrationDigest } from '../src/index.js'; + +const describePostgres = process.env.TEST_POSTGRES === '1' ? describe : describe.skip; +const request = { + business_intent_id: 'intent-storage-1', + recipient: '0x1111111111111111111111111111111111111111', + amount_atomic: '1250000', + asset: 'USDC', + network: 'eip155:5042002', + purpose: 'Invoice INV-1001', +}; + +describePostgres('PostgreSQL intent ledger', () => { + let container: StartedPostgreSqlContainer; + let pool: Pool; + let nextAttempt = 0; + + beforeAll(async () => { + container = await new PostgreSqlContainer('postgres:16.4-alpine').start(); + pool = new Pool({ connectionString: container.getConnectionUri(), max: 20 }); + await migrate(pool); + }); + + afterEach(async () => { + await pool.query( + 'TRUNCATE outbox_jobs, evidence_observations, settlements, attempts, business_intents RESTART IDENTITY', + ); + nextAttempt = 0; + }); + + afterAll(async () => { + await pool.end(); + await container.stop(); + }); + + const newLedger = (targetPool = pool) => + new IntentLedger(targetPool, { + now: () => new Date('2026-09-07T12:00:00.000Z'), + nextAttemptId: () => `attempt-${++nextAttempt}`, + }); + + it('bootstraps and applies the ordered migration set', async () => { + const versions = await pool.query<{ version: number }>( + 'SELECT version FROM schema_versions ORDER BY version', + ); + expect(versions.rows.map((row) => row.version)).toEqual([1, 2]); + expect(await migrationDigest()).toMatch(/^[0-9a-f]{64}$/u); + }); + + it('rolls back a failed forward migration transaction', async () => { + const directory = await mkdtemp(join(tmpdir(), 'oneshot-migration-')); + try { + await writeFile( + join(directory, '003_broken.sql'), + 'CREATE TABLE must_rollback (id integer); SELECT missing_function();', + 'utf8', + ); + await expect(migrate(pool, directory)).rejects.toThrow(); + const table = await pool.query<{ name: string | null }>( + "SELECT to_regclass('public.must_rollback')::text AS name", + ); + expect(table.rows[0]?.name).toBeNull(); + const version = await pool.query('SELECT 1 FROM schema_versions WHERE version = 3'); + expect(version.rowCount).toBe(0); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + + it('atomically accepts one of ten concurrent identical requests', async () => { + const ledger = newLedger(); + const results = await Promise.all( + Array.from({ length: 10 }, (_, index) => + ledger.createOrReplay(request, `correlation-${index + 1}`), + ), + ); + expect(results.filter((result) => result.kind === 'ACCEPTED')).toHaveLength(1); + expect(results.filter((result) => result.kind === 'REPLAY_IDENTICAL')).toHaveLength(9); + const counts = await pool.query<{ intents: string; attempts: string; jobs: string }>(` + SELECT + (SELECT count(*) FROM business_intents)::text AS intents, + (SELECT count(*) FROM attempts)::text AS attempts, + (SELECT count(*) FROM outbox_jobs)::text AS jobs + `); + expect(counts.rows[0]).toEqual({ intents: '1', attempts: '1', jobs: '1' }); + }); + + it('returns conflict without creating another durable right', async () => { + const ledger = newLedger(); + await ledger.createOrReplay(request, 'correlation-original'); + const conflict = await ledger.createOrReplay( + { ...request, amount_atomic: '1250001' }, + 'correlation-conflict', + ); + expect(conflict.kind).toBe('INTENT_PAYLOAD_CONFLICT'); + const counts = await pool.query<{ attempts: string; jobs: string; settlements: string }>(` + SELECT + (SELECT count(*) FROM attempts)::text AS attempts, + (SELECT count(*) FROM outbox_jobs)::text AS jobs, + (SELECT count(*) FROM settlements)::text AS settlements + `); + expect(counts.rows[0]).toEqual({ attempts: '1', jobs: '1', settlements: '0' }); + }); + + it('survives a client restart and preserves evidence order', async () => { + const ledger = newLedger(); + await ledger.createOrReplay(request, 'correlation-restart'); + await ledger.appendEvidence(request.business_intent_id, { + source: 'ONESHOT', + authority_class: 'AUTHORITATIVE', + retrieved_at: '2026-09-07T12:01:00.000Z', + digest: 'first', + }); + await ledger.appendEvidence(request.business_intent_id, { + source: 'ARC', + authority_class: 'OBSERVATION', + retrieved_at: '2026-09-07T12:02:00.000Z', + digest: 'second', + }); + const restartedPool = new Pool({ connectionString: container.getConnectionUri() }); + try { + const restarted = newLedger(restartedPool); + const loaded = await restarted.getIntent(request.business_intent_id); + expect(loaded?.evidence.map((item) => item.digest)).toEqual(['first', 'second']); + expect(loaded?.attempts).toHaveLength(1); + } finally { + await restartedPool.end(); + } + }); +}); diff --git a/packages/storage-postgres/test/migrations.test.ts b/packages/storage-postgres/test/migrations.test.ts new file mode 100644 index 0000000..6b4dcd9 --- /dev/null +++ b/packages/storage-postgres/test/migrations.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { + migrationDigest, + STORAGE_V1_SCHEMA_DIGEST, + SYNTHETIC_STORAGE_FIXTURES, + SYNTHETIC_ACCEPTED_INTENT_FIXTURE, + SYNTHETIC_COMMITTED_INTENT_FIXTURE, + SYNTHETIC_UNKNOWN_RECONCILING_FIXTURE, +} from '../src/index.js'; + +describe('storage-v1 migrations and schema', () => { + it('matches the frozen storage-v1 schema digest', async () => { + const digest = await migrationDigest(); + expect(digest).toBe(STORAGE_V1_SCHEMA_DIGEST); + expect(digest).toMatch(/^[0-9a-f]{64}$/u); + }); + + it('provides valid synthetic database snapshots', () => { + expect(SYNTHETIC_STORAGE_FIXTURES).toHaveLength(3); + + expect(SYNTHETIC_ACCEPTED_INTENT_FIXTURE.intents[0]?.state).toBe('AUTHORIZING'); + expect(SYNTHETIC_ACCEPTED_INTENT_FIXTURE.attempts).toHaveLength(1); + expect(SYNTHETIC_ACCEPTED_INTENT_FIXTURE.outbox_jobs).toHaveLength(1); + expect(SYNTHETIC_ACCEPTED_INTENT_FIXTURE.settlements).toHaveLength(0); + + expect(SYNTHETIC_COMMITTED_INTENT_FIXTURE.intents[0]?.state).toBe('COMMITTED'); + expect(SYNTHETIC_COMMITTED_INTENT_FIXTURE.settlements).toHaveLength(1); + expect(SYNTHETIC_COMMITTED_INTENT_FIXTURE.evidence).toHaveLength(1); + + expect(SYNTHETIC_UNKNOWN_RECONCILING_FIXTURE.intents[0]?.state).toBe('UNKNOWN'); + expect(SYNTHETIC_UNKNOWN_RECONCILING_FIXTURE.outbox_jobs[0]?.task_identifier).toBe( + 'reconcile_intent', + ); + }); +}); diff --git a/packages/storage-postgres/tsconfig.json b/packages/storage-postgres/tsconfig.json new file mode 100644 index 0000000..83d37b3 --- /dev/null +++ b/packages/storage-postgres/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "references": [{ "path": "../contracts" }, { "path": "../domain" }] +} diff --git a/packages/storage-postgres/vitest.config.ts b/packages/storage-postgres/vitest.config.ts new file mode 100644 index 0000000..244865b --- /dev/null +++ b/packages/storage-postgres/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.{test,spec}.ts'], + exclude: ['test/**/*.integration.test.ts'], + }, +}); diff --git a/packages/storage-postgres/vitest.integration.config.ts b/packages/storage-postgres/vitest.integration.config.ts new file mode 100644 index 0000000..e46356c --- /dev/null +++ b/packages/storage-postgres/vitest.integration.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.integration.test.ts'], + testTimeout: 60_000, + hookTimeout: 60_000, + fileParallelism: false, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e995caf..b58d359 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@types/node': specifier: 24.13.3 version: 24.13.3 + '@types/pg': + specifier: 8.23.1 + version: 8.23.1 eslint: specifier: 10.10.0 version: 10.10.0 @@ -31,10 +34,29 @@ importers: version: 8.69.0(eslint@10.10.0)(typescript@6.0.3) vite: specifier: 8.0.0 - version: 8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3) + version: 8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0) vitest: specifier: 5.0.0 - version: 5.0.0(@types/node@24.13.3)(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)) + version: 5.0.0(@types/node@24.13.3)(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0)) + + apps/api: + dependencies: + '@oneshot/contracts': + specifier: workspace:* + version: link:../../packages/contracts + '@oneshot/storage-postgres': + specifier: workspace:* + version: link:../../packages/storage-postgres + fastify: + specifier: 5.12.3 + version: 5.12.3 + devDependencies: + '@testcontainers/postgresql': + specifier: 12.1.0 + version: 12.1.0 + pg: + specifier: 8.23.0 + version: 8.23.0 packages/contracts: dependencies: @@ -45,8 +67,30 @@ importers: specifier: 3.0.1 version: 3.0.1(ajv@8.20.0) + packages/domain: + dependencies: + '@oneshot/contracts': + specifier: workspace:* + version: link:../contracts + packages/reconciliation: {} + packages/storage-postgres: + dependencies: + '@oneshot/contracts': + specifier: workspace:* + version: link:../contracts + '@oneshot/domain': + specifier: workspace:* + version: link:../domain + pg: + specifier: 8.23.0 + version: 8.23.0 + devDependencies: + '@testcontainers/postgresql': + specifier: 12.1.0 + version: 12.1.0 + packages/testkit-domain: dependencies: '@oneshot/contracts': @@ -55,6 +99,9 @@ importers: packages: + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + '@cacheable/memory@2.2.0': resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} @@ -109,6 +156,38 @@ packages: resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@fastify/ajv-compiler@4.0.6': + resolution: {integrity: sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==} + + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + + '@fastify/fast-json-stringify-compiler@5.1.0': + resolution: {integrity: sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==} + + '@fastify/forwarded@3.0.2': + resolution: {integrity: sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==} + + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + + '@grpc/grpc-js@1.14.4': + resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -129,6 +208,10 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -139,6 +222,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@keyv/bigmap@1.3.1': resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} engines: {node: '>= 18'} @@ -148,6 +234,9 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + '@napi-rs/wasm-runtime@1.2.3': resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} @@ -162,6 +251,40 @@ packages: '@oxc-project/types@0.115.0': resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + '@rolldown/binding-android-arm64@1.0.0-rc.9': resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -260,6 +383,9 @@ packages: '@rolldown/pluginutils@1.0.0-rc.9': resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} + '@testcontainers/postgresql@12.1.0': + resolution: {integrity: sha512-Pjf2VSVNirEPfz36nidyrVAnZvc2YhajOznY4VgyEsvfTd5qiMNOuPq96drREvxAUtXl5SFLX7vXj7sSq4aTcA==} + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -269,6 +395,12 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@4.0.1': + resolution: {integrity: sha512-cmUpB+dPN955PxBEuXE3f6lKO1hHiIGYJA46IVF3BJpNsZGvtBDcRnlrHYHtOH/B6vtDOyl2kZ2ShAu3mgc27Q==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -278,9 +410,24 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@24.13.3': resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + '@types/pg@8.23.1': + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + + '@types/ssh2-streams@0.1.13': + resolution: {integrity: sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA==} + + '@types/ssh2@0.5.52': + resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} + + '@types/ssh2@1.15.6': + resolution: {integrity: sha512-oGdxhBqcRTwSTKFm+9EiKzkNVYRLEFkcW44lhguvBalGJbWfGnDt/ezwSUZc+SF9m9bMc3VyklNAtp7zICjS5w==} + '@typescript-eslint/eslint-plugin@8.69.0': resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -354,6 +501,13 @@ packages: '@vitest/spy@5.0.0': resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -378,18 +532,136 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + avvio@9.3.0: + resolution: {integrity: sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + bare-events@2.9.2: + resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.8.1: + resolution: {integrity: sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==} + engines: {bare: '>=1.28.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-path@3.1.2: + resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} + + bare-stream@2.13.4: + resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.5.4: + resolution: {integrity: sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + brace-expansion@5.0.9: resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + buildcheck@0.0.7: + resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} + engines: {node: '>=10.0.0'} + + byline@5.0.0: + resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} + engines: {node: '>=0.10.0'} + cacheable@2.5.0: resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} @@ -397,6 +669,44 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -413,13 +723,45 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + docker-compose@1.4.2: + resolution: {integrity: sha512-rPHigTKGaEHpkUmfd69QgaOp+Os5vGJwG/Ry8lcr8W/382AmI+z/D7qoa9BybKIkqNppaIbs8RYeHSevdQjWww==} + engines: {node: '>= 6.0.0'} + + docker-modem@5.0.7: + resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==} + engines: {node: '>= 8.0'} + + dockerode@5.0.1: + resolution: {integrity: sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==} + engines: {node: '>= 14.17'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -469,22 +811,54 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + expect-type@1.4.0: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-json-stringify@7.0.1: + resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-uri@3.1.7: resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} + fast-uri@4.1.4: + resolution: {integrity: sha512-dODXrIxlS9JSdgAnhIUKOosKV1oMtU2VtVw87QRaHzyl5jxO290Ii5tEZfCfzfWNHi3jKWwBSdQj0qIyshdZdQ==} + + fastify@5.12.3: + resolution: {integrity: sha512-reZ8wce5VNCcufIt9AVtzZa3L4u1j8esikn7OEgHWLVpRpL5R7Y2+Xzj70OUkv5zDfzUAxXZT6cu4Rt0zr3EKA==} + + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -497,6 +871,10 @@ packages: file-entry-cache@11.1.5: resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} + find-my-way@9.9.0: + resolution: {integrity: sha512-sJsgZ1sQH2UDuowPuMKg8az7Qc8F0jnj+SKkFWU/+T0xcFlgV5skgXOGUqmQzOdmW6ALA7AhJINWx3qFBkbLHA==} + engines: {node: '>=20'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -507,19 +885,42 @@ packages: flatted@3.4.4: resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + globals@17.4.0: resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hashery@1.5.1: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} @@ -530,6 +931,9 @@ packages: hookified@2.2.0: resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -542,17 +946,41 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@2.5.0: + resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} + engines: {node: '>= 10'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -565,10 +993,17 @@ packages: keyv@5.6.0: resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + lightningcss-android-arm64@1.33.0: resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} @@ -647,6 +1082,18 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + magic-string@1.2.3: resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==} @@ -654,9 +1101,32 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nan@2.28.0: + resolution: {integrity: sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==} + nanoid@3.3.18: resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -665,10 +1135,21 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + obug@2.1.4: resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} engines: {node: '>=12.20.0'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -681,6 +1162,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -689,6 +1173,44 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -696,10 +1218,36 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -709,6 +1257,33 @@ packages: engines: {node: '>=14'} hasBin: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.1.0: + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + properties-reader@3.0.1: + resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} + engines: {node: '>=18'} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -717,20 +1292,86 @@ packages: resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} engines: {node: '>=20'} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rolldown@1.0.0-rc.9: resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -742,28 +1383,112 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + ssh-remote-port-forward@1.0.4: + resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} + + ssh2@1.17.0: + resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} + engines: {node: '>=10.16.0'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - tinybench@6.1.4: - resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} - engines: {node: '>=20.0.0'} + streamx@2.28.1: + resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} - engines: {node: '>=18'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} - tinyglobby@0.2.17: + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} + + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.2.1: + resolution: {integrity: sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + testcontainers@12.1.0: + resolution: {integrity: sha512-YjDLqIITuhGLMnM10yhg3oV6lIG5IMpz1R1DPBZoOOks83q7i7IVpeSWRTiyl7roozjiyLmwIoLK/KY8OnZmIA==} + engines: {node: '>= 22.22'} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + + tinybench@6.1.4: + resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + engines: {node: '>=20.0.0'} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + + toad-cache@3.7.4: + resolution: {integrity: sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==} + engines: {node: '>=20'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -773,6 +1498,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -789,12 +1517,22 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici@8.10.2: + resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} + engines: {node: '>=22.19.0'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vite@8.0.0: resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -893,12 +1631,50 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + snapshots: + '@balena/dockerignore@1.0.2': {} + '@cacheable/memory@2.2.0': dependencies: '@cacheable/utils': 2.5.0 @@ -961,6 +1737,48 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@fastify/ajv-compiler@4.0.6': + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.1.4 + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.1.0': + dependencies: + fast-json-stringify: 7.0.1 + + '@fastify/forwarded@3.0.2': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.2 + ipaddr.js: 2.5.0 + + '@grpc/grpc-js@1.14.4': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -977,6 +1795,15 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/sourcemap-codec@1.6.0': {} @@ -986,6 +1813,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 + '@js-sdsl/ordered-map@4.4.2': {} + '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: hashery: 1.5.1 @@ -994,6 +1823,12 @@ snapshots: '@keyv/serialize@1.1.1': {} + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': dependencies: '@emnapi/core': 1.11.3 @@ -1005,6 +1840,31 @@ snapshots: '@oxc-project/types@0.115.0': {} + '@pinojs/redact@0.4.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + '@rolldown/binding-android-arm64@1.0.0-rc.9': optional: true @@ -1057,6 +1917,15 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.9': {} + '@testcontainers/postgresql@12.1.0': + dependencies: + testcontainers: 12.1.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -1069,16 +1938,50 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 24.13.3 + '@types/ssh2': 1.15.6 + + '@types/dockerode@4.0.1': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 24.13.3 + '@types/ssh2': 1.15.6 + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@24.13.3': dependencies: undici-types: 7.18.2 + '@types/pg@8.23.1': + dependencies: + '@types/node': 24.13.3 + pg-protocol: 1.16.0 + pg-types: 2.2.0 + + '@types/ssh2-streams@0.1.13': + dependencies: + '@types/node': 24.13.3 + + '@types/ssh2@0.5.52': + dependencies: + '@types/node': 24.13.3 + '@types/ssh2-streams': 0.1.13 + + '@types/ssh2@1.15.6': + dependencies: + '@types/node': 18.19.130 + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.10.0)(typescript@6.0.3))(eslint@10.10.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -1170,17 +2073,23 @@ snapshots: '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 - '@vitest/mocker@5.0.0(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3))': + '@vitest/mocker@5.0.0(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0))': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.0 estree-walker: 3.0.3 magic-string: 1.2.3 optionalDependencies: - vite: 8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3) + vite: 8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0) '@vitest/spy@5.0.0': {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + abstract-logging@2.0.1: {} + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -1205,14 +2114,129 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-regex@5.0.1: {} + + ansi-regex@6.3.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.18.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.2.1 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + assertion-error@2.0.1: {} + async-lock@1.4.1: {} + + async@3.2.6: {} + + atomic-sleep@1.0.0: {} + + avvio@9.3.0: + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.20.3 + + b4a@1.8.1: {} + + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.9.2: {} + + bare-fs@4.8.1: + dependencies: + bare-events: 2.9.2 + bare-path: 3.1.2 + bare-stream: 2.13.4(bare-events@2.9.2) + bare-url: 2.5.4 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-path@3.1.2: {} + + bare-stream@2.13.4(bare-events@2.9.2): + dependencies: + b4a: 1.8.1 + streamx: 2.28.1 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.5.4: + dependencies: + bare-path: 3.1.2 + + base64-js@1.5.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 + buffer-crc32@1.0.0: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buildcheck@0.0.7: + optional: true + + byline@5.0.0: {} + cacheable@2.5.0: dependencies: '@cacheable/memory': 2.2.0 @@ -1223,6 +2247,45 @@ snapshots: chai@6.2.2: {} + chownr@1.1.4: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + cookie@1.1.1: {} + + core-util-is@1.0.3: {} + + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.7 + nan: 2.28.0 + optional: true + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1235,10 +2298,48 @@ snapshots: deep-is@0.1.4: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} + docker-compose@1.4.2: + dependencies: + yaml: 2.9.0 + + docker-modem@5.0.7: + dependencies: + debug: 4.4.3 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.17.0 + transitivePeerDependencies: + - supports-color + + dockerode@5.0.1: + dependencies: + '@balena/dockerignore': 1.0.2 + '@grpc/grpc-js': 1.14.4 + '@grpc/proto-loader': 0.7.15 + docker-modem: 5.0.7 + protobufjs: 7.6.6 + tar-fs: 2.1.5 + transitivePeerDependencies: + - supports-color + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + es-module-lexer@2.3.2: {} + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-scope@9.1.2: @@ -1309,16 +2410,67 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + expect-type@1.4.0: {} + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-json-stable-stringify@2.1.0: {} + fast-json-stringify@7.0.1: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.1.4 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + fast-levenshtein@2.0.6: {} + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + fast-uri@3.1.7: {} + fast-uri@4.1.4: {} + + fastify@5.12.3: + dependencies: + '@fastify/ajv-compiler': 4.0.6 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.1.0 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.3.0 + fast-json-stringify: 7.0.1 + find-my-way: 9.9.0 + light-my-request: 6.6.0 + pino: 10.3.1 + process-warning: 5.1.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.8.5 + toad-cache: 3.7.4 + + fastq@1.20.3: + dependencies: + reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 @@ -1327,6 +2479,12 @@ snapshots: dependencies: flat-cache: 6.1.23 + find-my-way@9.9.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.1 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -1340,15 +2498,37 @@ snapshots: flatted@3.4.4: {} + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs-constants@1.0.0: {} + fsevents@2.3.3: optional: true + get-caller-file@2.0.5: {} + + get-port@5.1.1: {} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@17.4.0: {} + graceful-fs@4.2.11: {} + hashery@1.5.1: dependencies: hookified: 1.15.1 @@ -1357,20 +2537,42 @@ snapshots: hookified@2.2.0: {} + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.8: {} imurmurhash@0.1.4: {} + inherits@2.0.4: {} + + ipaddr.js@2.5.0: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-stream@2.0.1: {} + + isarray@1.0.0: {} + isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + json-schema-ref-resolver@3.0.0: + dependencies: + dequal: 2.0.3 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -1381,11 +2583,21 @@ snapshots: dependencies: '@keyv/serialize': 1.1.1 + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + light-my-request@6.6.0: + dependencies: + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.2 + lightningcss-android-arm64@1.33.0: optional: true @@ -1439,6 +2651,14 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} + + lodash@4.18.1: {} + + long@5.3.2: {} + + lru-cache@10.4.3: {} + magic-string@1.2.3: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -1447,14 +2667,39 @@ snapshots: dependencies: brace-expansion: 5.0.9 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.4 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + minipass@7.1.3: {} + + mkdirp-classic@0.5.3: {} + + mkdirp@3.0.1: {} + ms@2.1.3: {} + nan@2.28.0: + optional: true + nanoid@3.3.18: {} natural-compare@1.4.0: {} + normalize-path@3.0.0: {} + obug@2.1.4: {} + on-exit-leak-free@2.1.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -1472,32 +2717,188 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + path-exists@4.0.0: {} path-key@3.1.1: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.14.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.23.0): + dependencies: + pg: 8.23.0 + + pg-protocol@1.16.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.23.0: + dependencies: + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.1.1: {} picomatch@4.0.7: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + postcss@8.5.28: dependencies: nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prelude-ls@1.2.1: {} prettier@3.9.6: {} + process-nextick-args@2.0.1: {} + + process-warning@4.0.1: {} + + process-warning@5.1.0: {} + + process@0.11.10: {} + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + properties-reader@3.0.1: + dependencies: + '@kwsites/file-exists': 1.1.1 + mkdirp: 3.0.1 + transitivePeerDependencies: + - supports-color + + protobufjs@7.6.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 24.13.3 + long: 5.3.2 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} qified@0.10.1: dependencies: hookified: 2.2.0 + quick-format-unescaped@4.0.4: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + + real-require@0.2.0: {} + + real-require@1.0.0: {} + + require-directory@2.1.1: {} + require-from-string@2.0.2: {} + ret@0.5.0: {} + + retry@0.12.0: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + rolldown@1.0.0-rc.9(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3): dependencies: '@oxc-project/types': 0.115.0 @@ -1522,8 +2923,24 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + secure-json-parse@4.1.0: {} + semver@7.8.5: {} + set-cookie-parser@2.7.2: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -1532,12 +2949,152 @@ snapshots: siginfo@2.0.0: {} + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split-ca@1.0.1: {} + + split2@4.2.0: {} + + ssh-remote-port-forward@1.0.4: + dependencies: + '@types/ssh2': 0.5.52 + ssh2: 1.17.0 + + ssh2@1.17.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.28.0 + stackback@0.0.2: {} std-env@4.2.0: {} + streamx@2.28.1: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 + + tar-fs@2.1.5: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-fs@3.1.3: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.1 + optionalDependencies: + bare-fs: 4.8.1 + bare-path: 3.1.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.2.1: + dependencies: + b4a: 1.8.1 + bare-fs: 4.8.1 + fast-fifo: 1.3.2 + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + testcontainers@12.1.0: + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 4.0.1 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.4.3 + docker-compose: 1.4.2 + dockerode: 5.0.1 + get-port: 5.1.1 + proper-lockfile: 4.1.2 + properties-reader: 3.0.1 + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.1.3 + tmp: 0.2.7 + undici: 8.10.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + tinybench@6.1.4: {} tinyexec@1.3.0: {} @@ -1547,6 +3104,10 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tmp@0.2.7: {} + + toad-cache@3.7.4: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -1554,6 +3115,8 @@ snapshots: tslib@2.8.1: optional: true + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -1571,13 +3134,19 @@ snapshots: typescript@6.0.3: {} + undici-types@5.26.5: {} + undici-types@7.18.2: {} + undici@8.10.2: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3): + util-deprecate@1.0.2: {} + + vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0): dependencies: '@oxc-project/runtime': 0.115.0 lightningcss: 1.33.0 @@ -1588,14 +3157,15 @@ snapshots: optionalDependencies: '@types/node': 24.13.3 fsevents: 2.3.3 + yaml: 2.9.0 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' - vitest@5.0.0(@types/node@24.13.3)(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)): + vitest@5.0.0(@types/node@24.13.3)(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)) + '@vitest/mocker': 5.0.0(vite@8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0)) chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -1606,7 +3176,7 @@ snapshots: tinybench: 6.1.4 tinyexec: 1.3.0 tinyglobby: 0.2.17 - vite: 8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3) + vite: 8.0.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@24.13.3)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.3 @@ -1624,4 +3194,42 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 266bb4c..caf4314 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,12 @@ packages: + - apps/* - packages/* saveExact: true strictPeerDependencies: true autoInstallPeers: true -onlyBuiltDependencies: - - esbuild +allowBuilds: + cpu-features: true + esbuild: true + protobufjs: true + ssh2: true diff --git a/tsconfig.json b/tsconfig.json index e0b39e1..7fdeea9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,10 @@ "files": [], "references": [ { "path": "./packages/contracts" }, + { "path": "./packages/domain" }, { "path": "./packages/reconciliation" }, - { "path": "./packages/testkit-domain" } + { "path": "./packages/storage-postgres" }, + { "path": "./packages/testkit-domain" }, + { "path": "./apps/api" } ] } diff --git a/vitest.config.ts b/vitest.config.ts index ccf823d..e31cc5a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { coverage: { enabled: false }, - include: ['packages/**/*.{test,spec}.{ts,mjs}'], + exclude: ['**/*.integration.test.ts', '**/node_modules/**', '**/dist/**'], + include: ['{apps,packages}/**/*.{test,spec}.{ts,mjs}'], }, });