bunsane 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +445 -318
- package/config/cache.config.ts +35 -1
- package/core/App.ts +24 -1064
- package/core/ArcheType.ts +78 -2110
- package/core/BatchLoader.ts +56 -32
- package/core/Entity.ts +85 -1043
- package/core/EntityHookManager.ts +52 -754
- package/core/Logger.ts +10 -0
- package/core/RequestContext.ts +64 -6
- package/core/RequestLoaders.ts +187 -36
- package/core/SchedulerManager.ts +28 -600
- package/core/app/bootstrap.ts +133 -0
- package/core/app/cors.ts +85 -0
- package/core/app/graphqlSetup.ts +56 -0
- package/core/app/healthEndpoints.ts +31 -0
- package/core/app/metricsCollector.ts +27 -0
- package/core/app/preparedStatementWarmup.ts +15 -0
- package/core/app/processHandlers.ts +43 -0
- package/core/app/requestRouter.ts +310 -0
- package/core/app/restRegistry.ts +80 -0
- package/core/app/shutdown.ts +97 -0
- package/core/app/studioRouter.ts +83 -0
- package/core/archetype/customTypes.ts +100 -0
- package/core/archetype/decorators.ts +171 -0
- package/core/archetype/fieldResolvers.ts +666 -0
- package/core/archetype/helpers.ts +29 -0
- package/core/archetype/relationLoader.ts +161 -0
- package/core/archetype/schemaBuilder.ts +141 -0
- package/core/archetype/weaver.ts +218 -0
- package/core/archetype/zodSchemaBuilder.ts +527 -0
- package/core/cache/CacheManager.ts +173 -267
- package/core/cache/CompressionUtils.ts +34 -3
- package/core/cache/MemoryCache.ts +40 -37
- package/core/cache/RedisCache.ts +4 -4
- package/core/cache/health.ts +30 -0
- package/core/cache/invalidation.ts +96 -0
- package/core/cache/strategies/writeInvalidate.ts +111 -0
- package/core/cache/strategies/writeThrough.ts +233 -0
- package/core/components/BaseComponent.ts +16 -8
- package/core/components/ComponentRegistry.ts +28 -0
- package/core/decorators/IndexedField.ts +1 -1
- package/core/entity/cacheStrategies.ts +97 -0
- package/core/entity/componentAccess.ts +364 -0
- package/core/entity/finders.ts +202 -0
- package/core/entity/pendingOps.ts +72 -0
- package/core/entity/saveEntity.ts +377 -0
- package/core/hooks/dispatcher.ts +439 -0
- package/core/hooks/guards.ts +155 -0
- package/core/hooks/registry.ts +247 -0
- package/core/metadata/definitions/Component.ts +1 -1
- package/core/metadata/index.ts +15 -4
- package/core/middleware/AccessLog.ts +8 -1
- package/core/middleware/RateLimit.ts +102 -105
- package/core/middleware/RequestId.ts +2 -9
- package/core/middleware/SecurityHeaders.ts +2 -11
- package/core/middleware/headers.ts +28 -0
- package/core/remote/OutboxWorker.ts +213 -183
- package/core/remote/RemoteManager.ts +401 -400
- package/core/remote/types.ts +153 -151
- package/core/requestScope.ts +34 -0
- package/core/scheduler/cronEvaluator.ts +174 -0
- package/core/scheduler/lifecycleHooks.ts +21 -0
- package/core/scheduler/lockCoordinator.ts +27 -0
- package/core/scheduler/metrics.ts +14 -0
- package/core/scheduler/taskRunner.ts +420 -0
- package/database/DatabaseHelper.ts +128 -101
- package/database/IndexingStrategy.ts +72 -2
- package/database/PreparedStatementCache.ts +20 -5
- package/database/cancellable.ts +35 -0
- package/database/index.ts +15 -3
- package/database/instrumentedDb.ts +141 -0
- package/endpoints/archetypes.ts +2 -8
- package/endpoints/tables.ts +6 -1
- package/gql/index.ts +1 -1
- package/gql/visitors/ResolverGeneratorVisitor.ts +25 -4
- package/package.json +22 -1
- package/query/CTENode.ts +5 -3
- package/query/ComponentInclusionNode.ts +240 -13
- package/query/OrNode.ts +6 -5
- package/query/Query.ts +203 -59
- package/query/QueryContext.ts +6 -0
- package/query/QueryDAG.ts +7 -2
- package/query/membershipSource.ts +66 -0
- package/storage/LocalStorageProvider.ts +8 -3
- package/studio/dist/assets/index-BMZ67Npg.js +254 -0
- package/studio/dist/assets/index-BpbuYz9g.css +1 -0
- package/studio/{index.html → dist/index.html} +3 -2
- package/swagger/generator.ts +11 -1
- package/upload/UploadManager.ts +8 -6
- package/utils/uuid.ts +40 -10
- package/.claude/settings.local.json +0 -47
- package/.prettierrc +0 -4
- package/.serena/memories/architectural-decision-no-dependency-injection.md +0 -76
- package/.serena/memories/architecture.md +0 -154
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +0 -165
- package/.serena/memories/code_style_and_conventions.md +0 -76
- package/.serena/memories/project_overview.md +0 -43
- package/.serena/memories/schema-dsl-plan.md +0 -107
- package/.serena/memories/suggested_commands.md +0 -80
- package/.serena/memories/typescript-compilation-status.md +0 -54
- package/.serena/project.yml +0 -114
- package/BunSane.jpg +0 -0
- package/CLAUDE.md +0 -198
- package/TODO.md +0 -2
- package/bun.lock +0 -302
- package/bunfig.toml +0 -10
- package/docs/SCALABILITY_PLAN.md +0 -175
- package/studio/bun.lock +0 -482
- package/studio/package.json +0 -39
- package/studio/postcss.config.js +0 -6
- package/studio/src/components/DataTable.tsx +0 -211
- package/studio/src/components/Layout.tsx +0 -13
- package/studio/src/components/PageContainer.tsx +0 -9
- package/studio/src/components/PageHeader.tsx +0 -13
- package/studio/src/components/SearchBar.tsx +0 -57
- package/studio/src/components/Sidebar.tsx +0 -294
- package/studio/src/components/ui/button.tsx +0 -56
- package/studio/src/components/ui/checkbox.tsx +0 -26
- package/studio/src/components/ui/input.tsx +0 -25
- package/studio/src/hooks/useDataTable.ts +0 -131
- package/studio/src/index.css +0 -36
- package/studio/src/lib/api.ts +0 -186
- package/studio/src/lib/utils.ts +0 -13
- package/studio/src/main.tsx +0 -17
- package/studio/src/pages/ArcheType.tsx +0 -239
- package/studio/src/pages/Components.tsx +0 -124
- package/studio/src/pages/EntityInspector.tsx +0 -302
- package/studio/src/pages/QueryRunner.tsx +0 -246
- package/studio/src/pages/Table.tsx +0 -94
- package/studio/src/pages/Welcome.tsx +0 -241
- package/studio/src/routes.tsx +0 -45
- package/studio/src/store/archeTypeSettings.ts +0 -30
- package/studio/src/store/studio.ts +0 -65
- package/studio/src/utils/columnHelpers.tsx +0 -114
- package/studio/studio-instructions.md +0 -81
- package/studio/tailwind.config.js +0 -77
- package/studio/utils.ts +0 -54
- package/studio/vite.config.js +0 -19
- package/tests/benchmark/BENCHMARK_DATABASES_PLAN.md +0 -338
- package/tests/benchmark/bunfig.toml +0 -9
- package/tests/benchmark/fixtures/EcommerceComponents.ts +0 -283
- package/tests/benchmark/fixtures/EcommerceDataGenerators.ts +0 -301
- package/tests/benchmark/fixtures/RelationTracker.ts +0 -159
- package/tests/benchmark/fixtures/index.ts +0 -6
- package/tests/benchmark/index.ts +0 -22
- package/tests/benchmark/noop-preload.ts +0 -3
- package/tests/benchmark/query-lateral-benchmark.test.ts +0 -372
- package/tests/benchmark/runners/BenchmarkLoader.ts +0 -132
- package/tests/benchmark/runners/index.ts +0 -4
- package/tests/benchmark/scenarios/query-benchmarks.test.ts +0 -465
- package/tests/benchmark/scripts/generate-db.ts +0 -344
- package/tests/benchmark/scripts/run-benchmarks.ts +0 -97
- package/tests/e2e/http.test.ts +0 -130
- package/tests/fixtures/archetypes/TestUserArchetype.ts +0 -21
- package/tests/fixtures/components/TestOrder.ts +0 -23
- package/tests/fixtures/components/TestProduct.ts +0 -23
- package/tests/fixtures/components/TestUser.ts +0 -20
- package/tests/fixtures/components/index.ts +0 -6
- package/tests/graphql/SchemaGeneration.test.ts +0 -90
- package/tests/graphql/builders/ResolverBuilder.test.ts +0 -223
- package/tests/graphql/builders/TypeDefBuilder.test.ts +0 -153
- package/tests/helpers/MockRedisClient.ts +0 -113
- package/tests/helpers/MockRedisStreamServer.ts +0 -448
- package/tests/integration/archetype/ArcheType.persistence.test.ts +0 -241
- package/tests/integration/cache/CacheInvalidation.test.ts +0 -259
- package/tests/integration/entity/Entity.persistence.test.ts +0 -333
- package/tests/integration/entity/Entity.saveTimeout.test.ts +0 -110
- package/tests/integration/query/Query.complexAnalysis.test.ts +0 -557
- package/tests/integration/query/Query.edgeCases.test.ts +0 -595
- package/tests/integration/query/Query.exec.test.ts +0 -576
- package/tests/integration/query/Query.explainAnalyze.test.ts +0 -233
- package/tests/integration/query/Query.jsonbArray.test.ts +0 -214
- package/tests/integration/remote/dlq.test.ts +0 -175
- package/tests/integration/remote/event-dispatch.test.ts +0 -114
- package/tests/integration/remote/outbox.test.ts +0 -130
- package/tests/integration/remote/rpc.test.ts +0 -177
- package/tests/pglite-setup.ts +0 -62
- package/tests/setup.ts +0 -164
- package/tests/stress/BenchmarkRunner.ts +0 -203
- package/tests/stress/DataSeeder.ts +0 -190
- package/tests/stress/StressTestReporter.ts +0 -229
- package/tests/stress/cursor-perf-test.ts +0 -171
- package/tests/stress/fixtures/RealisticComponents.ts +0 -235
- package/tests/stress/fixtures/StressTestComponents.ts +0 -58
- package/tests/stress/index.ts +0 -7
- package/tests/stress/scenarios/query-benchmarks.test.ts +0 -285
- package/tests/stress/scenarios/realistic-scenarios.test.ts +0 -1081
- package/tests/stress/scenarios/timeout-investigation.test.ts +0 -522
- package/tests/unit/BatchLoader.test.ts +0 -196
- package/tests/unit/archetype/ArcheType.test.ts +0 -107
- package/tests/unit/cache/CacheManager.test.ts +0 -367
- package/tests/unit/cache/MemoryCache.test.ts +0 -260
- package/tests/unit/cache/RedisCache.test.ts +0 -411
- package/tests/unit/entity/Entity.components.test.ts +0 -317
- package/tests/unit/entity/Entity.drainSideEffects.test.ts +0 -51
- package/tests/unit/entity/Entity.reload.test.ts +0 -63
- package/tests/unit/entity/Entity.requireComponents.test.ts +0 -72
- package/tests/unit/entity/Entity.test.ts +0 -345
- package/tests/unit/gql/depthLimit.test.ts +0 -203
- package/tests/unit/gql/operationMiddleware.test.ts +0 -293
- package/tests/unit/health/Health.test.ts +0 -129
- package/tests/unit/middleware/AccessLog.test.ts +0 -37
- package/tests/unit/middleware/Middleware.test.ts +0 -98
- package/tests/unit/middleware/RequestId.test.ts +0 -54
- package/tests/unit/middleware/SecurityHeaders.test.ts +0 -66
- package/tests/unit/query/FilterBuilder.test.ts +0 -111
- package/tests/unit/query/JsonbArrayBuilder.test.ts +0 -178
- package/tests/unit/query/Query.emptyString.test.ts +0 -69
- package/tests/unit/query/Query.test.ts +0 -310
- package/tests/unit/remote/CircuitBreaker.test.ts +0 -159
- package/tests/unit/remote/RemoteError.test.ts +0 -55
- package/tests/unit/remote/decorators.test.ts +0 -195
- package/tests/unit/remote/metrics.test.ts +0 -115
- package/tests/unit/remote/mockRedisStreamServer.test.ts +0 -104
- package/tests/unit/scheduler/DistributedLock.test.ts +0 -274
- package/tests/unit/scheduler/SchedulerManager.timeBased.test.ts +0 -95
- package/tests/unit/schema/schema-integration.test.ts +0 -426
- package/tests/unit/schema/schema.test.ts +0 -580
- package/tests/unit/storage/S3StorageProvider.test.ts +0 -567
- package/tests/unit/upload/RestUpload.test.ts +0 -267
- package/tests/unit/validateEnv.test.ts +0 -82
- package/tests/utils/entity-tracker.ts +0 -57
- package/tests/utils/index.ts +0 -13
- package/tests/utils/test-context.ts +0 -149
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for GraphQL Schema Generation
|
|
3
|
-
* Tests the overall schema generation process
|
|
4
|
-
*/
|
|
5
|
-
import { describe, test, expect, beforeAll } from 'bun:test';
|
|
6
|
-
import { TestUser, TestProduct, TestOrder } from '../fixtures/components';
|
|
7
|
-
import { TestUserArchetype } from '../fixtures/archetypes/TestUserArchetype';
|
|
8
|
-
import { ensureComponentsRegistered } from '../utils';
|
|
9
|
-
|
|
10
|
-
describe('GraphQL Schema Generation', () => {
|
|
11
|
-
beforeAll(async () => {
|
|
12
|
-
await ensureComponentsRegistered(TestUser, TestProduct, TestOrder);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('archetype schema generation', () => {
|
|
16
|
-
test('archetype has zodObjectSchema method', () => {
|
|
17
|
-
const archetype = new TestUserArchetype();
|
|
18
|
-
expect(typeof archetype.getZodObjectSchema).toBe('function');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('archetype has inputSchema method', () => {
|
|
22
|
-
const archetype = new TestUserArchetype();
|
|
23
|
-
expect(typeof archetype.getInputSchema).toBe('function');
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('component to GraphQL type mapping', () => {
|
|
28
|
-
test('archetype schema is defined', () => {
|
|
29
|
-
const archetype = new TestUserArchetype();
|
|
30
|
-
const schema = archetype.getZodObjectSchema();
|
|
31
|
-
|
|
32
|
-
expect(schema).toBeDefined();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('input schema is defined', () => {
|
|
36
|
-
const archetype = new TestUserArchetype();
|
|
37
|
-
const inputSchema = archetype.getInputSchema();
|
|
38
|
-
|
|
39
|
-
expect(inputSchema).toBeDefined();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('input type generation', () => {
|
|
44
|
-
test('generates input schema for archetype', () => {
|
|
45
|
-
const archetype = new TestUserArchetype();
|
|
46
|
-
const inputSchema = archetype.getInputSchema();
|
|
47
|
-
|
|
48
|
-
expect(inputSchema).toBeDefined();
|
|
49
|
-
expect(typeof inputSchema).toBe('object');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('input schema validates valid data', () => {
|
|
53
|
-
const archetype = new TestUserArchetype();
|
|
54
|
-
const schema = archetype.withValidation({
|
|
55
|
-
user: { name: 'Valid', email: 'valid@example.com', age: 25 }
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(schema).toBeDefined();
|
|
59
|
-
expect(schema.shape).toBeDefined();
|
|
60
|
-
expect(typeof schema.safeParse).toBe('function');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('schema consistency', () => {
|
|
65
|
-
test('multiple calls return structurally equivalent schemas', () => {
|
|
66
|
-
const archetype = new TestUserArchetype();
|
|
67
|
-
|
|
68
|
-
const schema1 = archetype.getZodObjectSchema();
|
|
69
|
-
const schema2 = archetype.getZodObjectSchema();
|
|
70
|
-
|
|
71
|
-
// Both should have the same shape keys
|
|
72
|
-
const keys1 = Object.keys(schema1.shape);
|
|
73
|
-
const keys2 = Object.keys(schema2.shape);
|
|
74
|
-
expect(keys1).toEqual(keys2);
|
|
75
|
-
expect(keys1.length).toBeGreaterThan(0);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('archetype with components', () => {
|
|
80
|
-
test('archetype has componentMap', () => {
|
|
81
|
-
const archetype = new TestUserArchetype();
|
|
82
|
-
expect(archetype.componentMap).toBeDefined();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('componentMap includes user component', () => {
|
|
86
|
-
const archetype = new TestUserArchetype();
|
|
87
|
-
expect(archetype.componentMap.user).toBeDefined();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for ResolverBuilder
|
|
3
|
-
* Tests GraphQL resolver building
|
|
4
|
-
*/
|
|
5
|
-
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
6
|
-
import { ResolverBuilder } from '../../../gql/builders/ResolverBuilder';
|
|
7
|
-
|
|
8
|
-
describe('ResolverBuilder', () => {
|
|
9
|
-
let builder: ResolverBuilder;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
builder = new ResolverBuilder();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('addResolver()', () => {
|
|
16
|
-
test('adds resolver to collection', () => {
|
|
17
|
-
const mockService = { myMethod: () => 'result' };
|
|
18
|
-
builder.addResolver({
|
|
19
|
-
name: 'myMethod',
|
|
20
|
-
propertyKey: 'myMethod',
|
|
21
|
-
type: 'Query',
|
|
22
|
-
service: mockService,
|
|
23
|
-
hasInput: false
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const stats = builder.getStats();
|
|
27
|
-
expect(stats.queries).toBe(1);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('categorizes Query resolvers', () => {
|
|
31
|
-
const mockService = { getUser: () => {} };
|
|
32
|
-
builder.addResolver({
|
|
33
|
-
name: 'getUser',
|
|
34
|
-
propertyKey: 'getUser',
|
|
35
|
-
type: 'Query',
|
|
36
|
-
service: mockService,
|
|
37
|
-
hasInput: false
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const stats = builder.getStats();
|
|
41
|
-
expect(stats.queries).toBe(1);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('categorizes Mutation resolvers', () => {
|
|
45
|
-
const mockService = { createUser: () => {} };
|
|
46
|
-
builder.addResolver({
|
|
47
|
-
name: 'createUser',
|
|
48
|
-
propertyKey: 'createUser',
|
|
49
|
-
type: 'Mutation',
|
|
50
|
-
service: mockService,
|
|
51
|
-
hasInput: true
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const stats = builder.getStats();
|
|
55
|
-
expect(stats.mutations).toBe(1);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('categorizes Subscription resolvers', () => {
|
|
59
|
-
const mockService = { userCreated: () => {} };
|
|
60
|
-
builder.addResolver({
|
|
61
|
-
name: 'userCreated',
|
|
62
|
-
propertyKey: 'userCreated',
|
|
63
|
-
type: 'Subscription',
|
|
64
|
-
service: mockService,
|
|
65
|
-
hasInput: false
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const stats = builder.getStats();
|
|
69
|
-
expect(stats.subscriptions).toBe(1);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('getResolvers()', () => {
|
|
74
|
-
test('returns empty object when no resolvers', () => {
|
|
75
|
-
const resolvers = builder.getResolvers();
|
|
76
|
-
expect(resolvers.Query).toEqual({});
|
|
77
|
-
expect(resolvers.Mutation).toEqual({});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('returns Query resolvers', () => {
|
|
81
|
-
const mockService = { getUser: () => 'user' };
|
|
82
|
-
builder.addResolver({
|
|
83
|
-
name: 'getUser',
|
|
84
|
-
propertyKey: 'getUser',
|
|
85
|
-
type: 'Query',
|
|
86
|
-
service: mockService,
|
|
87
|
-
hasInput: false
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const resolvers = builder.getResolvers();
|
|
91
|
-
expect(resolvers.Query!.getUser).toBeDefined();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('returns Mutation resolvers', () => {
|
|
95
|
-
const mockService = { createUser: () => 'user' };
|
|
96
|
-
builder.addResolver({
|
|
97
|
-
name: 'createUser',
|
|
98
|
-
propertyKey: 'createUser',
|
|
99
|
-
type: 'Mutation',
|
|
100
|
-
service: mockService,
|
|
101
|
-
hasInput: true
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const resolvers = builder.getResolvers();
|
|
105
|
-
expect(resolvers.Mutation!.createUser).toBeDefined();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('getResolversForType()', () => {
|
|
110
|
-
test('returns resolvers for specific type', () => {
|
|
111
|
-
const mockService = { getUser: () => {}, getUsers: () => {} };
|
|
112
|
-
builder.addResolver({
|
|
113
|
-
name: 'getUser',
|
|
114
|
-
propertyKey: 'getUser',
|
|
115
|
-
type: 'Query',
|
|
116
|
-
service: mockService,
|
|
117
|
-
hasInput: false
|
|
118
|
-
});
|
|
119
|
-
builder.addResolver({
|
|
120
|
-
name: 'getUsers',
|
|
121
|
-
propertyKey: 'getUsers',
|
|
122
|
-
type: 'Query',
|
|
123
|
-
service: mockService,
|
|
124
|
-
hasInput: false
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const queryResolvers = builder.getResolversForType('Query');
|
|
128
|
-
expect(Object.keys(queryResolvers).length).toBe(2);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('returns empty object for type with no resolvers', () => {
|
|
132
|
-
const resolvers = builder.getResolversForType('Subscription');
|
|
133
|
-
expect(resolvers).toEqual({});
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('createResolverWithoutInput()', () => {
|
|
138
|
-
test('creates resolver function', () => {
|
|
139
|
-
const mockService = {
|
|
140
|
-
getUser: async () => ({ id: '1', name: 'Test' })
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const resolver = (builder as any).createResolverWithoutInput(mockService, 'getUser');
|
|
144
|
-
expect(typeof resolver).toBe('function');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('resolver calls service method', async () => {
|
|
148
|
-
let called = false;
|
|
149
|
-
const mockService = {
|
|
150
|
-
getUser: async () => {
|
|
151
|
-
called = true;
|
|
152
|
-
return { id: '1' };
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const resolver = (builder as any).createResolverWithoutInput(mockService, 'getUser');
|
|
157
|
-
await resolver({}, {}, {}, {} as any);
|
|
158
|
-
expect(called).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('createResolverWithInput()', () => {
|
|
163
|
-
test('creates resolver function with input handling', () => {
|
|
164
|
-
const mockService = {
|
|
165
|
-
createUser: async (input: any) => ({ id: '1', ...input })
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const resolver = (builder as any).createResolverWithInput(mockService, 'createUser');
|
|
169
|
-
expect(typeof resolver).toBe('function');
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test('resolver passes input to service', async () => {
|
|
173
|
-
let receivedInput: any = null;
|
|
174
|
-
const mockService = {
|
|
175
|
-
createUser: async (input: any) => {
|
|
176
|
-
receivedInput = input;
|
|
177
|
-
return { id: '1', ...input };
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const resolver = (builder as any).createResolverWithInput(mockService, 'createUser');
|
|
182
|
-
await resolver({}, { input: { name: 'Test' } }, {}, {} as any);
|
|
183
|
-
expect(receivedInput).toEqual({ name: 'Test' });
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe('clear()', () => {
|
|
188
|
-
test('clears all resolvers', () => {
|
|
189
|
-
const mockService = { getUser: () => {} };
|
|
190
|
-
builder.addResolver({
|
|
191
|
-
name: 'getUser',
|
|
192
|
-
propertyKey: 'getUser',
|
|
193
|
-
type: 'Query',
|
|
194
|
-
service: mockService,
|
|
195
|
-
hasInput: false
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
builder.clear();
|
|
199
|
-
|
|
200
|
-
const stats = builder.getStats();
|
|
201
|
-
expect(stats.queries).toBe(0);
|
|
202
|
-
expect(stats.mutations).toBe(0);
|
|
203
|
-
expect(stats.subscriptions).toBe(0);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('getStats()', () => {
|
|
208
|
-
test('returns accurate statistics', () => {
|
|
209
|
-
const mockService = { m1: () => {}, m2: () => {}, m3: () => {} };
|
|
210
|
-
|
|
211
|
-
builder.addResolver({ name: 'q1', propertyKey: 'q1', type: 'Query', service: mockService, hasInput: false });
|
|
212
|
-
builder.addResolver({ name: 'q2', propertyKey: 'q2', type: 'Query', service: mockService, hasInput: false });
|
|
213
|
-
builder.addResolver({ name: 'm1', propertyKey: 'm1', type: 'Mutation', service: mockService, hasInput: true });
|
|
214
|
-
builder.addResolver({ name: 's1', propertyKey: 's1', type: 'Subscription', service: mockService, hasInput: false });
|
|
215
|
-
|
|
216
|
-
const stats = builder.getStats();
|
|
217
|
-
|
|
218
|
-
expect(stats.queries).toBe(2);
|
|
219
|
-
expect(stats.mutations).toBe(1);
|
|
220
|
-
expect(stats.subscriptions).toBe(1);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for TypeDefBuilder
|
|
3
|
-
* Tests GraphQL type definition building
|
|
4
|
-
*/
|
|
5
|
-
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
6
|
-
import { TypeDefBuilder } from '../../../gql/builders/TypeDefBuilder';
|
|
7
|
-
|
|
8
|
-
describe('TypeDefBuilder', () => {
|
|
9
|
-
let builder: TypeDefBuilder;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
builder = new TypeDefBuilder();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('addQueryField()', () => {
|
|
16
|
-
test('adds query field', () => {
|
|
17
|
-
builder.addQueryField({ name: 'getUser', fieldDef: 'getUser(id: ID!): User' });
|
|
18
|
-
const stats = builder.getStats();
|
|
19
|
-
expect(stats.queries).toBe(1);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('adds multiple query fields', () => {
|
|
23
|
-
builder.addQueryField({ name: 'getUser', fieldDef: 'getUser(id: ID!): User' });
|
|
24
|
-
builder.addQueryField({ name: 'getUsers', fieldDef: 'getUsers: [User!]!' });
|
|
25
|
-
const stats = builder.getStats();
|
|
26
|
-
expect(stats.queries).toBe(2);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('addMutationField()', () => {
|
|
31
|
-
test('adds mutation field', () => {
|
|
32
|
-
builder.addMutationField({ name: 'createUser', fieldDef: 'createUser(input: CreateUserInput!): User!' });
|
|
33
|
-
const stats = builder.getStats();
|
|
34
|
-
expect(stats.mutations).toBe(1);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('addSubscriptionField()', () => {
|
|
39
|
-
test('adds subscription field', () => {
|
|
40
|
-
builder.addSubscriptionField({ name: 'userCreated', fieldDef: 'userCreated: User!' });
|
|
41
|
-
const stats = builder.getStats();
|
|
42
|
-
expect(stats.subscriptions).toBe(1);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('buildQueryType()', () => {
|
|
47
|
-
test('returns empty string when no queries', () => {
|
|
48
|
-
const result = builder.buildQueryType();
|
|
49
|
-
expect(result).toBe('');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('builds Query type with fields', () => {
|
|
53
|
-
builder.addQueryField({ name: 'getUser', fieldDef: 'getUser(id: ID!): User' });
|
|
54
|
-
const result = builder.buildQueryType();
|
|
55
|
-
|
|
56
|
-
expect(result).toContain('type Query');
|
|
57
|
-
expect(result).toContain('getUser(id: ID!): User');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('sorts fields alphabetically', () => {
|
|
61
|
-
builder.addQueryField({ name: 'zebra', fieldDef: 'zebra: String' });
|
|
62
|
-
builder.addQueryField({ name: 'alpha', fieldDef: 'alpha: String' });
|
|
63
|
-
const result = builder.buildQueryType();
|
|
64
|
-
|
|
65
|
-
const alphaIndex = result.indexOf('alpha');
|
|
66
|
-
const zebraIndex = result.indexOf('zebra');
|
|
67
|
-
expect(alphaIndex).toBeLessThan(zebraIndex);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('buildMutationType()', () => {
|
|
72
|
-
test('returns empty string when no mutations', () => {
|
|
73
|
-
const result = builder.buildMutationType();
|
|
74
|
-
expect(result).toBe('');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('builds Mutation type with fields', () => {
|
|
78
|
-
builder.addMutationField({ name: 'createUser', fieldDef: 'createUser(input: CreateUserInput!): User!' });
|
|
79
|
-
const result = builder.buildMutationType();
|
|
80
|
-
|
|
81
|
-
expect(result).toContain('type Mutation');
|
|
82
|
-
expect(result).toContain('createUser');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('buildSubscriptionType()', () => {
|
|
87
|
-
test('returns empty string when no subscriptions', () => {
|
|
88
|
-
const result = builder.buildSubscriptionType();
|
|
89
|
-
expect(result).toBe('');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('builds Subscription type with fields', () => {
|
|
93
|
-
builder.addSubscriptionField({ name: 'userCreated', fieldDef: 'userCreated: User!' });
|
|
94
|
-
const result = builder.buildSubscriptionType();
|
|
95
|
-
|
|
96
|
-
expect(result).toContain('type Subscription');
|
|
97
|
-
expect(result).toContain('userCreated');
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('buildAllOperationTypes()', () => {
|
|
102
|
-
test('builds all operation types', () => {
|
|
103
|
-
builder.addQueryField({ name: 'getUser', fieldDef: 'getUser(id: ID!): User' });
|
|
104
|
-
builder.addMutationField({ name: 'createUser', fieldDef: 'createUser(input: CreateUserInput!): User!' });
|
|
105
|
-
builder.addSubscriptionField({ name: 'userCreated', fieldDef: 'userCreated: User!' });
|
|
106
|
-
|
|
107
|
-
const result = builder.buildAllOperationTypes();
|
|
108
|
-
|
|
109
|
-
expect(result).toContain('type Query');
|
|
110
|
-
expect(result).toContain('type Mutation');
|
|
111
|
-
expect(result).toContain('type Subscription');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('returns only defined types', () => {
|
|
115
|
-
builder.addQueryField({ name: 'getUser', fieldDef: 'getUser(id: ID!): User' });
|
|
116
|
-
|
|
117
|
-
const result = builder.buildAllOperationTypes();
|
|
118
|
-
|
|
119
|
-
expect(result).toContain('type Query');
|
|
120
|
-
expect(result).not.toContain('type Mutation');
|
|
121
|
-
expect(result).not.toContain('type Subscription');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('clear()', () => {
|
|
126
|
-
test('clears all fields', () => {
|
|
127
|
-
builder.addQueryField({ name: 'getUser', fieldDef: 'getUser: User' });
|
|
128
|
-
builder.addMutationField({ name: 'createUser', fieldDef: 'createUser: User' });
|
|
129
|
-
builder.addSubscriptionField({ name: 'userCreated', fieldDef: 'userCreated: User' });
|
|
130
|
-
|
|
131
|
-
builder.clear();
|
|
132
|
-
|
|
133
|
-
const stats = builder.getStats();
|
|
134
|
-
expect(stats.queries).toBe(0);
|
|
135
|
-
expect(stats.mutations).toBe(0);
|
|
136
|
-
expect(stats.subscriptions).toBe(0);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('getStats()', () => {
|
|
141
|
-
test('returns correct counts', () => {
|
|
142
|
-
builder.addQueryField({ name: 'q1', fieldDef: 'q1: String' });
|
|
143
|
-
builder.addQueryField({ name: 'q2', fieldDef: 'q2: String' });
|
|
144
|
-
builder.addMutationField({ name: 'm1', fieldDef: 'm1: String' });
|
|
145
|
-
|
|
146
|
-
const stats = builder.getStats();
|
|
147
|
-
|
|
148
|
-
expect(stats.queries).toBe(2);
|
|
149
|
-
expect(stats.mutations).toBe(1);
|
|
150
|
-
expect(stats.subscriptions).toBe(0);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
});
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ioredis-shaped client backed by a MockRedisStreamServer.
|
|
3
|
-
*
|
|
4
|
-
* Cast the returned instance to `Redis` (from "ioredis") when passing into
|
|
5
|
-
* the remote subsystem via `redisFactory`. Only methods the remote layer
|
|
6
|
-
* touches are implemented; others throw on use.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { MockRedisStreamServer } from "./MockRedisStreamServer";
|
|
10
|
-
|
|
11
|
-
export class MockRedisClient {
|
|
12
|
-
private server: MockRedisStreamServer;
|
|
13
|
-
private connected = true;
|
|
14
|
-
private listeners = new Map<string, Array<(...args: any[]) => void>>();
|
|
15
|
-
|
|
16
|
-
constructor(server: MockRedisStreamServer) {
|
|
17
|
-
this.server = server;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
on(event: string, listener: (...args: any[]) => void): this {
|
|
21
|
-
const arr = this.listeners.get(event) ?? [];
|
|
22
|
-
arr.push(listener);
|
|
23
|
-
this.listeners.set(event, arr);
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async xadd(key: string, ...args: any[]): Promise<string | null> {
|
|
28
|
-
this.ensureConnected();
|
|
29
|
-
try {
|
|
30
|
-
return this.server.xadd(key, ...args);
|
|
31
|
-
} catch (err: any) {
|
|
32
|
-
throw err;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async xgroup(...args: any[]): Promise<string> {
|
|
37
|
-
this.ensureConnected();
|
|
38
|
-
const [op, key, group, id, mk] = args;
|
|
39
|
-
return this.server.xgroup(op, key, group, id, mk);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async xreadgroup(...args: any[]): Promise<any> {
|
|
43
|
-
this.ensureConnected();
|
|
44
|
-
return this.server.xreadgroup(...args);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async xread(...args: any[]): Promise<any> {
|
|
48
|
-
this.ensureConnected();
|
|
49
|
-
return this.server.xread(...args);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async xack(key: string, group: string, msgId: string): Promise<number> {
|
|
53
|
-
this.ensureConnected();
|
|
54
|
-
return this.server.xack(key, group, msgId);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async xpending(...args: any[]): Promise<any> {
|
|
58
|
-
this.ensureConnected();
|
|
59
|
-
const [key, group, ...rest] = args;
|
|
60
|
-
return this.server.xpending(key, group, ...rest);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async xautoclaim(...args: any[]): Promise<any> {
|
|
64
|
-
this.ensureConnected();
|
|
65
|
-
return this.server.xautoclaim(
|
|
66
|
-
args[0],
|
|
67
|
-
args[1],
|
|
68
|
-
args[2],
|
|
69
|
-
Number(args[3]),
|
|
70
|
-
args[4],
|
|
71
|
-
...args.slice(5)
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async xlen(key: string): Promise<number> {
|
|
76
|
-
this.ensureConnected();
|
|
77
|
-
return this.server.xlen(key);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async xrange(...args: any[]): Promise<any> {
|
|
81
|
-
this.ensureConnected();
|
|
82
|
-
return this.server.xrange(
|
|
83
|
-
args[0],
|
|
84
|
-
args[1],
|
|
85
|
-
args[2],
|
|
86
|
-
...args.slice(3)
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async ping(): Promise<string> {
|
|
91
|
-
this.ensureConnected();
|
|
92
|
-
return this.server.ping();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
disconnect(): void {
|
|
96
|
-
this.connected = false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async quit(): Promise<string> {
|
|
100
|
-
this.connected = false;
|
|
101
|
-
return "OK";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private ensureConnected(): void {
|
|
105
|
-
if (!this.connected) {
|
|
106
|
-
throw new Error("Connection is closed");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function createMockRedisFactory(server: MockRedisStreamServer) {
|
|
112
|
-
return (_blocking: boolean) => new MockRedisClient(server);
|
|
113
|
-
}
|