Skip to content

Commit 62d333e

Browse files
authored
Merge pull request #738 from constructive-io/feat/postgraphile-v5-plugins-review
Feat/postgraphile v5 plugins review
2 parents b4e199d + 6494cbe commit 62d333e

117 files changed

Lines changed: 13548 additions & 7880 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

graphile/graphile-cache/src/graphile-cache.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ const disposeEntry = async (entry: GraphileCacheEntry, key: string): Promise<voi
125125
}
126126
} catch (err) {
127127
log.error(`Error disposing PostGraphile[${key}]:`, err);
128+
} finally {
129+
disposedKeys.delete(key);
128130
}
129131
};
130132

@@ -248,34 +250,38 @@ export const closeAllCaches = async (verbose = false): Promise<void> => {
248250
if (closePromise.promise) return closePromise.promise;
249251

250252
closePromise.promise = (async () => {
251-
if (verbose) log.info('Closing all server caches...');
253+
try {
254+
if (verbose) log.info('Closing all server caches...');
252255

253-
// Collect all entries and dispose them properly
254-
const entries = [...graphileCache.entries()];
256+
// Collect all entries and dispose them properly
257+
const entries = [...graphileCache.entries()];
255258

256-
// Mark all as manual evictions
257-
for (const [key] of entries) {
258-
manualEvictionKeys.add(key);
259-
}
259+
// Mark all as manual evictions
260+
for (const [key] of entries) {
261+
manualEvictionKeys.add(key);
262+
}
260263

261-
const disposePromises = entries.map(([key, entry]) =>
262-
disposeEntry(entry, key)
263-
);
264+
const disposePromises = entries.map(([key, entry]) =>
265+
disposeEntry(entry, key)
266+
);
264267

265-
// Wait for all disposals to complete
266-
await Promise.allSettled(disposePromises);
268+
// Wait for all disposals to complete
269+
await Promise.allSettled(disposePromises);
267270

268-
// Clear the cache after disposal (dispose callback will no-op due to disposedKeys)
269-
graphileCache.clear();
271+
// Clear the cache after disposal (dispose callback will no-op due to disposedKeys)
272+
graphileCache.clear();
270273

271-
// Clear disposed keys tracking after full cleanup
272-
disposedKeys.clear();
273-
manualEvictionKeys.clear();
274+
// Clear disposed keys tracking after full cleanup
275+
disposedKeys.clear();
276+
manualEvictionKeys.clear();
274277

275-
// Close pg pools
276-
await pgCache.close();
278+
// Close pg pools
279+
await pgCache.close();
277280

278-
if (verbose) log.success('All caches disposed.');
281+
if (verbose) log.success('All caches disposed.');
282+
} finally {
283+
closePromise.promise = null;
284+
}
279285
})();
280286

281287
return closePromise.promise;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# graphile-misc-plugins
2+
3+
Miscellaneous PostGraphile v5 plugins for Constructive: inflection, conflict detection, meta-schema, type mappings, public-key signature, and more.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { PublicKeySignature } from '../src/PublicKeySignature';
2+
import type { PublicKeyChallengeConfig } from '../src/PublicKeySignature';
3+
4+
const defaultConfig: PublicKeyChallengeConfig = {
5+
schema: 'app_private',
6+
crypto_network: 'btc',
7+
sign_up_with_key: 'sign_up_with_key',
8+
sign_in_request_challenge: 'sign_in_request_challenge',
9+
sign_in_record_failure: 'sign_in_record_failure',
10+
sign_in_with_challenge: 'sign_in_with_challenge',
11+
};
12+
13+
describe('PublicKeySignature plugin factory', () => {
14+
it('returns a valid GraphileConfig.Plugin object', () => {
15+
const plugin = PublicKeySignature(defaultConfig);
16+
expect(plugin).toBeDefined();
17+
expect(typeof plugin).toBe('object');
18+
// extendSchema returns a plugin with a name and version
19+
expect(plugin.name).toBeDefined();
20+
expect(typeof plugin.name).toBe('string');
21+
});
22+
23+
it('returns a plugin with schema hooks', () => {
24+
const plugin = PublicKeySignature(defaultConfig);
25+
expect(plugin.schema).toBeDefined();
26+
expect(plugin.schema!.hooks).toBeDefined();
27+
});
28+
29+
it('accepts custom config values', () => {
30+
const customConfig: PublicKeyChallengeConfig = {
31+
schema: 'custom_schema',
32+
crypto_network: 'eth',
33+
sign_up_with_key: 'custom_signup',
34+
sign_in_request_challenge: 'custom_challenge',
35+
sign_in_record_failure: 'custom_failure',
36+
sign_in_with_challenge: 'custom_verify',
37+
};
38+
const plugin = PublicKeySignature(customConfig);
39+
expect(plugin).toBeDefined();
40+
expect(typeof plugin.name).toBe('string');
41+
});
42+
43+
it('default export matches named export', async () => {
44+
const mod = await import('../src/PublicKeySignature');
45+
expect(mod.default).toBe(mod.PublicKeySignature);
46+
});
47+
});
48+
49+
describe('PublicKeyChallengeConfig interface', () => {
50+
it('requires all config fields', () => {
51+
// TypeScript enforces this at compile time; this runtime check
52+
// verifies that the factory does not throw with a complete config.
53+
expect(() => PublicKeySignature(defaultConfig)).not.toThrow();
54+
});
55+
});
56+
57+
describe('PublicKeySignature config validation', () => {
58+
it('throws on invalid schema name', () => {
59+
expect(() => PublicKeySignature({ ...defaultConfig, schema: 'DROP TABLE' })).toThrow(/invalid schema/);
60+
});
61+
62+
it('throws on invalid function name', () => {
63+
expect(() => PublicKeySignature({ ...defaultConfig, sign_up_with_key: 'evil"; DROP' })).toThrow(
64+
/invalid sign_up_with_key/,
65+
);
66+
});
67+
68+
it('throws on invalid crypto_network value', () => {
69+
expect(() => PublicKeySignature({ ...defaultConfig, crypto_network: 'btc mainnet' })).toThrow(
70+
/invalid crypto_network/,
71+
);
72+
});
73+
74+
it('throws on function name with uppercase letters', () => {
75+
expect(() => PublicKeySignature({ ...defaultConfig, sign_in_request_challenge: 'BadName' })).toThrow(
76+
/invalid sign_in_request_challenge/,
77+
);
78+
});
79+
80+
it('throws on function name starting with a number', () => {
81+
expect(() => PublicKeySignature({ ...defaultConfig, sign_in_record_failure: '1bad' })).toThrow(
82+
/invalid sign_in_record_failure/,
83+
);
84+
});
85+
86+
it('accepts valid snake_case identifiers', () => {
87+
expect(() => PublicKeySignature(defaultConfig)).not.toThrow();
88+
});
89+
});

graphile/graphile-settings/__tests__/__snapshots__/meta-schema.test.ts.snap renamed to graphile/graphile-misc-plugins/__tests__/__snapshots__/meta-schema.test.ts.snap

File renamed without changes.

graphile/graphile-settings/__tests__/meta-schema.test.ts renamed to graphile/graphile-misc-plugins/__tests__/meta-schema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
_pgTypeToGqlType,
44
_buildFieldMeta,
55
_cachedTablesMeta,
6-
} from '../src/plugins/meta-schema';
6+
} from '../src/meta-schema';
77
import {
88
parse,
99
print,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'node',
5+
transform: {
6+
'^.+\\.tsx?$': [
7+
'ts-jest',
8+
{
9+
babelConfig: false,
10+
tsconfig: 'tsconfig.json',
11+
},
12+
],
13+
},
14+
transformIgnorePatterns: [`/node_modules/*`],
15+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
16+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
17+
modulePathIgnorePatterns: ['dist/*']
18+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "graphile-misc-plugins",
3+
"version": "1.0.0",
4+
"author": "Constructive <developers@constructive.io>",
5+
"description": "Miscellaneous PostGraphile v5 plugins: inflection, conflict detection, meta-schema, type mappings, and more",
6+
"main": "index.js",
7+
"module": "esm/index.js",
8+
"types": "index.d.ts",
9+
"homepage": "https://github.com/constructive-io/constructive",
10+
"license": "MIT",
11+
"publishConfig": {
12+
"access": "public",
13+
"directory": "dist"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/constructive-io/constructive"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/constructive-io/constructive/issues"
21+
},
22+
"scripts": {
23+
"clean": "makage clean",
24+
"prepack": "npm run build",
25+
"build": "makage build",
26+
"build:dev": "makage build --dev",
27+
"lint": "eslint . --fix",
28+
"test": "jest",
29+
"test:watch": "jest --watch"
30+
},
31+
"dependencies": {
32+
"@graphile-contrib/pg-many-to-many": "2.0.0-rc.1",
33+
"graphile-utils": "^5.0.0-rc.5",
34+
"inflekt": "^0.3.0",
35+
"pg": "^8.17.1",
36+
"pg-query-context": "workspace:^"
37+
},
38+
"peerDependencies": {
39+
"grafast": "^1.0.0-rc.7",
40+
"graphile-build": "^5.0.0-rc.4",
41+
"graphile-build-pg": "^5.0.0-rc.5",
42+
"graphile-config": "^1.0.0-rc.5",
43+
"graphql": "^16.9.0",
44+
"pg-sql2": "^5.0.0-rc.4"
45+
},
46+
"devDependencies": {
47+
"@types/node": "^22.19.1",
48+
"makage": "^0.1.10"
49+
},
50+
"keywords": [
51+
"postgraphile",
52+
"graphile",
53+
"constructive",
54+
"plugin",
55+
"inflection",
56+
"meta-schema",
57+
"postgres",
58+
"graphql"
59+
]
60+
}

0 commit comments

Comments
 (0)