From 7a57b423d6504a2b5cadac368469993dc3a82f6f Mon Sep 17 00:00:00 2001 From: zetazzz Date: Mon, 17 Aug 2026 07:34:54 +0800 Subject: [PATCH 1/7] add Graphile readiness and disposal primitives --- .../src/__tests__/build-readiness.test.ts | 126 ++++++++++++++++ .../src/__tests__/disposal-lifecycle.test.ts | 137 +++++++++++++++++ .../src/__tests__/preset-services.test.ts | 50 +++++++ .../graphile-cache/src/build-readiness.ts | 32 ++++ .../graphile-cache/src/create-instance.ts | 43 +++++- graphile/graphile-cache/src/graphile-cache.ts | 140 +++++++++++------- graphile/graphile-cache/src/index.ts | 6 +- .../graphile-cache/src/preset-services.ts | 29 ++++ 8 files changed, 505 insertions(+), 58 deletions(-) create mode 100644 graphile/graphile-cache/src/__tests__/build-readiness.test.ts create mode 100644 graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts create mode 100644 graphile/graphile-cache/src/__tests__/preset-services.test.ts create mode 100644 graphile/graphile-cache/src/build-readiness.ts create mode 100644 graphile/graphile-cache/src/preset-services.ts diff --git a/graphile/graphile-cache/src/__tests__/build-readiness.test.ts b/graphile/graphile-cache/src/__tests__/build-readiness.test.ts new file mode 100644 index 0000000000..b876be741c --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/build-readiness.test.ts @@ -0,0 +1,126 @@ +import { awaitGraphileBuildReadiness } from '../build-readiness'; + +interface Deferred { + promise: Promise; + resolve(value: T): void; + reject(error: Error): void; +} + +const deferred = (): Deferred => { + let resolve!: (value: T) => void; + let reject!: (error: Error) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, resolve, reject }; +}; + +const flushPromises = (): Promise => + new Promise((resolve) => setImmediate(resolve)); + +describe('awaitGraphileBuildReadiness', () => { + it('does not resolve before schema gathering and Grafserv are ready', async () => { + const schemaResult = deferred(); + const ready = deferred(); + const release = jest.fn().mockResolvedValue(undefined); + let resolved = false; + const buildPromise = awaitGraphileBuildReadiness({ + schemaResult: schemaResult.promise, + addTo: jest.fn().mockResolvedValue(undefined), + ready: () => ready.promise, + release, + }).then(() => { + resolved = true; + }); + + schemaResult.resolve({}); + await flushPromises(); + expect(resolved).toBe(false); + + ready.resolve(undefined); + await buildPromise; + expect(release).not.toHaveBeenCalled(); + }); + + it('does not start readiness checks before the adapter is attached', async () => { + const addTo = deferred(); + const ready = jest.fn().mockResolvedValue(undefined); + const buildPromise = awaitGraphileBuildReadiness({ + schemaResult: Promise.resolve({}), + addTo: () => addTo.promise, + ready, + release: jest.fn().mockResolvedValue(undefined), + }); + + await flushPromises(); + expect(ready).not.toHaveBeenCalled(); + + addTo.resolve(undefined); + await buildPromise; + expect(ready).toHaveBeenCalledTimes(1); + }); + + it('observes schema failure while adapter attachment is pending', async () => { + const schemaResult = deferred(); + const addTo = deferred(); + const release = jest.fn().mockResolvedValue(undefined); + const failure = new Error('schema build failed early'); + const buildPromise = awaitGraphileBuildReadiness({ + schemaResult: schemaResult.promise, + addTo: () => addTo.promise, + ready: jest.fn().mockResolvedValue(undefined), + release, + }); + + schemaResult.reject(failure); + await flushPromises(); + expect(release).not.toHaveBeenCalled(); + + addTo.resolve(undefined); + await expect(buildPromise).rejects.toBe(failure); + expect(release).toHaveBeenCalledTimes(1); + }); + + it('awaits failed-generation release before rejecting', async () => { + const schemaResult = deferred(); + const release = deferred(); + const releaseFn = jest.fn(() => release.promise); + const failure = new Error('schema build failed'); + let rejected = false; + const buildPromise = awaitGraphileBuildReadiness({ + schemaResult: schemaResult.promise, + addTo: jest.fn().mockResolvedValue(undefined), + ready: jest.fn().mockResolvedValue(undefined), + release: releaseFn, + }).catch((error) => { + rejected = true; + throw error; + }); + + schemaResult.reject(failure); + await flushPromises(); + expect(releaseFn).toHaveBeenCalledTimes(1); + expect(rejected).toBe(false); + + release.resolve(undefined); + await expect(buildPromise).rejects.toBe(failure); + }); + + it('preserves the build failure when cleanup also fails', async () => { + const failure = new Error('schema build failed'); + const cleanupFailure = new Error('release failed'); + const onReleaseError = jest.fn(); + + await expect( + awaitGraphileBuildReadiness({ + schemaResult: Promise.reject(failure), + addTo: jest.fn().mockResolvedValue(undefined), + ready: jest.fn().mockResolvedValue(undefined), + release: jest.fn().mockRejectedValue(cleanupFailure), + onReleaseError, + }) + ).rejects.toBe(failure); + expect(onReleaseError).toHaveBeenCalledWith(cleanupFailure); + }); +}); diff --git a/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts b/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts new file mode 100644 index 0000000000..679488ace9 --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts @@ -0,0 +1,137 @@ +jest.mock('@pgpmjs/logger', () => ({ + Logger: jest.fn(() => ({ + debug: jest.fn(), + error: jest.fn(), + })), +})); + +import type { GraphileCacheEntry } from '../graphile-cache'; +import { + clearGraphileCache, + disposeUncachedEntry, + graphileCache, + waitForEntryDisposal, +} from '../graphile-cache'; + +interface Deferred { + promise: Promise; + resolve(value: T): void; +} + +const deferred = (): Deferred => { + let resolve!: (value: T) => void; + const promise = new Promise((resolvePromise) => { + resolve = resolvePromise; + }); + return { promise, resolve }; +}; + +const flushPromises = (): Promise => + new Promise((resolve) => setImmediate(resolve)); + +const makeEntry = ( + cacheKey: string, + release = jest.fn().mockResolvedValue(undefined), + releasePresetServices = jest.fn().mockResolvedValue(undefined) +): GraphileCacheEntry => + ({ + pgl: { release }, + serv: {}, + handler: {}, + httpServer: { listening: false }, + cacheKey, + createdAt: Date.now(), + releasePresetServices, + }) as unknown as GraphileCacheEntry; + +describe('Graphile cache disposal lifecycle', () => { + afterEach(async () => { + await clearGraphileCache(); + }); + + it('coalesces concurrent disposal of one exact entry', async () => { + const release = jest.fn().mockResolvedValue(undefined); + const releasePresetServices = jest.fn().mockResolvedValue(undefined); + const entry = makeEntry('same-entry', release, releasePresetServices); + + const first = disposeUncachedEntry(entry); + const second = disposeUncachedEntry(entry); + + expect(second).toBe(first); + await Promise.all([first, second]); + expect(release).toHaveBeenCalledTimes(1); + expect(releasePresetServices).toHaveBeenCalledTimes(1); + }); + + it('disposes distinct generations that reuse the same cache key', async () => { + const firstRelease = jest.fn().mockResolvedValue(undefined); + const secondRelease = jest.fn().mockResolvedValue(undefined); + const first = makeEntry('shared-key', firstRelease); + const second = makeEntry('shared-key', secondRelease); + + await Promise.all([ + disposeUncachedEntry(first), + disposeUncachedEntry(second), + ]); + + expect(firstRelease).toHaveBeenCalledTimes(1); + expect(secondRelease).toHaveBeenCalledTimes(1); + }); + + it('continues cleanup and exposes the first disposal failure', async () => { + const failure = new Error('realtime stop failed'); + const release = jest.fn().mockResolvedValue(undefined); + const releasePresetServices = jest.fn().mockResolvedValue(undefined); + const entry = makeEntry('failed-cleanup', release, releasePresetServices); + entry.realtimeManager = { stop: jest.fn().mockRejectedValue(failure) }; + + await expect(disposeUncachedEntry(entry)).rejects.toBe(failure); + expect(release).toHaveBeenCalledTimes(1); + expect(releasePresetServices).toHaveBeenCalledTimes(1); + }); + + it('lets callers await an eviction through the exact entry', async () => { + const release = deferred(); + const releasePresetServices = jest.fn().mockResolvedValue(undefined); + const entry = makeEntry( + 'evicted-entry', + jest.fn(() => release.promise), + releasePresetServices + ); + graphileCache.set(entry.cacheKey, entry); + graphileCache.delete(entry.cacheKey); + + let disposed = false; + const waiting = waitForEntryDisposal(entry).then(() => { + disposed = true; + }); + await flushPromises(); + expect(disposed).toBe(false); + + release.resolve(undefined); + await waiting; + expect(disposed).toBe(true); + expect(releasePresetServices).toHaveBeenCalledTimes(1); + }); + + it('does not resolve a cache clear before resident disposal completes', async () => { + const release = deferred(); + const entry = makeEntry( + 'clear-entry', + jest.fn(() => release.promise) + ); + graphileCache.set(entry.cacheKey, entry); + + let cleared = false; + const clearing = clearGraphileCache().then(() => { + cleared = true; + }); + await flushPromises(); + expect(graphileCache.size).toBe(0); + expect(cleared).toBe(false); + + release.resolve(undefined); + await clearing; + expect(cleared).toBe(true); + }); +}); diff --git a/graphile/graphile-cache/src/__tests__/preset-services.test.ts b/graphile/graphile-cache/src/__tests__/preset-services.test.ts new file mode 100644 index 0000000000..f4b38a855b --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/preset-services.test.ts @@ -0,0 +1,50 @@ +import { createPresetServicesReleaser } from '../preset-services'; + +describe('preset service ownership', () => { + it('releases unique services in reverse order exactly once', async () => { + const events: string[] = []; + const first = { + release: jest.fn(async () => { + events.push('first'); + }), + }; + const second = { + release: jest.fn(async () => { + events.push('second'); + }), + }; + const release = createPresetServicesReleaser({ + pgServices: [first, second, first], + }); + + const releases = [release(), release(), release()]; + expect(releases[0]).toBe(releases[1]); + expect(releases[1]).toBe(releases[2]); + await Promise.all(releases); + + expect(events).toEqual(['second', 'first']); + expect(first.release).toHaveBeenCalledTimes(1); + expect(second.release).toHaveBeenCalledTimes(1); + }); + + it('continues releasing services and preserves the first error', async () => { + const firstFailure = new Error('second failed'); + const first = { release: jest.fn().mockResolvedValue(undefined) }; + const second = { release: jest.fn().mockRejectedValue(firstFailure) }; + const release = createPresetServicesReleaser({ + pgServices: [first, second], + }); + + await expect(release()).rejects.toBe(firstFailure); + expect(first.release).toHaveBeenCalledTimes(1); + expect(second.release).toHaveBeenCalledTimes(1); + }); + + it('is a safe idempotent no-op when the preset has no services', async () => { + const release = createPresetServicesReleaser({}); + + const first = release(); + expect(release()).toBe(first); + await first; + }); +}); diff --git a/graphile/graphile-cache/src/build-readiness.ts b/graphile/graphile-cache/src/build-readiness.ts new file mode 100644 index 0000000000..2ab5e921b2 --- /dev/null +++ b/graphile/graphile-cache/src/build-readiness.ts @@ -0,0 +1,32 @@ +export interface GraphileBuildReadiness { + schemaResult: PromiseLike | unknown; + addTo(): PromiseLike | unknown; + ready(): PromiseLike | unknown; + release(): PromiseLike | unknown; + onReleaseError?(error: unknown): void; +} + +/** + * Resolve only after schema gathering and the HTTP adapter are ready. A failed + * generation reaches its release terminal state before the failure escapes. + */ +export const awaitGraphileBuildReadiness = async ( + build: GraphileBuildReadiness +): Promise => { + const schemaOutcome = Promise.resolve(build.schemaResult).then( + () => ({ ready: true as const }), + (error: unknown) => ({ ready: false as const, error }) + ); + try { + await build.addTo(); + const [schema] = await Promise.all([schemaOutcome, build.ready()]); + if ('error' in schema) throw schema.error; + } catch (error) { + try { + await build.release(); + } catch (releaseError) { + build.onReleaseError?.(releaseError); + } + throw error; + } +}; diff --git a/graphile/graphile-cache/src/create-instance.ts b/graphile/graphile-cache/src/create-instance.ts index 575b767589..1fe84ce156 100644 --- a/graphile/graphile-cache/src/create-instance.ts +++ b/graphile/graphile-cache/src/create-instance.ts @@ -5,7 +5,9 @@ import express from 'express'; import { grafserv } from 'grafserv/express/v4'; import { postgraphile } from 'postgraphile'; +import { awaitGraphileBuildReadiness } from './build-readiness'; import type { GraphileCacheEntry } from './graphile-cache'; +import { createPresetServicesReleaser } from './preset-services'; const log = new Logger('graphile-cache:create'); @@ -42,12 +44,47 @@ export const createGraphileInstance = async ( const { preset, cacheKey, enableRealtime = false } = opts; const pgl = postgraphile(preset); + const resolvedPreset = pgl.getResolvedPreset(); + const releasePresetServices = createPresetServicesReleaser(resolvedPreset); const serv = pgl.createServ(grafserv); const handler = express(); const httpServer = createServer(handler); - await serv.addTo(handler, httpServer); - await serv.ready(); + let failedBuildReleasePromise: Promise | null = null; + const releaseFailedBuild = (): Promise => { + if (failedBuildReleasePromise) return failedBuildReleasePromise; + failedBuildReleasePromise = (async () => { + let firstError: unknown; + let failed = false; + try { + await pgl.release(); + } catch (error) { + firstError = error; + failed = true; + } + try { + await releasePresetServices(); + } catch (error) { + if (!failed) firstError = error; + failed = true; + } + if (failed) throw firstError; + })(); + return failedBuildReleasePromise; + }; + + await awaitGraphileBuildReadiness({ + schemaResult: pgl.getSchemaResult(), + addTo: () => serv.addTo(handler, httpServer), + ready: () => serv.ready(), + release: releaseFailedBuild, + onReleaseError: (releaseError) => { + log.error( + `Failed to release PostGraphile[${cacheKey}] after build failure:`, + releaseError + ); + } + }); const entry: GraphileCacheEntry = { pgl, @@ -56,6 +93,7 @@ export const createGraphileInstance = async ( httpServer, cacheKey, createdAt: Date.now(), + releasePresetServices }; if (enableRealtime) { @@ -65,7 +103,6 @@ export const createGraphileInstance = async ( // Extract PgSubscriber and pool from the resolved preset's pgServices. // The pool is the same instance managed by pg-cache (via getPgPool) // and threaded into the preset by makePgService({ pool, schemas }). - const resolvedPreset = pgl.getResolvedPreset(); const pgService = (resolvedPreset as any).pgServices?.[0]; const pgSubscriber = pgService?.pgSubscriber ?? null; const pool = pgService?.adaptorSettings?.pool ?? null; diff --git a/graphile/graphile-cache/src/graphile-cache.ts b/graphile/graphile-cache/src/graphile-cache.ts index 83782c6a21..67dd35a59c 100644 --- a/graphile/graphile-cache/src/graphile-cache.ts +++ b/graphile/graphile-cache/src/graphile-cache.ts @@ -86,12 +86,14 @@ export interface GraphileCacheEntry { httpServer: HttpServer; cacheKey: string; createdAt: number; + /** Idempotent release for pgServices owned by this exact preset generation. */ + releasePresetServices?: () => Promise; /** Optional RealtimeManager for cursor-tracked subscription delivery */ realtimeManager?: { stop(): Promise } | null; } -// Track disposed entries to prevent double-disposal -const disposedKeys = new Set(); +const disposalPromises = new WeakMap>(); +const activeDisposals = new Set>(); // Track keys that are being manually evicted for accurate eviction reason const manualEvictionKeys = new Set(); @@ -101,42 +103,86 @@ const manualEvictionKeys = new Set(); * * Properly releases resources by: * 1. Closing the HTTP server if listening - * 2. Releasing the PostGraphile instance (which internally releases grafserv) - * - * Uses disposedKeys set to prevent double-disposal when closeAllCaches() - * explicitly disposes entries and then clear() triggers the dispose callback. + * 2. Stopping the realtime manager + * 3. Releasing PostGraphile/Grafserv and preset services */ -const disposeEntry = async (entry: GraphileCacheEntry, key: string): Promise => { - // Prevent double-disposal - if (disposedKeys.has(key)) { - return; - } - disposedKeys.add(key); - +const releaseEntry = async ( + entry: GraphileCacheEntry, + key: string +): Promise => { log.debug(`Disposing PostGraphile[${key}]`); + let firstError: unknown; + let failed = false; try { - // Close HTTP server if it's listening if (entry.httpServer?.listening) { await new Promise((resolve) => { entry.httpServer.close(() => resolve()); }); } - // Stop RealtimeManager if present (before releasing PostGraphile) + } catch (error) { + firstError = error; + failed = true; + } + try { if (entry.realtimeManager) { - try { - await entry.realtimeManager.stop(); - } catch (err) { - log.error(`Error stopping RealtimeManager for PostGraphile[${key}]:`, err); - } - } - // Release PostGraphile instance (this also releases grafserv internally) - if (entry.pgl) { - await entry.pgl.release(); + await entry.realtimeManager.stop(); } - } catch (err) { - log.error(`Error disposing PostGraphile[${key}]:`, err); - } finally { - disposedKeys.delete(key); + } catch (error) { + if (!failed) firstError = error; + failed = true; + } + try { + await entry.pgl.release(); + } catch (error) { + if (!failed) firstError = error; + failed = true; + } + try { + await entry.releasePresetServices?.(); + } catch (error) { + if (!failed) firstError = error; + failed = true; + } + if (failed) throw firstError; +}; + +/** + * Coalesce teardown by exact entry identity, not by its reusable cache key. + */ +const scheduleDisposal = ( + entry: GraphileCacheEntry, + key: string +): Promise => { + const existing = disposalPromises.get(entry); + if (existing) return existing; + + const pending = releaseEntry(entry, key); + + disposalPromises.set(entry, pending); + activeDisposals.add(pending); + void pending + .catch((error) => { + log.error(`Failed to dispose PostGraphile[${key}]:`, error); + }) + .finally(() => activeDisposals.delete(pending)); + return pending; +}; + +/** Dispose a generation that was built but never published in the cache. */ +export const disposeUncachedEntry = ( + entry: GraphileCacheEntry, + key = entry.cacheKey +): Promise => scheduleDisposal(entry, key); + +/** Await the terminal result for an entry whose disposal has been scheduled. */ +export const waitForEntryDisposal = ( + entry: GraphileCacheEntry +): Promise => disposalPromises.get(entry) ?? Promise.resolve(); + +/** Await every disposal that is active at or begins during this drain. */ +export const waitForActiveDisposals = async (): Promise => { + while (activeDisposals.size > 0) { + await Promise.allSettled([...activeDisposals]); } }; @@ -176,11 +222,7 @@ export const graphileCache = new LRUCache({ log.debug(`Evicting PostGraphile[${key}] (reason: ${reason})`); - // LRU dispose is synchronous, but v5 disposal is async - // Fire and forget the async cleanup - disposeEntry(entry, key).catch((err) => { - log.error(`Failed to dispose PostGraphile[${key}]:`, err); - }); + scheduleDisposal(entry, key); } }); @@ -245,6 +287,16 @@ const unregister = pgCache.registerCleanupCallback((pgPoolKey: string) => { // Enhanced close function that handles all caches const closePromise: { promise: Promise | null } = { promise: null }; +/** Clear all resident entries and await every exact-generation disposal. */ +export const clearGraphileCache = async (): Promise => { + for (const key of graphileCache.keys()) { + manualEvictionKeys.add(key); + } + graphileCache.clear(); + await waitForActiveDisposals(); + manualEvictionKeys.clear(); +}; + /** * Close all caches and release resources * @@ -263,27 +315,7 @@ export const closeAllCaches = async (verbose = false): Promise => { try { if (verbose) log.info('Closing all server caches...'); - // Collect all entries and dispose them properly - const entries = [...graphileCache.entries()]; - - // Mark all as manual evictions - for (const [key] of entries) { - manualEvictionKeys.add(key); - } - - const disposePromises = entries.map(([key, entry]) => - disposeEntry(entry, key) - ); - - // Wait for all disposals to complete - await Promise.allSettled(disposePromises); - - // Clear the cache after disposal (dispose callback will no-op due to disposedKeys) - graphileCache.clear(); - - // Clear disposed keys tracking after full cleanup - disposedKeys.clear(); - manualEvictionKeys.clear(); + await clearGraphileCache(); // Close pg pools await pgCache.close(); diff --git a/graphile/graphile-cache/src/index.ts b/graphile/graphile-cache/src/index.ts index 9a845fafe3..7cbed436e3 100644 --- a/graphile/graphile-cache/src/index.ts +++ b/graphile/graphile-cache/src/index.ts @@ -8,9 +8,11 @@ export { CacheEvictionEvent, // Cache stats CacheStats, + clearGraphileCache, // Clear matching entries clearMatchingEntries, closeAllCaches, + disposeUncachedEntry, // Eviction tracking EvictionReason, FIVE_MINUTES_MS, @@ -20,7 +22,9 @@ export { graphileCache, GraphileCacheEntry, // Time constants - ONE_HOUR_MS} from './graphile-cache'; + ONE_HOUR_MS, + waitForActiveDisposals, + waitForEntryDisposal} from './graphile-cache'; // Factory for creating PostGraphile v5 instances export { createGraphileInstance } from './create-instance'; diff --git a/graphile/graphile-cache/src/preset-services.ts b/graphile/graphile-cache/src/preset-services.ts new file mode 100644 index 0000000000..eaf3111c84 --- /dev/null +++ b/graphile/graphile-cache/src/preset-services.ts @@ -0,0 +1,29 @@ +interface ReleasablePresetService { + release?: () => void | Promise; +} + +/** Own and release the unique pgServices for one resolved preset generation. */ +export const createPresetServicesReleaser = (resolvedPreset: { + pgServices?: readonly ReleasablePresetService[]; +}): (() => Promise) => { + const services = [...new Set(resolvedPreset.pgServices ?? [])]; + let releasePromise: Promise | null = null; + + return (): Promise => { + if (releasePromise) return releasePromise; + releasePromise = (async () => { + let firstError: unknown; + let failed = false; + for (const service of [...services].reverse()) { + try { + await service.release?.(); + } catch (error) { + if (!failed) firstError = error; + failed = true; + } + } + if (failed) throw firstError; + })(); + return releasePromise; + }; +}; From e7d763665995924a4061b03fb5a08d43f14dfdd9 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Wed, 9 Sep 2026 04:19:11 +0000 Subject: [PATCH 2/7] fix(graphile-cache): await subscriber shutdown and failed build settlement --- .github/workflows/run-tests.yaml | 2 +- graphile/graphile-cache/jest.config.js | 3 +- graphile/graphile-cache/package.json | 1 + .../src/__tests__/build-readiness.test.ts | 38 ++ .../src/__tests__/disposal-lifecycle.test.ts | 38 ++ .../src/__tests__/pg-service-release.test.ts | 264 ++++++++++++++ .../graphile-cache/src/build-readiness.ts | 3 + .../graphile-cache/src/preset-services.ts | 2 +- patches/@dataplan__pg@1.1.1.patch | 327 ++++++++++++++++++ patches/README.md | 9 + pnpm-lock.yaml | 230 ++++++------ pnpm-workspace.yaml | 3 + 12 files changed, 806 insertions(+), 114 deletions(-) create mode 100644 graphile/graphile-cache/src/__tests__/pg-service-release.test.ts create mode 100644 patches/@dataplan__pg@1.1.1.patch create mode 100644 patches/README.md diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 5a4b52fdf1..2cf105a7f6 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -246,7 +246,7 @@ jobs: - batch: pg-graphql packages: 'graphile/graphile-search graphile/graphile-ltree graphile/graphile-bulk-mutations graphile/graphile-function-bindings graphile/graphile-history graphile/graphile-meta graphile/graphile-schema graphql/orm-test graphql/test graphql/playwright-test' - batch: pg-graphile-extras - packages: 'graphile/graphile-i18n graphile/graphile-pg-aggregates graphile/graphile-query graphile/graphile-realtime-test' + packages: 'graphile/graphile-cache graphile/graphile-i18n graphile/graphile-pg-aggregates graphile/graphile-query graphile/graphile-realtime-test' env: PGHOST: localhost diff --git a/graphile/graphile-cache/jest.config.js b/graphile/graphile-cache/jest.config.js index 057a9420ed..df73257b5a 100644 --- a/graphile/graphile-cache/jest.config.js +++ b/graphile/graphile-cache/jest.config.js @@ -7,7 +7,8 @@ module.exports = { 'ts-jest', { babelConfig: false, - tsconfig: 'tsconfig.json', + // ts-jest requires isolated transformation for NodeNext package exports. + tsconfig: { isolatedModules: true }, }, ], }, diff --git a/graphile/graphile-cache/package.json b/graphile/graphile-cache/package.json index d158a99c8a..a7c1838bc8 100644 --- a/graphile/graphile-cache/package.json +++ b/graphile/graphile-cache/package.json @@ -42,6 +42,7 @@ "@types/express": "^5.0.6", "makage": "^0.8.0", "nodemon": "^3.1.14", + "pgsql-test": "workspace:^", "ts-node": "^10.9.2" }, "keywords": [ diff --git a/graphile/graphile-cache/src/__tests__/build-readiness.test.ts b/graphile/graphile-cache/src/__tests__/build-readiness.test.ts index b876be741c..caaf7a5a44 100644 --- a/graphile/graphile-cache/src/__tests__/build-readiness.test.ts +++ b/graphile/graphile-cache/src/__tests__/build-readiness.test.ts @@ -61,6 +61,44 @@ describe('awaitGraphileBuildReadiness', () => { expect(ready).toHaveBeenCalledTimes(1); }); + it('waits for schema construction before releasing after adapter attachment fails', async () => { + const schemaResult = deferred(); + const attachmentFailure = new Error('adapter attachment failed'); + const release = jest.fn().mockResolvedValue(undefined); + const buildPromise = awaitGraphileBuildReadiness({ + schemaResult: schemaResult.promise, + addTo: jest.fn().mockRejectedValue(attachmentFailure), + ready: jest.fn().mockResolvedValue(undefined), + release, + }); + + await flushPromises(); + expect(release).not.toHaveBeenCalled(); + + schemaResult.resolve({}); + await expect(buildPromise).rejects.toBe(attachmentFailure); + expect(release).toHaveBeenCalledTimes(1); + }); + + it('waits for schema construction before releasing after readiness fails', async () => { + const schemaResult = deferred(); + const readinessFailure = new Error('grafserv readiness failed'); + const release = jest.fn().mockResolvedValue(undefined); + const buildPromise = awaitGraphileBuildReadiness({ + schemaResult: schemaResult.promise, + addTo: jest.fn().mockResolvedValue(undefined), + ready: jest.fn().mockRejectedValue(readinessFailure), + release, + }); + + await flushPromises(); + expect(release).not.toHaveBeenCalled(); + + schemaResult.resolve({}); + await expect(buildPromise).rejects.toBe(readinessFailure); + expect(release).toHaveBeenCalledTimes(1); + }); + it('observes schema failure while adapter attachment is pending', async () => { const schemaResult = deferred(); const addTo = deferred(); diff --git a/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts b/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts index 679488ace9..9a9b2c7a00 100644 --- a/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts +++ b/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts @@ -134,4 +134,42 @@ describe('Graphile cache disposal lifecycle', () => { await clearing; expect(cleared).toBe(true); }); + + it('does not complete entry disposal before preset services are released', async () => { + const releasePresetServices = deferred(); + const entry = makeEntry( + 'delayed-preset-service', + jest.fn().mockResolvedValue(undefined), + jest.fn(() => releasePresetServices.promise) + ); + graphileCache.set(entry.cacheKey, entry); + + graphileCache.delete(entry.cacheKey); + const disposal = waitForEntryDisposal(entry); + let disposed = false; + void disposal.then(() => { + disposed = true; + }); + + await flushPromises(); + expect(disposed).toBe(false); + + releasePresetServices.resolve(undefined); + await disposal; + expect(disposed).toBe(true); + }); + + it('disposes a new generation after an older same-key generation fails', async () => { + const firstFailure = new Error('first generation release failed'); + const firstRelease = jest.fn().mockRejectedValue(firstFailure); + const secondRelease = jest.fn().mockResolvedValue(undefined); + const first = makeEntry('retry-by-generation', firstRelease); + const second = makeEntry('retry-by-generation', secondRelease); + + await expect(disposeUncachedEntry(first)).rejects.toBe(firstFailure); + await expect(disposeUncachedEntry(second)).resolves.toBeUndefined(); + + expect(firstRelease).toHaveBeenCalledTimes(1); + expect(secondRelease).toHaveBeenCalledTimes(1); + }); }); diff --git a/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts b/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts new file mode 100644 index 0000000000..fc13d516f5 --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts @@ -0,0 +1,264 @@ +import { EventEmitter } from 'node:events'; + +import { makePgService, PgSubscriber } from 'postgraphile/adaptors/pg'; +import { getConnections } from 'pgsql-test'; + +interface Deferred { + promise: Promise; + resolve(value: T): void; + reject(error: unknown): void; +} + +const deferred = (): Deferred => { + let resolve!: (value: T) => void; + let reject!: (error: unknown) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, resolve, reject }; +}; + +const flushPromises = (): Promise => + new Promise((resolve) => setImmediate(resolve)); + +const waitFor = async (predicate: () => boolean): Promise => { + for (let attempt = 0; attempt < 20; attempt++) { + if (predicate()) return; + await flushPromises(); + } + throw new Error('Timed out waiting for the fake Postgres boundary'); +}; + +class FakeClient extends EventEmitter { + readonly queryCalls: string[] = []; + readonly releaseCalls: boolean[] = []; + queryHandler: (text: string) => Promise = async () => undefined; + + query(text: string): Promise { + this.queryCalls.push(text); + return this.queryHandler(text); + } + + escapeIdentifier(identifier: string): string { + return `"${identifier}"`; + } + + release(destroy?: Error | boolean): void { + this.releaseCalls.push(destroy === true); + } +} + +type SubscriberPool = ConstructorParameters[0]; + +const makePool = ( + connect: () => Promise +): { + pool: SubscriberPool; + connect: jest.Mock, []>; +} => { + const connectMock = jest.fn(connect); + return { + pool: { connect: connectMock } as unknown as SubscriberPool, + connect: connectMock, + }; +}; + +describe('public PostGraphile PostgreSQL release contracts', () => { + it('waits for UNLISTEN and memoizes repeated subscriber release calls', async () => { + const client = new FakeClient(); + const unlisten = deferred(); + client.queryHandler = async (text) => { + if (text.startsWith('UNLISTEN')) return unlisten.promise; + return undefined; + }; + const { pool } = makePool(async () => client); + const subscriber = new PgSubscriber(pool); + subscriber.subscribe('topic'); + + await waitFor(() => client.queryCalls.some((text) => text.startsWith('LISTEN'))); + + const firstRelease = subscriber.release(); + const secondRelease = subscriber.release(); + expect(secondRelease).toBe(firstRelease); + + await waitFor(() => + client.queryCalls.some((text) => text.startsWith('UNLISTEN')) + ); + expect(client.releaseCalls).toEqual([]); + + unlisten.resolve(undefined); + await firstRelease; + expect(client.releaseCalls).toEqual([false]); + expect(client.listenerCount('notification')).toBe(0); + expect(client.listenerCount('error')).toBe(0); + }); + + it('finishes every pending iterator for one topic during release', async () => { + const client = new FakeClient(); + const { pool } = makePool(async () => client); + const subscriber = new PgSubscriber(pool); + const first = subscriber.subscribe('topic'); + const second = subscriber.subscribe('topic'); + const firstNext = first.next(); + const secondNext = second.next(); + + await subscriber.release(); + await expect(firstNext).resolves.toMatchObject({ done: true }); + await expect(secondNext).resolves.toMatchObject({ done: true }); + }); + + it('destroys a client that resolves after shutdown starts', async () => { + const client = new FakeClient(); + const connection = deferred(); + const { pool, connect } = makePool(() => connection.promise); + const subscriber = new PgSubscriber(pool); + subscriber.subscribe('topic'); + await waitFor(() => connect.mock.calls.length === 1); + + const release = subscriber.release(); + await flushPromises(); + expect(client.releaseCalls).toEqual([]); + + connection.resolve(client); + await release; + expect(client.releaseCalls).toEqual([true]); + expect(client.queryCalls).toEqual([]); + expect(client.listenerCount('notification')).toBe(0); + expect(client.listenerCount('error')).toBe(0); + }); + + it('surfaces a pending connection failure during release', async () => { + const connectionFailure = new Error('connection failed during shutdown'); + const connection = deferred(); + const { pool, connect } = makePool(() => connection.promise); + const subscriber = new PgSubscriber(pool); + subscriber.subscribe('topic'); + await waitFor(() => connect.mock.calls.length === 1); + + const release = subscriber.release(); + connection.reject(connectionFailure); + + await expect(release).rejects.toBe(connectionFailure); + }); + + it('destroys the client and surfaces an UNLISTEN failure', async () => { + const client = new FakeClient(); + const unlistenFailure = new Error('UNLISTEN failed'); + client.queryHandler = async (text) => { + if (text.startsWith('UNLISTEN')) throw unlistenFailure; + return undefined; + }; + const { pool } = makePool(async () => client); + const subscriber = new PgSubscriber(pool); + subscriber.subscribe('topic'); + + await waitFor(() => client.queryCalls.some((text) => text.startsWith('LISTEN'))); + await expect(subscriber.release()).rejects.toBe(unlistenFailure); + + expect(client.releaseCalls).toEqual([true]); + expect(client.listenerCount('notification')).toBe(0); + expect(client.listenerCount('error')).toBe(0); + }); + + it('attempts every owned service releaser and preserves ordered failures', async () => { + const subscriberFailure = new Error('subscriber release failed'); + const poolFailure = new Error('pool end failed'); + const events: string[] = []; + const service = makePgService({ + connectionString: 'postgres://unused/release-contract', + schemas: ['public'], + }); + const subscriber = service.pgSubscriber as unknown as { + release: () => Promise; + }; + const pool = service.adaptorSettings?.pool as unknown as { + end: () => Promise; + }; + subscriber.release = jest.fn(async () => { + events.push('subscriber'); + throw subscriberFailure; + }); + pool.end = jest.fn(async () => { + events.push('pool'); + throw poolFailure; + }); + + const firstRelease = service.release(); + const secondRelease = service.release(); + expect(secondRelease).toBe(firstRelease); + + let error: unknown; + try { + await firstRelease; + } catch (caught) { + error = caught; + } + + expect(error).toBeInstanceOf(AggregateError); + expect((error as AggregateError).errors).toEqual([ + subscriberFailure, + poolFailure, + ]); + expect(events).toEqual(['subscriber', 'pool']); + }); + + it('rethrows one owned service failure unchanged after attempting the pool', async () => { + const subscriberFailure = new Error('subscriber release failed'); + const events: string[] = []; + const service = makePgService({ + connectionString: 'postgres://unused/release-contract-single', + schemas: ['public'], + }); + const subscriber = service.pgSubscriber as unknown as { + release: () => Promise; + }; + const pool = service.adaptorSettings?.pool as unknown as { + end: () => Promise; + }; + subscriber.release = jest.fn(async () => { + events.push('subscriber'); + throw subscriberFailure; + }); + pool.end = jest.fn(async () => { + events.push('pool'); + }); + + await expect(service.release()).rejects.toBe(subscriberFailure); + expect(events).toEqual(['subscriber', 'pool']); + }); + + it('unlistens and returns the same real backend before service release resolves', async () => { + const fixture = await getConnections({}, []); + const poolConfig = { ...fixture.pg.config, max: 1 }; + const pool = fixture.manager.getPool(poolConfig); + const producer = fixture.pg; + const service = makePgService({ pool, schemas: ['public'] }); + try { + const iterator = await service.pgSubscriber!.subscribe('graphile_cache_release_test'); + let received = false; + const notification = iterator.next().then(result => { + expect(result.value).toBe('ready'); + received = true; + }); + // LISTEN startup is asynchronous; send until the real subscriber confirms + // readiness instead of assuming a timer implies the query has completed. + for (let attempt = 0; attempt < 100 && !received; attempt++) { + await producer.query("SELECT pg_notify('graphile_cache_release_test', 'ready')"); + await new Promise(resolve => setTimeout(resolve, 10)); + } + expect(received).toBe(true); + await notification; + expect(pool.totalCount).toBe(1); + expect(pool.idleCount).toBe(0); + await service.release(); + expect(pool.idleCount).toBe(1); + const channels = await pool.query('SELECT pg_listening_channels()'); + expect(channels.rows).toEqual([]); + expect(pool.totalCount).toBe(1); + } finally { + try { await service.release(); } + finally { await fixture.teardown(); } + } + }); +}); diff --git a/graphile/graphile-cache/src/build-readiness.ts b/graphile/graphile-cache/src/build-readiness.ts index 2ab5e921b2..f51d63ed46 100644 --- a/graphile/graphile-cache/src/build-readiness.ts +++ b/graphile/graphile-cache/src/build-readiness.ts @@ -22,6 +22,9 @@ export const awaitGraphileBuildReadiness = async ( const [schema] = await Promise.all([schemaOutcome, build.ready()]); if ('error' in schema) throw schema.error; } catch (error) { + // A failed adapter/readiness operation can race schema construction. Wait + // for schema work to settle before releasing the services it may still use. + await schemaOutcome; try { await build.release(); } catch (releaseError) { diff --git a/graphile/graphile-cache/src/preset-services.ts b/graphile/graphile-cache/src/preset-services.ts index eaf3111c84..9c76c16ed7 100644 --- a/graphile/graphile-cache/src/preset-services.ts +++ b/graphile/graphile-cache/src/preset-services.ts @@ -1,5 +1,5 @@ interface ReleasablePresetService { - release?: () => void | Promise; + release?: () => void | PromiseLike; } /** Own and release the unique pgServices for one resolved preset generation. */ diff --git a/patches/@dataplan__pg@1.1.1.patch b/patches/@dataplan__pg@1.1.1.patch new file mode 100644 index 0000000000..19072afabe --- /dev/null +++ b/patches/@dataplan__pg@1.1.1.patch @@ -0,0 +1,327 @@ +diff --git a/dist/adaptors/pg.d.ts b/dist/adaptors/pg.d.ts +index 2c69e9046a83c61e59b42d9a733ea3051a658f59..22797b5aa5fbf277a71bb1691b200e853f94be95 100644 +--- a/dist/adaptors/pg.d.ts ++++ b/dist/adaptors/pg.d.ts +@@ -96,8 +96,10 @@ export declare class PgSubscriber; + private _chain; + private chain; + } +diff --git a/dist/adaptors/pg.js b/dist/adaptors/pg.js +index afa172c99d66784cbefb9aad271978d87e0d4277..3e9f00c711b545945a6c31e9353043c98028159a 100644 +--- a/dist/adaptors/pg.js ++++ b/dist/adaptors/pg.js +@@ -349,6 +349,22 @@ const adaptor = { + makePgService, + }; + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); ++const pgSubscriberReleaseAbort = Symbol("PgSubscriber release abort"); ++const makePgSubscriberReleaseAbortError = () => { ++ const error = new Error("PgSubscriber released; aborting getClient"); ++ error[pgSubscriberReleaseAbort] = true; ++ return error; ++}; ++const isPgSubscriberReleaseAbortError = (error) => error != null && ++ typeof error === "object" && ++ error[pgSubscriberReleaseAbort] === true; ++const releaseErrorFrom = (errors, message) => { ++ if (errors.length === 0) ++ return null; ++ if (errors.length === 1) ++ return errors[0]; ++ return new AggregateError(errors, message); ++}; + /** + * This class provides helpers for Postgres' LISTEN/NOTIFY pub/sub + * implementation. We aggregate all LISTEN/NOTIFY events so that we can supply +@@ -369,6 +385,8 @@ class PgSubscriber { + this.subscribedTopics = new Set(); + this.listeningClient = null; + this.listeningClientPromise = null; ++ this.listeningClientErrorListener = null; ++ this.releasePromise = null; + // Avoid race conditions by chaining everything + this._chain = Promise.resolve(); + this.pool = pool; +@@ -480,7 +498,7 @@ class PgSubscriber { + } + async syncWithClient(client) { + if (!this.alive) { +- throw new Error("PgSubscriber released; aborting syncWithClient"); ++ throw makePgSubscriberReleaseAbortError(); + } + const expectedTopics = Object.keys(this.topics); + const topicsToAdd = expectedTopics.filter((t) => !this.subscribedTopics.has(t)); +@@ -501,6 +519,10 @@ class PgSubscriber { + } + const client = this.listeningClient; + if (client !== null) { ++ if (this.listeningClientErrorListener) { ++ client.off("error", this.listeningClientErrorListener); ++ this.listeningClientErrorListener = null; ++ } + client.off("notification", this.recordNotification); + client.release(); + this.listeningClient = null; +@@ -525,7 +547,7 @@ class PgSubscriber { + } + getClient() { + if (!this.alive) { +- return Promise.reject(new Error("Released; aborting getClient")); ++ return Promise.reject(makePgSubscriberReleaseAbortError()); + } + if (this.listeningClient) { + return Promise.resolve(this.listeningClient); +@@ -536,34 +558,65 @@ class PgSubscriber { + for (let attempts = 0;; attempts++) { + try { + if (!this.alive) { +- return Promise.reject(new Error("PgSubscriber released; aborting getClient")); ++ throw makePgSubscriberReleaseAbortError(); + } + const logError = (e) => { + console.error(`Error on listening client: ${e}`); + }; + const client = await this.pool.connect(); ++ if (!this.alive) { ++ await client.release(true); ++ throw makePgSubscriberReleaseAbortError(); ++ } + try { + client.on("error", logError); + client.on("notification", this.recordNotification); + await this.syncWithClient(client); ++ if (!this.alive) { ++ throw makePgSubscriberReleaseAbortError(); ++ } + // All good; we can return this client finally! + this.listeningClientPromise = null; + this.listeningClient = client; + client.off("error", logError); +- client.on("error", (e) => { ++ const listeningClientErrorListener = (e) => { + logError(e); + this.resetClient(); +- }); ++ }; ++ this.listeningClientErrorListener = listeningClientErrorListener; ++ client.on("error", listeningClientErrorListener); + return client; + } + catch (e) { +- client.off("error", logError); +- client.off("notification", this.recordNotification); +- client.release(); ++ const cleanupErrors = []; ++ try { ++ client.off("error", logError); ++ } ++ catch (error) { ++ cleanupErrors.push(error); ++ } ++ try { ++ client.off("notification", this.recordNotification); ++ } ++ catch (error) { ++ cleanupErrors.push(error); ++ } ++ try { ++ await client.release(!this.alive); ++ } ++ catch (error) { ++ cleanupErrors.push(error); ++ } ++ if (cleanupErrors.length > 0) { ++ throw releaseErrorFrom([e, ...cleanupErrors], "Failed to release a PgSubscriber client after getClient failed"); ++ } + throw e; + } + } + catch (e) { ++ if (!this.alive) { ++ throw e; ++ } + console.error(`Error with listening client during getClient (attempt ${attempts + 1}): ${e}`); + // Exponential back-off (maximum 30 seconds) + await sleep(Math.min(100 * Math.exp(attempts), 30000)); +@@ -582,15 +635,30 @@ class PgSubscriber { + } + } + release() { +- if (this.alive) { ++ if (this.releasePromise) { ++ return this.releasePromise; ++ } ++ this.releasePromise = (async () => { ++ const errors = []; + this.alive = false; ++ const iteratorPromises = []; + for (const topic of Object.keys(this.topics)) { +- for (const asyncIterableIterator of this.topics[topic]) { ++ for (const asyncIterableIterator of [...this.topics[topic]]) { + if (asyncIterableIterator.return) { +- asyncIterableIterator.return().then(null, grafast_1.noop); ++ try { ++ iteratorPromises.push(Promise.resolve(asyncIterableIterator.return())); ++ } ++ catch (error) { ++ errors.push(error); ++ } + } + else if (asyncIterableIterator.throw) { +- asyncIterableIterator.throw(new Error("Released")).then(null, grafast_1.noop); ++ try { ++ iteratorPromises.push(Promise.resolve(asyncIterableIterator.throw(new Error("Released")))); ++ } ++ catch (error) { ++ errors.push(error); ++ } + } + else { + // What do we do now?! +@@ -600,27 +668,89 @@ class PgSubscriber { + } + delete this.topics[topic]; + } ++ const chain = this._chain; ++ const listeningClientPromise = this.listeningClientPromise; ++ const iteratorResults = await Promise.allSettled(iteratorPromises); ++ for (const result of iteratorResults) { ++ if (result.status === "rejected") { ++ errors.push(result.reason); ++ } ++ } ++ try { ++ await chain; ++ } ++ catch (error) { ++ errors.push(error); ++ } ++ if (listeningClientPromise) { ++ try { ++ await listeningClientPromise; ++ } ++ catch (error) { ++ if (!isPgSubscriberReleaseAbortError(error)) { ++ errors.push(error); ++ } ++ } ++ } + const unlistenAndRelease = async (client) => { ++ const errors = []; ++ try { ++ if (this.listeningClientErrorListener) { ++ client.off("error", this.listeningClientErrorListener); ++ this.listeningClientErrorListener = null; ++ } ++ } ++ catch (error) { ++ errors.push(error); ++ } ++ try { ++ client.off("notification", this.recordNotification); ++ } ++ catch (error) { ++ errors.push(error); ++ } + try { + for (const topic of this.subscribedTopics) { + await client.query(`UNLISTEN ${client.escapeIdentifier(topic)}`); + this.subscribedTopics.delete(topic); + } + } +- catch (_e) { +- // ignore ++ catch (error) { ++ errors.push(error); ++ } ++ finally { ++ this.subscribedTopics.clear(); ++ try { ++ await client.release(errors.length > 0); ++ } ++ catch (error) { ++ errors.push(error); ++ } ++ } ++ const releaseError = releaseErrorFrom(errors, "Failed to release a PgSubscriber client"); ++ if (releaseError) { ++ throw releaseError; + } +- client.release(); + }; + if (this.listeningClient) { +- unlistenAndRelease(this.listeningClient).then(null, grafast_1.noop); ++ const client = this.listeningClient; ++ try { ++ await unlistenAndRelease(client); ++ } ++ catch (error) { ++ errors.push(error); ++ } ++ finally { ++ this.listeningClient = null; ++ } + } +- else if (this.listeningClientPromise) { +- this.listeningClientPromise.then(unlistenAndRelease, () => { +- /* ignore */ +- }); ++ this.listeningClientPromise = null; ++ const releaseError = releaseErrorFrom(errors, "Failed to release a PgSubscriber"); ++ if (releaseError) { ++ throw releaseError; + } +- } ++ })(); ++ return this.releasePromise; + } + chain(callback) { + this._chain = this._chain.then(callback, callback).then(null, grafast_1.noop); +@@ -661,6 +791,7 @@ function makePgService(options) { + pgSubscriber = new PgSubscriber(pool); + releasers.push(() => pgSubscriber.release?.()); + } ++ let releasePromise = null; + const service = { + name, + schemas: Array.isArray(schemas) ? schemas : [schemas ?? "public"], +@@ -675,11 +806,27 @@ function makePgService(options) { + pool, + superuserPool, + }, +- async release() { +- // Release in reverse order +- for (const releaser of [...releasers].reverse()) { +- await releaser(); ++ release() { ++ if (releasePromise) { ++ return releasePromise; + } ++ releasePromise = (async () => { ++ const errors = []; ++ // Release in reverse order, but always attempt every releaser. ++ for (const releaser of [...releasers].reverse()) { ++ try { ++ await releaser(); ++ } ++ catch (error) { ++ errors.push(error); ++ } ++ } ++ const releaseError = releaseErrorFrom(errors, "Failed to release a PgService"); ++ if (releaseError) { ++ throw releaseError; ++ } ++ })(); ++ return releasePromise; + }, + }; + return service; diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 0000000000..f152123a5b --- /dev/null +++ b/patches/README.md @@ -0,0 +1,9 @@ +# Maintained dependency patches + +## @dataplan/pg 1.1.1 + +`@dataplan__pg@1.1.1.patch` makes the public `PgSubscriber.release()` promise await iterator termination, pending connection acquisition, serialized subscription work, `UNLISTEN`, and client return. Failed cleanup destroys the connection and rejects. `makePgService.release()` attempts all reverse releasers, including owned pools, and shares its terminal result across callers. + +The patch also updates the published declaration. It preserves the pinned upstream version. PNPM records the patch content hash in `pnpm-lock.yaml`; frozen installs apply it on development machines and CI. Recreate with `pnpm patch @dataplan/pg@1.1.1` and `pnpm patch-commit `. Reassess this patch when upgrading PostGraphile; remove it only once the upstream release has the same completion contract and the graphile-cache public-boundary tests pass without it. + +Regression tests live in `graphile/graphile-cache`; that package runs in the PostgreSQL CI matrix. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca6ae59d08..16cdd812da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,11 @@ overrides: packageExtensionsChecksum: sha256-x8B4zkJ4KLRX+yspUWxuggXWlz6zrBLSIh72pNhpPiE= +patchedDependencies: + '@dataplan/pg@1.1.1': + hash: 7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee + path: patches/@dataplan__pg@1.1.1.patch + importers: .: devDependencies: @@ -431,7 +436,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -440,13 +445,13 @@ importers: version: link:../graphile-storage-registry/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@types/node': specifier: ^22.19.11 @@ -460,7 +465,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -469,7 +474,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -481,7 +486,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@types/node': specifier: ^22.19.11 @@ -522,7 +527,7 @@ importers: version: link:../../postgres/pg-cache/dist postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) devDependencies: '@types/express': specifier: ^5.0.6 @@ -533,6 +538,9 @@ importers: nodemon: specifier: ^3.1.14 version: 3.1.14 + pgsql-test: + specifier: workspace:^ + version: link:../../postgres/pgsql-test/dist ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@25.9.1)(typescript@5.9.3) @@ -542,13 +550,13 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) graphile-build: specifier: 5.1.1 version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -563,7 +571,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(f51a9bb2feb8b06fca136f1c2a1a2f6a) + version: 5.1.4(3cfb8ae93140145a791ab179ae3cec49) devDependencies: '@types/node': specifier: ^22.19.11 @@ -586,7 +594,7 @@ importers: version: link:../../postgres/query-builder/dist '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@pgpmjs/logger': specifier: workspace:^ version: link:../../pgpm/logger/dist @@ -598,7 +606,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -636,7 +644,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -645,7 +653,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -657,7 +665,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(f282a162d8bd20a217e08c60f5396af8) + version: 5.1.4(9d965dba21fac51f59273c4256716137) devDependencies: '@types/node': specifier: ^22.19.11 @@ -683,7 +691,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) accept-language-parser: specifier: ^1.5.0 version: 1.5.0 @@ -695,7 +703,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -710,7 +718,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(f282a162d8bd20a217e08c60f5396af8) + version: 5.1.4(9d965dba21fac51f59273c4256716137) devDependencies: '@types/accept-language-parser': specifier: ^1.5.4 @@ -748,7 +756,7 @@ importers: version: link:../../packages/llm-env/dist '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -757,7 +765,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-cache: specifier: workspace:^ version: link:../graphile-cache/dist @@ -766,7 +774,7 @@ importers: version: 1.1.0 graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -775,7 +783,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@types/node': specifier: ^22.19.11 @@ -801,7 +809,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -810,7 +818,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -822,7 +830,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@types/node': specifier: ^22.19.11 @@ -848,7 +856,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -860,7 +868,7 @@ importers: version: 16.13.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(f282a162d8bd20a217e08c60f5396af8) + version: 5.1.4(9d965dba21fac51f59273c4256716137) devDependencies: '@types/node': specifier: ^22.19.11 @@ -874,7 +882,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -883,7 +891,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -898,7 +906,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@types/node': specifier: ^22.19.11 @@ -921,13 +929,13 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) graphile-build: specifier: 5.1.1 version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -939,7 +947,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(f282a162d8bd20a217e08c60f5396af8) + version: 5.1.4(9d965dba21fac51f59273c4256716137) devDependencies: '@types/node': specifier: ^22.19.11 @@ -953,7 +961,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -962,7 +970,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -974,7 +982,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(f51a9bb2feb8b06fca136f1c2a1a2f6a) + version: 5.1.4(3cfb8ae93140145a791ab179ae3cec49) devDependencies: '@types/geojson': specifier: ^7946.0.14 @@ -1018,7 +1026,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1030,7 +1038,7 @@ importers: version: link:../graphile-storage-registry/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1042,7 +1050,7 @@ importers: version: link:../../uploads/mime-bytes/dist postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@constructive-io/s3-utils': specifier: workspace:^ @@ -1065,7 +1073,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1080,7 +1088,7 @@ importers: version: 8.21.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) devDependencies: '@types/pg': specifier: ^8.20.4 @@ -1109,19 +1117,19 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(138876491c004694bff1843ee79c8872) + version: 5.1.4(92324343608439594bbe299323c80ef9) devDependencies: '@types/node': specifier: ^22.19.11 @@ -1141,7 +1149,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1187,7 +1195,7 @@ importers: version: link:../../postgres/pgsql-test/dist postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) publishDirectory: dist graphile/graphile-schema: @@ -1229,13 +1237,13 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) graphile-build: specifier: 5.1.1 version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1250,7 +1258,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(f51a9bb2feb8b06fca136f1c2a1a2f6a) + version: 5.1.4(3cfb8ae93140145a791ab179ae3cec49) devDependencies: '@types/node': specifier: ^22.19.11 @@ -1303,7 +1311,7 @@ importers: version: 1.0.1(grafast@1.1.2(graphql@16.13.0)) '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile-contrib/pg-many-to-many': specifier: 2.0.0-rc.2 version: 2.0.0-rc.2 @@ -1336,7 +1344,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-bulk-mutations: specifier: workspace:^ version: link:../graphile-bulk-mutations/dist @@ -1381,7 +1389,7 @@ importers: version: link:../graphile-upload-plugin/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1405,7 +1413,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) request-ip: specifier: ^3.3.0 version: 3.3.0 @@ -1449,7 +1457,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1500,7 +1508,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1518,7 +1526,7 @@ importers: version: link:../../postgres/pgsql-test/dist postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) devDependencies: '@types/pg': specifier: ^8.20.4 @@ -1538,7 +1546,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1547,7 +1555,7 @@ importers: version: 16.13.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(f51a9bb2feb8b06fca136f1c2a1a2f6a) + version: 5.1.4(3cfb8ae93140145a791ab179ae3cec49) devDependencies: '@types/node': specifier: ^22.19.11 @@ -1689,7 +1697,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-cache: specifier: workspace:^ version: link:../../graphile/graphile-cache/dist @@ -1701,7 +1709,7 @@ importers: version: link:../../graphile/graphile-settings/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1719,7 +1727,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) devDependencies: '@types/express': specifier: ^5.0.6 @@ -1792,7 +1800,7 @@ importers: version: link:../../postgres/pg-env/dist postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) devDependencies: '@types/express': specifier: ^5.0.6 @@ -1936,7 +1944,7 @@ importers: version: 1.1.2(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1957,7 +1965,7 @@ importers: version: 11.2.7 postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) devDependencies: makage: specifier: ^0.8.0 @@ -1977,7 +1985,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -2001,7 +2009,7 @@ importers: version: 8.20.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(cf7ef086788d1d3f644d1dc7345cb344) + version: 5.1.4(fdde9aa28a0299d2757f9ae98191ef07) ws: specifier: ^8.20.0 version: 8.20.1 @@ -2090,7 +2098,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-cache: specifier: workspace:^ version: link:../../graphile/graphile-cache/dist @@ -2105,7 +2113,7 @@ importers: version: link:../../graphile/graphile-settings/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -2132,7 +2140,7 @@ importers: version: 5.0.1 postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) request-ip: specifier: ^3.3.0 version: 3.3.0 @@ -2289,7 +2297,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -2313,7 +2321,7 @@ importers: version: link:../../postgres/pgsql-test/dist postgraphile: specifier: 5.1.4 - version: 5.1.4(b3fb9399b7b000ca872a8f427cb73d08) + version: 5.1.4(278bb7eee576e8287d20e8783c1ec9ea) typescript: specifier: ^5.0.0 version: 5.9.3 @@ -2718,7 +2726,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -2730,7 +2738,7 @@ importers: version: 8.21.0 postgraphile: specifier: 5.1.4 - version: 5.1.4(f282a162d8bd20a217e08c60f5396af8) + version: 5.1.4(9d965dba21fac51f59273c4256716137) devDependencies: '@types/node': specifier: ^22.19.11 @@ -16302,7 +16310,7 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) tslib: 2.8.1 - '@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)': + '@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)': dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) '@graphile/lru': 5.0.0 @@ -16322,7 +16330,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)': + '@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)': dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) '@graphile/lru': 5.0.0 @@ -20407,9 +20415,9 @@ snapshots: - supports-color - use-sync-external-store - graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1): + graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) '@types/node': 22.19.19 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) @@ -20426,9 +20434,9 @@ snapshots: transitivePeerDependencies: - supports-color - graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1): + graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@types/node': 22.19.19 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) @@ -20475,9 +20483,9 @@ snapshots: transitivePeerDependencies: - supports-color - graphile-utils@5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): + graphile-utils@5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) @@ -20487,13 +20495,13 @@ snapshots: tamedevil: 0.1.1 tslib: 2.8.1 optionalDependencies: - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) transitivePeerDependencies: - supports-color - graphile-utils@5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): + graphile-utils@5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) @@ -20503,7 +20511,7 @@ snapshots: tamedevil: 0.1.1 tslib: 2.8.1 optionalDependencies: - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) transitivePeerDependencies: - supports-color @@ -22505,20 +22513,20 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postgraphile@5.1.4(138876491c004694bff1843ee79c8872): + postgraphile@5.1.4(278bb7eee576e8287d20e8783c1ec9ea): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) - grafserv: 1.0.1(@types/node@22.19.15)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) + grafserv: 1.0.1(@types/node@25.9.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22532,20 +22540,20 @@ snapshots: - supports-color - utf-8-validate - postgraphile@5.1.4(b3fb9399b7b000ca872a8f427cb73d08): + postgraphile@5.1.4(3cfb8ae93140145a791ab179ae3cec49): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) - grafserv: 1.0.1(@types/node@25.9.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) + grafserv: 1.0.1(@types/node@22.19.11)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22559,24 +22567,24 @@ snapshots: - supports-color - utf-8-validate - postgraphile@5.1.4(cf7ef086788d1d3f644d1dc7345cb344): + postgraphile@5.1.4(92324343608439594bbe299323c80ef9): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) - grafserv: 1.0.1(@types/node@25.9.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) + grafserv: 1.0.1(@types/node@22.19.15)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 - pg: 8.20.0 + pg: 8.21.0 pg-sql2: 5.0.1 tamedevil: 0.1.1 tslib: 2.8.1 @@ -22586,10 +22594,10 @@ snapshots: - supports-color - utf-8-validate - postgraphile@5.1.4(f282a162d8bd20a217e08c60f5396af8): + postgraphile@5.1.4(9d965dba21fac51f59273c4256716137): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 @@ -22597,9 +22605,9 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) grafserv: 1.0.1(@types/node@22.19.19)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22613,24 +22621,24 @@ snapshots: - supports-color - utf-8-validate - postgraphile@5.1.4(f51a9bb2feb8b06fca136f1c2a1a2f6a): + postgraphile@5.1.4(fdde9aa28a0299d2757f9ae98191ef07): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) - grafserv: 1.0.1(@types/node@22.19.11)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) + grafserv: 1.0.1(@types/node@25.9.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 - pg: 8.21.0 + pg: 8.20.0 pg-sql2: 5.0.1 tamedevil: 0.1.1 tslib: 2.8.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b4da2891a3..dd8ea4ff1f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -105,3 +105,6 @@ packageExtensions: allowBuilds: "@launchql/protobufjs": true core-js-pure: true + +patchedDependencies: + '@dataplan/pg@1.1.1': patches/@dataplan__pg@1.1.1.patch From 1c9dfb13d121c2bcea06a0ca40b149937916567f Mon Sep 17 00:00:00 2001 From: zetazzz Date: Mon, 21 Sep 2026 15:46:53 +0000 Subject: [PATCH 3/7] Remove dataplan release patch and validate connection cleanup --- .../src/__tests__/pg-service-release.test.ts | 263 ++------------ .../src/__tests__/preset-services.test.ts | 19 + graphile/graphile-cache/src/graphile-cache.ts | 2 +- .../graphile-cache/src/preset-services.ts | 7 +- packages/perf-harness/README.md | 46 +++ packages/perf-harness/package.json | 4 +- .../src/connection-lifecycle/suite.ts | 37 ++ .../src/connection-lifecycle/worker.ts | 267 ++++++++++++++ patches/@dataplan__pg@1.1.1.patch | 327 ------------------ patches/README.md | 9 - pnpm-lock.yaml | 157 ++++----- pnpm-workspace.yaml | 3 - 12 files changed, 493 insertions(+), 648 deletions(-) create mode 100644 packages/perf-harness/src/connection-lifecycle/suite.ts create mode 100644 packages/perf-harness/src/connection-lifecycle/worker.ts delete mode 100644 patches/@dataplan__pg@1.1.1.patch delete mode 100644 patches/README.md diff --git a/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts b/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts index fc13d516f5..01f44a9dfa 100644 --- a/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts +++ b/graphile/graphile-cache/src/__tests__/pg-service-release.test.ts @@ -1,239 +1,39 @@ -import { EventEmitter } from 'node:events'; +import { type GetConnectionResult, getConnections } from 'pgsql-test'; +import { makePgService } from 'postgraphile/adaptors/pg'; -import { makePgService, PgSubscriber } from 'postgraphile/adaptors/pg'; -import { getConnections } from 'pgsql-test'; - -interface Deferred { - promise: Promise; - resolve(value: T): void; - reject(error: unknown): void; -} - -const deferred = (): Deferred => { - let resolve!: (value: T) => void; - let reject!: (error: unknown) => void; - const promise = new Promise((resolvePromise, rejectPromise) => { - resolve = resolvePromise; - reject = rejectPromise; - }); - return { promise, resolve, reject }; -}; - -const flushPromises = (): Promise => - new Promise((resolve) => setImmediate(resolve)); +import { createPresetServicesReleaser } from '../preset-services'; const waitFor = async (predicate: () => boolean): Promise => { - for (let attempt = 0; attempt < 20; attempt++) { - if (predicate()) return; - await flushPromises(); - } - throw new Error('Timed out waiting for the fake Postgres boundary'); -}; - -class FakeClient extends EventEmitter { - readonly queryCalls: string[] = []; - readonly releaseCalls: boolean[] = []; - queryHandler: (text: string) => Promise = async () => undefined; - - query(text: string): Promise { - this.queryCalls.push(text); - return this.queryHandler(text); - } - - escapeIdentifier(identifier: string): string { - return `"${identifier}"`; - } - - release(destroy?: Error | boolean): void { - this.releaseCalls.push(destroy === true); + const deadline = Date.now() + 5000; + while (!predicate()) { + if (Date.now() >= deadline) throw new Error('Timed out waiting for subscriber cleanup'); + await new Promise(resolve => setTimeout(resolve, 5)); } -} - -type SubscriberPool = ConstructorParameters[0]; - -const makePool = ( - connect: () => Promise -): { - pool: SubscriberPool; - connect: jest.Mock, []>; -} => { - const connectMock = jest.fn(connect); - return { - pool: { connect: connectMock } as unknown as SubscriberPool, - connect: connectMock, - }; }; -describe('public PostGraphile PostgreSQL release contracts', () => { - it('waits for UNLISTEN and memoizes repeated subscriber release calls', async () => { - const client = new FakeClient(); - const unlisten = deferred(); - client.queryHandler = async (text) => { - if (text.startsWith('UNLISTEN')) return unlisten.promise; - return undefined; - }; - const { pool } = makePool(async () => client); - const subscriber = new PgSubscriber(pool); - subscriber.subscribe('topic'); - - await waitFor(() => client.queryCalls.some((text) => text.startsWith('LISTEN'))); - - const firstRelease = subscriber.release(); - const secondRelease = subscriber.release(); - expect(secondRelease).toBe(firstRelease); - - await waitFor(() => - client.queryCalls.some((text) => text.startsWith('UNLISTEN')) - ); - expect(client.releaseCalls).toEqual([]); - - unlisten.resolve(undefined); - await firstRelease; - expect(client.releaseCalls).toEqual([false]); - expect(client.listenerCount('notification')).toBe(0); - expect(client.listenerCount('error')).toBe(0); - }); - - it('finishes every pending iterator for one topic during release', async () => { - const client = new FakeClient(); - const { pool } = makePool(async () => client); - const subscriber = new PgSubscriber(pool); - const first = subscriber.subscribe('topic'); - const second = subscriber.subscribe('topic'); - const firstNext = first.next(); - const secondNext = second.next(); - - await subscriber.release(); - await expect(firstNext).resolves.toMatchObject({ done: true }); - await expect(secondNext).resolves.toMatchObject({ done: true }); - }); - - it('destroys a client that resolves after shutdown starts', async () => { - const client = new FakeClient(); - const connection = deferred(); - const { pool, connect } = makePool(() => connection.promise); - const subscriber = new PgSubscriber(pool); - subscriber.subscribe('topic'); - await waitFor(() => connect.mock.calls.length === 1); - - const release = subscriber.release(); - await flushPromises(); - expect(client.releaseCalls).toEqual([]); +describe('unpatched PostgreSQL service release', () => { + let fixture: GetConnectionResult; - connection.resolve(client); - await release; - expect(client.releaseCalls).toEqual([true]); - expect(client.queryCalls).toEqual([]); - expect(client.listenerCount('notification')).toBe(0); - expect(client.listenerCount('error')).toBe(0); + beforeAll(async () => { + fixture = await getConnections({}, []); }); - - it('surfaces a pending connection failure during release', async () => { - const connectionFailure = new Error('connection failed during shutdown'); - const connection = deferred(); - const { pool, connect } = makePool(() => connection.promise); - const subscriber = new PgSubscriber(pool); - subscriber.subscribe('topic'); - await waitFor(() => connect.mock.calls.length === 1); - - const release = subscriber.release(); - connection.reject(connectionFailure); - - await expect(release).rejects.toBe(connectionFailure); - }); - - it('destroys the client and surfaces an UNLISTEN failure', async () => { - const client = new FakeClient(); - const unlistenFailure = new Error('UNLISTEN failed'); - client.queryHandler = async (text) => { - if (text.startsWith('UNLISTEN')) throw unlistenFailure; - return undefined; - }; - const { pool } = makePool(async () => client); - const subscriber = new PgSubscriber(pool); - subscriber.subscribe('topic'); - - await waitFor(() => client.queryCalls.some((text) => text.startsWith('LISTEN'))); - await expect(subscriber.release()).rejects.toBe(unlistenFailure); - - expect(client.releaseCalls).toEqual([true]); - expect(client.listenerCount('notification')).toBe(0); - expect(client.listenerCount('error')).toBe(0); + beforeEach(async () => { + await fixture.pg.beforeEach(); + await fixture.db.beforeEach(); }); - - it('attempts every owned service releaser and preserves ordered failures', async () => { - const subscriberFailure = new Error('subscriber release failed'); - const poolFailure = new Error('pool end failed'); - const events: string[] = []; - const service = makePgService({ - connectionString: 'postgres://unused/release-contract', - schemas: ['public'], - }); - const subscriber = service.pgSubscriber as unknown as { - release: () => Promise; - }; - const pool = service.adaptorSettings?.pool as unknown as { - end: () => Promise; - }; - subscriber.release = jest.fn(async () => { - events.push('subscriber'); - throw subscriberFailure; - }); - pool.end = jest.fn(async () => { - events.push('pool'); - throw poolFailure; - }); - - const firstRelease = service.release(); - const secondRelease = service.release(); - expect(secondRelease).toBe(firstRelease); - - let error: unknown; - try { - await firstRelease; - } catch (caught) { - error = caught; - } - - expect(error).toBeInstanceOf(AggregateError); - expect((error as AggregateError).errors).toEqual([ - subscriberFailure, - poolFailure, - ]); - expect(events).toEqual(['subscriber', 'pool']); + afterEach(async () => { + await fixture.db.afterEach(); + await fixture.pg.afterEach(); }); - - it('rethrows one owned service failure unchanged after attempting the pool', async () => { - const subscriberFailure = new Error('subscriber release failed'); - const events: string[] = []; - const service = makePgService({ - connectionString: 'postgres://unused/release-contract-single', - schemas: ['public'], - }); - const subscriber = service.pgSubscriber as unknown as { - release: () => Promise; - }; - const pool = service.adaptorSettings?.pool as unknown as { - end: () => Promise; - }; - subscriber.release = jest.fn(async () => { - events.push('subscriber'); - throw subscriberFailure; - }); - pool.end = jest.fn(async () => { - events.push('pool'); - }); - - await expect(service.release()).rejects.toBe(subscriberFailure); - expect(events).toEqual(['subscriber', 'pool']); + afterAll(async () => { + await fixture?.teardown(); }); - it('unlistens and returns the same real backend before service release resolves', async () => { - const fixture = await getConnections({}, []); + it('eventually returns the subscriber backend and removes its LISTEN channels', async () => { const poolConfig = { ...fixture.pg.config, max: 1 }; const pool = fixture.manager.getPool(poolConfig); - const producer = fixture.pg; const service = makePgService({ pool, schemas: ['public'] }); + const release = createPresetServicesReleaser({ pgServices: [service] }); try { const iterator = await service.pgSubscriber!.subscribe('graphile_cache_release_test'); let received = false; @@ -241,24 +41,31 @@ describe('public PostGraphile PostgreSQL release contracts', () => { expect(result.value).toBe('ready'); received = true; }); - // LISTEN startup is asynchronous; send until the real subscriber confirms - // readiness instead of assuming a timer implies the query has completed. + // pg_notify must commit independently of the fixture's savepoint. for (let attempt = 0; attempt < 100 && !received; attempt++) { - await producer.query("SELECT pg_notify('graphile_cache_release_test', 'ready')"); + await fixture.pg.query("SELECT pg_notify('graphile_cache_release_test', 'ready')"); + await fixture.pg.publish(); await new Promise(resolve => setTimeout(resolve, 10)); } expect(received).toBe(true); await notification; expect(pool.totalCount).toBe(1); expect(pool.idleCount).toBe(0); - await service.release(); - expect(pool.idleCount).toBe(1); + const before = await fixture.pg.query( + 'SELECT pid FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()' + ); + + await release(); + // The public call succeeding is the disposal contract. Pool return is a + // separate eventual observation, not a prerequisite for that success. + await waitFor(() => pool.idleCount === 1); const channels = await pool.query('SELECT pg_listening_channels()'); expect(channels.rows).toEqual([]); + const backend = await pool.query('SELECT pg_backend_pid() AS pid'); + expect(before.rows.map(row => row.pid)).toContain(backend.rows[0].pid); expect(pool.totalCount).toBe(1); } finally { - try { await service.release(); } - finally { await fixture.teardown(); } + await release(); } }); }); diff --git a/graphile/graphile-cache/src/__tests__/preset-services.test.ts b/graphile/graphile-cache/src/__tests__/preset-services.test.ts index f4b38a855b..dcc76edc81 100644 --- a/graphile/graphile-cache/src/__tests__/preset-services.test.ts +++ b/graphile/graphile-cache/src/__tests__/preset-services.test.ts @@ -40,6 +40,25 @@ describe('preset service ownership', () => { expect(second.release).toHaveBeenCalledTimes(1); }); + it('accepts a void release without waiting for private background cleanup', async () => { + let finishCleanup!: () => void; + let cleaned = false; + const background = new Promise(resolve => { finishCleanup = resolve; }); + const service = { + release: jest.fn((): void => { + void background.then(() => { cleaned = true; }); + }), + }; + const release = createPresetServicesReleaser({ pgServices: [service] }); + + await release(); + expect(service.release).toHaveBeenCalledTimes(1); + expect(cleaned).toBe(false); + finishCleanup(); + await background; + expect(cleaned).toBe(true); + }); + it('is a safe idempotent no-op when the preset has no services', async () => { const release = createPresetServicesReleaser({}); diff --git a/graphile/graphile-cache/src/graphile-cache.ts b/graphile/graphile-cache/src/graphile-cache.ts index 67dd35a59c..6ef9dda7fe 100644 --- a/graphile/graphile-cache/src/graphile-cache.ts +++ b/graphile/graphile-cache/src/graphile-cache.ts @@ -174,7 +174,7 @@ export const disposeUncachedEntry = ( key = entry.cacheKey ): Promise => scheduleDisposal(entry, key); -/** Await the terminal result for an entry whose disposal has been scheduled. */ +/** Await disposal calls, not background connection cleanup inside upstream. */ export const waitForEntryDisposal = ( entry: GraphileCacheEntry ): Promise => disposalPromises.get(entry) ?? Promise.resolve(); diff --git a/graphile/graphile-cache/src/preset-services.ts b/graphile/graphile-cache/src/preset-services.ts index 9c76c16ed7..99e8c49dd3 100644 --- a/graphile/graphile-cache/src/preset-services.ts +++ b/graphile/graphile-cache/src/preset-services.ts @@ -2,7 +2,12 @@ interface ReleasablePresetService { release?: () => void | PromiseLike; } -/** Own and release the unique pgServices for one resolved preset generation. */ +/** + * Own and release the unique pgServices for one resolved preset generation. + * Success means the public release calls returned successfully (including any + * promise they expose). Upstream may continue subscriber UNLISTEN/client return + * in the background; this does not promise that the pool is already idle. + */ export const createPresetServicesReleaser = (resolvedPreset: { pgServices?: readonly ReleasablePresetService[]; }): (() => Promise) => { diff --git a/packages/perf-harness/README.md b/packages/perf-harness/README.md index ff22327cee..ff5e07b34c 100644 --- a/packages/perf-harness/README.md +++ b/packages/perf-harness/README.md @@ -80,3 +80,49 @@ may be visible to other local processes. The PostgreSQL fixture command only creates a previously absent schema whose name starts with `cperf_`; it never drops or replaces schemas. + +## Connection lifecycle stability suite + +The TypeScript entry points in `src/connection-lifecycle` exercise the unpatched +PostGraphile service and the real `graphile-cache` lifecycle. Build the workspace +(or `pnpm --filter @constructive-io/perf-harness... build`), then run: + +```sh +node packages/perf-harness/dist/connection-lifecycle/suite.js \ + --database-url 'postgresql://postgres:password@localhost:5432/postgres' \ + --output /tmp/graphile-connection-lifecycle.json +``` + +Use a local PostgreSQL test administrator URL without query parameters. Its +named database is the administrative connection database; `pgsql-test` creates +and tears down a separate test database per worker. The suite defaults to one +fresh-process repetition, one concurrent lifecycle, a two-connection pool, and +`--scale 0.01`: 20 cycles for each service-only case and three cycles for each +Graphile case (72 lifecycles total). This is a low-resource functional check. + +For a separate stress run on a suitable machine, pass `--scale 1 --repetitions 3 +--concurrency 8`: 2,000 cycles per service-only case, 256 per Graphile case, and a +16-connection pool (21,072 lifecycles total). Concurrency accepts 1–16; the pool +limit is twice concurrency, capped at 16. Each worker retains the harness's +five-minute deadline. + +Cases cover idle services, confirmed LISTEN subscriptions, release during +subscription startup, Graphile build/dispose, subscribed build/dispose, failed +schema builds, and replacement of generations using the same cache key. Runtime +GraphQL queries verify the built schema against a real table. Every 64 cycles +(and at the end), the worker separately observes pool quiescence and checks every +remaining backend for LISTEN channels. It also verifies that `pg_stat_activity` +returns to its pre-run baseline after idle expiry, before fixture teardown or +process exit can hide a leak. + +A successful public release call is counted immediately; it does not imply that +upstream background UNLISTEN/client return has completed. Reports distinguish +pending connections at return, peak pool size/checkout/waiting counts, cleanup +settlement latency, quiescent checkpoints, and retained client event listeners. +Listener accumulation is reported separately from checked-out connection leaks. +For this suite `buildMs` measures the whole churn loop, including its validation +checkpoints, rather than one +schema build. The failed-build case expects its injected schema errors. Network +partitions, backend termination, and production workload distributions are not +modeled. Keep generated JSON reports outside the repository; commit only the +TypeScript suite and its documentation. diff --git a/packages/perf-harness/package.json b/packages/perf-harness/package.json index 094465062b..60539162b6 100644 --- a/packages/perf-harness/package.json +++ b/packages/perf-harness/package.json @@ -28,7 +28,9 @@ "devDependencies": { "@types/node": "^22.19.11", "@types/pg": "^8.20.4", - "makage": "^0.8.0" + "graphile-cache": "workspace:^", + "makage": "^0.8.0", + "pgsql-test": "workspace:^" }, "engines": { "node": ">=22" diff --git a/packages/perf-harness/src/connection-lifecycle/suite.ts b/packages/perf-harness/src/connection-lifecycle/suite.ts new file mode 100644 index 0000000000..8fff587b65 --- /dev/null +++ b/packages/perf-harness/src/connection-lifecycle/suite.ts @@ -0,0 +1,37 @@ +import { resolve } from 'node:path'; + +import { parseValueArgs } from '../process'; +import { runBenchmarkSuite, writeJsonAtomically } from '../run'; +import type { BenchmarkSuiteDefinition } from '../types'; + +const main = async (): Promise => { + const { values } = parseValueArgs(process.argv.slice(2)); + const databaseUrl = values.get('database-url'); + const output = values.get('output'); + if (!databaseUrl || !output) throw new Error('--database-url and --output are required'); + const repetitions = Number(values.get('repetitions') ?? 1); + const scale = Number(values.get('scale') ?? 0.01); + const concurrency = Number(values.get('concurrency') ?? 1); + if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 16) { + throw new Error('concurrency must be between 1 and 16'); + } + if (!Number.isSafeInteger(repetitions) || repetitions < 1 || repetitions > 50 || !Number.isFinite(scale) || scale <= 0) { + throw new Error('repetitions must be 1–50 and scale must be positive'); + } + const suite: BenchmarkSuiteDefinition = { + name: 'unpatched-graphile-connection-lifecycle', + cases: ['idle', 'subscribed', 'startup-race', 'cache', 'cache-subscribed', 'failed-build', 'replacement'].map(mode => ({ + name: mode, + workerConfig: { mode, cycles: Math.max(1, Math.ceil((['idle', 'subscribed', 'startup-race'].includes(mode) ? 2000 : 256) * scale)), concurrency }, + ...(['cache', 'cache-subscribed', 'replacement'].includes(mode) ? { expectedSchemaGroup: 'lifecycle' } : {}), + })), + }; + const report = await runBenchmarkSuite(suite, { + databaseUrl, repetitions, seed: 1747, order: null, workerTimeoutMs: 300_000, + }, resolve(__dirname, 'worker.js')); + await writeJsonAtomically(output, report); + process.stdout.write(`${JSON.stringify({ output, validation: report.validation })}\n`); + if (report.validation.errors.length) process.exitCode = 1; +}; + +void main().catch(error => { console.error(error); process.exitCode = 1; }); diff --git a/packages/perf-harness/src/connection-lifecycle/worker.ts b/packages/perf-harness/src/connection-lifecycle/worker.ts new file mode 100644 index 0000000000..7a22256f30 --- /dev/null +++ b/packages/perf-harness/src/connection-lifecycle/worker.ts @@ -0,0 +1,267 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { performance } from 'node:perf_hooks'; +import { setTimeout as delay } from 'node:timers/promises'; + +import { + clearGraphileCache, + createGraphileInstance, + disposeUncachedEntry, + graphileCache, + type GraphileCacheEntry, + waitForActiveDisposals, +} from 'graphile-cache'; +import { lexicographicSortSchema, printSchema } from 'graphql'; +import type { Pool } from 'pg'; +import { getConnections } from 'pgsql-test'; +import { makePgService } from 'postgraphile/adaptors/pg'; +import { grafast } from 'postgraphile/grafast'; +import { PostGraphileAmberPreset } from 'postgraphile/presets/amber'; + +import { measureBenchmarkCase } from '../metrics'; +import { parseWorkerProcessArgs, redactSecret, writeWorkerResult } from '../process'; +import type { JsonValue, SuccessfulWorkerResult } from '../types'; + +const modes = ['idle', 'subscribed', 'startup-race', 'cache', 'cache-subscribed', 'failed-build', 'replacement'] as const; +type Mode = typeof modes[number]; +interface Config { mode: Mode; cycles: number; concurrency: number } + +const parseConfig = (value: JsonValue): Config => { + const config = value as unknown as Config; + assert(config && modes.includes(config.mode), 'unknown lifecycle mode'); + assert(Number.isSafeInteger(config.cycles) && config.cycles > 0, 'cycles must be positive'); + assert(Number.isSafeInteger(config.concurrency) && config.concurrency > 0 && config.concurrency <= 16, + 'concurrency must be between 1 and 16'); + return config; +}; + +const waitUntil = async (predicate: () => boolean | Promise, label: string): Promise => { + const start = performance.now(); + while (!await predicate()) { + if (performance.now() - start > 5000) throw new Error(`Timed out: ${label}`); + await delay(2); + } + return performance.now() - start; +}; + +const run = async (): Promise => { + const { databaseUrl, envelope } = parseWorkerProcessArgs(process.argv.slice(2)); + const config = parseConfig(envelope.workerConfig); + // pgsql-test owns the isolated database, clients, pool, and final teardown. + const url = new URL(databaseUrl); + assert(['postgres:', 'postgresql:'].includes(url.protocol) && !url.search, + 'use a local PostgreSQL URL without query parameters'); + const fixture = await getConnections({ + pg: { host: url.hostname, port: Number(url.port || 5432), + user: decodeURIComponent(url.username), password: decodeURIComponent(url.password) }, + db: { rootDb: decodeURIComponent(url.pathname.slice(1)) || 'postgres' }, + }, []); + const poolConfig = { + ...fixture.pg.config, max: Math.min(16, config.concurrency * 2), + connectionTimeoutMillis: 5000, idleTimeoutMillis: 1000, + }; + const pool = fixture.manager.getPool(poolConfig); + let result: SuccessfulWorkerResult | undefined; + let primaryError: unknown; + const cleanupErrors: unknown[] = []; + let created = 0; + let released = 0; + let pendingAtReturn = 0; + let expectedBuildFailures = 0; + let peakTotal = 0; + let peakCheckedOut = 0; + let peakWaiting = 0; + let maxSettleMs = 0; + let peakNotificationListeners = 0; + let peakErrorListeners = 0; + let listenerWarnings = 0; + const onWarning = (warning: Error): void => { + if (warning.name === 'MaxListenersExceededWarning') listenerWarnings++; + }; + process.on('warning', onWarning); + let schemaHash = ''; + let schemaTypeCount = 0; + const checkpoints: JsonValue[] = []; + const releaseTimes: number[] = []; + const poolErrors: string[] = []; + pool.on('error', error => poolErrors.push(error.message)); + const sample = (): void => { + peakTotal = Math.max(peakTotal, pool.totalCount); + peakCheckedOut = Math.max(peakCheckedOut, pool.totalCount - pool.idleCount); + peakWaiting = Math.max(peakWaiting, pool.waitingCount); + }; + const sampler = setInterval(sample, 2); + const newService = () => { + created++; + return makePgService({ pool, schemas: ['cperf_lifecycle'] }); + }; + const releaseCall = async (release: () => void | PromiseLike): Promise => { + const start = performance.now(); + await release(); + releaseTimes.push(performance.now() - start); + released++; + if (pool.totalCount !== pool.idleCount) pendingAtReturn++; + sample(); + }; + const subscribe = async (service: ReturnType, id: number): Promise => { + const topic = `cperf_${id}`; + const iterator = await service.pgSubscriber!.subscribe(topic); + if (config.mode === 'startup-race') return; + let received = false; + const next = iterator.next().then(value => { + assert.equal(value.value, 'ready'); + received = true; + }); + const deadline = performance.now() + 5000; + while (!received) { + assert(performance.now() < deadline, 'LISTEN readiness timed out'); + await fixture.pg.query('SELECT pg_notify($1, $2)', [topic, 'ready']); + await delay(2); + } + await next; + }; + const verifyChannels = async (target: Pool): Promise => { + // Hold all acquired clients until every idle backend has been inspected. + const clients = await Promise.all(Array.from({ length: target.totalCount }, () => target.connect())); + try { + for (const client of clients) { + peakNotificationListeners = Math.max(peakNotificationListeners, client.listenerCount('notification')); + peakErrorListeners = Math.max(peakErrorListeners, client.listenerCount('error')); + } + const results = await Promise.all(clients.map(client => client.query('SELECT pg_listening_channels()'))); + return results.reduce((count, result) => count + result.rowCount!, 0); + } finally { + for (const client of clients) client.release(); + } + }; + + try { + // Fixture setup is committed so independent Graphile connections can see it. + await fixture.pg.query('CREATE SCHEMA cperf_lifecycle; CREATE TABLE cperf_lifecycle.item (id int PRIMARY KEY, value int NOT NULL); INSERT INTO cperf_lifecycle.item VALUES (1, 42)'); + await fixture.db.query('SELECT 1'); + const baseline = await fixture.pg.query('SELECT count(*)::int AS n FROM pg_stat_activity WHERE datname = current_database()'); + result = await measureBenchmarkCase(envelope.caseName, async () => { + for (let offset = 0; offset < config.cycles; offset += config.concurrency) { + await Promise.all(Array.from({ length: Math.min(config.concurrency, config.cycles - offset) }, async (_, index) => { + const id = offset + index; + const service = newService(); + if (config.mode === 'idle' || config.mode === 'subscribed' || config.mode === 'startup-race') { + try { + if (config.mode !== 'idle') await subscribe(service, id); + } finally { + await releaseCall(() => service.release()); + } + return; + } + const failure = new Error('intentional lifecycle build failure'); + const entry = await createGraphileInstance({ + cacheKey: `cperf_${index}`, + preset: { + extends: [PostGraphileAmberPreset], + pgServices: [service], + grafserv: { graphqlPath: '/graphql' }, + ...(config.mode === 'failed-build' ? { + plugins: [{ name: 'LifecycleFailurePlugin', version: '1.0.0', schema: { + hooks: { GraphQLSchema: () => { throw failure; } }, + } }], + } : {}), + }, + }).catch((error): GraphileCacheEntry | null => { + if (config.mode !== 'failed-build' || error !== failure) throw error; + expectedBuildFailures++; + released++; + return null; + }); + if (!entry) return; + try { + const { schema } = await entry.pgl.getSchemaResult(); + const execution = await grafast({ schema, source: '{ allItems { nodes { value } } }', + resolvedPreset: entry.pgl.getResolvedPreset(), requestContext: {} }); + assert('data' in execution, `runtime query failed: ${JSON.stringify(execution)}`); + assert.equal(execution.errors, undefined); + assert.deepEqual(JSON.parse(JSON.stringify(execution.data)), { allItems: { nodes: [{ value: 42 }] } }); + schemaHash = createHash('sha256').update(printSchema(lexicographicSortSchema(schema))).digest('hex'); + schemaTypeCount = Object.keys(schema.getTypeMap()).length; + if (config.mode === 'cache-subscribed' || config.mode === 'replacement') await subscribe(service, id); + if (config.mode === 'replacement') { + // Reuse each key across generations while the old teardown runs. + graphileCache.set(entry.cacheKey, entry); + } else { + await releaseCall(() => disposeUncachedEntry(entry)); + } + } catch (error) { + await disposeUncachedEntry(entry); + throw error; + } + })); + sample(); + // Observe a long churn window before quiescence, not after every release. + if ((offset + config.concurrency) % 64 === 0 || offset + config.concurrency >= config.cycles) { + if (config.mode === 'replacement') { + await clearGraphileCache(); + released = created; + } + await waitForActiveDisposals(); + maxSettleMs = Math.max(maxSettleMs, await waitUntil( + () => pool.totalCount === pool.idleCount && pool.waitingCount === 0, 'pool quiescence' + )); + const channels = await verifyChannels(pool); + assert.equal(channels, 0, 'LISTEN channels remain after quiescence'); + const activity = await fixture.pg.query('SELECT count(*)::int AS n FROM pg_stat_activity WHERE datname = current_database()'); + checkpoints.push({ cycles: Math.min(offset + config.concurrency, config.cycles), + total: pool.totalCount, idle: pool.idleCount, waiting: pool.waitingCount, + backends: activity.rows[0].n, channels, peakNotificationListeners, peakErrorListeners }); + } + } + return baseline.rows[0].n as number; + }, async baselineBackends => { + assert.equal(released, created); + assert.equal(pool.totalCount - pool.idleCount, 0); + assert.deepEqual(poolErrors, []); + assert.equal(graphileCache.size, 0); + if (config.mode === 'failed-build') assert.equal(expectedBuildFailures, config.cycles); + const probe = await pool.query('SELECT 1 AS ok'); + assert.equal(probe.rows[0].ok, 1); + await waitUntil(() => pool.totalCount === 0, 'idle connection expiry'); + let finalBackends = -1; + const backendSettleMs = await waitUntil(async () => { + const activity = await fixture.pg.query('SELECT count(*)::int AS n FROM pg_stat_activity WHERE datname = current_database()'); + finalBackends = activity.rows[0].n; + return finalBackends === baselineBackends; + }, 'database backend baseline after idle expiry'); + releaseTimes.sort((a, b) => a - b); + return { + schemaHash: schemaHash || createHash('sha256').update('SELECT 1 AS ok').digest('hex'), + schemaTypeCount, runtimeVerified: true, + metadata: { ...config, poolMax: poolConfig.max, idleTimeoutMs: poolConfig.idleTimeoutMillis, + created, released, expectedBuildFailures, pendingAtReturn, + peakTotal, peakCheckedOut, peakWaiting, maxSettleMs, baselineBackends, + finalBackends, backendSettleMs, releaseP99Ms: releaseTimes[Math.floor(releaseTimes.length * 0.99)] ?? 0, + checkpoints, poolErrors, peakNotificationListeners, peakErrorListeners, listenerWarnings }, + }; + }); + } catch (error) { + primaryError = error; + process.stderr.write(`Lifecycle failure: ${redactSecret(String(error), databaseUrl)}\n`); + process.stderr.write(`${JSON.stringify({ created, released, total: pool.totalCount, + idle: pool.idleCount, waiting: pool.waitingCount, checkpoints })}\n`); + } finally { + clearInterval(sampler); + process.off('warning', onWarning); + try { await clearGraphileCache(); } catch (error) { cleanupErrors.push(error); } + try { await fixture.teardown(); } catch (error) { cleanupErrors.push(error); } + } + if (primaryError || cleanupErrors.length) { + throw new AggregateError([...(primaryError ? [primaryError] : []), ...cleanupErrors], 'Lifecycle benchmark failed'); + } + writeWorkerResult(result!); +}; + +void run().catch(error => { + const { databaseUrl, envelope } = parseWorkerProcessArgs(process.argv.slice(2)); + const message = error instanceof AggregateError + ? error.errors.map(item => String(item?.stack ?? item)).join('\n') : String(error?.stack ?? error); + writeWorkerResult({ status: 'error', pid: process.pid, caseName: envelope.caseName, + error: redactSecret(message, databaseUrl) }); + process.exitCode = 1; +}); diff --git a/patches/@dataplan__pg@1.1.1.patch b/patches/@dataplan__pg@1.1.1.patch deleted file mode 100644 index 19072afabe..0000000000 --- a/patches/@dataplan__pg@1.1.1.patch +++ /dev/null @@ -1,327 +0,0 @@ -diff --git a/dist/adaptors/pg.d.ts b/dist/adaptors/pg.d.ts -index 2c69e9046a83c61e59b42d9a733ea3051a658f59..22797b5aa5fbf277a71bb1691b200e853f94be95 100644 ---- a/dist/adaptors/pg.d.ts -+++ b/dist/adaptors/pg.d.ts -@@ -96,8 +96,10 @@ export declare class PgSubscriber; - private _chain; - private chain; - } -diff --git a/dist/adaptors/pg.js b/dist/adaptors/pg.js -index afa172c99d66784cbefb9aad271978d87e0d4277..3e9f00c711b545945a6c31e9353043c98028159a 100644 ---- a/dist/adaptors/pg.js -+++ b/dist/adaptors/pg.js -@@ -349,6 +349,22 @@ const adaptor = { - makePgService, - }; - const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); -+const pgSubscriberReleaseAbort = Symbol("PgSubscriber release abort"); -+const makePgSubscriberReleaseAbortError = () => { -+ const error = new Error("PgSubscriber released; aborting getClient"); -+ error[pgSubscriberReleaseAbort] = true; -+ return error; -+}; -+const isPgSubscriberReleaseAbortError = (error) => error != null && -+ typeof error === "object" && -+ error[pgSubscriberReleaseAbort] === true; -+const releaseErrorFrom = (errors, message) => { -+ if (errors.length === 0) -+ return null; -+ if (errors.length === 1) -+ return errors[0]; -+ return new AggregateError(errors, message); -+}; - /** - * This class provides helpers for Postgres' LISTEN/NOTIFY pub/sub - * implementation. We aggregate all LISTEN/NOTIFY events so that we can supply -@@ -369,6 +385,8 @@ class PgSubscriber { - this.subscribedTopics = new Set(); - this.listeningClient = null; - this.listeningClientPromise = null; -+ this.listeningClientErrorListener = null; -+ this.releasePromise = null; - // Avoid race conditions by chaining everything - this._chain = Promise.resolve(); - this.pool = pool; -@@ -480,7 +498,7 @@ class PgSubscriber { - } - async syncWithClient(client) { - if (!this.alive) { -- throw new Error("PgSubscriber released; aborting syncWithClient"); -+ throw makePgSubscriberReleaseAbortError(); - } - const expectedTopics = Object.keys(this.topics); - const topicsToAdd = expectedTopics.filter((t) => !this.subscribedTopics.has(t)); -@@ -501,6 +519,10 @@ class PgSubscriber { - } - const client = this.listeningClient; - if (client !== null) { -+ if (this.listeningClientErrorListener) { -+ client.off("error", this.listeningClientErrorListener); -+ this.listeningClientErrorListener = null; -+ } - client.off("notification", this.recordNotification); - client.release(); - this.listeningClient = null; -@@ -525,7 +547,7 @@ class PgSubscriber { - } - getClient() { - if (!this.alive) { -- return Promise.reject(new Error("Released; aborting getClient")); -+ return Promise.reject(makePgSubscriberReleaseAbortError()); - } - if (this.listeningClient) { - return Promise.resolve(this.listeningClient); -@@ -536,34 +558,65 @@ class PgSubscriber { - for (let attempts = 0;; attempts++) { - try { - if (!this.alive) { -- return Promise.reject(new Error("PgSubscriber released; aborting getClient")); -+ throw makePgSubscriberReleaseAbortError(); - } - const logError = (e) => { - console.error(`Error on listening client: ${e}`); - }; - const client = await this.pool.connect(); -+ if (!this.alive) { -+ await client.release(true); -+ throw makePgSubscriberReleaseAbortError(); -+ } - try { - client.on("error", logError); - client.on("notification", this.recordNotification); - await this.syncWithClient(client); -+ if (!this.alive) { -+ throw makePgSubscriberReleaseAbortError(); -+ } - // All good; we can return this client finally! - this.listeningClientPromise = null; - this.listeningClient = client; - client.off("error", logError); -- client.on("error", (e) => { -+ const listeningClientErrorListener = (e) => { - logError(e); - this.resetClient(); -- }); -+ }; -+ this.listeningClientErrorListener = listeningClientErrorListener; -+ client.on("error", listeningClientErrorListener); - return client; - } - catch (e) { -- client.off("error", logError); -- client.off("notification", this.recordNotification); -- client.release(); -+ const cleanupErrors = []; -+ try { -+ client.off("error", logError); -+ } -+ catch (error) { -+ cleanupErrors.push(error); -+ } -+ try { -+ client.off("notification", this.recordNotification); -+ } -+ catch (error) { -+ cleanupErrors.push(error); -+ } -+ try { -+ await client.release(!this.alive); -+ } -+ catch (error) { -+ cleanupErrors.push(error); -+ } -+ if (cleanupErrors.length > 0) { -+ throw releaseErrorFrom([e, ...cleanupErrors], "Failed to release a PgSubscriber client after getClient failed"); -+ } - throw e; - } - } - catch (e) { -+ if (!this.alive) { -+ throw e; -+ } - console.error(`Error with listening client during getClient (attempt ${attempts + 1}): ${e}`); - // Exponential back-off (maximum 30 seconds) - await sleep(Math.min(100 * Math.exp(attempts), 30000)); -@@ -582,15 +635,30 @@ class PgSubscriber { - } - } - release() { -- if (this.alive) { -+ if (this.releasePromise) { -+ return this.releasePromise; -+ } -+ this.releasePromise = (async () => { -+ const errors = []; - this.alive = false; -+ const iteratorPromises = []; - for (const topic of Object.keys(this.topics)) { -- for (const asyncIterableIterator of this.topics[topic]) { -+ for (const asyncIterableIterator of [...this.topics[topic]]) { - if (asyncIterableIterator.return) { -- asyncIterableIterator.return().then(null, grafast_1.noop); -+ try { -+ iteratorPromises.push(Promise.resolve(asyncIterableIterator.return())); -+ } -+ catch (error) { -+ errors.push(error); -+ } - } - else if (asyncIterableIterator.throw) { -- asyncIterableIterator.throw(new Error("Released")).then(null, grafast_1.noop); -+ try { -+ iteratorPromises.push(Promise.resolve(asyncIterableIterator.throw(new Error("Released")))); -+ } -+ catch (error) { -+ errors.push(error); -+ } - } - else { - // What do we do now?! -@@ -600,27 +668,89 @@ class PgSubscriber { - } - delete this.topics[topic]; - } -+ const chain = this._chain; -+ const listeningClientPromise = this.listeningClientPromise; -+ const iteratorResults = await Promise.allSettled(iteratorPromises); -+ for (const result of iteratorResults) { -+ if (result.status === "rejected") { -+ errors.push(result.reason); -+ } -+ } -+ try { -+ await chain; -+ } -+ catch (error) { -+ errors.push(error); -+ } -+ if (listeningClientPromise) { -+ try { -+ await listeningClientPromise; -+ } -+ catch (error) { -+ if (!isPgSubscriberReleaseAbortError(error)) { -+ errors.push(error); -+ } -+ } -+ } - const unlistenAndRelease = async (client) => { -+ const errors = []; -+ try { -+ if (this.listeningClientErrorListener) { -+ client.off("error", this.listeningClientErrorListener); -+ this.listeningClientErrorListener = null; -+ } -+ } -+ catch (error) { -+ errors.push(error); -+ } -+ try { -+ client.off("notification", this.recordNotification); -+ } -+ catch (error) { -+ errors.push(error); -+ } - try { - for (const topic of this.subscribedTopics) { - await client.query(`UNLISTEN ${client.escapeIdentifier(topic)}`); - this.subscribedTopics.delete(topic); - } - } -- catch (_e) { -- // ignore -+ catch (error) { -+ errors.push(error); -+ } -+ finally { -+ this.subscribedTopics.clear(); -+ try { -+ await client.release(errors.length > 0); -+ } -+ catch (error) { -+ errors.push(error); -+ } -+ } -+ const releaseError = releaseErrorFrom(errors, "Failed to release a PgSubscriber client"); -+ if (releaseError) { -+ throw releaseError; - } -- client.release(); - }; - if (this.listeningClient) { -- unlistenAndRelease(this.listeningClient).then(null, grafast_1.noop); -+ const client = this.listeningClient; -+ try { -+ await unlistenAndRelease(client); -+ } -+ catch (error) { -+ errors.push(error); -+ } -+ finally { -+ this.listeningClient = null; -+ } - } -- else if (this.listeningClientPromise) { -- this.listeningClientPromise.then(unlistenAndRelease, () => { -- /* ignore */ -- }); -+ this.listeningClientPromise = null; -+ const releaseError = releaseErrorFrom(errors, "Failed to release a PgSubscriber"); -+ if (releaseError) { -+ throw releaseError; - } -- } -+ })(); -+ return this.releasePromise; - } - chain(callback) { - this._chain = this._chain.then(callback, callback).then(null, grafast_1.noop); -@@ -661,6 +791,7 @@ function makePgService(options) { - pgSubscriber = new PgSubscriber(pool); - releasers.push(() => pgSubscriber.release?.()); - } -+ let releasePromise = null; - const service = { - name, - schemas: Array.isArray(schemas) ? schemas : [schemas ?? "public"], -@@ -675,11 +806,27 @@ function makePgService(options) { - pool, - superuserPool, - }, -- async release() { -- // Release in reverse order -- for (const releaser of [...releasers].reverse()) { -- await releaser(); -+ release() { -+ if (releasePromise) { -+ return releasePromise; - } -+ releasePromise = (async () => { -+ const errors = []; -+ // Release in reverse order, but always attempt every releaser. -+ for (const releaser of [...releasers].reverse()) { -+ try { -+ await releaser(); -+ } -+ catch (error) { -+ errors.push(error); -+ } -+ } -+ const releaseError = releaseErrorFrom(errors, "Failed to release a PgService"); -+ if (releaseError) { -+ throw releaseError; -+ } -+ })(); -+ return releasePromise; - }, - }; - return service; diff --git a/patches/README.md b/patches/README.md deleted file mode 100644 index f152123a5b..0000000000 --- a/patches/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Maintained dependency patches - -## @dataplan/pg 1.1.1 - -`@dataplan__pg@1.1.1.patch` makes the public `PgSubscriber.release()` promise await iterator termination, pending connection acquisition, serialized subscription work, `UNLISTEN`, and client return. Failed cleanup destroys the connection and rejects. `makePgService.release()` attempts all reverse releasers, including owned pools, and shares its terminal result across callers. - -The patch also updates the published declaration. It preserves the pinned upstream version. PNPM records the patch content hash in `pnpm-lock.yaml`; frozen installs apply it on development machines and CI. Recreate with `pnpm patch @dataplan/pg@1.1.1` and `pnpm patch-commit `. Reassess this patch when upgrading PostGraphile; remove it only once the upstream release has the same completion contract and the graphile-cache public-boundary tests pass without it. - -Regression tests live in `graphile/graphile-cache`; that package runs in the PostgreSQL CI matrix. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16cdd812da..41288c5600 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,6 @@ overrides: packageExtensionsChecksum: sha256-x8B4zkJ4KLRX+yspUWxuggXWlz6zrBLSIh72pNhpPiE= -patchedDependencies: - '@dataplan/pg@1.1.1': - hash: 7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee - path: patches/@dataplan__pg@1.1.1.patch - importers: .: devDependencies: @@ -436,7 +431,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -445,7 +440,7 @@ importers: version: link:../graphile-storage-registry/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -465,7 +460,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -474,7 +469,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -550,13 +545,13 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) graphile-build: specifier: 5.1.1 version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -594,7 +589,7 @@ importers: version: link:../../postgres/query-builder/dist '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@pgpmjs/logger': specifier: workspace:^ version: link:../../pgpm/logger/dist @@ -606,7 +601,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -644,7 +639,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -653,7 +648,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -691,7 +686,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) accept-language-parser: specifier: ^1.5.0 version: 1.5.0 @@ -703,7 +698,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -756,7 +751,7 @@ importers: version: link:../../packages/llm-env/dist '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -765,7 +760,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-cache: specifier: workspace:^ version: link:../graphile-cache/dist @@ -774,7 +769,7 @@ importers: version: 1.1.0 graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -809,7 +804,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -818,7 +813,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -856,7 +851,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -882,7 +877,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -891,7 +886,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -929,13 +924,13 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) graphile-build: specifier: 5.1.1 version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -961,7 +956,7 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) grafast: specifier: 1.1.2 version: 1.1.2(graphql@16.13.0) @@ -970,7 +965,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1026,7 +1021,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1038,7 +1033,7 @@ importers: version: link:../graphile-storage-registry/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1073,7 +1068,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1117,13 +1112,13 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1149,7 +1144,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1237,13 +1232,13 @@ importers: dependencies: '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) graphile-build: specifier: 5.1.1 version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1311,7 +1306,7 @@ importers: version: 1.0.1(grafast@1.1.2(graphql@16.13.0)) '@dataplan/pg': specifier: 1.1.1 - version: 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + version: 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile-contrib/pg-many-to-many': specifier: 2.0.0-rc.2 version: 2.0.0-rc.2 @@ -1344,7 +1339,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-bulk-mutations: specifier: workspace:^ version: link:../graphile-bulk-mutations/dist @@ -1389,7 +1384,7 @@ importers: version: link:../graphile-upload-plugin/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1457,7 +1452,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1508,7 +1503,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1546,7 +1541,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1697,7 +1692,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-cache: specifier: workspace:^ version: link:../../graphile/graphile-cache/dist @@ -1709,7 +1704,7 @@ importers: version: link:../../graphile/graphile-settings/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -1944,7 +1939,7 @@ importers: version: 1.1.2(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -1985,7 +1980,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -2098,7 +2093,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-cache: specifier: workspace:^ version: link:../../graphile/graphile-cache/dist @@ -2113,7 +2108,7 @@ importers: version: link:../../graphile/graphile-settings/dist graphile-utils: specifier: 5.0.3 - version: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + version: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: specifier: 16.13.0 version: 16.13.0 @@ -2297,7 +2292,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -2726,7 +2721,7 @@ importers: version: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) graphile-build-pg: specifier: 5.1.3 - version: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + version: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: specifier: 1.1.0 version: 1.1.0 @@ -2746,9 +2741,15 @@ importers: '@types/pg': specifier: ^8.20.4 version: 8.20.4 + graphile-cache: + specifier: workspace:^ + version: link:../../graphile/graphile-cache/dist makage: specifier: ^0.8.0 version: 0.8.0 + pgsql-test: + specifier: workspace:^ + version: link:../../postgres/pgsql-test/dist packages/postmaster: dependencies: @@ -16310,7 +16311,7 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) tslib: 2.8.1 - '@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)': + '@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)': dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) '@graphile/lru': 5.0.0 @@ -16330,7 +16331,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)': + '@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)': dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) '@graphile/lru': 5.0.0 @@ -20415,9 +20416,9 @@ snapshots: - supports-color - use-sync-external-store - graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1): + graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) '@types/node': 22.19.19 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) @@ -20434,9 +20435,9 @@ snapshots: transitivePeerDependencies: - supports-color - graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1): + graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@types/node': 22.19.19 debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) @@ -20483,9 +20484,9 @@ snapshots: transitivePeerDependencies: - supports-color - graphile-utils@5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): + graphile-utils@5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) @@ -20495,13 +20496,13 @@ snapshots: tamedevil: 0.1.1 tslib: 2.8.1 optionalDependencies: - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) transitivePeerDependencies: - supports-color - graphile-utils@5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): + graphile-utils@5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1): dependencies: - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) debug: 4.4.3(supports-color@5.5.0) grafast: 1.1.2(graphql@16.13.0) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) @@ -20511,7 +20512,7 @@ snapshots: tamedevil: 0.1.1 tslib: 2.8.1 optionalDependencies: - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) transitivePeerDependencies: - supports-color @@ -22516,7 +22517,7 @@ snapshots: postgraphile@5.1.4(278bb7eee576e8287d20e8783c1ec9ea): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 @@ -22524,9 +22525,9 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) grafserv: 1.0.1(@types/node@25.9.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22543,7 +22544,7 @@ snapshots: postgraphile@5.1.4(3cfb8ae93140145a791ab179ae3cec49): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 @@ -22551,9 +22552,9 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) grafserv: 1.0.1(@types/node@22.19.11)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22570,7 +22571,7 @@ snapshots: postgraphile@5.1.4(92324343608439594bbe299323c80ef9): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 @@ -22578,9 +22579,9 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) grafserv: 1.0.1(@types/node@22.19.15)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22597,7 +22598,7 @@ snapshots: postgraphile@5.1.4(9d965dba21fac51f59273c4256716137): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 @@ -22605,9 +22606,9 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) grafserv: 1.0.1(@types/node@22.19.19)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.21.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 @@ -22624,7 +22625,7 @@ snapshots: postgraphile@5.1.4(fdde9aa28a0299d2757f9ae98191ef07): dependencies: '@dataplan/json': 1.0.1(grafast@1.1.2(graphql@16.13.0)) - '@dataplan/pg': 1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) + '@dataplan/pg': 1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0) '@graphile/lru': 5.0.0 '@types/node': 22.19.19 '@types/pg': 8.20.4 @@ -22632,9 +22633,9 @@ snapshots: grafast: 1.1.2(graphql@16.13.0) grafserv: 1.0.1(@types/node@25.9.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))(ws@8.20.1) graphile-build: 5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0) - graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) + graphile-build-pg: 5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1) graphile-config: 1.1.0 - graphile-utils: 5.0.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(patch_hash=7daa6b70ceb989edcb723554bf795df5f363de5f850d0bb2deb9b7911fa4ebee)(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) + graphile-utils: 5.0.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build-pg@5.1.3(@dataplan/pg@1.1.1(@dataplan/json@1.0.1(grafast@1.1.2(graphql@16.13.0)))(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0))(grafast@1.1.2(graphql@16.13.0))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(pg-sql2@5.0.1)(pg@8.20.0)(tamedevil@0.1.1))(graphile-build@5.1.1(grafast@1.1.2(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0))(graphile-config@1.1.0)(graphql@16.13.0)(tamedevil@0.1.1) graphql: 16.13.0 iterall: 1.3.0 jsonwebtoken: 9.0.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd8ea4ff1f..b4da2891a3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -105,6 +105,3 @@ packageExtensions: allowBuilds: "@launchql/protobufjs": true core-js-pure: true - -patchedDependencies: - '@dataplan/pg@1.1.1': patches/@dataplan__pg@1.1.1.patch From 9f64b1312c2953e225f59e641bb514a517bbec21 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Mon, 21 Sep 2026 16:23:37 +0000 Subject: [PATCH 4/7] Bind Graphile cache reuse to the exact build contract --- graphile/graphile-cache/README.md | 15 ++ graphile/graphile-cache/package.json | 1 + .../src/__tests__/build-identity.test.ts | 138 +++++++++++ .../src/__tests__/build-invalidation.test.ts | 69 ++++++ graphile/graphile-cache/src/build-identity.ts | 234 ++++++++++++++++++ graphile/graphile-cache/src/graphile-cache.ts | 41 ++- graphile/graphile-cache/src/index.ts | 10 + graphql/explorer/src/server.ts | 73 ++++-- graphql/explorer/src/settings.ts | 9 +- .../src/middleware/__tests__/flush.test.ts | 46 +++- .../__tests__/graphile-build-snapshot.test.ts | 104 ++++++++ .../__tests__/graphile-single-flight.test.ts | 25 +- graphql/server/src/middleware/flush.ts | 22 +- .../src/middleware/graphile-build-snapshot.ts | 91 +++++++ graphql/server/src/middleware/graphile.ts | 173 ++++++------- pnpm-lock.yaml | 3 + 16 files changed, 903 insertions(+), 151 deletions(-) create mode 100644 graphile/graphile-cache/src/__tests__/build-identity.test.ts create mode 100644 graphile/graphile-cache/src/__tests__/build-invalidation.test.ts create mode 100644 graphile/graphile-cache/src/build-identity.ts create mode 100644 graphql/server/src/middleware/__tests__/graphile-build-snapshot.test.ts create mode 100644 graphql/server/src/middleware/graphile-build-snapshot.ts diff --git a/graphile/graphile-cache/README.md b/graphile/graphile-cache/README.md index 5eb5c674ba..d053fc0f20 100644 --- a/graphile/graphile-cache/README.md +++ b/graphile/graphile-cache/README.md @@ -37,6 +37,21 @@ When you import this package, it automatically registers a cleanup callback with ## Usage +### Exact build identity + +The GraphQL server and Explorer cache by `createGraphileBuildCacheKey(domain, +snapshot)`. A key includes the effective build inputs, the exact pool reference, +and the configuration owner. Server and Explorer use separate domains. Plain +data is copied with `snapshotGraphileBuildValue` before it is used for both the +fingerprint and the preset; opaque objects and functions retain reference +identity. Keys are HMAC fingerprints with a process-local secret and must not be +persisted or compared across processes. + +Entries carry `serviceKey`, `databaseId` (when known), and `poolKey` separately +from the fingerprint. Use `clearGraphileEntriesForService`, +`clearGraphileEntriesForDatabase`, or `clearGraphileEntriesForPool` to invalidate +all build variants for that owner. Pool cleanup matches `poolKey` exactly. + ### Basic Usage ```typescript diff --git a/graphile/graphile-cache/package.json b/graphile/graphile-cache/package.json index 5209a00c21..faa8f25acf 100644 --- a/graphile/graphile-cache/package.json +++ b/graphile/graphile-cache/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "12factor-env": "workspace:^", + "@constructive-io/errors": "workspace:^", "@pgpmjs/logger": "workspace:^", "express": "^5.2.1", "grafserv": "1.0.1", diff --git a/graphile/graphile-cache/src/__tests__/build-identity.test.ts b/graphile/graphile-cache/src/__tests__/build-identity.test.ts new file mode 100644 index 0000000000..b9e16968d0 --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/build-identity.test.ts @@ -0,0 +1,138 @@ +import { + createGraphileBuildCacheKey, + referenceGraphileBuildValue, + snapshotGraphileBuildValue +} from '../build-identity'; + +const makeIdentity = (overrides: Record = {}) => ({ + owner: Symbol.for('server-owner'), + serviceKey: 'api:db-1:public-api', + pool: referenceGraphileBuildValue({}), + pgConfig: { host: 'db.internal', database: 'tenant_one', password: 'secret-value' }, + databaseName: 'tenant_one', + databaseId: 'db-1', + apiId: 'api-1', + schemas: ['app', 'auth'], + authRoles: { anon: 'app_anon', served: 'app_user', introspection: 'app_reader' }, + featureFlags: { enableSearch: true, enableRealtime: false }, + compute: [{ schemaName: 'compute', bindingsTableName: 'bindings' }], + explain: false, + surface: { graphqlPath: '/graphql', graphiql: true }, + callback: (): void => {}, + ...overrides +}); + +describe('Graphile build identity', () => { + it('canonicalizes plain object keys without depending on insertion order', () => { + expect(createGraphileBuildCacheKey('server', { a: 1, b: 2 })) + .toBe(createGraphileBuildCacheKey('server', { b: 2, a: 1 })); + }); + + it('preserves array order and distinguishes undefined from null', () => { + expect(createGraphileBuildCacheKey('server', ['app', 'auth'])) + .not.toBe(createGraphileBuildCacheKey('server', ['auth', 'app'])); + expect(createGraphileBuildCacheKey('server', { role: undefined })) + .not.toBe(createGraphileBuildCacheKey('server', { role: null })); + }); + + it.each([ + ['configuration owner', 'owner', Symbol.for('other-owner')], + ['service key', 'serviceKey', 'api:db-2:public-api'], + ['PostgreSQL config', 'pgConfig', { host: 'db.internal', database: 'tenant_two', password: 'secret-value' }], + ['database name', 'databaseName', 'tenant_two'], + ['database id', 'databaseId', 'db-2'], + ['API id', 'apiId', 'api-2'], + ['schema order', 'schemas', ['auth', 'app']], + ['served role', 'authRoles', { anon: 'app_anon', served: 'app_admin', introspection: 'app_reader' }], + ['introspection role', 'authRoles', { anon: 'app_anon', served: 'app_user', introspection: 'app_introspector' }], + ['plugin flags', 'featureFlags', { enableSearch: false, enableRealtime: false }], + ['compute bindings', 'compute', [{ schemaName: 'compute', bindingsTableName: 'other_bindings' }]], + ['explain', 'explain', true], + ['effective surface', 'surface', { graphqlPath: '/api/graphql', graphiql: true }] + ])('changes the key when %s changes', (_label, key, value) => { + const base = makeIdentity(); + const changed = { ...base, [key as string]: value }; + expect(createGraphileBuildCacheKey('server', changed)) + .not.toBe(createGraphileBuildCacheKey('server', base)); + }); + + it('uses function, symbol, opaque object, and explicit plain-object identity', () => { + const callback = (): void => {}; + class OpaqueInstance {} + const opaque = new OpaqueInstance(); + const plainPool = {}; + const same = { + callback, + symbol: Symbol.for('stable-symbol'), + opaque, + pool: referenceGraphileBuildValue(plainPool) + }; + const equivalent = { + callback, + symbol: Symbol.for('stable-symbol'), + opaque, + pool: referenceGraphileBuildValue(plainPool) + }; + expect(createGraphileBuildCacheKey('server', same)) + .toBe(createGraphileBuildCacheKey('server', equivalent)); + expect(createGraphileBuildCacheKey('server', { ...same, callback: (): void => {} })) + .not.toBe(createGraphileBuildCacheKey('server', same)); + expect(createGraphileBuildCacheKey('server', { ...same, symbol: Symbol('stable-symbol') })) + .not.toBe(createGraphileBuildCacheKey('server', same)); + expect(createGraphileBuildCacheKey('server', { ...same, opaque: new OpaqueInstance() })) + .not.toBe(createGraphileBuildCacheKey('server', same)); + expect(createGraphileBuildCacheKey('server', { + ...same, + pool: referenceGraphileBuildValue({}) + })).not.toBe(createGraphileBuildCacheKey('server', same)); + }); + + it('separates artifact domains and type-tagged adversarial values', () => { + const date = new Date('2025-01-01T00:00:00.000Z'); + const spoofedDate = { [Symbol.toStringTag]: 'Date', value: date.getTime() }; + const identity = makeIdentity(); + expect(createGraphileBuildCacheKey('server', identity)) + .not.toBe(createGraphileBuildCacheKey('explorer', identity)); + expect(createGraphileBuildCacheKey('server', date)) + .not.toBe(createGraphileBuildCacheKey('server', spoofedDate)); + expect(createGraphileBuildCacheKey('server', NaN)) + .not.toBe(createGraphileBuildCacheKey('server', 'NaN')); + expect(createGraphileBuildCacheKey('server', 0)) + .not.toBe(createGraphileBuildCacheKey('server', '0')); + expect(createGraphileBuildCacheKey('server', [, undefined])) + .not.toBe(createGraphileBuildCacheKey('server', [undefined, undefined])); + }); + + it('returns secret-free fingerprints and rejects cyclic or accessor data safely', () => { + const secret = 'unique-password-sentinel'; + const key = createGraphileBuildCacheKey('server', { password: secret }); + expect(key).not.toContain(secret); + expect(() => { + const cyclic: Record = {}; + cyclic.self = cyclic; + createGraphileBuildCacheKey('server', cyclic); + }).toThrow('Invalid Graphile build identity'); + expect(() => createGraphileBuildCacheKey('server', { + get password() { + throw new Error('getter should not run'); + } + })).toThrow('Invalid Graphile build identity'); + }); + + it('uses the immutable snapshot for both the key and consumed build input', () => { + const source = { pg: { ssl: { rejectUnauthorized: false } }, schemas: ['app'] }; + const snapshot = snapshotGraphileBuildValue(source); + const before = createGraphileBuildCacheKey('server', snapshot); + + source.pg.ssl.rejectUnauthorized = true; + source.schemas.push('private'); + + expect(createGraphileBuildCacheKey('server', snapshot)).toBe(before); + expect(snapshot.pg.ssl.rejectUnauthorized).toBe(false); + expect(snapshot.schemas).toEqual(['app']); + expect(Object.isFrozen(snapshot)).toBe(true); + expect(Object.isFrozen(snapshot.pg)).toBe(true); + expect(Object.isFrozen(snapshot.pg.ssl)).toBe(true); + expect(Object.isFrozen(snapshot.schemas)).toBe(true); + }); +}); diff --git a/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts b/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts new file mode 100644 index 0000000000..8567294085 --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts @@ -0,0 +1,69 @@ +import { + clearGraphileCache, + clearGraphileEntriesForDatabase, + clearGraphileEntriesForPool, + clearGraphileEntriesForService, + graphileCache, + type GraphileCacheEntry +} from '../graphile-cache'; + +const makeEntry = ( + cacheKey: string, + metadata: Pick +): GraphileCacheEntry => ({ + pgl: { release: jest.fn().mockResolvedValue(undefined) } as any, + serv: {} as any, + handler: {} as any, + httpServer: { listening: false } as any, + cacheKey, + createdAt: Date.now(), + ...metadata +}); + +describe('Graphile cache identity invalidation', () => { + afterEach(async () => { + await clearGraphileCache(); + }); + + it('clears every exact-key variant for only the matching service', () => { + const variants = [ + makeEntry('graphile:server:one', { serviceKey: 'service-a', databaseId: 'db-a', poolKey: 'pool-a' }), + makeEntry('graphile:server:two', { serviceKey: 'service-a', databaseId: 'db-a', poolKey: 'pool-a' }), + makeEntry('graphile:explorer:three', { serviceKey: 'service-b', databaseId: 'db-a', poolKey: 'pool-a' }) + ]; + for (const entry of variants) graphileCache.set(entry.cacheKey, entry); + + expect(clearGraphileEntriesForService('service-a')).toBe(2); + expect(graphileCache.has('graphile:server:one')).toBe(false); + expect(graphileCache.has('graphile:server:two')).toBe(false); + expect(graphileCache.has('graphile:explorer:three')).toBe(true); + }); + + it('clears every service variant for only the matching database', () => { + const variants = [ + makeEntry('graphile:server:one', { serviceKey: 'service-a', databaseId: 'db-a', poolKey: 'pool-a' }), + makeEntry('graphile:server:two', { serviceKey: 'service-b', databaseId: 'db-a', poolKey: 'pool-a' }), + makeEntry('graphile:explorer:three', { serviceKey: 'service-a', databaseId: 'db-b', poolKey: 'pool-b' }) + ]; + for (const entry of variants) graphileCache.set(entry.cacheKey, entry); + + expect(clearGraphileEntriesForDatabase('db-a')).toBe(2); + expect(graphileCache.has('graphile:server:one')).toBe(false); + expect(graphileCache.has('graphile:server:two')).toBe(false); + expect(graphileCache.has('graphile:explorer:three')).toBe(true); + }); + + it('clears every Graphile variant backed by only the disposed pool key', () => { + const variants = [ + makeEntry('graphile:server:one', { serviceKey: 'service-a', databaseId: 'db-a', poolKey: 'pool-a' }), + makeEntry('graphile:explorer:two', { serviceKey: 'service-b', databaseId: null, poolKey: 'pool-a' }), + makeEntry('graphile:server:three', { serviceKey: 'service-a', databaseId: 'db-a', poolKey: 'pool-ab' }) + ]; + for (const entry of variants) graphileCache.set(entry.cacheKey, entry); + + expect(clearGraphileEntriesForPool('pool-a')).toBe(2); + expect(graphileCache.has('graphile:server:one')).toBe(false); + expect(graphileCache.has('graphile:explorer:two')).toBe(false); + expect(graphileCache.has('graphile:server:three')).toBe(true); + }); +}); diff --git a/graphile/graphile-cache/src/build-identity.ts b/graphile/graphile-cache/src/build-identity.ts new file mode 100644 index 0000000000..6d356bab2f --- /dev/null +++ b/graphile/graphile-cache/src/build-identity.ts @@ -0,0 +1,234 @@ +import { errors } from '@constructive-io/errors'; +import { createHmac, randomBytes } from 'crypto'; + +/** A per-process key prevents cache fingerprints from exposing config values. */ +const fingerprintSecret = randomBytes(32); + +const referenceIds = new WeakMap(); +const symbolIds = new Map(); +let nextReferenceId = 1; + +export interface GraphileBuildReference { + readonly value: T; +} + +class GraphileBuildReferenceToken implements GraphileBuildReference { + constructor(readonly value: T) {} +} + +/** Compare a value by identity even when it is a plain object. */ +export const referenceGraphileBuildValue = ( + value: T +): GraphileBuildReference => Object.freeze(new GraphileBuildReferenceToken(value)); + +const invalidIdentity = (): never => { + throw errors.INTERNAL_FAILURE({ details: 'Invalid Graphile build identity' }); +}; + +const referenceId = (value: object | Function): number => { + let id = referenceIds.get(value as object); + if (id === undefined) { + id = nextReferenceId++; + referenceIds.set(value as object, id); + } + return id; +}; + +const symbolId = (value: symbol): number => { + let id = symbolIds.get(value); + if (id === undefined) { + id = nextReferenceId++; + symbolIds.set(value, id); + } + return id; +}; + +const numberValue = (value: number): string | number => { + if (Number.isNaN(value)) return 'NaN'; + if (value === Infinity) return '+Infinity'; + if (value === -Infinity) return '-Infinity'; + if (Object.is(value, -0)) return '-0'; + return value; +}; + +const isArrayIndex = (key: string, length: number): boolean => { + const index = Number(key); + return Number.isInteger(index) && index >= 0 && index < length && String(index) === key; +}; + +const canonicalObjectEntries = ( + value: object, + active: Set, + omit: (key: PropertyKey) => boolean = () => false +): Array<[unknown, unknown]> => { + const entries: Array<[unknown, unknown]> = []; + for (const key of Reflect.ownKeys(value)) { + if (omit(key)) continue; + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (!descriptor?.enumerable) continue; + if (!('value' in descriptor)) return invalidIdentity(); + const canonicalKey = typeof key === 'symbol' + ? ['symbol-key', symbolId(key)] + : ['string-key', key]; + entries.push([canonicalKey, canonicalize(descriptor.value, active)]); + } + entries.sort((left, right) => { + const leftKey = JSON.stringify(left[0]); + const rightKey = JSON.stringify(right[0]); + return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0; + }); + return entries; +}; + +const canonicalize = (value: unknown, active: Set): unknown => { + if (value === undefined) return ['undefined']; + if (value === null) return ['null']; + + switch (typeof value) { + case 'boolean': return ['boolean', value]; + case 'string': return ['string', value]; + case 'number': return ['number', numberValue(value)]; + case 'bigint': return ['bigint', value.toString()]; + case 'symbol': return ['symbol', symbolId(value)]; + case 'function': return ['function', referenceId(value)]; + case 'object': break; + default: return invalidIdentity(); + } + + const object = value as object; + if (value instanceof GraphileBuildReferenceToken) { + return ['explicit-reference', referenceId(value.value)]; + } + if (active.has(object)) return invalidIdentity(); + + if (Array.isArray(value)) { + active.add(object); + try { + const items = Array.from({ length: value.length }, (_, index) => + index in value ? canonicalize(value[index], active) : ['array-hole'] + ); + const extraEntries = canonicalObjectEntries(value, active, (key) => + key === 'length' || (typeof key === 'string' && isArrayIndex(key, value.length)) + ); + return ['array', value.length, items, extraEntries]; + } finally { + active.delete(object); + } + } + + if (Object.getPrototypeOf(value) === Date.prototype) { + const time = Date.prototype.getTime.call(value); + return ['date', Number.isNaN(time) ? 'Invalid Date' : time]; + } + + const prototype = Object.getPrototypeOf(value); + if (prototype !== Object.prototype && prototype !== null) { + return ['object-reference', referenceId(object)]; + } + + active.add(object); + try { + const entries = canonicalObjectEntries(value, active); + return [prototype === null ? 'null-prototype-object' : 'plain-object', entries]; + } finally { + active.delete(object); + } +}; + +/** + * Create a process-local, secret fingerprint for an exact Graphile build. + * Plain objects compare by sorted enumerable data properties, arrays retain + * order, and functions, symbols, and opaque instances compare by identity. + */ +export const createGraphileBuildCacheKey = ( + domain: 'server' | 'explorer', + snapshot: unknown +): string => { + try { + const canonical = JSON.stringify(['graphile-build', domain, canonicalize(snapshot, new Set())]); + const fingerprint = createHmac('sha256', fingerprintSecret).update(canonical).digest('hex'); + return `graphile:${domain}:${fingerprint}`; + } catch { + return invalidIdentity(); + } +}; + +const cloneSnapshotValue = (value: unknown, active: Set): unknown => { + if (value === null || typeof value !== 'object') return value; + + const object = value as object; + if (active.has(object)) return invalidIdentity(); + + if (Object.getPrototypeOf(value) === Date.prototype) { + const snapshot = new Date(Date.prototype.getTime.call(value)); + const mutators = [ + 'setTime', 'setMilliseconds', 'setUTCMilliseconds', 'setSeconds', 'setUTCSeconds', + 'setMinutes', 'setUTCMinutes', 'setHours', 'setUTCHours', 'setDate', 'setUTCDate', + 'setMonth', 'setUTCMonth', 'setFullYear', 'setUTCFullYear', 'setYear' + ]; + for (const name of mutators) { + Object.defineProperty(snapshot, name, { + value: invalidIdentity, + configurable: false, + writable: false + }); + } + return Object.freeze(snapshot); + } + + if (Array.isArray(value)) { + active.add(object); + try { + const result = new Array(value.length) as unknown[] & Record; + for (let index = 0; index < value.length; index++) { + if (index in value) result[index] = cloneSnapshotValue(value[index], active); + } + for (const key of Reflect.ownKeys(value)) { + if (key === 'length' || (typeof key === 'string' && isArrayIndex(key, value.length))) continue; + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (!descriptor?.enumerable) continue; + if (!('value' in descriptor)) return invalidIdentity(); + Object.defineProperty(result, key, { + value: cloneSnapshotValue(descriptor.value, active), + enumerable: true, + configurable: false, + writable: false + }); + } + return Object.freeze(result); + } finally { + active.delete(object); + } + } + + const prototype = Object.getPrototypeOf(value); + if (prototype !== Object.prototype && prototype !== null) return value; + + active.add(object); + try { + const result = Object.create(prototype) as Record; + for (const key of Reflect.ownKeys(value)) { + const descriptor = Object.getOwnPropertyDescriptor(value, key); + if (!descriptor?.enumerable) continue; + if (!('value' in descriptor)) return invalidIdentity(); + Object.defineProperty(result, key, { + value: cloneSnapshotValue(descriptor.value, active), + enumerable: true, + configurable: false, + writable: false + }); + } + return Object.freeze(result); + } finally { + active.delete(object); + } +}; + +/** Clone and freeze the plain data used by both fingerprinting and the build. */ +export const snapshotGraphileBuildValue = (value: T): Readonly => { + try { + return cloneSnapshotValue(value, new Set()) as Readonly; + } catch { + return invalidIdentity(); + } +}; diff --git a/graphile/graphile-cache/src/graphile-cache.ts b/graphile/graphile-cache/src/graphile-cache.ts index 6ef9dda7fe..f80056310d 100644 --- a/graphile/graphile-cache/src/graphile-cache.ts +++ b/graphile/graphile-cache/src/graphile-cache.ts @@ -86,6 +86,12 @@ export interface GraphileCacheEntry { httpServer: HttpServer; cacheKey: string; createdAt: number; + /** Logical service that owns this build (for example an API key or explorer route). */ + serviceKey?: string; + /** Resolved database identifier, when the serving surface has one. */ + databaseId?: string | null; + /** pg-cache's physical pool key (currently the resolved database name). */ + poolKey?: string; /** Idempotent release for pgServices owned by this exact preset generation. */ releasePresetServices?: () => Promise; /** Optional RealtimeManager for cursor-tracked subscription delivery */ @@ -269,19 +275,36 @@ export function clearMatchingEntries(pattern: RegExp): number { return cleared; } +const clearEntries = (matches: (key: string, entry: GraphileCacheEntry) => boolean): number => { + let cleared = 0; + for (const [key, entry] of graphileCache.entries()) { + if (!matches(key, entry)) continue; + manualEvictionKeys.add(key); + graphileCache.delete(key); + cleared++; + } + return cleared; +}; + +/** Clear all cached build variants owned by one logical service. */ +export const clearGraphileEntriesForService = (serviceKey: string): number => + clearEntries((key, entry) => entry.serviceKey === serviceKey || (!entry.serviceKey && key === serviceKey)); + +/** Clear all cached build variants for one resolved database identifier. */ +export const clearGraphileEntriesForDatabase = (databaseId: string): number => + clearEntries((_key, entry) => entry.databaseId === databaseId); + +/** Clear all cached build variants backed by one pg-cache pool key. */ +export const clearGraphileEntriesForPool = (poolKey: string): number => + clearEntries((_key, entry) => entry.poolKey === poolKey); + + // Register cleanup callback with pgCache // When a pg pool is disposed, clean up any graphile instances using it const unregister = pgCache.registerCleanupCallback((pgPoolKey: string) => { log.debug(`pgPool[${pgPoolKey}] disposed - checking graphile entries`); - - // Remove graphile entries that reference this pool key - graphileCache.forEach((entry, k) => { - if (entry.cacheKey.includes(pgPoolKey)) { - log.debug(`Removing graphileCache[${k}] due to pgPool[${pgPoolKey}] disposal`); - manualEvictionKeys.add(k); - graphileCache.delete(k); - } - }); + const cleared = clearGraphileEntriesForPool(pgPoolKey); + if (cleared > 0) log.debug(`Removed ${cleared} graphile entries for pgPool[${pgPoolKey}]`); }); // Enhanced close function that handles all caches diff --git a/graphile/graphile-cache/src/index.ts b/graphile/graphile-cache/src/index.ts index 7cbed436e3..e02ec2c6de 100644 --- a/graphile/graphile-cache/src/index.ts +++ b/graphile/graphile-cache/src/index.ts @@ -9,6 +9,9 @@ export { // Cache stats CacheStats, clearGraphileCache, + clearGraphileEntriesForDatabase, + clearGraphileEntriesForPool, + clearGraphileEntriesForService, // Clear matching entries clearMatchingEntries, closeAllCaches, @@ -26,6 +29,13 @@ export { waitForActiveDisposals, waitForEntryDisposal} from './graphile-cache'; +export { + createGraphileBuildCacheKey, + referenceGraphileBuildValue, + snapshotGraphileBuildValue +} from './build-identity'; +export type { GraphileBuildReference } from './build-identity'; + // Factory for creating PostGraphile v5 instances export { createGraphileInstance } from './create-instance'; diff --git a/graphql/explorer/src/server.ts b/graphql/explorer/src/server.ts index 26035d5e12..ebd4de5ebd 100644 --- a/graphql/explorer/src/server.ts +++ b/graphql/explorer/src/server.ts @@ -3,7 +3,15 @@ import type { ConstructiveOptions } from '@constructive-io/graphql-types'; import { middleware as parseDomains } from '@constructive-io/url-domains'; import { cors, healthz, poweredBy } from '@pgpmjs/server-utils'; import express, { Express, NextFunction, Request, Response } from 'express'; -import { createGraphileInstance, graphileCache, GraphileCacheEntry } from 'graphile-cache'; +import { + clearGraphileEntriesForService, + createGraphileBuildCacheKey, + createGraphileInstance, + graphileCache, + GraphileCacheEntry, + referenceGraphileBuildValue, + snapshotGraphileBuildValue +} from 'graphile-cache'; import type { GraphileConfig } from 'graphile-config'; import { makePgService } from 'graphile-settings'; import { getPgPool } from 'pg-cache'; @@ -14,6 +22,7 @@ import { getGraphilePreset } from './settings'; export const GraphQLExplorer = (rawOpts: ConstructiveOptions = {}): Express => { const opts = getEnvOptions(rawOpts); + const ownerIdentity = {}; const { pg, server } = opts; @@ -21,13 +30,6 @@ export const GraphQLExplorer = (rawOpts: ConstructiveOptions = {}): Express => { dbname: string, schemaname: string ): Promise => { - const key = `${dbname}.${schemaname}`; - - const cached = graphileCache.get(key); - if (cached) { - return cached; - } - const pgConfig = getPgEnvOptions({ ...pg, database: dbname, @@ -37,20 +39,45 @@ export const GraphQLExplorer = (rawOpts: ConstructiveOptions = {}): Express => { // properly, preventing leaked connections during database teardown. const pool = getPgPool(pgConfig); - const basePreset = getGraphilePreset(opts); - const preset: GraphileConfig.Preset = { - ...basePreset, - pgServices: [ - makePgService({ pool, schemas: [schemaname] }), - ], - grafserv: { + const serviceKey = `${dbname}.${schemaname}`; + const snapshot = snapshotGraphileBuildValue({ + ownerIdentity: referenceGraphileBuildValue(ownerIdentity), + serviceKey, + poolIdentity: referenceGraphileBuildValue(pool), + poolKey: pgConfig.database, + pgConfig, + databaseName: dbname, + databaseId: null, + apiId: null, + schemas: [schemaname], + role: pg.user ?? 'postgres', + surface: { graphqlPath: '/graphql', graphiqlPath: '/graphiql', graphiql: true, }, + explain: undefined, + enableRealtime: false, + }); + const key = createGraphileBuildCacheKey('explorer', snapshot); + const cached = graphileCache.get(key); + if (cached) return cached; + + const basePreset = getGraphilePreset(opts, snapshot.role); + const preset: GraphileConfig.Preset = { + ...basePreset, + pgServices: [ + makePgService({ pool, schemas: snapshot.schemas }), + ], + grafserv: snapshot.surface, }; const instance = await createGraphileInstance({ preset, cacheKey: key }); + Object.assign(instance, { + serviceKey, + databaseId: snapshot.databaseId, + poolKey: snapshot.poolKey, + } satisfies Partial); graphileCache.set(key, instance); return instance; }; @@ -130,6 +157,11 @@ export const GraphQLExplorer = (rawOpts: ConstructiveOptions = {}): Express => { if (req.urlDomains?.subdomains.length === 2) { const [schemaName, dbName] = req.urlDomains.subdomains; try { + if (req.url === '/flush') { + clearGraphileEntriesForService(`${dbName}.${schemaName}`); + res.status(200).send('OK'); + return; + } const instance = await getGraphileInstanceObj(dbName, schemaName); instance.handler(req, res, next); return; @@ -141,17 +173,6 @@ export const GraphQLExplorer = (rawOpts: ConstructiveOptions = {}): Express => { return next(); }); - app.use(async (req: Request, res: Response, next: NextFunction) => { - if (req.urlDomains?.subdomains.length === 2 && req.url === '/flush') { - const [schemaName, dbName] = req.urlDomains.subdomains; - const key = `${dbName}.${schemaName}`; - graphileCache.delete(key); - res.status(200).send('OK'); - return; - } - return next(); - }); - app.use(async (req: Request, res: Response, next: NextFunction) => { if (req.urlDomains?.subdomains.length === 0) { try { diff --git a/graphql/explorer/src/settings.ts b/graphql/explorer/src/settings.ts index 4d2047f9f1..8890edfd70 100644 --- a/graphql/explorer/src/settings.ts +++ b/graphql/explorer/src/settings.ts @@ -8,14 +8,17 @@ import { ConstructivePreset } from 'graphile-settings'; * * This returns a v5 preset that can be extended with pgServices. */ -export const getGraphilePreset = (rawOpts: ConstructiveOptions): GraphileConfig.Preset => { - const opts = getEnvOptions(rawOpts); +export const getGraphilePreset = ( + rawOpts: ConstructiveOptions, + effectiveRole?: string +): GraphileConfig.Preset => { + const role = effectiveRole ?? getEnvOptions(rawOpts).pg?.user ?? 'postgres'; return { extends: [ConstructivePreset], grafast: { context: () => ({ - pgSettings: { role: opts.pg?.user ?? 'postgres' }, + pgSettings: { role }, }), }, }; diff --git a/graphql/server/src/middleware/__tests__/flush.test.ts b/graphql/server/src/middleware/__tests__/flush.test.ts index a5a44d1671..ae3ebd5eba 100644 --- a/graphql/server/src/middleware/__tests__/flush.test.ts +++ b/graphql/server/src/middleware/__tests__/flush.test.ts @@ -35,16 +35,57 @@ const opts = (flushToken?: string): ConstructiveOptions => ({ api: { ...(flushToken && { flushToken }) } } as ConstructiveOptions); describe('createFlushMiddleware', () => { + const hashedVariantKeys = [ + 'graphile:server:variant-one', + 'graphile:server:variant-two', + 'graphile:explorer:other-service' + ]; + let next: NextFunction; beforeEach(() => { - graphileCache.set('tenant.example.com', { cached: true } as any); + graphileCache.set('tenant.example.com', { + cacheKey: 'tenant.example.com', + pgl: { release: jest.fn() }, + serv: {}, + handler: {}, + httpServer: { listening: false }, + createdAt: Date.now() + } as any); + graphileCache.set(hashedVariantKeys[0], { + cacheKey: hashedVariantKeys[0], + serviceKey: 'tenant.example.com', + pgl: { release: jest.fn() }, + serv: {}, + handler: {}, + httpServer: { listening: false }, + createdAt: Date.now() + } as any); + graphileCache.set(hashedVariantKeys[1], { + cacheKey: hashedVariantKeys[1], + serviceKey: 'tenant.example.com', + pgl: { release: jest.fn() }, + serv: {}, + handler: {}, + httpServer: { listening: false }, + createdAt: Date.now() + } as any); + graphileCache.set(hashedVariantKeys[2], { + cacheKey: hashedVariantKeys[2], + serviceKey: 'other.example.com', + pgl: { release: jest.fn() }, + serv: {}, + handler: {}, + httpServer: { listening: false }, + createdAt: Date.now() + } as any); svcCache.set('tenant.example.com', { cached: true } as any); next = jest.fn(); }); afterEach(() => { graphileCache.delete('tenant.example.com'); + for (const key of hashedVariantKeys) graphileCache.delete(key); svcCache.delete('tenant.example.com'); }); @@ -64,6 +105,9 @@ describe('createFlushMiddleware', () => { ); expect(res.statusCode).toBe(200); expect(graphileCache.get('tenant.example.com')).toBeUndefined(); + expect(graphileCache.has(hashedVariantKeys[0])).toBe(false); + expect(graphileCache.has(hashedVariantKeys[1])).toBe(false); + expect(graphileCache.has(hashedVariantKeys[2])).toBe(true); expect(svcCache.get('tenant.example.com')).toBeUndefined(); }); diff --git a/graphql/server/src/middleware/__tests__/graphile-build-snapshot.test.ts b/graphql/server/src/middleware/__tests__/graphile-build-snapshot.test.ts new file mode 100644 index 0000000000..dc6f6abed4 --- /dev/null +++ b/graphql/server/src/middleware/__tests__/graphile-build-snapshot.test.ts @@ -0,0 +1,104 @@ +import type { DatabaseSettings } from '@constructive-io/express-context'; +import type { PgConfig } from 'pg-env'; +import type { Pool } from 'pg'; + +import { createGraphileBuildCacheKey } from 'graphile-cache'; + +import { createGraphileServerBuildSnapshot } from '../graphile-build-snapshot'; + +const owner = {}; +const pool = {} as Pool; +const maskError = (): void => {}; + +const makeInput = (overrides: Record = {}) => ({ + ownerIdentity: owner, + serviceKey: 'api:db-1:main', + pool, + pgConfig: { + host: 'localhost', + port: 5432, + database: 'tenant_one', + password: 'private-password' + } as PgConfig, + databaseName: 'tenant_one', + databaseId: 'db-1', + apiId: 'api-1', + schemas: ['app', 'auth'], + anonRole: 'app_anon', + roleName: 'app_user', + introspectionRole: 'app_reader', + databaseSettings: { enableSearch: true, enableRealtime: false } as DatabaseSettings, + compute: { + modules: [{ + schemaName: 'compute', + definitionsTableName: 'definitions', + bindingsTableName: 'bindings', + invocationsSchemaName: 'private', + invocationsTableName: 'invocations', + invocationsEntityField: 'database_id' + }] + }, + explain: false, + maskError, + ...overrides +}); + +const key = (input: Record = makeInput()): string => + createGraphileBuildCacheKey('server', createGraphileServerBuildSnapshot(input as any)); + +describe('server Graphile build snapshot', () => { + it.each([ + ['service key', { serviceKey: 'api:db-2:main' }], + ['pool object', { pool: {} as Pool }], + ['normalized PostgreSQL config', { pgConfig: { ...makeInput().pgConfig, database: 'tenant_two' } }], + ['database name', { databaseName: 'tenant_two' }], + ['database id', { databaseId: 'db-2' }], + ['API id', { apiId: 'api-2' }], + ['schema order', { schemas: ['auth', 'app'] }], + ['anonymous role', { anonRole: 'other_anon' }], + ['served role', { roleName: 'other_user' }], + ['introspection role', { introspectionRole: 'other_reader' }], + ['caller presets', { graphileOptions: { preset: { schema: { defaultBehavior: '-insert' } } } }], + ['Grafast cache settings', { graphileOptions: { grafastCache: { queryCacheMaxLength: 100 } } }], + ['plugin flags', { databaseSettings: { enableSearch: false, enableRealtime: false } }], + ['compute binding config', { + compute: { modules: [{ + schemaName: 'compute', + definitionsTableName: 'definitions', + bindingsTableName: 'other_bindings', + invocationsSchemaName: 'private', + invocationsTableName: 'invocations', + invocationsEntityField: 'database_id' + }] } + }], + ['explain option', { explain: true }], + ['mask-error closure', { maskError: (): string => 'different closure' }], + ['configuration owner', { ownerIdentity: {} }] + ])('includes the actual producer input for %s', (_label, override) => { + expect(key({ ...makeInput(), ...override })).not.toBe(key()); + }); + + it('keeps the snapshot and its fingerprint stable after the source inputs mutate', () => { + const input = makeInput(); + const snapshot = createGraphileServerBuildSnapshot(input as any); + const before = createGraphileBuildCacheKey('server', snapshot); + + input.schemas.push('private'); + (input.pgConfig as PgConfig).database = 'changed'; + (input.databaseSettings as DatabaseSettings).enableSearch = false; + input.compute.modules[0].bindingsTableName = 'changed'; + + expect(createGraphileBuildCacheKey('server', snapshot)).toBe(before); + expect(snapshot.schemas).toEqual(['app', 'auth']); + expect(snapshot.pgConfig.database).toBe('tenant_one'); + expect(snapshot.databaseSettings?.enableSearch).toBe(true); + expect(snapshot.computeModules[0].bindingsTableName).toBe('bindings'); + expect(Object.isFrozen(snapshot)).toBe(true); + expect(Object.isFrozen(snapshot.schemas)).toBe(true); + expect(Object.isFrozen(snapshot.computeModules[0])).toBe(true); + }); + + it('uses the same stable owner and physical pool references across snapshots', () => { + expect(key()).toBe(key()); + }); +}); diff --git a/graphql/server/src/middleware/__tests__/graphile-single-flight.test.ts b/graphql/server/src/middleware/__tests__/graphile-single-flight.test.ts index 5e276a9045..53cdc34a9c 100644 --- a/graphql/server/src/middleware/__tests__/graphile-single-flight.test.ts +++ b/graphql/server/src/middleware/__tests__/graphile-single-flight.test.ts @@ -4,14 +4,19 @@ const mockCreateGraphileInstance = jest.fn(); const mockGetPgPool = jest.fn(); const mockMakeIntrospectionWiring = jest.fn(); const mockCreateGrafastCacheLimitsPreset = jest.fn(); +const mockRegisterPoolCleanup = jest.fn(() => jest.fn()); -jest.mock('graphile-cache', () => ({ - createGraphileInstance: mockCreateGraphileInstance, - graphileCache: { - get: mockCacheGet, - set: mockCacheSet - } -})); +jest.mock('graphile-cache', () => { + const actual = jest.requireActual('graphile-cache'); + return { + ...actual, + createGraphileInstance: mockCreateGraphileInstance, + graphileCache: { + get: mockCacheGet, + set: mockCacheSet + } + }; +}); jest.mock('graphile-settings', () => ({ createConstructivePreset: jest.fn(() => ({})), @@ -19,7 +24,11 @@ jest.mock('graphile-settings', () => ({ })); jest.mock('pg-cache', () => ({ - getPgPool: mockGetPgPool + getPgPool: mockGetPgPool, + pgCache: { + registerCleanupCallback: mockRegisterPoolCleanup, + close: jest.fn() + } })); jest.mock('../graphile-introspection', () => ({ diff --git a/graphql/server/src/middleware/flush.ts b/graphql/server/src/middleware/flush.ts index 635ad2657e..9b0775c38f 100644 --- a/graphql/server/src/middleware/flush.ts +++ b/graphql/server/src/middleware/flush.ts @@ -5,7 +5,10 @@ import { Logger } from '@pgpmjs/logger'; import { svcCache } from '@pgpmjs/server-utils'; import { createHash, timingSafeEqual } from 'crypto'; import { NextFunction, Request, RequestHandler, Response } from 'express'; -import { graphileCache } from 'graphile-cache'; +import { + clearGraphileEntriesForDatabase, + clearGraphileEntriesForService +} from 'graphile-cache'; import { getPgPool } from 'pg-cache'; import { getRoutingSchema, isValidSchemaName } from './routing'; @@ -61,8 +64,9 @@ export const createFlushMiddleware = ( return; } - graphileCache.delete((req as any).svc_key); - svcCache.delete((req as any).svc_key); + const serviceKey = (req as any).svc_key; + clearGraphileEntriesForService(serviceKey); + svcCache.delete(serviceKey); res.status(200).send('OK'); }; }; @@ -77,14 +81,12 @@ export const flushService = async ( const api = new RegExp(`^api:${databaseId}:.*`); const schemata = new RegExp(`^schemata:${databaseId}:.*`); const meta = new RegExp(`^metaschema:api:${databaseId}`); + clearGraphileEntriesForDatabase(databaseId); if (!opts.api.isPublic) { - graphileCache.forEach((_, k: string) => { - if (api.test(k) || schemata.test(k) || meta.test(k)) { - graphileCache.delete(k); - svcCache.delete(k); - } - }); + for (const key of svcCache.keys()) { + if (api.test(key) || schemata.test(key) || meta.test(key)) svcCache.delete(key); + } } const routingSchema = getRoutingSchema(opts); @@ -104,7 +106,7 @@ export const flushService = async ( for (const row of svc.rows) { const key: string | undefined = row.hostname || undefined; if (key) { - graphileCache.delete(key); + clearGraphileEntriesForService(key); svcCache.delete(key); } } diff --git a/graphql/server/src/middleware/graphile-build-snapshot.ts b/graphql/server/src/middleware/graphile-build-snapshot.ts new file mode 100644 index 0000000000..78d1200b93 --- /dev/null +++ b/graphql/server/src/middleware/graphile-build-snapshot.ts @@ -0,0 +1,91 @@ +import type { ConstructiveOptions } from '@constructive-io/graphql-types'; +import type { DatabaseSettings, ComputeConfig, ComputeModuleConfig } from '@constructive-io/express-context'; +import type { PgConfig } from 'pg-env'; +import type { Pool } from 'pg'; + +import { referenceGraphileBuildValue, snapshotGraphileBuildValue } from 'graphile-cache'; +import type { GraphileBuildReference } from 'graphile-cache'; + +export interface GraphileServerBuildSnapshot { + ownerIdentity: GraphileBuildReference; + serviceKey: string; + poolIdentity: GraphileBuildReference; + poolKey: string; + pgConfig: PgConfig; + databaseName: string; + databaseId: string | null; + apiId?: string; + schemas: string[]; + anonRole: string; + roleName: string; + introspectionRole?: string; + databaseSettings?: DatabaseSettings; + graphileOptions?: ConstructiveOptions['graphile']; + computeModules: ComputeModuleConfig[]; + explain: boolean; + grafserv: { + graphqlPath: string; + graphiqlPath: string; + graphiql: boolean; + graphiqlOnGraphQLGET: boolean; + maskError: (...args: any[]) => any; + }; +} + +interface GraphileServerBuildSnapshotInput { + ownerIdentity: object; + serviceKey: string; + pool: Pool; + pgConfig: PgConfig; + databaseName: string; + databaseId?: string | null; + apiId?: string; + schemas: string[]; + anonRole: string; + roleName: string; + introspectionRole?: string; + databaseSettings?: DatabaseSettings; + graphileOptions?: ConstructiveOptions['graphile']; + compute?: ComputeConfig; + explain: boolean; + maskError: (...args: any[]) => any; +} + +export const createGraphileServerBuildSnapshot = ( + input: GraphileServerBuildSnapshotInput +): Readonly => { + const computeModules = (input.compute?.modules ?? []).map((module) => ({ + schemaName: module.schemaName, + definitionsTableName: module.definitionsTableName, + bindingsTableName: module.bindingsTableName, + invocationsSchemaName: module.invocationsSchemaName, + invocationsTableName: module.invocationsTableName, + invocationsEntityField: module.invocationsEntityField + })); + + return snapshotGraphileBuildValue({ + ownerIdentity: referenceGraphileBuildValue(input.ownerIdentity), + serviceKey: input.serviceKey, + poolIdentity: referenceGraphileBuildValue(input.pool), + poolKey: input.pgConfig.database, + pgConfig: input.pgConfig, + databaseName: input.databaseName, + databaseId: input.databaseId ?? null, + apiId: input.apiId, + schemas: input.schemas, + anonRole: input.anonRole, + roleName: input.roleName, + introspectionRole: input.introspectionRole, + databaseSettings: input.databaseSettings, + graphileOptions: input.graphileOptions, + computeModules, + explain: input.explain, + grafserv: { + graphqlPath: '/graphql', + graphiqlPath: '/graphiql', + graphiql: true, + graphiqlOnGraphQLGET: false, + maskError: input.maskError + } + }); +}; diff --git a/graphql/server/src/middleware/graphile.ts b/graphql/server/src/middleware/graphile.ts index e888d0acff..c97f86193d 100644 --- a/graphql/server/src/middleware/graphile.ts +++ b/graphql/server/src/middleware/graphile.ts @@ -1,19 +1,21 @@ import './types'; // for Request type import { errors } from '@constructive-io/errors'; -import type { ComputeConfig } from '@constructive-io/express-context'; import { DEFAULT_REQUEST_PROTECTION, protectionPgSettings } from '@constructive-io/express-context'; import type { ConstructiveOptions } from '@constructive-io/graphql-types'; import { getNodeEnv } from '@pgpmjs/env'; import { Logger } from '@pgpmjs/logger'; import type { NextFunction, Request, RequestHandler, Response } from 'express'; -import { createGraphileInstance, graphileCache,type GraphileCacheEntry } from 'graphile-cache'; +import { + createGraphileBuildCacheKey, + createGraphileInstance, + graphileCache, + snapshotGraphileBuildValue, + type GraphileCacheEntry +} from 'graphile-cache'; import type { GraphileConfig } from 'graphile-config'; import { createFunctionBindingsPlugin } from 'graphile-function-bindings'; -import { - createConstructivePreset, - createGrafastCacheLimitsPreset -} from 'graphile-settings'; +import { createConstructivePreset, createGrafastCacheLimitsPreset } from 'graphile-settings'; import { getPgPool } from 'pg-cache'; import { getPgEnvOptions } from 'pg-env'; @@ -23,10 +25,10 @@ import { respondWithGraphQLError } from '../errors/graphql-response'; import { AuthCookiePlugin } from '../plugins/auth-cookie-plugin'; import { createErrorEventsPlugin } from '../plugins/error-events-plugin'; import { RequestProtectionPlugin } from '../plugins/request-protection-plugin'; -import type { DatabaseSettings } from '../types'; import { makeIntrospectionWiring } from './graphile-introspection'; import { maskError } from './mask-error'; import { observeGraphileBuild } from './observability/graphile-build-stats'; +import { createGraphileServerBuildSnapshot } from './graphile-build-snapshot'; const isDev = (): boolean => getNodeEnv() === 'development'; @@ -76,29 +78,20 @@ const reqLabel = (req: Request): string => (req.requestId ? `[${req.requestId}]` * (everything on except aggregates). */ const buildPreset = async ( - pool: import('pg').Pool, - schemas: string[], - anonRole: string, - roleName: string, - introspectionRole: string | undefined, - graphileOptions: ConstructiveOptions['graphile'], - databaseSettings?: DatabaseSettings, - apiId?: string, - compute?: ComputeConfig + snapshot: ReturnType, + pool: import('pg').Pool ): Promise => { - const introspection = await makeIntrospectionWiring(pool, schemas, graphileOptions, undefined, introspectionRole); - const configuredPreset = graphileOptions?.preset ?? {}; - const grafastCachePreset = createGrafastCacheLimitsPreset(graphileOptions?.grafastCache); + const introspection = await makeIntrospectionWiring(pool, snapshot.schemas, snapshot.graphileOptions, undefined, snapshot.introspectionRole); + const configuredPreset = snapshot.graphileOptions?.preset ?? {}; + const grafastCachePreset = createGrafastCacheLimitsPreset(snapshot.graphileOptions?.grafastCache); return { ...configuredPreset, extends: [ - createConstructivePreset(databaseSettings), - ...(graphileOptions?.extends ?? []), + createConstructivePreset(snapshot.databaseSettings), + ...(snapshot.graphileOptions?.extends ?? []), ...(configuredPreset.extends ?? []), ...introspection.presets, - ...(Object.keys(grafastCachePreset).length > 0 - ? [grafastCachePreset] - : []) + ...(Object.keys(grafastCachePreset).length > 0 ? [grafastCachePreset] : []) ], plugins: [ ...(configuredPreset.plugins ?? []), @@ -109,11 +102,11 @@ const buildPreset = async ( // database — all schema/table names come from the constructive // metaschema (express-context compute module loader); the plugin has // no fallbacks or discovery of its own. - ...(apiId && compute?.modules.length + ...(snapshot.apiId && snapshot.computeModules.length ? [ createFunctionBindingsPlugin({ - apiId, - modules: compute.modules.map((m) => ({ + apiId: snapshot.apiId, + modules: snapshot.computeModules.map((m) => ({ computeSchema: m.schemaName, bindingsTable: m.bindingsTableName, definitionsTable: m.definitionsTableName, @@ -126,15 +119,9 @@ const buildPreset = async ( : []) ], pgServices: [introspection.pgService], - grafserv: { - graphqlPath: '/graphql', - graphiqlPath: '/graphiql', - graphiql: true, - graphiqlOnGraphQLGET: false, - maskError - }, + grafserv: snapshot.grafserv, grafast: { - explain: process.env.NODE_ENV === 'development', + explain: snapshot.explain, context: (requestContext: Partial) => { // In grafserv/express/v4, the request is available at requestContext.expressv4.req const req = (requestContext as { expressv4?: { req?: Request } })?.expressv4?.req; @@ -173,7 +160,7 @@ const buildPreset = async ( if (req.token?.user_id) { const pgSettings: Record = { ...timeouts, - role: roleName, + role: snapshot.roleName, 'jwt.claims.token_id': req.token.id, 'jwt.claims.user_id': req.token.user_id, ...context @@ -227,7 +214,7 @@ const buildPreset = async ( if (req.api?.isPublic === false && headerActorId) { const pgSettings: Record = { ...timeouts, - role: roleName, + role: snapshot.roleName, 'jwt.claims.user_id': headerActorId, 'jwt.claims.principal_id': headerActorId, ...context @@ -260,7 +247,7 @@ const buildPreset = async ( // (`ATTRIBUTION_REQUIRED`), so a public mutation cannot enqueue a job. const anonSettings: Record = { ...timeouts, - role: anonRole, + role: snapshot.anonRole, ...context }; if (req?.databaseId) { @@ -281,6 +268,7 @@ const buildPreset = async ( export const graphile = (opts: ConstructiveOptions): RequestHandler => { const observabilityEnabled = isGraphqlObservabilityEnabled(opts.server?.host); + const ownerIdentity = {}; return async (req: Request, res: Response, next: NextFunction) => { const label = reqLabel(req); @@ -291,8 +279,8 @@ export const graphile = (opts: ConstructiveOptions): RequestHandler => { respondWithGraphQLError(res, errors.INTERNAL_FAILURE({ details: 'Missing API info' })); return; } - const key = req.svc_key; - if (!key) { + const serviceKey = req.svc_key; + if (!serviceKey) { log.error(`${label} Missing service cache key`); respondWithGraphQLError( res, @@ -301,49 +289,67 @@ export const graphile = (opts: ConstructiveOptions): RequestHandler => { return; } const { dbname, anonRole, roleName, schema } = api; - const schemaLabel = schema?.join(',') || 'unknown'; + const schemas = [...(schema ?? [])]; + const schemaLabel = schemas.join(',') || 'unknown'; + const pgConfig = snapshotGraphileBuildValue(getPgEnvOptions({ + ...opts.pg, + database: dbname + })); + + // Route through pg-cache so the pool is tracked and can be cleaned up + // properly, preventing leaked connections during database teardown. + const pool = getPgPool(pgConfig); + + const databaseId = api.databaseId ?? null; + const apiId = api.apiId; + const databaseSettings = api.databaseSettings + ? snapshotGraphileBuildValue(api.databaseSettings) + : undefined; + const introspectionRole = opts.api?.introspectionRole; + const explain = getNodeEnv() === 'development'; + const snapshotInput = { + ownerIdentity, + serviceKey, + pool, + pgConfig, + databaseName: dbname, + databaseId, + apiId, + schemas, + anonRole, + roleName, + introspectionRole, + databaseSettings, + graphileOptions: snapshotGraphileBuildValue(opts.graphile), + explain, + maskError + }; + // Resolve the request-owned compute snapshot before cache lookup because + // its provisioned bindings change the exact Graphile schema. + const compute = apiId ? await req.constructive?.useModule('compute') : undefined; + const snapshot = createGraphileServerBuildSnapshot({ ...snapshotInput, compute }); + const key = createGraphileBuildCacheKey('server', snapshot); - // ========================================================================= - // Phase A: Cache Check (fast path) - // ========================================================================= const cached = graphileCache.get(key); if (cached) { log.debug(`${label} PostGraphile cache hit key=${key} db=${dbname} schemas=${schemaLabel}`); return cached.handler(req, res, next); } - log.debug(`${label} PostGraphile cache miss key=${key} db=${dbname} schemas=${schemaLabel}`); - // ========================================================================= - // Phase B: In-Flight Check (single-flight coalescing) - // ========================================================================= const inFlight = creating.get(key); if (inFlight) { - log.debug(`${label} Coalescing request for PostGraphile[${key}] - waiting for in-flight creation`); try { const instance = await inFlight; return instance.handler(req, res, next); } catch (error) { log.warn(`${label} Coalesced request failed for PostGraphile[${key}], retrying`); - // Fall through to Phase C to retry creation } } - - // ========================================================================= - // Phase C: Create New Handler (first request for this key) - // ========================================================================= - - // Re-check cache after coalesced request failure (another retry may have succeeded) const recheckedCache = graphileCache.get(key); - if (recheckedCache) { - log.debug(`${label} PostGraphile cache hit on re-check key=${key}`); - return recheckedCache.handler(req, res, next); - } - - // Re-check in-flight map (another retry may have started creation) + if (recheckedCache) return recheckedCache.handler(req, res, next); const retryInFlight = creating.get(key); if (retryInFlight) { - log.debug(`${label} Re-coalescing request for PostGraphile[${key}]`); const retryInstance = await retryInFlight; return retryInstance.handler(req, res, next); } @@ -352,48 +358,27 @@ export const graphile = (opts: ConstructiveOptions): RequestHandler => { `${label} Building PostGraphile v5 handler key=${key} db=${dbname} schemas=${schemaLabel} role=${roleName} anon=${anonRole}` ); - const pgConfig = getPgEnvOptions({ - ...opts.pg, - database: dbname - }); - - // Route through pg-cache so the pool is tracked and can be cleaned up - // properly, preventing leaked connections during database teardown. - const pool = getPgPool(pgConfig); - - // Register the creation promise before any asynchronous preset work so - // concurrent requests coalesce while optional plugin modules load. - const creationPromise = (async () => { - const compute = api.apiId ? await req.constructive?.useModule('compute') : undefined; - const preset = await buildPreset( - pool, - schema || [], - anonRole, - roleName, - opts.api?.introspectionRole, - opts.graphile, - api.databaseSettings, - api.apiId, - compute - ); + const creationPromise = Promise.resolve().then(async () => { + const preset = await buildPreset(snapshot, pool); return observeGraphileBuild( - { - cacheKey: key, - serviceKey: key, - databaseId: api.databaseId ?? null - }, + { cacheKey: key, serviceKey, databaseId }, () => createGraphileInstance({ preset, cacheKey: key, - enableRealtime: api.databaseSettings?.enableRealtime + enableRealtime: snapshot.databaseSettings?.enableRealtime }), { enabled: observabilityEnabled } ); - })(); + }); creating.set(key, creationPromise); try { const instance = await creationPromise; + Object.assign(instance, { + serviceKey, + databaseId, + poolKey: snapshot.poolKey + } satisfies Partial); graphileCache.set(key, instance); log.info(`${label} Cached PostGraphile v5 handler key=${key} db=${dbname}`); return instance.handler(req, res, next); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b11cde92e5..0a3189c238 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -541,6 +541,9 @@ importers: graphile/graphile-cache: dependencies: + '@constructive-io/errors': + specifier: workspace:^ + version: link:../../packages/errors/dist 12factor-env: specifier: workspace:^ version: link:../../packages/12factor-env/dist From 66f1caa55dbff49fffd745963f02d0266333bc08 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Mon, 21 Sep 2026 22:42:47 +0000 Subject: [PATCH 5/7] fix(graphile-cache): ignore missing service invalidation keys --- .../src/__tests__/build-invalidation.test.ts | 13 +++++++++++++ graphile/graphile-cache/src/graphile-cache.ts | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts b/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts index 8567294085..723c4c0dff 100644 --- a/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts +++ b/graphile/graphile-cache/src/__tests__/build-invalidation.test.ts @@ -25,6 +25,19 @@ describe('Graphile cache identity invalidation', () => { await clearGraphileCache(); }); + it.each([undefined, null, ''])('ignores an absent service key (%s)', (serviceKey) => { + const legacy = makeEntry('legacy-key', {}); + const tagged = makeEntry('graphile:server:one', { serviceKey: 'service-a' }); + graphileCache.set(legacy.cacheKey, legacy); + graphileCache.set(tagged.cacheKey, tagged); + + expect(clearGraphileEntriesForService(serviceKey as unknown as string)).toBe(0); + expect(graphileCache.get(legacy.cacheKey)).toBe(legacy); + expect(graphileCache.get(tagged.cacheKey)).toBe(tagged); + expect(legacy.pgl.release).not.toHaveBeenCalled(); + expect(tagged.pgl.release).not.toHaveBeenCalled(); + }); + it('clears every exact-key variant for only the matching service', () => { const variants = [ makeEntry('graphile:server:one', { serviceKey: 'service-a', databaseId: 'db-a', poolKey: 'pool-a' }), diff --git a/graphile/graphile-cache/src/graphile-cache.ts b/graphile/graphile-cache/src/graphile-cache.ts index f80056310d..5f5b2c4599 100644 --- a/graphile/graphile-cache/src/graphile-cache.ts +++ b/graphile/graphile-cache/src/graphile-cache.ts @@ -287,8 +287,10 @@ const clearEntries = (matches: (key: string, entry: GraphileCacheEntry) => boole }; /** Clear all cached build variants owned by one logical service. */ -export const clearGraphileEntriesForService = (serviceKey: string): number => - clearEntries((key, entry) => entry.serviceKey === serviceKey || (!entry.serviceKey && key === serviceKey)); +export const clearGraphileEntriesForService = (serviceKey: string): number => { + if (typeof serviceKey !== 'string' || serviceKey.length === 0) return 0; + return clearEntries((key, entry) => entry.serviceKey === serviceKey || (!entry.serviceKey && key === serviceKey)); +}; /** Clear all cached build variants for one resolved database identifier. */ export const clearGraphileEntriesForDatabase = (databaseId: string): number => From 89f456161e6c5558bd1fd41db315d27f02ab79a1 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Tue, 22 Sep 2026 01:20:18 +0000 Subject: [PATCH 6/7] fix(graphile-cache): preserve cleanup failures across disposal drains --- .../src/__tests__/build-readiness.test.ts | 2 +- .../src/__tests__/disposal-drain.test.ts | 90 +++++++++++++++++++ .../src/__tests__/disposal-lifecycle.test.ts | 2 + .../graphile-cache/src/build-readiness.ts | 8 +- .../graphile-cache/src/create-instance.ts | 31 ++++--- graphile/graphile-cache/src/graphile-cache.ts | 76 ++++++++++------ .../graphile-cache/src/preset-services.ts | 9 +- 7 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 graphile/graphile-cache/src/__tests__/disposal-drain.test.ts diff --git a/graphile/graphile-cache/src/__tests__/build-readiness.test.ts b/graphile/graphile-cache/src/__tests__/build-readiness.test.ts index caaf7a5a44..ccc1b2851c 100644 --- a/graphile/graphile-cache/src/__tests__/build-readiness.test.ts +++ b/graphile/graphile-cache/src/__tests__/build-readiness.test.ts @@ -158,7 +158,7 @@ describe('awaitGraphileBuildReadiness', () => { release: jest.fn().mockRejectedValue(cleanupFailure), onReleaseError, }) - ).rejects.toBe(failure); + ).rejects.toMatchObject({ errors: [failure, cleanupFailure], cause: failure }); expect(onReleaseError).toHaveBeenCalledWith(cleanupFailure); }); }); diff --git a/graphile/graphile-cache/src/__tests__/disposal-drain.test.ts b/graphile/graphile-cache/src/__tests__/disposal-drain.test.ts new file mode 100644 index 0000000000..63026b44fe --- /dev/null +++ b/graphile/graphile-cache/src/__tests__/disposal-drain.test.ts @@ -0,0 +1,90 @@ +import { pgCache } from 'pg-cache'; + +import { + clearGraphileCache, + closeAllCaches, + disposeUncachedEntry, + graphileCache, + waitForActiveDisposals, + type GraphileCacheEntry +} from '../graphile-cache'; + +const entry = (key: string, release: () => Promise): GraphileCacheEntry => ({ + cacheKey: key, createdAt: Date.now(), pgl: { release }, + serv: {}, handler: {}, httpServer: { listening: false } +} as unknown as GraphileCacheEntry); + +afterEach(async () => { + jest.restoreAllMocks(); + await clearGraphileCache(); +}); + +it('reports already-settled failures to concurrent drains without repeating acknowledged failures', async () => { + const failure = new Error('release failed'); + const failed = entry('failed', async () => { throw failure; }); + await expect(disposeUncachedEntry(failed)).rejects.toBe(failure); + const drains = [waitForActiveDisposals(), waitForActiveDisposals()]; + await Promise.all(drains.map(drain => expect(drain).rejects.toBe(failure))); + await expect(waitForActiveDisposals()).resolves.toBeUndefined(); + await expect(disposeUncachedEntry(failed)).rejects.toBe(failure); +}); + +it('reports multiple generations in scheduling order and attempts every resource', async () => { + const stopFailure = new Error('stop failed'); + const releaseFailure = new Error('release failed'); + const laterFailure = new Error('later failed'); + const first = entry('first', async () => { throw releaseFailure; }); + first.realtimeManager = { stop: async () => { throw stopFailure; } }; + first.releasePresetServices = jest.fn().mockResolvedValue(undefined); + const firstDisposal = disposeUncachedEntry(first); + const later = disposeUncachedEntry(entry('later', async () => { throw laterFailure; })); + await expect(firstDisposal).rejects.toMatchObject({ errors: [stopFailure, releaseFailure] }); + await expect(later).rejects.toBe(laterFailure); + await expect(waitForActiveDisposals()).rejects.toMatchObject({ + errors: [expect.objectContaining({ errors: [stopFailure, releaseFailure] }), laterFailure] + }); + expect(first.releasePresetServices).toHaveBeenCalledTimes(1); +}); + +it('leaves work scheduled after the drain boundary for the next caller', async () => { + let finishFirst!: () => void; + let finishLater!: () => void; + const first = disposeUncachedEntry(entry('first', () => new Promise(resolve => { finishFirst = resolve; }))); + const drain = waitForActiveDisposals(); + const later = disposeUncachedEntry(entry('later', () => new Promise(resolve => { finishLater = resolve; }))); + finishFirst(); + await drain; + await first; + let laterDrained = false; + const next = waitForActiveDisposals().then(() => { laterDrained = true; }); + await new Promise(resolve => setImmediate(resolve)); + expect(laterDrained).toBe(false); + finishLater(); + await Promise.all([next, later]); +}); + +it('still closes pools when cache cleanup fails', async () => { + const failure = new Error('generation failed'); + const poolFailure = new Error('pool failed'); + graphileCache.set('failed', entry('failed', async () => { throw failure; })); + const close = jest.spyOn(pgCache, 'close').mockRejectedValue(poolFailure); + await expect(closeAllCaches()).rejects.toMatchObject({ errors: [failure, poolFailure] }); + expect(close).toHaveBeenCalledTimes(1); +}); + +it('includes synchronous reentrant disposal scheduled while clearing residents', async () => { + const failure = new Error('nested release failed'); + const nested = entry('nested', async () => { throw failure; }); + graphileCache.set('outer', entry('outer', () => disposeUncachedEntry(nested))); + await expect(clearGraphileCache()).rejects.toMatchObject({ errors: [failure, failure] }); +}); + +it('preserves a close callback error and still releases the remaining resources', async () => { + const failure = new Error('server close failed'); + const release = jest.fn().mockResolvedValue(undefined); + const resident = entry('server', release); + resident.httpServer = { listening: true, close: (callback: (error: Error) => void) => callback(failure) } as any; + await expect(disposeUncachedEntry(resident)).rejects.toBe(failure); + expect(release).toHaveBeenCalledTimes(1); + await expect(waitForActiveDisposals()).rejects.toBe(failure); +}); diff --git a/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts b/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts index 9a9b2c7a00..62cf6420e5 100644 --- a/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts +++ b/graphile/graphile-cache/src/__tests__/disposal-lifecycle.test.ts @@ -88,6 +88,7 @@ describe('Graphile cache disposal lifecycle', () => { await expect(disposeUncachedEntry(entry)).rejects.toBe(failure); expect(release).toHaveBeenCalledTimes(1); expect(releasePresetServices).toHaveBeenCalledTimes(1); + await expect(clearGraphileCache()).rejects.toBe(failure); }); it('lets callers await an eviction through the exact entry', async () => { @@ -171,5 +172,6 @@ describe('Graphile cache disposal lifecycle', () => { expect(firstRelease).toHaveBeenCalledTimes(1); expect(secondRelease).toHaveBeenCalledTimes(1); + await expect(clearGraphileCache()).rejects.toBe(firstFailure); }); }); diff --git a/graphile/graphile-cache/src/build-readiness.ts b/graphile/graphile-cache/src/build-readiness.ts index f51d63ed46..308d56f767 100644 --- a/graphile/graphile-cache/src/build-readiness.ts +++ b/graphile/graphile-cache/src/build-readiness.ts @@ -28,7 +28,13 @@ export const awaitGraphileBuildReadiness = async ( try { await build.release(); } catch (releaseError) { - build.onReleaseError?.(releaseError); + const failures = [error, releaseError]; + try { + build.onReleaseError?.(releaseError); + } catch (observerError) { + failures.push(observerError); + } + throw new AggregateError(failures, 'Graphile build and cleanup failed', { cause: error }); } throw error; } diff --git a/graphile/graphile-cache/src/create-instance.ts b/graphile/graphile-cache/src/create-instance.ts index e68cde0b52..0ab4bb77f0 100644 --- a/graphile/graphile-cache/src/create-instance.ts +++ b/graphile/graphile-cache/src/create-instance.ts @@ -7,7 +7,7 @@ import { RealtimeManager } from 'graphile-realtime-subscriptions'; import { postgraphile } from 'postgraphile'; import { awaitGraphileBuildReadiness } from './build-readiness'; -import type { GraphileCacheEntry } from './graphile-cache'; +import { disposeUncachedEntry, type GraphileCacheEntry } from './graphile-cache'; import { createPresetServicesReleaser } from './preset-services'; const log = new Logger('graphile-cache:create'); @@ -55,21 +55,19 @@ export const createGraphileInstance = async ( const releaseFailedBuild = (): Promise => { if (failedBuildReleasePromise) return failedBuildReleasePromise; failedBuildReleasePromise = (async () => { - let firstError: unknown; - let failed = false; + const failures: unknown[] = []; try { await pgl.release(); } catch (error) { - firstError = error; - failed = true; + failures.push(error); } try { await releasePresetServices(); } catch (error) { - if (!failed) firstError = error; - failed = true; + failures.push(error); } - if (failed) throw firstError; + if (failures.length === 1) throw failures[0]; + if (failures.length > 1) throw new AggregateError(failures, 'Graphile service cleanup failed'); })(); return failedBuildReleasePromise; }; @@ -81,8 +79,8 @@ export const createGraphileInstance = async ( release: releaseFailedBuild, onReleaseError: (releaseError) => { log.error( - `Failed to release PostGraphile[${cacheKey}] after build failure:`, - releaseError + 'Graphile build cleanup failed', + { stage: 'failed-build-release' } ); } }); @@ -107,9 +105,9 @@ export const createGraphileInstance = async ( const pool = pgService?.adaptorSettings?.pool ?? null; if (!pgSubscriber) { - log.warn(`PostGraphile[${cacheKey}] has no pgSubscriber — RealtimeManager will not be started`); + throw new Error('Realtime requires a PostgreSQL subscriber'); } else if (!pool) { - log.warn(`PostGraphile[${cacheKey}] has no pool in pgService — RealtimeManager will not be started`); + throw new Error('Realtime requires a PostgreSQL pool'); } else { const manager = new RealtimeManager({ pgSubscriber, @@ -118,12 +116,17 @@ export const createGraphileInstance = async ( schema: 'realtime_public', }); - await manager.start(); entry.realtimeManager = manager; + await manager.start(); log.info(`RealtimeManager started for PostGraphile[${cacheKey}]`); } } catch (err) { - log.error(`Failed to start RealtimeManager for PostGraphile[${cacheKey}]:`, err); + try { + await disposeUncachedEntry(entry); + } catch (cleanupError) { + throw new AggregateError([err, cleanupError], 'Realtime startup and cleanup failed', { cause: err }); + } + throw err; } } diff --git a/graphile/graphile-cache/src/graphile-cache.ts b/graphile/graphile-cache/src/graphile-cache.ts index 6ef9dda7fe..34f3f10cc3 100644 --- a/graphile/graphile-cache/src/graphile-cache.ts +++ b/graphile/graphile-cache/src/graphile-cache.ts @@ -94,6 +94,10 @@ export interface GraphileCacheEntry { const disposalPromises = new WeakMap>(); const activeDisposals = new Set>(); +// Retain failed generations until a bulk drain observes them. Successful work +// is removed immediately; exact-entry outcomes remain in the WeakMap above. +const disposalRecords = new Map>(); +let disposalSequence = 0; // Track keys that are being manually evicted for accurate eviction reason const manualEvictionKeys = new Set(); @@ -111,39 +115,35 @@ const releaseEntry = async ( key: string ): Promise => { log.debug(`Disposing PostGraphile[${key}]`); - let firstError: unknown; - let failed = false; + const failures: unknown[] = []; try { if (entry.httpServer?.listening) { - await new Promise((resolve) => { - entry.httpServer.close(() => resolve()); + await new Promise((resolve, reject) => { + entry.httpServer.close((error) => error ? reject(error) : resolve()); }); } } catch (error) { - firstError = error; - failed = true; + failures.push(error); } try { if (entry.realtimeManager) { await entry.realtimeManager.stop(); } } catch (error) { - if (!failed) firstError = error; - failed = true; + failures.push(error); } try { await entry.pgl.release(); } catch (error) { - if (!failed) firstError = error; - failed = true; + failures.push(error); } try { await entry.releasePresetServices?.(); } catch (error) { - if (!failed) firstError = error; - failed = true; + failures.push(error); } - if (failed) throw firstError; + if (failures.length === 1) throw failures[0]; + if (failures.length > 1) throw new AggregateError(failures, 'Graphile resource cleanup failed'); }; /** @@ -160,9 +160,12 @@ const scheduleDisposal = ( disposalPromises.set(entry, pending); activeDisposals.add(pending); + const sequence = ++disposalSequence; + disposalRecords.set(sequence, pending); void pending - .catch((error) => { - log.error(`Failed to dispose PostGraphile[${key}]:`, error); + .then(() => { disposalRecords.delete(sequence); }, () => { + // The original rejection remains observable through the owner APIs. + log.error('Graphile resource cleanup failed', { generation: sequence }); }) .finally(() => activeDisposals.delete(pending)); return pending; @@ -179,11 +182,21 @@ export const waitForEntryDisposal = ( entry: GraphileCacheEntry ): Promise => disposalPromises.get(entry) ?? Promise.resolve(); -/** Await every disposal that is active at or begins during this drain. */ +/** + * Await disposals scheduled before this call, including failures that already + * settled. Later generations belong to the next drain. Concurrent callers hold + * their own snapshot, so acknowledging a failure cannot hide it from a peer. + */ export const waitForActiveDisposals = async (): Promise => { - while (activeDisposals.size > 0) { - await Promise.allSettled([...activeDisposals]); - } + const records = [...disposalRecords]; + const outcomes = await Promise.allSettled(records.map(([, pending]) => pending)); + const failures: unknown[] = []; + outcomes.forEach((outcome, index) => { + disposalRecords.delete(records[index][0]); + if (outcome.status === 'rejected') failures.push(outcome.reason); + }); + if (failures.length === 1) throw failures[0]; + if (failures.length > 1) throw new AggregateError(failures, 'Graphile disposal drain failed'); }; /** @@ -293,8 +306,11 @@ export const clearGraphileCache = async (): Promise => { manualEvictionKeys.add(key); } graphileCache.clear(); - await waitForActiveDisposals(); - manualEvictionKeys.clear(); + try { + await waitForActiveDisposals(); + } finally { + manualEvictionKeys.clear(); + } }; /** @@ -315,10 +331,20 @@ export const closeAllCaches = async (verbose = false): Promise => { try { if (verbose) log.info('Closing all server caches...'); - await clearGraphileCache(); - - // Close pg pools - await pgCache.close(); + const failures: unknown[] = []; + try { + await clearGraphileCache(); + } catch (error) { + failures.push(error); + } + // Attempt pool cleanup even when a generation's release rejected. + try { + await pgCache.close(); + } catch (error) { + failures.push(error); + } + if (failures.length === 1) throw failures[0]; + if (failures.length > 1) throw new AggregateError(failures, 'Server cache cleanup failed'); if (verbose) log.success('All caches disposed.'); } finally { diff --git a/graphile/graphile-cache/src/preset-services.ts b/graphile/graphile-cache/src/preset-services.ts index 99e8c49dd3..5bb2109345 100644 --- a/graphile/graphile-cache/src/preset-services.ts +++ b/graphile/graphile-cache/src/preset-services.ts @@ -17,17 +17,16 @@ export const createPresetServicesReleaser = (resolvedPreset: { return (): Promise => { if (releasePromise) return releasePromise; releasePromise = (async () => { - let firstError: unknown; - let failed = false; + const failures: unknown[] = []; for (const service of [...services].reverse()) { try { await service.release?.(); } catch (error) { - if (!failed) firstError = error; - failed = true; + failures.push(error); } } - if (failed) throw firstError; + if (failures.length === 1) throw failures[0]; + if (failures.length > 1) throw new AggregateError(failures, 'Graphile service cleanup failed'); })(); return releasePromise; }; From 36841ebfb556b42f9a589ace5e9fbf10f3c557e2 Mon Sep 17 00:00:00 2001 From: zetazzz Date: Tue, 22 Sep 2026 02:31:16 +0000 Subject: [PATCH 7/7] test(graphql-server): apply fixture transaction hooks in the F17 layer --- .../graphile-build.integration.test.ts | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/graphql/server-test/__tests__/graphile-build.integration.test.ts b/graphql/server-test/__tests__/graphile-build.integration.test.ts index 3102f4ce26..622cd77085 100644 --- a/graphql/server-test/__tests__/graphile-build.integration.test.ts +++ b/graphql/server-test/__tests__/graphile-build.integration.test.ts @@ -169,6 +169,7 @@ const allowConcurrentRequestToReachGraphile = async (): Promise => { describe('Graphile build and cache over the real scoped HTTP server', () => { let request: supertest.Agent | undefined; let teardown: (() => Promise) | undefined; + let transactionCleanup: (() => Promise) | undefined; let releaseBuild: (() => void) | undefined; const pendingRequests = new Set>(); @@ -200,6 +201,15 @@ describe('Graphile build and cache over the real scoped HTTP server', () => { ); request = connection.request; teardown = connection.teardown; + transactionCleanup = async () => { + try { + await connection.db.afterEach(); + } finally { + await connection.pg.afterEach(); + } + }; + await connection.pg.beforeEach(); + await connection.db.beforeEach(); }; const postGraphQL = () => { @@ -239,20 +249,29 @@ describe('Graphile build and cache over the real scoped HTTP server', () => { releaseBuild?.(); releaseBuild = undefined; const pending = [...pendingRequests]; - await waitForSignal( - Promise.allSettled(pending).then((): void => undefined), - 'HTTP requests to finish', - 15000 - ); - pendingRequests.clear(); const currentTeardown = teardown; + const currentTransactionCleanup = transactionCleanup; teardown = undefined; + transactionCleanup = undefined; request = undefined; try { - // Drain schema handlers before pgsql-test rolls back and drops the - // fixture database. The server's close path also drains this cache, but - // the explicit ordering keeps failed tests from poisoning teardown. - await clearGraphileCache(); + try { + await waitForSignal( + Promise.allSettled(pending).then((): void => undefined), + 'HTTP requests to finish', + 15000 + ); + } finally { + pendingRequests.clear(); + } + try { + // Drain schema handlers before pgsql-test rolls back and drops the + // fixture database. The server's close path also drains this cache, but + // the explicit ordering keeps failed tests from poisoning teardown. + await clearGraphileCache(); + } finally { + await currentTransactionCleanup?.(); + } } finally { await currentTeardown?.(); }