@danceroutine/tango-openapi 0.1.0 → 1.0.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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/dist/chunk-BkvOhyD0.js +12 -0
  4. package/dist/domain/describeResources.d.ts +10 -0
  5. package/dist/domain/index.d.ts +2 -1
  6. package/dist/domain/index.js +3 -4
  7. package/dist/domain/types.d.ts +76 -12
  8. package/dist/domain-B-7sApJT.js +34 -0
  9. package/dist/domain-B-7sApJT.js.map +1 -0
  10. package/dist/generators/index.js +2 -2
  11. package/dist/generators/spec/generateOpenAPISpec.d.ts +3 -0
  12. package/dist/generators-JMALItMS.js +346 -0
  13. package/dist/generators-JMALItMS.js.map +1 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.js +5 -10
  16. package/dist/mappers/index.d.ts +1 -1
  17. package/dist/mappers/index.js +3 -3
  18. package/dist/mappers/schema/generateSchemaFromModel.d.ts +3 -0
  19. package/dist/mappers/schema/generateSchemaFromZod.d.ts +6 -0
  20. package/dist/mappers/schema/index.d.ts +1 -0
  21. package/dist/mappers/schema/mapTypeToOpenAPI.d.ts +3 -0
  22. package/dist/mappers-bmN95TGV.js +15 -0
  23. package/dist/{mappers-CIfnOwl2.js.map → mappers-bmN95TGV.js.map} +1 -1
  24. package/dist/{schema-DVxdID48.js → schema-D3ybOrpr.js} +19 -12
  25. package/dist/schema-D3ybOrpr.js.map +1 -0
  26. package/package.json +57 -53
  27. package/dist/domain/types.js +0 -1
  28. package/dist/domain-Cufz6y1q.js +0 -7
  29. package/dist/domain-Cufz6y1q.js.map +0 -1
  30. package/dist/generators/spec/generateOpenAPISpec.js +0 -133
  31. package/dist/generators-Bvwyyja4.js +0 -131
  32. package/dist/generators-Bvwyyja4.js.map +0 -1
  33. package/dist/index.js.map +0 -1
  34. package/dist/mappers/schema/generateSchemaFromModel.js +0 -19
  35. package/dist/mappers-CIfnOwl2.js +0 -13
  36. package/dist/schema-DVxdID48.js.map +0 -1
  37. package/dist/version.d.ts +0 -1
