bunsane 0.1.4 → 0.2.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/.claude/settings.local.json +47 -0
- package/.claude/skills/update-memory.md +74 -0
- package/.prettierrc +4 -0
- package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
- package/.serena/memories/architecture.md +154 -0
- package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
- package/.serena/memories/code_style_and_conventions.md +76 -0
- package/.serena/memories/project_overview.md +43 -0
- package/.serena/memories/schema-dsl-plan.md +107 -0
- package/.serena/memories/suggested_commands.md +80 -0
- package/.serena/memories/typescript-compilation-status.md +54 -0
- package/.serena/project.yml +114 -0
- package/TODO.md +1 -7
- package/bun.lock +150 -4
- package/bunfig.toml +10 -0
- package/config/cache.config.ts +77 -0
- package/config/upload.config.ts +4 -5
- package/core/App.ts +870 -123
- package/core/ArcheType.ts +2268 -377
- package/core/BatchLoader.ts +181 -71
- package/core/Config.ts +153 -0
- package/core/Decorators.ts +4 -1
- package/core/Entity.ts +621 -92
- package/core/EntityHookManager.ts +1 -1
- package/core/EntityInterface.ts +3 -1
- package/core/EntityManager.ts +1 -13
- package/core/ErrorHandler.ts +8 -2
- package/core/Logger.ts +9 -0
- package/core/Middleware.ts +34 -0
- package/core/RequestContext.ts +5 -1
- package/core/RequestLoaders.ts +227 -93
- package/core/SchedulerManager.ts +193 -52
- package/core/cache/CacheAnalytics.ts +399 -0
- package/core/cache/CacheFactory.ts +145 -0
- package/core/cache/CacheManager.ts +520 -0
- package/core/cache/CacheProvider.ts +34 -0
- package/core/cache/CacheWarmer.ts +157 -0
- package/core/cache/CompressionUtils.ts +110 -0
- package/core/cache/MemoryCache.ts +251 -0
- package/core/cache/MultiLevelCache.ts +180 -0
- package/core/cache/NoOpCache.ts +53 -0
- package/core/cache/RedisCache.ts +464 -0
- package/core/cache/TTLStrategy.ts +254 -0
- package/core/cache/index.ts +6 -0
- package/core/components/BaseComponent.ts +120 -0
- package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
- package/core/components/Decorators.ts +88 -0
- package/core/components/Interfaces.ts +7 -0
- package/core/components/index.ts +5 -0
- package/core/decorators/EntityHooks.ts +0 -3
- package/core/decorators/IndexedField.ts +26 -0
- package/core/decorators/ScheduledTask.ts +0 -47
- package/core/events/EntityLifecycleEvents.ts +1 -1
- package/core/health.ts +112 -0
- package/core/metadata/definitions/ArcheType.ts +14 -0
- package/core/metadata/definitions/Component.ts +9 -0
- package/core/metadata/definitions/gqlObject.ts +1 -1
- package/core/metadata/index.ts +42 -1
- package/core/metadata/metadata-storage.ts +28 -2
- package/core/middleware/AccessLog.ts +59 -0
- package/core/middleware/RequestId.ts +38 -0
- package/core/middleware/SecurityHeaders.ts +62 -0
- package/core/middleware/index.ts +3 -0
- package/core/scheduler/DistributedLock.ts +266 -0
- package/core/scheduler/index.ts +15 -0
- package/core/validateEnv.ts +92 -0
- package/database/DatabaseHelper.ts +416 -40
- package/database/IndexingStrategy.ts +342 -0
- package/database/PreparedStatementCache.ts +226 -0
- package/database/index.ts +32 -7
- package/database/sqlHelpers.ts +14 -2
- package/endpoints/archetypes.ts +362 -0
- package/endpoints/components.ts +58 -0
- package/endpoints/entity.ts +80 -0
- package/endpoints/index.ts +27 -0
- package/endpoints/query.ts +93 -0
- package/endpoints/stats.ts +76 -0
- package/endpoints/tables.ts +212 -0
- package/endpoints/types.ts +155 -0
- package/gql/ArchetypeOperations.ts +32 -86
- package/gql/Generator.ts +27 -315
- package/gql/GeneratorV2.ts +37 -0
- package/gql/builders/InputTypeBuilder.ts +99 -0
- package/gql/builders/ResolverBuilder.ts +234 -0
- package/gql/builders/TypeDefBuilder.ts +105 -0
- package/gql/builders/index.ts +3 -0
- package/gql/decorators/Upload.ts +1 -1
- package/gql/depthLimit.ts +85 -0
- package/gql/graph/GraphNode.ts +224 -0
- package/gql/graph/SchemaGraph.ts +278 -0
- package/gql/helpers.ts +8 -2
- package/gql/index.ts +56 -4
- package/gql/middleware.ts +79 -0
- package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
- package/gql/orchestration/index.ts +1 -0
- package/gql/scanner/ServiceScanner.ts +347 -0
- package/gql/schema/index.ts +458 -0
- package/gql/strategies/TypeGenerationStrategy.ts +329 -0
- package/gql/types.ts +1 -0
- package/gql/utils/TypeSignature.ts +220 -0
- package/gql/utils/index.ts +1 -0
- package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
- package/gql/visitors/DeduplicationVisitor.ts +82 -0
- package/gql/visitors/GraphVisitor.ts +78 -0
- package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
- package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
- package/gql/visitors/TypeCollectorVisitor.ts +79 -0
- package/gql/visitors/VisitorComposer.ts +96 -0
- package/gql/visitors/index.ts +7 -0
- package/package.json +59 -37
- package/plugins/index.ts +2 -2
- package/query/CTENode.ts +97 -0
- package/query/ComponentInclusionNode.ts +689 -0
- package/query/FilterBuilder.ts +127 -0
- package/query/FilterBuilderRegistry.ts +202 -0
- package/query/OrNode.ts +517 -0
- package/query/OrQuery.ts +42 -0
- package/query/Query.ts +1022 -0
- package/query/QueryContext.ts +170 -0
- package/query/QueryDAG.ts +122 -0
- package/query/QueryNode.ts +65 -0
- package/query/SourceNode.ts +53 -0
- package/query/builders/FullTextSearchBuilder.ts +236 -0
- package/query/index.ts +21 -0
- package/scheduler/index.ts +40 -8
- package/service/Service.ts +2 -1
- package/service/ServiceRegistry.ts +6 -5
- package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
- package/storage/S3StorageProvider.ts +316 -0
- package/{core/storage → storage}/StorageProvider.ts +7 -3
- package/studio/bun.lock +482 -0
- package/studio/index.html +13 -0
- package/studio/package.json +39 -0
- package/studio/postcss.config.js +6 -0
- package/studio/src/components/DataTable.tsx +211 -0
- package/studio/src/components/Layout.tsx +13 -0
- package/studio/src/components/PageContainer.tsx +9 -0
- package/studio/src/components/PageHeader.tsx +13 -0
- package/studio/src/components/SearchBar.tsx +57 -0
- package/studio/src/components/Sidebar.tsx +294 -0
- package/studio/src/components/ui/button.tsx +56 -0
- package/studio/src/components/ui/checkbox.tsx +26 -0
- package/studio/src/components/ui/input.tsx +25 -0
- package/studio/src/hooks/useDataTable.ts +131 -0
- package/studio/src/index.css +36 -0
- package/studio/src/lib/api.ts +186 -0
- package/studio/src/lib/utils.ts +13 -0
- package/studio/src/main.tsx +17 -0
- package/studio/src/pages/ArcheType.tsx +239 -0
- package/studio/src/pages/Components.tsx +124 -0
- package/studio/src/pages/EntityInspector.tsx +302 -0
- package/studio/src/pages/QueryRunner.tsx +246 -0
- package/studio/src/pages/Table.tsx +94 -0
- package/studio/src/pages/Welcome.tsx +241 -0
- package/studio/src/routes.tsx +45 -0
- package/studio/src/store/archeTypeSettings.ts +30 -0
- package/studio/src/store/studio.ts +65 -0
- package/studio/src/utils/columnHelpers.tsx +114 -0
- package/studio/studio-instructions.md +81 -0
- package/studio/tailwind.config.js +77 -0
- package/studio/tsconfig.json +24 -0
- package/studio/utils.ts +54 -0
- package/studio/vite.config.js +19 -0
- package/swagger/generator.ts +1 -1
- package/tests/e2e/http.test.ts +126 -0
- package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
- package/tests/fixtures/components/TestOrder.ts +23 -0
- package/tests/fixtures/components/TestProduct.ts +23 -0
- package/tests/fixtures/components/TestUser.ts +20 -0
- package/tests/fixtures/components/index.ts +6 -0
- package/tests/graphql/SchemaGeneration.test.ts +90 -0
- package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
- package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
- package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
- package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
- package/tests/integration/entity/Entity.persistence.test.ts +333 -0
- package/tests/integration/query/Query.exec.test.ts +523 -0
- package/tests/pglite-setup.ts +61 -0
- package/tests/setup.ts +164 -0
- package/tests/stress/BenchmarkRunner.ts +203 -0
- package/tests/stress/DataSeeder.ts +190 -0
- package/tests/stress/StressTestReporter.ts +229 -0
- package/tests/stress/cursor-perf-test.ts +171 -0
- package/tests/stress/fixtures/StressTestComponents.ts +58 -0
- package/tests/stress/index.ts +7 -0
- package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
- package/tests/unit/BatchLoader.test.ts +82 -0
- package/tests/unit/archetype/ArcheType.test.ts +107 -0
- package/tests/unit/cache/CacheManager.test.ts +347 -0
- package/tests/unit/cache/MemoryCache.test.ts +260 -0
- package/tests/unit/cache/RedisCache.test.ts +411 -0
- package/tests/unit/entity/Entity.components.test.ts +244 -0
- package/tests/unit/entity/Entity.test.ts +345 -0
- package/tests/unit/gql/depthLimit.test.ts +203 -0
- package/tests/unit/gql/operationMiddleware.test.ts +293 -0
- package/tests/unit/health/Health.test.ts +129 -0
- package/tests/unit/middleware/AccessLog.test.ts +37 -0
- package/tests/unit/middleware/Middleware.test.ts +98 -0
- package/tests/unit/middleware/RequestId.test.ts +54 -0
- package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
- package/tests/unit/query/FilterBuilder.test.ts +111 -0
- package/tests/unit/query/Query.test.ts +308 -0
- package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
- package/tests/unit/schema/schema-integration.test.ts +426 -0
- package/tests/unit/schema/schema.test.ts +580 -0
- package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
- package/tests/unit/upload/RestUpload.test.ts +267 -0
- package/tests/unit/validateEnv.test.ts +82 -0
- package/tests/utils/entity-tracker.ts +57 -0
- package/tests/utils/index.ts +13 -0
- package/tests/utils/test-context.ts +149 -0
- package/tsconfig.json +5 -1
- package/types/archetype.types.ts +6 -0
- package/types/hooks.types.ts +1 -1
- package/types/query.types.ts +110 -0
- package/types/scheduler.types.ts +68 -7
- package/types/upload.types.ts +1 -0
- package/{core → upload}/FileValidator.ts +10 -1
- package/upload/RestUpload.ts +130 -0
- package/{core/components → upload}/UploadComponent.ts +11 -11
- package/{core → upload}/UploadManager.ts +3 -3
- package/upload/index.ts +23 -7
- package/utils/UploadHelper.ts +27 -6
- package/utils/cronParser.ts +16 -6
- package/.github/workflows/deploy-docs.yml +0 -57
- package/core/Components.ts +0 -202
- package/core/EntityCache.ts +0 -15
- package/core/Query.ts +0 -880
- package/docs/README.md +0 -149
- package/docs/_coverpage.md +0 -36
- package/docs/_sidebar.md +0 -23
- package/docs/api/core.md +0 -568
- package/docs/api/hooks.md +0 -554
- package/docs/api/index.md +0 -222
- package/docs/api/query.md +0 -678
- package/docs/api/service.md +0 -744
- package/docs/core-concepts/archetypes.md +0 -512
- package/docs/core-concepts/components.md +0 -498
- package/docs/core-concepts/entity.md +0 -314
- package/docs/core-concepts/hooks.md +0 -683
- package/docs/core-concepts/query.md +0 -588
- package/docs/core-concepts/services.md +0 -647
- package/docs/examples/code-examples.md +0 -425
- package/docs/getting-started.md +0 -337
- package/docs/index.html +0 -97
- package/tests/bench/insert.bench.ts +0 -60
- package/tests/bench/relations.bench.ts +0 -270
- package/tests/bench/sorting.bench.ts +0 -416
- package/tests/component-hooks-simple.test.ts +0 -117
- package/tests/component-hooks.test.ts +0 -1461
- package/tests/component.test.ts +0 -339
- package/tests/errorHandling.test.ts +0 -155
- package/tests/hooks.test.ts +0 -667
- package/tests/query-sorting.test.ts +0 -101
- package/tests/query.test.ts +0 -81
- package/tests/relations.test.ts +0 -170
- package/tests/scheduler.test.ts +0 -724
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { z, type ZodType } from "zod";
|
|
2
|
+
|
|
3
|
+
// ─── Marker ────────────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export const SCHEMA_DSL_MARKER = Symbol.for("bunsane.schema_dsl");
|
|
6
|
+
|
|
7
|
+
// ─── Constraint Types ──────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
type Constraint =
|
|
10
|
+
| { kind: "min"; value: number }
|
|
11
|
+
| { kind: "max"; value: number }
|
|
12
|
+
| { kind: "minLength"; value: number }
|
|
13
|
+
| { kind: "maxLength"; value: number }
|
|
14
|
+
| { kind: "email" }
|
|
15
|
+
| { kind: "url" }
|
|
16
|
+
| { kind: "uuid" }
|
|
17
|
+
| { kind: "pattern"; value: RegExp };
|
|
18
|
+
|
|
19
|
+
// ─── SchemaType Interface ──────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface SchemaType<T = unknown> {
|
|
22
|
+
readonly [SCHEMA_DSL_MARKER]: true;
|
|
23
|
+
readonly _output: T;
|
|
24
|
+
readonly _required: boolean;
|
|
25
|
+
readonly _nullable: boolean;
|
|
26
|
+
readonly _graphqlType: string;
|
|
27
|
+
required(): this;
|
|
28
|
+
optional(): this;
|
|
29
|
+
nullable(): this;
|
|
30
|
+
toGraphQL(): string;
|
|
31
|
+
toZod(): ZodType;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Inference Utilities ───────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export type InferField<T extends SchemaType> =
|
|
37
|
+
T extends { _required: true } ? T["_output"] : T["_output"] | undefined;
|
|
38
|
+
|
|
39
|
+
export type InferInput<T extends Record<string, SchemaType>> = {
|
|
40
|
+
[K in keyof T as T[K] extends { _required: true } ? K : never]: T[K]["_output"];
|
|
41
|
+
} & {
|
|
42
|
+
[K in keyof T as T[K] extends { _required: true } ? never : K]?: T[K]["_output"];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ─── Base Abstract Class ───────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export abstract class BaseSchemaType<T = unknown> implements SchemaType<T> {
|
|
48
|
+
readonly [SCHEMA_DSL_MARKER] = true as const;
|
|
49
|
+
declare readonly _output: T;
|
|
50
|
+
_required: boolean = false;
|
|
51
|
+
_nullable: boolean = false;
|
|
52
|
+
protected _internalGraphqlType: string = "";
|
|
53
|
+
protected _constraints: Constraint[] = [];
|
|
54
|
+
|
|
55
|
+
get _graphqlType(): string {
|
|
56
|
+
return this._internalGraphqlType;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
set _graphqlType(value: string) {
|
|
60
|
+
this._internalGraphqlType = value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
required(): this {
|
|
64
|
+
this._required = true;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
optional(): this {
|
|
69
|
+
this._required = false;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
nullable(): this {
|
|
74
|
+
this._nullable = true;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
toGraphQL(): string {
|
|
79
|
+
return this._required ? `${this._graphqlType}!` : this._graphqlType;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
abstract toZod(): ZodType;
|
|
83
|
+
|
|
84
|
+
protected wrapZod(schema: ZodType): ZodType {
|
|
85
|
+
let result = schema;
|
|
86
|
+
if (this._nullable) {
|
|
87
|
+
result = result.nullable();
|
|
88
|
+
}
|
|
89
|
+
if (!this._required) {
|
|
90
|
+
result = result.optional();
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Scalar Builders ───────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
class StringType extends BaseSchemaType<string> {
|
|
99
|
+
override _internalGraphqlType = "String";
|
|
100
|
+
|
|
101
|
+
minLength(value: number): this {
|
|
102
|
+
this._constraints.push({ kind: "minLength", value });
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
maxLength(value: number): this {
|
|
107
|
+
this._constraints.push({ kind: "maxLength", value });
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
email(): this {
|
|
112
|
+
this._constraints.push({ kind: "email" });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
url(): this {
|
|
117
|
+
this._constraints.push({ kind: "url" });
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
uuid(): this {
|
|
122
|
+
this._constraints.push({ kind: "uuid" });
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pattern(regex: RegExp): this {
|
|
127
|
+
this._constraints.push({ kind: "pattern", value: regex });
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
toZod(): ZodType {
|
|
132
|
+
let schema = z.string();
|
|
133
|
+
for (const c of this._constraints) {
|
|
134
|
+
switch (c.kind) {
|
|
135
|
+
case "minLength":
|
|
136
|
+
schema = schema.min(c.value);
|
|
137
|
+
break;
|
|
138
|
+
case "maxLength":
|
|
139
|
+
schema = schema.max(c.value);
|
|
140
|
+
break;
|
|
141
|
+
case "email":
|
|
142
|
+
schema = schema.email();
|
|
143
|
+
break;
|
|
144
|
+
case "url":
|
|
145
|
+
schema = schema.url();
|
|
146
|
+
break;
|
|
147
|
+
case "uuid":
|
|
148
|
+
schema = schema.uuid();
|
|
149
|
+
break;
|
|
150
|
+
case "pattern":
|
|
151
|
+
schema = schema.regex(c.value);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return this.wrapZod(schema);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class IntType extends BaseSchemaType<number> {
|
|
160
|
+
override _internalGraphqlType = "Int";
|
|
161
|
+
|
|
162
|
+
min(value: number): this {
|
|
163
|
+
this._constraints.push({ kind: "min", value });
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
max(value: number): this {
|
|
168
|
+
this._constraints.push({ kind: "max", value });
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
toZod(): ZodType {
|
|
173
|
+
let schema = z.int();
|
|
174
|
+
for (const c of this._constraints) {
|
|
175
|
+
switch (c.kind) {
|
|
176
|
+
case "min":
|
|
177
|
+
schema = schema.min(c.value);
|
|
178
|
+
break;
|
|
179
|
+
case "max":
|
|
180
|
+
schema = schema.max(c.value);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return this.wrapZod(schema);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class FloatType extends BaseSchemaType<number> {
|
|
189
|
+
override _internalGraphqlType = "Float";
|
|
190
|
+
|
|
191
|
+
min(value: number): this {
|
|
192
|
+
this._constraints.push({ kind: "min", value });
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
max(value: number): this {
|
|
197
|
+
this._constraints.push({ kind: "max", value });
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
toZod(): ZodType {
|
|
202
|
+
let schema = z.number();
|
|
203
|
+
for (const c of this._constraints) {
|
|
204
|
+
switch (c.kind) {
|
|
205
|
+
case "min":
|
|
206
|
+
schema = schema.min(c.value);
|
|
207
|
+
break;
|
|
208
|
+
case "max":
|
|
209
|
+
schema = schema.max(c.value);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return this.wrapZod(schema);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
class BooleanType extends BaseSchemaType<boolean> {
|
|
218
|
+
override _internalGraphqlType = "Boolean";
|
|
219
|
+
|
|
220
|
+
toZod(): ZodType {
|
|
221
|
+
return this.wrapZod(z.boolean());
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
class IDType extends BaseSchemaType<string> {
|
|
226
|
+
override _internalGraphqlType = "ID";
|
|
227
|
+
|
|
228
|
+
toZod(): ZodType {
|
|
229
|
+
return this.wrapZod(z.string());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Composite Builders ────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
export class RefType<T = unknown> extends BaseSchemaType<T> {
|
|
236
|
+
private readonly _zodSchema: ZodType;
|
|
237
|
+
|
|
238
|
+
constructor(graphqlTypeName: string, zodSchema?: ZodType) {
|
|
239
|
+
super();
|
|
240
|
+
this._internalGraphqlType = graphqlTypeName;
|
|
241
|
+
this._zodSchema = zodSchema ?? z.any();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
toZod(): ZodType {
|
|
245
|
+
return this.wrapZod(this._zodSchema);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export class ObjectType<T extends Record<string, SchemaType> = Record<string, SchemaType>> extends BaseSchemaType<{
|
|
250
|
+
[K in keyof T]: InferField<T[K]>;
|
|
251
|
+
}> {
|
|
252
|
+
readonly shape: T;
|
|
253
|
+
private readonly _typeName: string;
|
|
254
|
+
|
|
255
|
+
constructor(shape: T, typeName: string) {
|
|
256
|
+
super();
|
|
257
|
+
this.shape = shape;
|
|
258
|
+
this._typeName = typeName;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
override get _graphqlType(): string {
|
|
262
|
+
return this._typeName;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
override set _graphqlType(_v: string) {
|
|
266
|
+
// no-op: type name is set via constructor
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
toGraphQLTypeDef(): string {
|
|
270
|
+
const fields = Object.entries(this.shape)
|
|
271
|
+
.map(([name, field]) => ` ${name}: ${field.toGraphQL()}`)
|
|
272
|
+
.join("\n");
|
|
273
|
+
return `input ${this._typeName} {\n${fields}\n}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
toZod(): ZodType {
|
|
277
|
+
const shape: Record<string, ZodType> = {};
|
|
278
|
+
for (const [key, field] of Object.entries(this.shape)) {
|
|
279
|
+
shape[key] = field.toZod();
|
|
280
|
+
}
|
|
281
|
+
return this.wrapZod(z.object(shape));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export class ListType<T extends SchemaType = SchemaType> extends BaseSchemaType<T["_output"][]> {
|
|
286
|
+
readonly element: T;
|
|
287
|
+
|
|
288
|
+
constructor(element: T) {
|
|
289
|
+
super();
|
|
290
|
+
this.element = element;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
override get _graphqlType(): string {
|
|
294
|
+
return `[${this.element.toGraphQL()}]`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
override set _graphqlType(_v: string) {
|
|
298
|
+
// no-op: type is derived from element
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
minItems(value: number): this {
|
|
302
|
+
this._constraints.push({ kind: "min", value });
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
maxItems(value: number): this {
|
|
307
|
+
this._constraints.push({ kind: "max", value });
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
toZod(): ZodType {
|
|
312
|
+
let schema = z.array(this.element.toZod());
|
|
313
|
+
for (const c of this._constraints) {
|
|
314
|
+
switch (c.kind) {
|
|
315
|
+
case "min":
|
|
316
|
+
schema = schema.min(c.value);
|
|
317
|
+
break;
|
|
318
|
+
case "max":
|
|
319
|
+
schema = schema.max(c.value);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return this.wrapZod(schema);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export class EnumType<T extends readonly string[] = readonly string[]> extends BaseSchemaType<T[number]> {
|
|
328
|
+
readonly values: T;
|
|
329
|
+
private readonly _enumName: string;
|
|
330
|
+
|
|
331
|
+
constructor(values: T, enumName: string) {
|
|
332
|
+
super();
|
|
333
|
+
this.values = values;
|
|
334
|
+
this._enumName = enumName;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
override get _graphqlType(): string {
|
|
338
|
+
return this._enumName;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
override set _graphqlType(_v: string) {
|
|
342
|
+
// no-op: type name is set via constructor
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
toGraphQLTypeDef(): string {
|
|
346
|
+
const entries = this.values.join("\n ");
|
|
347
|
+
return `enum ${this._enumName} {\n ${entries}\n}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
toZod(): ZodType {
|
|
351
|
+
return this.wrapZod(z.enum(this.values as unknown as readonly [string, ...string[]]));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ─── Nested Type Definition Collector ──────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
export function collectNestedTypeDefs(
|
|
358
|
+
shape: Record<string, SchemaType>,
|
|
359
|
+
): Map<string, string> {
|
|
360
|
+
const defs = new Map<string, string>();
|
|
361
|
+
|
|
362
|
+
for (const field of Object.values(shape)) {
|
|
363
|
+
collectFromField(field, defs);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return defs;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function collectFromField(field: SchemaType, defs: Map<string, string>): void {
|
|
370
|
+
if (field instanceof ObjectType) {
|
|
371
|
+
// Depth-first: recurse into nested shape first (dependencies before dependents)
|
|
372
|
+
for (const nested of Object.values(field.shape) as SchemaType[]) {
|
|
373
|
+
collectFromField(nested, defs);
|
|
374
|
+
}
|
|
375
|
+
if (!defs.has(field._graphqlType)) {
|
|
376
|
+
defs.set(field._graphqlType, field.toGraphQLTypeDef());
|
|
377
|
+
}
|
|
378
|
+
} else if (field instanceof ListType) {
|
|
379
|
+
collectFromField(field.element, defs);
|
|
380
|
+
} else if (field instanceof EnumType) {
|
|
381
|
+
if (!defs.has(field._graphqlType)) {
|
|
382
|
+
defs.set(field._graphqlType, field.toGraphQLTypeDef());
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ─── Detection Utilities ───────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
export function isSchemaType(value: unknown): value is SchemaType {
|
|
390
|
+
return (
|
|
391
|
+
typeof value === "object" &&
|
|
392
|
+
value !== null &&
|
|
393
|
+
SCHEMA_DSL_MARKER in value &&
|
|
394
|
+
(value as Record<symbol, unknown>)[SCHEMA_DSL_MARKER] === true
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function isSchemaInput(
|
|
399
|
+
input: unknown,
|
|
400
|
+
): input is Record<string, SchemaType> {
|
|
401
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return false;
|
|
402
|
+
if ("_def" in input) return false;
|
|
403
|
+
const entries = Object.values(input);
|
|
404
|
+
return entries.length > 0 && entries.every(isSchemaType);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── Validation ─────────────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
export class SchemaValidationError extends Error {
|
|
410
|
+
readonly fieldErrors: Array<{ path: string; message: string }>;
|
|
411
|
+
|
|
412
|
+
constructor(operationName: string, fieldErrors: Array<{ path: string; message: string }>) {
|
|
413
|
+
const details = fieldErrors
|
|
414
|
+
.map(e => ` ${operationName}.${e.path}: ${e.message}`)
|
|
415
|
+
.join("\n");
|
|
416
|
+
super(`Validation failed for ${operationName}:\n${details}`);
|
|
417
|
+
this.name = "SchemaValidationError";
|
|
418
|
+
this.fieldErrors = fieldErrors;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function validateInput<T extends Record<string, SchemaType>>(
|
|
423
|
+
schema: T,
|
|
424
|
+
data: unknown,
|
|
425
|
+
operationName: string = "input",
|
|
426
|
+
): InferInput<T> {
|
|
427
|
+
const zodShape: Record<string, ZodType> = {};
|
|
428
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
429
|
+
zodShape[key] = field.toZod();
|
|
430
|
+
}
|
|
431
|
+
const result = z.object(zodShape).safeParse(data);
|
|
432
|
+
if (result.success) {
|
|
433
|
+
return result.data as InferInput<T>;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const fieldErrors = result.error.issues.map(issue => ({
|
|
437
|
+
path: issue.path.join(".") || "(root)",
|
|
438
|
+
message: issue.message,
|
|
439
|
+
}));
|
|
440
|
+
throw new SchemaValidationError(operationName, fieldErrors);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ─── Public API ────────────────────────────────────────────────────────────────
|
|
444
|
+
|
|
445
|
+
export const t = {
|
|
446
|
+
string: () => new StringType(),
|
|
447
|
+
int: () => new IntType(),
|
|
448
|
+
float: () => new FloatType(),
|
|
449
|
+
boolean: () => new BooleanType(),
|
|
450
|
+
id: () => new IDType(),
|
|
451
|
+
object: <T extends Record<string, SchemaType>>(shape: T, name: string) =>
|
|
452
|
+
new ObjectType(shape, name),
|
|
453
|
+
list: <T extends SchemaType>(element: T) => new ListType(element),
|
|
454
|
+
enum: <T extends readonly string[]>(values: T, name: string) =>
|
|
455
|
+
new EnumType(values, name),
|
|
456
|
+
ref: <T = unknown>(typeName: string, zodSchema?: ZodType) =>
|
|
457
|
+
new RefType<T>(typeName, zodSchema),
|
|
458
|
+
} as const;
|