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.
Files changed (257) hide show
  1. package/.claude/settings.local.json +47 -0
  2. package/.claude/skills/update-memory.md +74 -0
  3. package/.prettierrc +4 -0
  4. package/.serena/memories/architectural-decision-no-dependency-injection.md +76 -0
  5. package/.serena/memories/architecture.md +154 -0
  6. package/.serena/memories/cache-interface-refactoring-2026-01-24.md +165 -0
  7. package/.serena/memories/code_style_and_conventions.md +76 -0
  8. package/.serena/memories/project_overview.md +43 -0
  9. package/.serena/memories/schema-dsl-plan.md +107 -0
  10. package/.serena/memories/suggested_commands.md +80 -0
  11. package/.serena/memories/typescript-compilation-status.md +54 -0
  12. package/.serena/project.yml +114 -0
  13. package/TODO.md +1 -7
  14. package/bun.lock +150 -4
  15. package/bunfig.toml +10 -0
  16. package/config/cache.config.ts +77 -0
  17. package/config/upload.config.ts +4 -5
  18. package/core/App.ts +870 -123
  19. package/core/ArcheType.ts +2268 -377
  20. package/core/BatchLoader.ts +181 -71
  21. package/core/Config.ts +153 -0
  22. package/core/Decorators.ts +4 -1
  23. package/core/Entity.ts +621 -92
  24. package/core/EntityHookManager.ts +1 -1
  25. package/core/EntityInterface.ts +3 -1
  26. package/core/EntityManager.ts +1 -13
  27. package/core/ErrorHandler.ts +8 -2
  28. package/core/Logger.ts +9 -0
  29. package/core/Middleware.ts +34 -0
  30. package/core/RequestContext.ts +5 -1
  31. package/core/RequestLoaders.ts +227 -93
  32. package/core/SchedulerManager.ts +193 -52
  33. package/core/cache/CacheAnalytics.ts +399 -0
  34. package/core/cache/CacheFactory.ts +145 -0
  35. package/core/cache/CacheManager.ts +520 -0
  36. package/core/cache/CacheProvider.ts +34 -0
  37. package/core/cache/CacheWarmer.ts +157 -0
  38. package/core/cache/CompressionUtils.ts +110 -0
  39. package/core/cache/MemoryCache.ts +251 -0
  40. package/core/cache/MultiLevelCache.ts +180 -0
  41. package/core/cache/NoOpCache.ts +53 -0
  42. package/core/cache/RedisCache.ts +464 -0
  43. package/core/cache/TTLStrategy.ts +254 -0
  44. package/core/cache/index.ts +6 -0
  45. package/core/components/BaseComponent.ts +120 -0
  46. package/core/{ComponentRegistry.ts → components/ComponentRegistry.ts} +148 -54
  47. package/core/components/Decorators.ts +88 -0
  48. package/core/components/Interfaces.ts +7 -0
  49. package/core/components/index.ts +5 -0
  50. package/core/decorators/EntityHooks.ts +0 -3
  51. package/core/decorators/IndexedField.ts +26 -0
  52. package/core/decorators/ScheduledTask.ts +0 -47
  53. package/core/events/EntityLifecycleEvents.ts +1 -1
  54. package/core/health.ts +112 -0
  55. package/core/metadata/definitions/ArcheType.ts +14 -0
  56. package/core/metadata/definitions/Component.ts +9 -0
  57. package/core/metadata/definitions/gqlObject.ts +1 -1
  58. package/core/metadata/index.ts +42 -1
  59. package/core/metadata/metadata-storage.ts +28 -2
  60. package/core/middleware/AccessLog.ts +59 -0
  61. package/core/middleware/RequestId.ts +38 -0
  62. package/core/middleware/SecurityHeaders.ts +62 -0
  63. package/core/middleware/index.ts +3 -0
  64. package/core/scheduler/DistributedLock.ts +266 -0
  65. package/core/scheduler/index.ts +15 -0
  66. package/core/validateEnv.ts +92 -0
  67. package/database/DatabaseHelper.ts +416 -40
  68. package/database/IndexingStrategy.ts +342 -0
  69. package/database/PreparedStatementCache.ts +226 -0
  70. package/database/index.ts +32 -7
  71. package/database/sqlHelpers.ts +14 -2
  72. package/endpoints/archetypes.ts +362 -0
  73. package/endpoints/components.ts +58 -0
  74. package/endpoints/entity.ts +80 -0
  75. package/endpoints/index.ts +27 -0
  76. package/endpoints/query.ts +93 -0
  77. package/endpoints/stats.ts +76 -0
  78. package/endpoints/tables.ts +212 -0
  79. package/endpoints/types.ts +155 -0
  80. package/gql/ArchetypeOperations.ts +32 -86
  81. package/gql/Generator.ts +27 -315
  82. package/gql/GeneratorV2.ts +37 -0
  83. package/gql/builders/InputTypeBuilder.ts +99 -0
  84. package/gql/builders/ResolverBuilder.ts +234 -0
  85. package/gql/builders/TypeDefBuilder.ts +105 -0
  86. package/gql/builders/index.ts +3 -0
  87. package/gql/decorators/Upload.ts +1 -1
  88. package/gql/depthLimit.ts +85 -0
  89. package/gql/graph/GraphNode.ts +224 -0
  90. package/gql/graph/SchemaGraph.ts +278 -0
  91. package/gql/helpers.ts +8 -2
  92. package/gql/index.ts +56 -4
  93. package/gql/middleware.ts +79 -0
  94. package/gql/orchestration/GraphQLSchemaOrchestrator.ts +241 -0
  95. package/gql/orchestration/index.ts +1 -0
  96. package/gql/scanner/ServiceScanner.ts +347 -0
  97. package/gql/schema/index.ts +458 -0
  98. package/gql/strategies/TypeGenerationStrategy.ts +329 -0
  99. package/gql/types.ts +1 -0
  100. package/gql/utils/TypeSignature.ts +220 -0
  101. package/gql/utils/index.ts +1 -0
  102. package/gql/visitors/ArchetypePreprocessorVisitor.ts +80 -0
  103. package/gql/visitors/DeduplicationVisitor.ts +82 -0
  104. package/gql/visitors/GraphVisitor.ts +78 -0
  105. package/gql/visitors/ResolverGeneratorVisitor.ts +122 -0
  106. package/gql/visitors/SchemaGeneratorVisitor.ts +851 -0
  107. package/gql/visitors/TypeCollectorVisitor.ts +79 -0
  108. package/gql/visitors/VisitorComposer.ts +96 -0
  109. package/gql/visitors/index.ts +7 -0
  110. package/package.json +59 -37
  111. package/plugins/index.ts +2 -2
  112. package/query/CTENode.ts +97 -0
  113. package/query/ComponentInclusionNode.ts +689 -0
  114. package/query/FilterBuilder.ts +127 -0
  115. package/query/FilterBuilderRegistry.ts +202 -0
  116. package/query/OrNode.ts +517 -0
  117. package/query/OrQuery.ts +42 -0
  118. package/query/Query.ts +1022 -0
  119. package/query/QueryContext.ts +170 -0
  120. package/query/QueryDAG.ts +122 -0
  121. package/query/QueryNode.ts +65 -0
  122. package/query/SourceNode.ts +53 -0
  123. package/query/builders/FullTextSearchBuilder.ts +236 -0
  124. package/query/index.ts +21 -0
  125. package/scheduler/index.ts +40 -8
  126. package/service/Service.ts +2 -1
  127. package/service/ServiceRegistry.ts +6 -5
  128. package/{core/storage → storage}/LocalStorageProvider.ts +2 -2
  129. package/storage/S3StorageProvider.ts +316 -0
  130. package/{core/storage → storage}/StorageProvider.ts +7 -3
  131. package/studio/bun.lock +482 -0
  132. package/studio/index.html +13 -0
  133. package/studio/package.json +39 -0
  134. package/studio/postcss.config.js +6 -0
  135. package/studio/src/components/DataTable.tsx +211 -0
  136. package/studio/src/components/Layout.tsx +13 -0
  137. package/studio/src/components/PageContainer.tsx +9 -0
  138. package/studio/src/components/PageHeader.tsx +13 -0
  139. package/studio/src/components/SearchBar.tsx +57 -0
  140. package/studio/src/components/Sidebar.tsx +294 -0
  141. package/studio/src/components/ui/button.tsx +56 -0
  142. package/studio/src/components/ui/checkbox.tsx +26 -0
  143. package/studio/src/components/ui/input.tsx +25 -0
  144. package/studio/src/hooks/useDataTable.ts +131 -0
  145. package/studio/src/index.css +36 -0
  146. package/studio/src/lib/api.ts +186 -0
  147. package/studio/src/lib/utils.ts +13 -0
  148. package/studio/src/main.tsx +17 -0
  149. package/studio/src/pages/ArcheType.tsx +239 -0
  150. package/studio/src/pages/Components.tsx +124 -0
  151. package/studio/src/pages/EntityInspector.tsx +302 -0
  152. package/studio/src/pages/QueryRunner.tsx +246 -0
  153. package/studio/src/pages/Table.tsx +94 -0
  154. package/studio/src/pages/Welcome.tsx +241 -0
  155. package/studio/src/routes.tsx +45 -0
  156. package/studio/src/store/archeTypeSettings.ts +30 -0
  157. package/studio/src/store/studio.ts +65 -0
  158. package/studio/src/utils/columnHelpers.tsx +114 -0
  159. package/studio/studio-instructions.md +81 -0
  160. package/studio/tailwind.config.js +77 -0
  161. package/studio/tsconfig.json +24 -0
  162. package/studio/utils.ts +54 -0
  163. package/studio/vite.config.js +19 -0
  164. package/swagger/generator.ts +1 -1
  165. package/tests/e2e/http.test.ts +126 -0
  166. package/tests/fixtures/archetypes/TestUserArchetype.ts +21 -0
  167. package/tests/fixtures/components/TestOrder.ts +23 -0
  168. package/tests/fixtures/components/TestProduct.ts +23 -0
  169. package/tests/fixtures/components/TestUser.ts +20 -0
  170. package/tests/fixtures/components/index.ts +6 -0
  171. package/tests/graphql/SchemaGeneration.test.ts +90 -0
  172. package/tests/graphql/builders/ResolverBuilder.test.ts +223 -0
  173. package/tests/graphql/builders/TypeDefBuilder.test.ts +153 -0
  174. package/tests/integration/archetype/ArcheType.persistence.test.ts +241 -0
  175. package/tests/integration/cache/CacheInvalidation.test.ts +259 -0
  176. package/tests/integration/entity/Entity.persistence.test.ts +333 -0
  177. package/tests/integration/query/Query.exec.test.ts +523 -0
  178. package/tests/pglite-setup.ts +61 -0
  179. package/tests/setup.ts +164 -0
  180. package/tests/stress/BenchmarkRunner.ts +203 -0
  181. package/tests/stress/DataSeeder.ts +190 -0
  182. package/tests/stress/StressTestReporter.ts +229 -0
  183. package/tests/stress/cursor-perf-test.ts +171 -0
  184. package/tests/stress/fixtures/StressTestComponents.ts +58 -0
  185. package/tests/stress/index.ts +7 -0
  186. package/tests/stress/scenarios/query-benchmarks.test.ts +285 -0
  187. package/tests/unit/BatchLoader.test.ts +82 -0
  188. package/tests/unit/archetype/ArcheType.test.ts +107 -0
  189. package/tests/unit/cache/CacheManager.test.ts +347 -0
  190. package/tests/unit/cache/MemoryCache.test.ts +260 -0
  191. package/tests/unit/cache/RedisCache.test.ts +411 -0
  192. package/tests/unit/entity/Entity.components.test.ts +244 -0
  193. package/tests/unit/entity/Entity.test.ts +345 -0
  194. package/tests/unit/gql/depthLimit.test.ts +203 -0
  195. package/tests/unit/gql/operationMiddleware.test.ts +293 -0
  196. package/tests/unit/health/Health.test.ts +129 -0
  197. package/tests/unit/middleware/AccessLog.test.ts +37 -0
  198. package/tests/unit/middleware/Middleware.test.ts +98 -0
  199. package/tests/unit/middleware/RequestId.test.ts +54 -0
  200. package/tests/unit/middleware/SecurityHeaders.test.ts +66 -0
  201. package/tests/unit/query/FilterBuilder.test.ts +111 -0
  202. package/tests/unit/query/Query.test.ts +308 -0
  203. package/tests/unit/scheduler/DistributedLock.test.ts +274 -0
  204. package/tests/unit/schema/schema-integration.test.ts +426 -0
  205. package/tests/unit/schema/schema.test.ts +580 -0
  206. package/tests/unit/storage/S3StorageProvider.test.ts +571 -0
  207. package/tests/unit/upload/RestUpload.test.ts +267 -0
  208. package/tests/unit/validateEnv.test.ts +82 -0
  209. package/tests/utils/entity-tracker.ts +57 -0
  210. package/tests/utils/index.ts +13 -0
  211. package/tests/utils/test-context.ts +149 -0
  212. package/tsconfig.json +5 -1
  213. package/types/archetype.types.ts +6 -0
  214. package/types/hooks.types.ts +1 -1
  215. package/types/query.types.ts +110 -0
  216. package/types/scheduler.types.ts +68 -7
  217. package/types/upload.types.ts +1 -0
  218. package/{core → upload}/FileValidator.ts +10 -1
  219. package/upload/RestUpload.ts +130 -0
  220. package/{core/components → upload}/UploadComponent.ts +11 -11
  221. package/{core → upload}/UploadManager.ts +3 -3
  222. package/upload/index.ts +23 -7
  223. package/utils/UploadHelper.ts +27 -6
  224. package/utils/cronParser.ts +16 -6
  225. package/.github/workflows/deploy-docs.yml +0 -57
  226. package/core/Components.ts +0 -202
  227. package/core/EntityCache.ts +0 -15
  228. package/core/Query.ts +0 -880
  229. package/docs/README.md +0 -149
  230. package/docs/_coverpage.md +0 -36
  231. package/docs/_sidebar.md +0 -23
  232. package/docs/api/core.md +0 -568
  233. package/docs/api/hooks.md +0 -554
  234. package/docs/api/index.md +0 -222
  235. package/docs/api/query.md +0 -678
  236. package/docs/api/service.md +0 -744
  237. package/docs/core-concepts/archetypes.md +0 -512
  238. package/docs/core-concepts/components.md +0 -498
  239. package/docs/core-concepts/entity.md +0 -314
  240. package/docs/core-concepts/hooks.md +0 -683
  241. package/docs/core-concepts/query.md +0 -588
  242. package/docs/core-concepts/services.md +0 -647
  243. package/docs/examples/code-examples.md +0 -425
  244. package/docs/getting-started.md +0 -337
  245. package/docs/index.html +0 -97
  246. package/tests/bench/insert.bench.ts +0 -60
  247. package/tests/bench/relations.bench.ts +0 -270
  248. package/tests/bench/sorting.bench.ts +0 -416
  249. package/tests/component-hooks-simple.test.ts +0 -117
  250. package/tests/component-hooks.test.ts +0 -1461
  251. package/tests/component.test.ts +0 -339
  252. package/tests/errorHandling.test.ts +0 -155
  253. package/tests/hooks.test.ts +0 -667
  254. package/tests/query-sorting.test.ts +0 -101
  255. package/tests/query.test.ts +0 -81
  256. package/tests/relations.test.ts +0 -170
  257. 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;