@@ -0,0 +1,346 @@
1
+ import { __export } from "./chunk-BkvOhyD0.js";
2
+ import { generateSchemaFromModel, generateSchemaFromZod } from "./schema-D3ybOrpr.js";
3
+ import { z } from "zod";
4
+
5
+ //#region src/generators/spec/generateOpenAPISpec.ts
6
+ const JSON_CONTENT_TYPE = "application/json";
7
+ function toOpenAPIModel(metadata) {
8
+ return {
9
+ name: metadata.name,
10
+ fields: Object.fromEntries(metadata.fields.map((field) => [field.name, {
11
+ type: field.type,
12
+ nullable: field.notNull !== true,
13
+ default: field.default,
14
+ primaryKey: field.primaryKey
15
+ }]))
16
+ };
17
+ }
18
+ function normalizePath(path) {
19
+ const trimmed = path.trim();
20
+ if (!trimmed) throw new Error("OpenAPI paths must not be empty.");
21
+ if (/(^|\/):/.test(trimmed)) throw new Error(`OpenAPI paths must use {param} syntax, received '${trimmed}'.`);
22
+ const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
23
+ if (withLeadingSlash === "/") return "/";
24
+ return withLeadingSlash.replace(/\/+$/g, "");
25
+ }
26
+ function joinPath(base, segment) {
27
+ const normalizedSegment = segment.replace(/^\/+|\/+$/g, "");
28
+ return base === "/" ? `/${normalizedSegment}` : `${base}/${normalizedSegment}`;
29
+ }
30
+ function toMethodKey(method) {
31
+ return method.toLowerCase();
32
+ }
33
+ function isReferenceObject(value) {
34
+ return typeof value === "object" && value !== null && typeof value.$ref === "string";
35
+ }
36
+ function isZodSchema(value) {
37
+ return value instanceof z.ZodType;
38
+ }
39
+ function toSchema(value) {
40
+ if (isReferenceObject(value)) return value;
41
+ if (isZodSchema(value)) return generateSchemaFromZod(value);
42
+ return value;
43
+ }
44
+ function arraySchema(items) {
45
+ return {
46
+ type: "array",
47
+ items
48
+ };
49
+ }
50
+ function paginatedResultsSchema(items) {
51
+ return {
52
+ type: "object",
53
+ properties: {
54
+ count: { type: "integer" },
55
+ next: { type: "string" },
56
+ previous: { type: "string" },
57
+ results: arraySchema(items)
58
+ },
59
+ required: ["count", "results"]
60
+ };
61
+ }
62
+ function jsonResponse(description, schema) {
63
+ return schema ? {
64
+ description,
65
+ content: { [JSON_CONTENT_TYPE]: { schema } }
66
+ } : { description };
67
+ }
68
+ function jsonRequestBody(schema, required = true) {
69
+ return {
70
+ required,
71
+ content: { [JSON_CONTENT_TYPE]: { schema } }
72
+ };
73
+ }
74
+ function withOverride(operation, override, fallbackTags) {
75
+ if (!override) return operation;
76
+ const nextOperation = {
77
+ ...operation,
78
+ summary: override.summary ?? operation.summary,
79
+ description: override.description ?? operation.description,
80
+ tags: override.tags ?? operation.tags ?? fallbackTags,
81
+ parameters: override.parameters ?? operation.parameters,
82
+ requestBody: override.requestBody ? jsonRequestBody(toSchema(override.requestBody.schema), override.requestBody.required ?? true) : operation.requestBody,
83
+ responses: operation.responses
84
+ };
85
+ if (override.responses) {
86
+ nextOperation.responses = Object.fromEntries(Object.entries(override.responses).map(([status, response]) => {
87
+ return [status, jsonResponse(response.description, response.schema ? toSchema(response.schema) : undefined)];
88
+ }));
89
+ return nextOperation;
90
+ }
91
+ if (override.responseSchema || override.responseStatus || override.responseDescription) {
92
+ const status = override.responseStatus ?? "200";
93
+ nextOperation.responses = { [status]: jsonResponse(override.responseDescription ?? "Successful response", override.responseSchema ? toSchema(override.responseSchema) : undefined) };
94
+ }
95
+ return nextOperation;
96
+ }
97
+ function ensurePath(paths, path) {
98
+ const existing = paths[path];
99
+ if (existing) return existing;
100
+ const created = {};
101
+ paths[path] = created;
102
+ return created;
103
+ }
104
+ function setOperation(paths, path, method, operation) {
105
+ const pathItem = ensurePath(paths, path);
106
+ pathItem[toMethodKey(method)] = operation;
107
+ }
108
+ function buildListParameters(searchFields, orderingFields, usesDefaultOffsetPagination) {
109
+ const parameters = [];
110
+ if (usesDefaultOffsetPagination) parameters.push({
111
+ name: "limit",
112
+ in: "query",
113
+ schema: { type: "integer" }
114
+ }, {
115
+ name: "offset",
116
+ in: "query",
117
+ schema: { type: "integer" }
118
+ });
119
+ if (searchFields.length > 0) parameters.push({
120
+ name: "search",
121
+ in: "query",
122
+ schema: { type: "string" }
123
+ });
124
+ if (orderingFields.length > 0) parameters.push({
125
+ name: "ordering",
126
+ in: "query",
127
+ schema: { type: "string" }
128
+ });
129
+ return parameters;
130
+ }
131
+ function buildPathParameter(name) {
132
+ return {
133
+ name,
134
+ in: "path",
135
+ required: true,
136
+ schema: { type: "string" }
137
+ };
138
+ }
139
+ function stringifyFields(fields) {
140
+ return fields.map(String);
141
+ }
142
+ function titleize(input) {
143
+ return input.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
144
+ }
145
+ function validateGenericDetailPath(detailPath, lookupParam) {
146
+ if (!detailPath.includes(`{${lookupParam}}`)) throw new Error(`GenericAPIView detail paths must include '{${lookupParam}}', received '${detailPath}'.`);
147
+ }
148
+ function ensureModelSchema(schemas, metadata) {
149
+ schemas[metadata.name] = generateSchemaFromModel(toOpenAPIModel(metadata));
150
+ }
151
+ function registerViewSetDescriptor(paths, schemas, descriptor) {
152
+ const metadata = descriptor.resource.describeOpenAPI();
153
+ const modelName = metadata.model.metadata.name;
154
+ const tags = descriptor.tags ?? [modelName];
155
+ const collectionPath = normalizePath(descriptor.basePath);
156
+ const detailPath = collectionPath === "/" ? "/{id}" : `${collectionPath}/{id}`;
157
+ const readSchema = toSchema(metadata.outputSchema);
158
+ const writeSchema = toSchema(metadata.createSchema);
159
+ const updateSchema = toSchema(metadata.updateSchema);
160
+ ensureModelSchema(schemas, metadata.model.metadata);
161
+ const listOperation = withOverride({
162
+ summary: `List ${modelName}s`,
163
+ tags,
164
+ parameters: buildListParameters(stringifyFields(metadata.searchFields), stringifyFields(metadata.orderingFields), metadata.usesDefaultOffsetPagination),
165
+ responses: { "200": jsonResponse("Successful response", metadata.usesDefaultOffsetPagination ? paginatedResultsSchema(readSchema) : undefined) }
166
+ }, undefined, tags);
167
+ setOperation(paths, collectionPath, "GET", listOperation);
168
+ setOperation(paths, collectionPath, "POST", {
169
+ summary: `Create ${modelName}`,
170
+ tags,
171
+ requestBody: jsonRequestBody(writeSchema),
172
+ responses: { "201": jsonResponse("Created", readSchema) }
173
+ });
174
+ setOperation(paths, detailPath, "GET", {
175
+ summary: `Get ${modelName}`,
176
+ tags,
177
+ parameters: [buildPathParameter("id")],
178
+ responses: {
179
+ "200": jsonResponse("Successful response", readSchema),
180
+ "404": { description: "Not found" }
181
+ }
182
+ });
183
+ const updateResponses = {
184
+ "200": jsonResponse("Updated", readSchema),
185
+ "404": { description: "Not found" }
186
+ };
187
+ setOperation(paths, detailPath, "PUT", {
188
+ summary: `Update ${modelName}`,
189
+ tags,
190
+ parameters: [buildPathParameter("id")],
191
+ requestBody: jsonRequestBody(updateSchema),
192
+ responses: updateResponses
193
+ });
194
+ setOperation(paths, detailPath, "PATCH", {
195
+ summary: `Update ${modelName}`,
196
+ tags,
197
+ parameters: [buildPathParameter("id")],
198
+ requestBody: jsonRequestBody(updateSchema),
199
+ responses: updateResponses
200
+ });
201
+ setOperation(paths, detailPath, "DELETE", {
202
+ summary: `Delete ${modelName}`,
203
+ tags,
204
+ parameters: [buildPathParameter("id")],
205
+ responses: {
206
+ "204": { description: "Deleted" },
207
+ "404": { description: "Not found" }
208
+ }
209
+ });
210
+ for (const action of metadata.actions) {
211
+ const actionPath = action.scope === "detail" ? joinPath(detailPath, action.path) : joinPath(collectionPath, action.path);
212
+ const baseOperation = {
213
+ summary: titleize(action.name),
214
+ tags,
215
+ ...action.scope === "detail" ? { parameters: [buildPathParameter("id")] } : {},
216
+ responses: { "200": { description: "Successful response" } }
217
+ };
218
+ const override = descriptor.actions?.[action.name];
219
+ for (const method of action.methods) setOperation(paths, actionPath, method, withOverride(baseOperation, override, tags));
220
+ }
221
+ }
222
+ function registerGenericDescriptor(paths, schemas, descriptor) {
223
+ if (!descriptor.collectionPath && !descriptor.detailPath) throw new Error("GenericAPIView OpenAPI descriptors require at least one of collectionPath or detailPath.");
224
+ const metadata = descriptor.resource.describeOpenAPI();
225
+ const modelName = metadata.model.metadata.name;
226
+ const tags = descriptor.tags ?? [modelName];
227
+ const readSchema = toSchema(metadata.outputSchema);
228
+ const writeSchema = toSchema(metadata.createSchema);
229
+ const updateSchema = toSchema(metadata.updateSchema);
230
+ const allowed = new Set(metadata.allowedMethods);
231
+ ensureModelSchema(schemas, metadata.model.metadata);
232
+ if (descriptor.collectionPath) {
233
+ const collectionPath = normalizePath(descriptor.collectionPath);
234
+ if (allowed.has("GET")) setOperation(paths, collectionPath, "GET", withOverride({
235
+ summary: `List ${modelName}s`,
236
+ tags,
237
+ parameters: buildListParameters(stringifyFields(metadata.searchFields), stringifyFields(metadata.orderingFields), metadata.usesDefaultOffsetPagination),
238
+ responses: { "200": jsonResponse("Successful response", metadata.usesDefaultOffsetPagination ? paginatedResultsSchema(readSchema) : undefined) }
239
+ }, descriptor.methods?.GET, tags));
240
+ if (allowed.has("POST")) setOperation(paths, collectionPath, "POST", withOverride({
241
+ summary: `Create ${modelName}`,
242
+ tags,
243
+ requestBody: jsonRequestBody(writeSchema),
244
+ responses: { "201": jsonResponse("Created", readSchema) }
245
+ }, descriptor.methods?.POST, tags));
246
+ }
247
+ if (descriptor.detailPath) {
248
+ const detailPath = normalizePath(descriptor.detailPath);
249
+ validateGenericDetailPath(detailPath, metadata.lookupParam);
250
+ const detailParameters = [buildPathParameter(metadata.lookupParam)];
251
+ if (allowed.has("GET")) setOperation(paths, detailPath, "GET", withOverride({
252
+ summary: `Get ${modelName}`,
253
+ tags,
254
+ parameters: detailParameters,
255
+ responses: {
256
+ "200": jsonResponse("Successful response", readSchema),
257
+ "404": { description: "Not found" }
258
+ }
259
+ }, descriptor.methods?.GET, tags));
260
+ if (allowed.has("PUT")) setOperation(paths, detailPath, "PUT", withOverride({
261
+ summary: `Update ${modelName}`,
262
+ tags,
263
+ parameters: detailParameters,
264
+ requestBody: jsonRequestBody(updateSchema),
265
+ responses: {
266
+ "200": jsonResponse("Updated", readSchema),
267
+ "404": { description: "Not found" }
268
+ }
269
+ }, descriptor.methods?.PUT, tags));
270
+ if (allowed.has("PATCH")) setOperation(paths, detailPath, "PATCH", withOverride({
271
+ summary: `Update ${modelName}`,
272
+ tags,
273
+ parameters: detailParameters,
274
+ requestBody: jsonRequestBody(updateSchema),
275
+ responses: {
276
+ "200": jsonResponse("Updated", readSchema),
277
+ "404": { description: "Not found" }
278
+ }
279
+ }, descriptor.methods?.PATCH, tags));
280
+ if (allowed.has("DELETE")) setOperation(paths, detailPath, "DELETE", withOverride({
281
+ summary: `Delete ${modelName}`,
282
+ tags,
283
+ parameters: detailParameters,
284
+ responses: {
285
+ "204": { description: "Deleted" },
286
+ "404": { description: "Not found" }
287
+ }
288
+ }, descriptor.methods?.DELETE, tags));
289
+ }
290
+ }
291
+ function registerAPIViewDescriptor(paths, descriptor) {
292
+ const path = normalizePath(descriptor.path);
293
+ const allowed = new Set(descriptor.resource.getAllowedMethods());
294
+ const methods = Object.entries(descriptor.methods);
295
+ for (const [method, override] of methods) {
296
+ if (!allowed.has(method)) throw new Error(`APIView method '${method}' is not implemented on ${descriptor.resource.constructor.name}.`);
297
+ setOperation(paths, path, method, withOverride({
298
+ summary: `${method} ${titleize(descriptor.resource.constructor.name.replace(/APIView$/, ""))}`.trim(),
299
+ responses: { [override.responseStatus ?? "200"]: jsonResponse(override.responseDescription ?? "Successful response", override.responseSchema ? toSchema(override.responseSchema) : undefined) }
300
+ }, override, descriptor.tags ?? [descriptor.resource.constructor.name.replace(/APIView$/, "") || "APIView"]));
301
+ }
302
+ }
303
+ function generateOpenAPISpec(config) {
304
+ const paths = {};
305
+ const schemas = {};
306
+ for (const resource of config.resources ?? []) registerDescriptor(paths, schemas, resource);
307
+ return {
308
+ openapi: "3.1.0",
309
+ info: {
310
+ title: config.title,
311
+ version: config.version,
312
+ description: config.description
313
+ },
314
+ servers: config.servers,
315
+ paths,
316
+ components: { schemas }
317
+ };
318
+ }
319
+ function registerDescriptor(paths, schemas, descriptor) {
320
+ if (descriptor.kind === "viewset") {
321
+ registerViewSetDescriptor(paths, schemas, descriptor);
322
+ return;
323
+ }
324
+ if (descriptor.kind === "generic") {
325
+ registerGenericDescriptor(paths, schemas, descriptor);
326
+ return;
327
+ }
328
+ registerAPIViewDescriptor(paths, descriptor);
329
+ }
330
+
331
+ //#endregion
332
+ //#region src/generators/spec/index.ts
333
+ var spec_exports = {};
334
+ __export(spec_exports, { generateOpenAPISpec: () => generateOpenAPISpec });
335
+
336
+ //#endregion
337
+ //#region src/generators/index.ts
338
+ var generators_exports = {};
339
+ __export(generators_exports, {
340
+ generateOpenAPISpec: () => generateOpenAPISpec,
341
+ spec: () => spec_exports
342
+ });
343
+
344
+ //#endregion
345
+ export { generateOpenAPISpec, generators_exports, spec_exports };
346
+ //# sourceMappingURL=generators-JMALItMS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generators-JMALItMS.js","names":["metadata: ResourceModelMetadata","path: string","base: string","segment: string","method: HttpMethod","value: unknown","value: z.ZodType | SchemaObject | ReferenceObject","items: SchemaObject | ReferenceObject","description: string","schema?: SchemaObject | ReferenceObject","schema: SchemaObject | ReferenceObject","required: boolean","operation: OperationObject","override: OpenAPIOperationOverride | undefined","fallbackTags: string[]","nextOperation: OperationObject","paths: Record<string, PathItemObject>","created: PathItemObject","searchFields: readonly string[]","orderingFields: readonly string[]","usesDefaultOffsetPagination: boolean","parameters: ParameterObject[]","name: string","fields: readonly unknown[]","input: string","detailPath: string","lookupParam: string","schemas: Record<string, SchemaObject>","descriptor: OpenAPIViewSetDescriptor","baseOperation: OperationObject","descriptor: OpenAPIGenericAPIViewDescriptor","descriptor: OpenAPIAPIViewDescriptor","config: OpenAPIGeneratorConfig","descriptor: OpenAPIResourceDescriptor"],"sources":["../src/generators/spec/generateOpenAPISpec.ts","../src/generators/spec/index.ts","../src/generators/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport { generateSchemaFromModel, generateSchemaFromZod } from '../../mappers/schema';\nimport type {\n OpenAPIAPIViewDescriptor,\n OpenAPIGeneratorConfig,\n OpenAPIGenericAPIViewDescriptor,\n OpenAPIOperationOverride,\n OpenAPIResourceDescriptor,\n OpenAPIViewSetDescriptor,\n OpenAPISpec,\n OperationObject,\n ParameterObject,\n PathItemObject,\n ReferenceObject,\n RequestBodyObject,\n ResponseObject,\n SchemaObject,\n} from '../../domain';\n\nconst JSON_CONTENT_TYPE = 'application/json';\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\ntype PathMethodKey = Lowercase<HttpMethod>;\n\ntype ResourceModelMetadata = {\n name: string;\n fields: Array<{\n name: string;\n type: string;\n notNull?: boolean;\n default?: unknown;\n primaryKey?: boolean;\n }>;\n};\n\nfunction toOpenAPIModel(metadata: ResourceModelMetadata) {\n return {\n name: metadata.name,\n fields: Object.fromEntries(\n metadata.fields.map((field) => [\n field.name,\n {\n type: field.type,\n nullable: field.notNull !== true,\n default: field.default,\n primaryKey: field.primaryKey,\n },\n ])\n ),\n };\n}\n\nfunction normalizePath(path: string): string {\n const trimmed = path.trim();\n if (!trimmed) {\n throw new Error('OpenAPI paths must not be empty.');\n }\n if (/(^|\\/):/.test(trimmed)) {\n throw new Error(`OpenAPI paths must use {param} syntax, received '${trimmed}'.`);\n }\n\n const withLeadingSlash = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n if (withLeadingSlash === '/') {\n return '/';\n }\n\n return withLeadingSlash.replace(/\\/+$/g, '');\n}\n\nfunction joinPath(base: string, segment: string): string {\n const normalizedSegment = segment.replace(/^\\/+|\\/+$/g, '');\n return base === '/' ? `/${normalizedSegment}` : `${base}/${normalizedSegment}`;\n}\n\nfunction toMethodKey(method: HttpMethod): PathMethodKey {\n return method.toLowerCase() as PathMethodKey;\n}\n\nfunction isReferenceObject(value: unknown): value is ReferenceObject {\n return typeof value === 'object' && value !== null && typeof (value as { $ref?: unknown }).$ref === 'string';\n}\n\nfunction isZodSchema(value: unknown): value is z.ZodType {\n // oxlint-disable-next-line eslint-js/no-restricted-syntax\n return value instanceof z.ZodType;\n}\n\nfunction toSchema(value: z.ZodType | SchemaObject | ReferenceObject): SchemaObject | ReferenceObject {\n if (isReferenceObject(value)) {\n return value;\n }\n if (isZodSchema(value)) {\n return generateSchemaFromZod(value);\n }\n return value;\n}\n\nfunction arraySchema(items: SchemaObject | ReferenceObject): SchemaObject {\n return {\n type: 'array',\n items,\n };\n}\n\nfunction paginatedResultsSchema(items: SchemaObject | ReferenceObject): SchemaObject {\n return {\n type: 'object',\n properties: {\n count: { type: 'integer' },\n next: { type: 'string' },\n previous: { type: 'string' },\n results: arraySchema(items),\n },\n required: ['count', 'results'],\n };\n}\n\nfunction jsonResponse(description: string, schema?: SchemaObject | ReferenceObject): ResponseObject {\n return schema\n ? {\n description,\n content: {\n [JSON_CONTENT_TYPE]: {\n schema,\n },\n },\n }\n : { description };\n}\n\nfunction jsonRequestBody(schema: SchemaObject | ReferenceObject, required: boolean = true): RequestBodyObject {\n return {\n required,\n content: {\n [JSON_CONTENT_TYPE]: {\n schema,\n },\n },\n };\n}\n\nfunction withOverride(\n operation: OperationObject,\n override: OpenAPIOperationOverride | undefined,\n fallbackTags: string[]\n): OperationObject {\n if (!override) {\n return operation;\n }\n\n const nextOperation: OperationObject = {\n ...operation,\n summary: override.summary ?? operation.summary,\n description: override.description ?? operation.description,\n tags: override.tags ?? operation.tags ?? fallbackTags,\n parameters: override.parameters ?? operation.parameters,\n requestBody: override.requestBody\n ? jsonRequestBody(toSchema(override.requestBody.schema), override.requestBody.required ?? true)\n : operation.requestBody,\n responses: operation.responses,\n };\n\n if (override.responses) {\n nextOperation.responses = Object.fromEntries(\n Object.entries(override.responses).map(([status, response]) => {\n return [\n status,\n jsonResponse(response.description, response.schema ? toSchema(response.schema) : undefined),\n ];\n })\n );\n return nextOperation;\n }\n\n if (override.responseSchema || override.responseStatus || override.responseDescription) {\n const status = override.responseStatus ?? '200';\n nextOperation.responses = {\n [status]: jsonResponse(\n override.responseDescription ?? 'Successful response',\n override.responseSchema ? toSchema(override.responseSchema) : undefined\n ),\n };\n }\n\n return nextOperation;\n}\n\nfunction ensurePath(paths: Record<string, PathItemObject>, path: string): PathItemObject {\n const existing = paths[path];\n if (existing) {\n return existing;\n }\n\n const created: PathItemObject = {};\n paths[path] = created;\n return created;\n}\n\nfunction setOperation(\n paths: Record<string, PathItemObject>,\n path: string,\n method: HttpMethod,\n operation: OperationObject\n): void {\n const pathItem = ensurePath(paths, path);\n pathItem[toMethodKey(method)] = operation;\n}\n\nfunction buildListParameters(\n searchFields: readonly string[],\n orderingFields: readonly string[],\n usesDefaultOffsetPagination: boolean\n): ParameterObject[] {\n const parameters: ParameterObject[] = [];\n\n if (usesDefaultOffsetPagination) {\n parameters.push(\n {\n name: 'limit',\n in: 'query',\n schema: { type: 'integer' },\n },\n {\n name: 'offset',\n in: 'query',\n schema: { type: 'integer' },\n }\n );\n }\n\n if (searchFields.length > 0) {\n parameters.push({\n name: 'search',\n in: 'query',\n schema: { type: 'string' },\n });\n }\n\n if (orderingFields.length > 0) {\n parameters.push({\n name: 'ordering',\n in: 'query',\n schema: { type: 'string' },\n });\n }\n\n return parameters;\n}\n\nfunction buildPathParameter(name: string): ParameterObject {\n return {\n name,\n in: 'path',\n required: true,\n schema: { type: 'string' },\n };\n}\n\nfunction stringifyFields(fields: readonly unknown[]): string[] {\n return fields.map(String);\n}\n\nfunction titleize(input: string): string {\n return input\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nfunction validateGenericDetailPath(detailPath: string, lookupParam: string): void {\n if (!detailPath.includes(`{${lookupParam}}`)) {\n throw new Error(`GenericAPIView detail paths must include '{${lookupParam}}', received '${detailPath}'.`);\n }\n}\n\nfunction ensureModelSchema(schemas: Record<string, SchemaObject>, metadata: ResourceModelMetadata): void {\n schemas[metadata.name] = generateSchemaFromModel(toOpenAPIModel(metadata));\n}\n\nfunction registerViewSetDescriptor(\n paths: Record<string, PathItemObject>,\n schemas: Record<string, SchemaObject>,\n descriptor: OpenAPIViewSetDescriptor\n): void {\n const metadata = descriptor.resource.describeOpenAPI();\n const modelName = metadata.model.metadata.name;\n const tags = descriptor.tags ?? [modelName];\n const collectionPath = normalizePath(descriptor.basePath);\n const detailPath = collectionPath === '/' ? '/{id}' : `${collectionPath}/{id}`;\n const readSchema = toSchema(metadata.outputSchema);\n const writeSchema = toSchema(metadata.createSchema);\n const updateSchema = toSchema(metadata.updateSchema);\n\n ensureModelSchema(schemas, metadata.model.metadata);\n\n const listOperation = withOverride(\n {\n summary: `List ${modelName}s`,\n tags,\n parameters: buildListParameters(\n stringifyFields(metadata.searchFields),\n stringifyFields(metadata.orderingFields),\n metadata.usesDefaultOffsetPagination\n ),\n responses: {\n '200': jsonResponse(\n 'Successful response',\n metadata.usesDefaultOffsetPagination ? paginatedResultsSchema(readSchema) : undefined\n ),\n },\n },\n undefined,\n tags\n );\n\n setOperation(paths, collectionPath, 'GET', listOperation);\n setOperation(paths, collectionPath, 'POST', {\n summary: `Create ${modelName}`,\n tags,\n requestBody: jsonRequestBody(writeSchema),\n responses: {\n '201': jsonResponse('Created', readSchema),\n },\n });\n setOperation(paths, detailPath, 'GET', {\n summary: `Get ${modelName}`,\n tags,\n parameters: [buildPathParameter('id')],\n responses: {\n '200': jsonResponse('Successful response', readSchema),\n '404': { description: 'Not found' },\n },\n });\n const updateResponses = {\n '200': jsonResponse('Updated', readSchema),\n '404': { description: 'Not found' },\n };\n setOperation(paths, detailPath, 'PUT', {\n summary: `Update ${modelName}`,\n tags,\n parameters: [buildPathParameter('id')],\n requestBody: jsonRequestBody(updateSchema),\n responses: updateResponses,\n });\n setOperation(paths, detailPath, 'PATCH', {\n summary: `Update ${modelName}`,\n tags,\n parameters: [buildPathParameter('id')],\n requestBody: jsonRequestBody(updateSchema),\n responses: updateResponses,\n });\n setOperation(paths, detailPath, 'DELETE', {\n summary: `Delete ${modelName}`,\n tags,\n parameters: [buildPathParameter('id')],\n responses: {\n '204': { description: 'Deleted' },\n '404': { description: 'Not found' },\n },\n });\n\n for (const action of metadata.actions) {\n const actionPath =\n action.scope === 'detail' ? joinPath(detailPath, action.path) : joinPath(collectionPath, action.path);\n const baseOperation: OperationObject = {\n summary: titleize(action.name),\n tags,\n ...(action.scope === 'detail' ? { parameters: [buildPathParameter('id')] } : {}),\n responses: {\n '200': { description: 'Successful response' },\n },\n };\n\n const override = descriptor.actions?.[action.name];\n for (const method of action.methods) {\n setOperation(paths, actionPath, method, withOverride(baseOperation, override, tags));\n }\n }\n}\n\nfunction registerGenericDescriptor(\n paths: Record<string, PathItemObject>,\n schemas: Record<string, SchemaObject>,\n descriptor: OpenAPIGenericAPIViewDescriptor\n): void {\n if (!descriptor.collectionPath && !descriptor.detailPath) {\n throw new Error('GenericAPIView OpenAPI descriptors require at least one of collectionPath or detailPath.');\n }\n\n const metadata = descriptor.resource.describeOpenAPI();\n const modelName = metadata.model.metadata.name;\n const tags = descriptor.tags ?? [modelName];\n const readSchema = toSchema(metadata.outputSchema);\n const writeSchema = toSchema(metadata.createSchema);\n const updateSchema = toSchema(metadata.updateSchema);\n const allowed = new Set(metadata.allowedMethods);\n\n ensureModelSchema(schemas, metadata.model.metadata);\n\n if (descriptor.collectionPath) {\n const collectionPath = normalizePath(descriptor.collectionPath);\n if (allowed.has('GET')) {\n setOperation(\n paths,\n collectionPath,\n 'GET',\n withOverride(\n {\n summary: `List ${modelName}s`,\n tags,\n parameters: buildListParameters(\n stringifyFields(metadata.searchFields),\n stringifyFields(metadata.orderingFields),\n metadata.usesDefaultOffsetPagination\n ),\n responses: {\n '200': jsonResponse(\n 'Successful response',\n metadata.usesDefaultOffsetPagination ? paginatedResultsSchema(readSchema) : undefined\n ),\n },\n },\n descriptor.methods?.GET,\n tags\n )\n );\n }\n\n if (allowed.has('POST')) {\n setOperation(\n paths,\n collectionPath,\n 'POST',\n withOverride(\n {\n summary: `Create ${modelName}`,\n tags,\n requestBody: jsonRequestBody(writeSchema),\n responses: {\n '201': jsonResponse('Created', readSchema),\n },\n },\n descriptor.methods?.POST,\n tags\n )\n );\n }\n }\n\n if (descriptor.detailPath) {\n const detailPath = normalizePath(descriptor.detailPath);\n validateGenericDetailPath(detailPath, metadata.lookupParam);\n const detailParameters = [buildPathParameter(metadata.lookupParam)];\n\n if (allowed.has('GET')) {\n setOperation(\n paths,\n detailPath,\n 'GET',\n withOverride(\n {\n summary: `Get ${modelName}`,\n tags,\n parameters: detailParameters,\n responses: {\n '200': jsonResponse('Successful response', readSchema),\n '404': { description: 'Not found' },\n },\n },\n descriptor.methods?.GET,\n tags\n )\n );\n }\n\n if (allowed.has('PUT')) {\n setOperation(\n paths,\n detailPath,\n 'PUT',\n withOverride(\n {\n summary: `Update ${modelName}`,\n tags,\n parameters: detailParameters,\n requestBody: jsonRequestBody(updateSchema),\n responses: {\n '200': jsonResponse('Updated', readSchema),\n '404': { description: 'Not found' },\n },\n },\n descriptor.methods?.PUT,\n tags\n )\n );\n }\n\n if (allowed.has('PATCH')) {\n setOperation(\n paths,\n detailPath,\n 'PATCH',\n withOverride(\n {\n summary: `Update ${modelName}`,\n tags,\n parameters: detailParameters,\n requestBody: jsonRequestBody(updateSchema),\n responses: {\n '200': jsonResponse('Updated', readSchema),\n '404': { description: 'Not found' },\n },\n },\n descriptor.methods?.PATCH,\n tags\n )\n );\n }\n\n if (allowed.has('DELETE')) {\n setOperation(\n paths,\n detailPath,\n 'DELETE',\n withOverride(\n {\n summary: `Delete ${modelName}`,\n tags,\n parameters: detailParameters,\n responses: {\n '204': { description: 'Deleted' },\n '404': { description: 'Not found' },\n },\n },\n descriptor.methods?.DELETE,\n tags\n )\n );\n }\n }\n}\n\nfunction registerAPIViewDescriptor(paths: Record<string, PathItemObject>, descriptor: OpenAPIAPIViewDescriptor): void {\n const path = normalizePath(descriptor.path);\n const allowed = new Set(descriptor.resource.getAllowedMethods());\n const methods = Object.entries(descriptor.methods) as Array<[HttpMethod, OpenAPIOperationOverride]>;\n\n for (const [method, override] of methods) {\n if (!allowed.has(method)) {\n throw new Error(\n `APIView method '${method}' is not implemented on ${descriptor.resource.constructor.name}.`\n );\n }\n\n setOperation(\n paths,\n path,\n method,\n withOverride(\n {\n summary:\n `${method} ${titleize(descriptor.resource.constructor.name.replace(/APIView$/, ''))}`.trim(),\n responses: {\n [(override.responseStatus ?? '200') as string]: jsonResponse(\n override.responseDescription ?? 'Successful response',\n override.responseSchema ? toSchema(override.responseSchema) : undefined\n ),\n },\n },\n override,\n descriptor.tags ?? [descriptor.resource.constructor.name.replace(/APIView$/, '') || 'APIView']\n )\n );\n }\n}\n\n/**\n * Build an OpenAPI 3.1 document from Tango resource configuration.\n */\nexport function generateOpenAPISpec(config: OpenAPIGeneratorConfig): OpenAPISpec {\n const paths: Record<string, PathItemObject> = {};\n const schemas: Record<string, SchemaObject> = {};\n\n for (const resource of config.resources ?? []) {\n registerDescriptor(paths, schemas, resource);\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: config.title,\n version: config.version,\n description: config.description,\n },\n servers: config.servers,\n paths,\n components: {\n schemas,\n },\n };\n}\n\nfunction registerDescriptor(\n paths: Record<string, PathItemObject>,\n schemas: Record<string, SchemaObject>,\n descriptor: OpenAPIResourceDescriptor\n): void {\n if (descriptor.kind === 'viewset') {\n registerViewSetDescriptor(paths, schemas, descriptor);\n return;\n }\n\n if (descriptor.kind === 'generic') {\n registerGenericDescriptor(paths, schemas, descriptor);\n return;\n }\n\n registerAPIViewDescriptor(paths, descriptor);\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { generateOpenAPISpec } from './generateOpenAPISpec';\n","/**\n * Domain boundary barrel: exposes namespaced exports for Django-style drill-down\n * imports and curated flat exports for TS-native ergonomics.\n */\n\nexport * as spec from './spec/index';\nexport { generateOpenAPISpec } from './spec/index';\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  export * as domain from './domain/index';
6
6
  export * as generators from './generators/index';
7
7
  export * as mappers from './mappers/index';
8
- export { VERSION as version } from './version';
9
- export type { OpenAPIGeneratorConfig, OpenAPIModel, OpenAPIModelFieldMeta, OpenAPIOptions, OpenAPISpec, ViewSetLike, } from './domain/index';
8
+ export type { OpenAPIAPIViewDescriptor, OpenAPIGeneratorConfig, OpenAPIModel, OpenAPIModelFieldMeta, OpenAPIOptions, OpenAPIOperationOverride, OpenAPIResponseOverride, OpenAPIResourceDescriptor, OpenAPISchemaInput, OpenAPISpec, OpenAPIGenericAPIViewDescriptor, OpenAPIViewSetDescriptor, } from './domain/index';
9
+ export { describeAPIView, describeGenericAPIView, describeViewSet } from './domain/index';
10
10
  export { generateOpenAPISpec } from './generators/index';
11
- export { generateSchemaFromModel, mapTypeToOpenAPI } from './mappers/index';
11
+ export { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI } from './mappers/index';
package/dist/index.js CHANGED
@@ -1,11 +1,6 @@
1
- import { generateSchemaFromModel, mapTypeToOpenAPI } from "./schema-DVxdID48.js";
2
- import { domain_exports } from "./domain-Cufz6y1q.js";
3
- import { generateOpenAPISpec, generators_exports } from "./generators-Bvwyyja4.js";
4
- import { mappers_exports } from "./mappers-CIfnOwl2.js";
1
+ import { describeAPIView, describeGenericAPIView, describeViewSet, domain_exports } from "./domain-B-7sApJT.js";
2
+ import { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI } from "./schema-D3ybOrpr.js";
3
+ import { generateOpenAPISpec, generators_exports } from "./generators-JMALItMS.js";
4
+ import { mappers_exports } from "./mappers-bmN95TGV.js";
5
5
 
6
- //#region src/version.ts
7
- const VERSION = "0.1.0";
8
-
9
- //#endregion
10
- export { domain_exports as domain, generateOpenAPISpec, generateSchemaFromModel, generators_exports as generators, mapTypeToOpenAPI, mappers_exports as mappers, VERSION as version };
11
- //# sourceMappingURL=index.js.map
6
+ export { describeAPIView, describeGenericAPIView, describeViewSet, domain_exports as domain, generateOpenAPISpec, generateSchemaFromModel, generateSchemaFromZod, generators_exports as generators, mapTypeToOpenAPI, mappers_exports as mappers };
@@ -3,4 +3,4 @@
3
3
  * imports and curated flat exports for TS-native ergonomics.
4
4
  */
5
5
  export * as schema from './schema/index';
6
- export { generateSchemaFromModel, mapTypeToOpenAPI } from './schema/index';
6
+ export { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI } from './schema/index';
@@ -1,4 +1,4 @@
1
- import { generateSchemaFromModel, mapTypeToOpenAPI, schema_exports } from "../schema-DVxdID48.js";
2
- import "../mappers-CIfnOwl2.js";
1
+ import { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI, schema_exports } from "../schema-D3ybOrpr.js";
2
+ import "../mappers-bmN95TGV.js";
3
3
 
4
- export { generateSchemaFromModel, mapTypeToOpenAPI, schema_exports as schema };
4
+ export { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI, schema_exports as schema };
@@ -1,2 +1,5 @@
1
1
  import type { OpenAPIModel, SchemaObject } from '../../domain';
2
+ /**
3
+ * Derive an OpenAPI schema object from Tango model metadata.
4
+ */
2
5
  export declare function generateSchemaFromModel(model: OpenAPIModel): SchemaObject;
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ import type { SchemaObject } from '../../domain';
3
+ /**
4
+ * Derive an OpenAPI-compatible schema object from a Zod schema.
5
+ */
6
+ export declare function generateSchemaFromZod(schema: z.ZodType): SchemaObject;
@@ -2,4 +2,5 @@
2
2
  * Domain boundary barrel: centralizes this subdomain's public contract.
3
3
  */
4
4
  export { generateSchemaFromModel } from './generateSchemaFromModel';
5
+ export { generateSchemaFromZod } from './generateSchemaFromZod';
5
6
  export { mapTypeToOpenAPI } from './mapTypeToOpenAPI';
@@ -1 +1,4 @@
1
+ /**
2
+ * Map Tango field types to OpenAPI scalar/object type names.
3
+ */
1
4
  export declare function mapTypeToOpenAPI(type: string): string;
@@ -0,0 +1,15 @@
1
+ import { __export } from "./chunk-BkvOhyD0.js";
2
+ import { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI, schema_exports } from "./schema-D3ybOrpr.js";
3
+
4
+ //#region src/mappers/index.ts
5
+ var mappers_exports = {};
6
+ __export(mappers_exports, {
7
+ generateSchemaFromModel: () => generateSchemaFromModel,
8
+ generateSchemaFromZod: () => generateSchemaFromZod,
9
+ mapTypeToOpenAPI: () => mapTypeToOpenAPI,
10
+ schema: () => schema_exports
11
+ });
12
+
13
+ //#endregion
14
+ export { mappers_exports };
15
+ //# sourceMappingURL=mappers-bmN95TGV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mappers-CIfnOwl2.js","names":[],"sources":["../src/mappers/index.ts"],"sourcesContent":["/**\n * Domain boundary barrel: exposes namespaced exports for Django-style drill-down\n * imports and curated flat exports for TS-native ergonomics.\n */\n\nexport * as schema from './schema/index';\nexport { generateSchemaFromModel, mapTypeToOpenAPI } from './schema/index';\n"],"mappings":""}
1
+ {"version":3,"file":"mappers-bmN95TGV.js","names":[],"sources":["../src/mappers/index.ts"],"sourcesContent":["/**\n * Domain boundary barrel: exposes namespaced exports for Django-style drill-down\n * imports and curated flat exports for TS-native ergonomics.\n */\n\nexport * as schema from './schema/index';\nexport { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI } from './schema/index';\n"],"mappings":""}
@@ -1,14 +1,6 @@
1
+ import { __export } from "./chunk-BkvOhyD0.js";
2
+ import { z } from "zod";
1
3
 
2
- //#region rolldown:runtime
3
- var __defProp = Object.defineProperty;
4
- var __export = (target, all) => {
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- };
10
-
11
- //#endregion
12
4
  //#region src/mappers/schema/mapTypeToOpenAPI.ts
13
5
  function mapTypeToOpenAPI(type) {
14
6
  const typeMap = {
@@ -47,14 +39,29 @@ function generateSchemaFromModel(model) {
47
39
  };
48
40
  }
49
41
 
42
+ //#endregion
43
+ //#region src/mappers/schema/generateSchemaFromZod.ts
44
+ function stripDialect(schema) {
45
+ if (Array.isArray(schema)) return schema.map((entry) => stripDialect(entry));
46
+ if (schema && typeof schema === "object") {
47
+ const input = schema;
48
+ return Object.fromEntries(Object.entries(input).filter(([key]) => key !== "$schema").map(([key, value]) => [key, stripDialect(value)]));
49
+ }
50
+ return schema;
51
+ }
52
+ function generateSchemaFromZod(schema) {
53
+ return stripDialect(z.toJSONSchema(schema));
54
+ }
55
+
50
56
  //#endregion
51
57
  //#region src/mappers/schema/index.ts
52
58
  var schema_exports = {};
53
59
  __export(schema_exports, {
54
60
  generateSchemaFromModel: () => generateSchemaFromModel,
61
+ generateSchemaFromZod: () => generateSchemaFromZod,
55
62
  mapTypeToOpenAPI: () => mapTypeToOpenAPI
56
63
  });
57
64
 
58
65
  //#endregion
59
- export { __export, generateSchemaFromModel, mapTypeToOpenAPI, schema_exports };
60
- //# sourceMappingURL=schema-DVxdID48.js.map
66
+ export { generateSchemaFromModel, generateSchemaFromZod, mapTypeToOpenAPI, schema_exports };
67
+ //# sourceMappingURL=schema-D3ybOrpr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-D3ybOrpr.js","names":["type: string","typeMap: Record<string, string>","model: OpenAPIModel","properties: Record<string, SchemaObject>","required: string[]","schema: unknown","schema: z.ZodType"],"sources":["../src/mappers/schema/mapTypeToOpenAPI.ts","../src/mappers/schema/generateSchemaFromModel.ts","../src/mappers/schema/generateSchemaFromZod.ts","../src/mappers/schema/index.ts"],"sourcesContent":["/**\n * Map Tango field types to OpenAPI scalar/object type names.\n */\nexport function mapTypeToOpenAPI(type: string): string {\n const typeMap: Record<string, string> = {\n string: 'string',\n number: 'number',\n boolean: 'boolean',\n date: 'string',\n serial: 'integer',\n int: 'integer',\n bigint: 'integer',\n text: 'string',\n bool: 'boolean',\n uuid: 'string',\n jsonb: 'object',\n timestamptz: 'string',\n };\n\n return typeMap[type] || 'string';\n}\n","import type { OpenAPIModel, SchemaObject } from '../../domain';\nimport { mapTypeToOpenAPI } from './mapTypeToOpenAPI';\n\n/**\n * Derive an OpenAPI schema object from Tango model metadata.\n */\nexport function generateSchemaFromModel(model: OpenAPIModel): SchemaObject {\n const properties: Record<string, SchemaObject> = {};\n const required: string[] = [];\n\n for (const [name, meta] of Object.entries(model.fields)) {\n properties[name] = {\n type: mapTypeToOpenAPI(meta.type),\n description: meta.description,\n };\n\n if (!meta.nullable && meta.default === undefined && !meta.primaryKey) {\n required.push(name);\n }\n }\n\n return {\n type: 'object',\n properties,\n required: required.length > 0 ? required : undefined,\n };\n}\n","import { z } from 'zod';\nimport type { SchemaObject } from '../../domain';\n\nfunction stripDialect(schema: unknown): unknown {\n if (Array.isArray(schema)) {\n return schema.map((entry) => stripDialect(entry));\n }\n\n if (schema && typeof schema === 'object') {\n const input = schema as Record<string, unknown>;\n return Object.fromEntries(\n Object.entries(input)\n .filter(([key]) => key !== '$schema')\n .map(([key, value]) => [key, stripDialect(value)])\n );\n }\n\n return schema;\n}\n\n/**\n * Derive an OpenAPI-compatible schema object from a Zod schema.\n */\nexport function generateSchemaFromZod(schema: z.ZodType): SchemaObject {\n return stripDialect(z.toJSONSchema(schema)) as SchemaObject;\n}\n","/**\n * Domain boundary barrel: centralizes this subdomain's public contract.\n */\n\nexport { generateSchemaFromModel } from './generateSchemaFromModel';\nexport { generateSchemaFromZod } from './generateSchemaFromZod';\nexport { mapTypeToOpenAPI } from './mapTypeToOpenAPI';\n"],"mappings":";;;;AAGO,SAAS,iBAAiBA,MAAsB;CACnD,MAAMC,UAAkC;EACpC,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,MAAM;EACN,QAAQ;EACR,KAAK;EACL,QAAQ;EACR,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO;EACP,aAAa;CAChB;AAED,QAAO,QAAQ,SAAS;AAC3B;;;;ACdM,SAAS,wBAAwBC,OAAmC;CACvE,MAAMC,aAA2C,CAAE;CACnD,MAAMC,WAAqB,CAAE;AAE7B,MAAK,MAAM,CAAC,MAAM,KAAK,IAAI,OAAO,QAAQ,MAAM,OAAO,EAAE;AACrD,aAAW,QAAQ;GACf,MAAM,iBAAiB,KAAK,KAAK;GACjC,aAAa,KAAK;EACrB;AAED,OAAK,KAAK,YAAY,KAAK,YAAY,cAAc,KAAK,WACtD,UAAS,KAAK,KAAK;CAE1B;AAED,QAAO;EACH,MAAM;EACN;EACA,UAAU,SAAS,SAAS,IAAI,WAAW;CAC9C;AACJ;;;;ACvBD,SAAS,aAAaC,QAA0B;AAC5C,KAAI,MAAM,QAAQ,OAAO,CACrB,QAAO,OAAO,IAAI,CAAC,UAAU,aAAa,MAAM,CAAC;AAGrD,KAAI,iBAAiB,WAAW,UAAU;EACtC,MAAM,QAAQ;AACd,SAAO,OAAO,YACV,OAAO,QAAQ,MAAM,CAChB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,UAAU,CACpC,IAAI,CAAC,CAAC,KAAK,MAAM,KAAK,CAAC,KAAK,aAAa,MAAM,AAAC,EAAC,CACzD;CACJ;AAED,QAAO;AACV;AAKM,SAAS,sBAAsBC,QAAiC;AACnE,QAAO,aAAa,EAAE,aAAa,OAAO,CAAC;AAC9C"}