@proseql/rest 0.1.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/LICENSE +21 -0
- package/dist/error-mapping.d.ts +60 -0
- package/dist/error-mapping.d.ts.map +1 -0
- package/dist/error-mapping.js +183 -0
- package/dist/error-mapping.js.map +1 -0
- package/dist/handlers.d.ts +112 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +402 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/query-params.d.ts +132 -0
- package/dist/query-params.d.ts.map +1 -0
- package/dist/query-params.js +380 -0
- package/dist/query-params.js.map +1 -0
- package/dist/relationship-routes.d.ts +82 -0
- package/dist/relationship-routes.d.ts.map +1 -0
- package/dist/relationship-routes.js +273 -0
- package/dist/relationship-routes.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Simon W. Jackson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error-to-HTTP-status mapping for REST API responses.
|
|
3
|
+
*
|
|
4
|
+
* Maps proseql tagged errors to appropriate HTTP status codes and
|
|
5
|
+
* structured error response bodies. Each error's _tag is the discriminant,
|
|
6
|
+
* and the response includes the error's fields for debugging.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Structured error response returned by mapErrorToResponse.
|
|
12
|
+
* Contains the HTTP status code and the response body to send.
|
|
13
|
+
*/
|
|
14
|
+
export interface ErrorResponse {
|
|
15
|
+
/** HTTP status code (e.g., 400, 404, 409, 422, 500) */
|
|
16
|
+
readonly status: number;
|
|
17
|
+
/** Response body containing error details */
|
|
18
|
+
readonly body: {
|
|
19
|
+
/** Error tag identifying the error type */
|
|
20
|
+
readonly _tag: string;
|
|
21
|
+
/** Human-readable error message */
|
|
22
|
+
readonly error: string;
|
|
23
|
+
/** Additional error fields for debugging */
|
|
24
|
+
readonly details?: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Map a proseql tagged error to an HTTP response.
|
|
29
|
+
*
|
|
30
|
+
* Matches on the error's `_tag` property and returns the appropriate HTTP
|
|
31
|
+
* status code along with a structured error body. Unknown errors default
|
|
32
|
+
* to 500 Internal Server Error.
|
|
33
|
+
*
|
|
34
|
+
* @param error - The error to map (typically a proseql tagged error)
|
|
35
|
+
* @returns An ErrorResponse with status code and body
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { mapErrorToResponse } from "@proseql/rest"
|
|
40
|
+
* import { NotFoundError } from "@proseql/core"
|
|
41
|
+
*
|
|
42
|
+
* const error = new NotFoundError({
|
|
43
|
+
* collection: "books",
|
|
44
|
+
* id: "123",
|
|
45
|
+
* message: "Book not found"
|
|
46
|
+
* })
|
|
47
|
+
*
|
|
48
|
+
* const response = mapErrorToResponse(error)
|
|
49
|
+
* // response = {
|
|
50
|
+
* // status: 404,
|
|
51
|
+
* // body: {
|
|
52
|
+
* // _tag: "NotFoundError",
|
|
53
|
+
* // error: "Not found",
|
|
54
|
+
* // details: { collection: "books", id: "123", message: "Book not found" }
|
|
55
|
+
* // }
|
|
56
|
+
* // }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const mapErrorToResponse: (error: unknown) => ErrorResponse;
|
|
60
|
+
//# sourceMappingURL=error-mapping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-mapping.d.ts","sourceRoot":"","sources":["../src/error-mapping.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE;QACd,2CAA2C;QAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,mCAAmC;QACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,4CAA4C;QAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC3C,CAAC;CACF;AAuHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,aA8CnD,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error-to-HTTP-status mapping for REST API responses.
|
|
3
|
+
*
|
|
4
|
+
* Maps proseql tagged errors to appropriate HTTP status codes and
|
|
5
|
+
* structured error response bodies. Each error's _tag is the discriminant,
|
|
6
|
+
* and the response includes the error's fields for debugging.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { Cause, Option, Runtime } from "effect";
|
|
11
|
+
/**
|
|
12
|
+
* Extract a tagged error from an unknown error value.
|
|
13
|
+
*
|
|
14
|
+
* Effect.runPromise throws a FiberFailure when the Effect fails.
|
|
15
|
+
* This function extracts the underlying tagged error from the FiberFailure
|
|
16
|
+
* or returns the error directly if it's already a tagged error.
|
|
17
|
+
*/
|
|
18
|
+
const extractTaggedError = (error) => {
|
|
19
|
+
// Check if it's a FiberFailure (from Effect.runPromise)
|
|
20
|
+
if (Runtime.isFiberFailure(error)) {
|
|
21
|
+
// Get the cause from the FiberFailure using the well-known symbol
|
|
22
|
+
const causeSymbol = Symbol.for("effect/Runtime/FiberFailure/Cause");
|
|
23
|
+
const cause = error[causeSymbol];
|
|
24
|
+
// Extract the failure from the cause
|
|
25
|
+
const failure = Cause.failureOption(cause);
|
|
26
|
+
if (Option.isSome(failure)) {
|
|
27
|
+
const value = failure.value;
|
|
28
|
+
if (value !== null && typeof value === "object" && "_tag" in value) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Check if it's already a tagged error
|
|
34
|
+
if (error !== null && typeof error === "object" && "_tag" in error) {
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Type guard for tagged errors.
|
|
41
|
+
* Checks if an unknown value is an object with a _tag property.
|
|
42
|
+
* Handles both direct tagged errors and FiberFailure wrappers.
|
|
43
|
+
*/
|
|
44
|
+
const _isTaggedError = (error) => {
|
|
45
|
+
return extractTaggedError(error) !== null;
|
|
46
|
+
};
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Status Code Mapping
|
|
49
|
+
// ============================================================================
|
|
50
|
+
/**
|
|
51
|
+
* Static mapping from error _tag values to HTTP status codes.
|
|
52
|
+
*
|
|
53
|
+
* Mapping rationale:
|
|
54
|
+
* - 400 Bad Request: ValidationError (invalid input data)
|
|
55
|
+
* - 404 Not Found: NotFoundError (entity doesn't exist)
|
|
56
|
+
* - 409 Conflict: DuplicateKeyError, UniqueConstraintError (resource conflict)
|
|
57
|
+
* - 422 Unprocessable Entity: ForeignKeyError, HookError, DanglingReferenceError (semantic errors)
|
|
58
|
+
* - 500 Internal Server Error: TransactionError, and unknown errors
|
|
59
|
+
*/
|
|
60
|
+
const ERROR_STATUS_MAP = {
|
|
61
|
+
// CRUD Errors
|
|
62
|
+
NotFoundError: 404,
|
|
63
|
+
ValidationError: 400,
|
|
64
|
+
DuplicateKeyError: 409,
|
|
65
|
+
UniqueConstraintError: 409,
|
|
66
|
+
ForeignKeyError: 422,
|
|
67
|
+
HookError: 422,
|
|
68
|
+
TransactionError: 500,
|
|
69
|
+
ConcurrencyError: 409,
|
|
70
|
+
OperationError: 400,
|
|
71
|
+
// Query Errors
|
|
72
|
+
DanglingReferenceError: 422,
|
|
73
|
+
CollectionNotFoundError: 404,
|
|
74
|
+
PopulationError: 422,
|
|
75
|
+
// Storage Errors
|
|
76
|
+
StorageError: 500,
|
|
77
|
+
SerializationError: 500,
|
|
78
|
+
UnsupportedFormatError: 400,
|
|
79
|
+
// Migration Errors
|
|
80
|
+
MigrationError: 500,
|
|
81
|
+
// Plugin Errors
|
|
82
|
+
PluginError: 500,
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Human-readable error messages for each error type.
|
|
86
|
+
*/
|
|
87
|
+
const ERROR_MESSAGES = {
|
|
88
|
+
NotFoundError: "Not found",
|
|
89
|
+
ValidationError: "Validation error",
|
|
90
|
+
DuplicateKeyError: "Duplicate key",
|
|
91
|
+
UniqueConstraintError: "Unique constraint violation",
|
|
92
|
+
ForeignKeyError: "Foreign key violation",
|
|
93
|
+
HookError: "Hook error",
|
|
94
|
+
TransactionError: "Transaction error",
|
|
95
|
+
ConcurrencyError: "Concurrency error",
|
|
96
|
+
OperationError: "Operation error",
|
|
97
|
+
DanglingReferenceError: "Dangling reference",
|
|
98
|
+
CollectionNotFoundError: "Collection not found",
|
|
99
|
+
PopulationError: "Population error",
|
|
100
|
+
StorageError: "Storage error",
|
|
101
|
+
SerializationError: "Serialization error",
|
|
102
|
+
UnsupportedFormatError: "Unsupported format",
|
|
103
|
+
MigrationError: "Migration error",
|
|
104
|
+
PluginError: "Plugin error",
|
|
105
|
+
};
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Error Mapping Function
|
|
108
|
+
// ============================================================================
|
|
109
|
+
/**
|
|
110
|
+
* Map a proseql tagged error to an HTTP response.
|
|
111
|
+
*
|
|
112
|
+
* Matches on the error's `_tag` property and returns the appropriate HTTP
|
|
113
|
+
* status code along with a structured error body. Unknown errors default
|
|
114
|
+
* to 500 Internal Server Error.
|
|
115
|
+
*
|
|
116
|
+
* @param error - The error to map (typically a proseql tagged error)
|
|
117
|
+
* @returns An ErrorResponse with status code and body
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* import { mapErrorToResponse } from "@proseql/rest"
|
|
122
|
+
* import { NotFoundError } from "@proseql/core"
|
|
123
|
+
*
|
|
124
|
+
* const error = new NotFoundError({
|
|
125
|
+
* collection: "books",
|
|
126
|
+
* id: "123",
|
|
127
|
+
* message: "Book not found"
|
|
128
|
+
* })
|
|
129
|
+
*
|
|
130
|
+
* const response = mapErrorToResponse(error)
|
|
131
|
+
* // response = {
|
|
132
|
+
* // status: 404,
|
|
133
|
+
* // body: {
|
|
134
|
+
* // _tag: "NotFoundError",
|
|
135
|
+
* // error: "Not found",
|
|
136
|
+
* // details: { collection: "books", id: "123", message: "Book not found" }
|
|
137
|
+
* // }
|
|
138
|
+
* // }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export const mapErrorToResponse = (error) => {
|
|
142
|
+
// Extract tagged error from FiberFailure or direct tagged error
|
|
143
|
+
const taggedError = extractTaggedError(error);
|
|
144
|
+
// Handle tagged errors
|
|
145
|
+
if (taggedError !== null) {
|
|
146
|
+
const tag = taggedError._tag;
|
|
147
|
+
const status = ERROR_STATUS_MAP[tag] ?? 500;
|
|
148
|
+
const errorMessage = ERROR_MESSAGES[tag] ?? "Internal server error";
|
|
149
|
+
// Extract all fields except _tag for the details object
|
|
150
|
+
const { _tag, ...fields } = taggedError;
|
|
151
|
+
return {
|
|
152
|
+
status,
|
|
153
|
+
body: {
|
|
154
|
+
_tag: tag,
|
|
155
|
+
error: errorMessage,
|
|
156
|
+
details: Object.keys(fields).length > 0 ? fields : undefined,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Handle standard Error instances
|
|
161
|
+
if (error instanceof Error) {
|
|
162
|
+
return {
|
|
163
|
+
status: 500,
|
|
164
|
+
body: {
|
|
165
|
+
_tag: "UnknownError",
|
|
166
|
+
error: "Internal server error",
|
|
167
|
+
details: {
|
|
168
|
+
message: error.message,
|
|
169
|
+
name: error.name,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Handle unknown error types
|
|
175
|
+
return {
|
|
176
|
+
status: 500,
|
|
177
|
+
body: {
|
|
178
|
+
_tag: "UnknownError",
|
|
179
|
+
error: "Internal server error",
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=error-mapping.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-mapping.js","sourceRoot":"","sources":["../src/error-mapping.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST Handler Generation for proseql databases.
|
|
3
|
+
*
|
|
4
|
+
* Generates framework-agnostic HTTP handlers for CRUD operations, queries,
|
|
5
|
+
* and aggregations from a DatabaseConfig and EffectDatabase instance.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { DatabaseConfig, GenerateDatabase, GenerateDatabaseWithPersistence } from "@proseql/core";
|
|
10
|
+
/**
|
|
11
|
+
* Framework-agnostic request object shape.
|
|
12
|
+
* Adapters for specific frameworks (Express, Hono, Bun.serve) convert their
|
|
13
|
+
* native request objects to this shape before invoking handlers.
|
|
14
|
+
*/
|
|
15
|
+
export interface RestRequest {
|
|
16
|
+
/**
|
|
17
|
+
* URL path parameters extracted by the framework's router.
|
|
18
|
+
* Example: for route "/books/:id", params = { id: "123" }
|
|
19
|
+
*/
|
|
20
|
+
readonly params: Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* URL query parameters (search params).
|
|
23
|
+
* Values can be strings or arrays of strings for repeated parameters.
|
|
24
|
+
* Example: ?genre=sci-fi&year=1984 → { genre: "sci-fi", year: "1984" }
|
|
25
|
+
* Example: ?tags=a&tags=b → { tags: ["a", "b"] }
|
|
26
|
+
*/
|
|
27
|
+
readonly query: Record<string, string | ReadonlyArray<string>>;
|
|
28
|
+
/**
|
|
29
|
+
* Parsed request body (for POST/PUT/PATCH requests).
|
|
30
|
+
* The framework adapter is responsible for parsing JSON bodies.
|
|
31
|
+
*/
|
|
32
|
+
readonly body: unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Framework-agnostic response object shape.
|
|
36
|
+
* Handlers return this shape; framework adapters convert it to native responses.
|
|
37
|
+
*/
|
|
38
|
+
export interface RestResponse {
|
|
39
|
+
/** HTTP status code (e.g., 200, 201, 400, 404, 500) */
|
|
40
|
+
readonly status: number;
|
|
41
|
+
/** Response body to serialize as JSON */
|
|
42
|
+
readonly body: unknown;
|
|
43
|
+
/** Optional additional headers */
|
|
44
|
+
readonly headers?: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Framework-agnostic HTTP handler function.
|
|
48
|
+
* Receives a request object and returns a promise resolving to a response object.
|
|
49
|
+
*/
|
|
50
|
+
export type RestHandler = (req: RestRequest) => Promise<RestResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* HTTP method type for route definitions.
|
|
53
|
+
*/
|
|
54
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
55
|
+
/**
|
|
56
|
+
* Route descriptor returned by createRestHandlers.
|
|
57
|
+
* Contains the HTTP method, path pattern, and handler function.
|
|
58
|
+
*/
|
|
59
|
+
export interface RouteDescriptor {
|
|
60
|
+
/** HTTP method for this route */
|
|
61
|
+
readonly method: HttpMethod;
|
|
62
|
+
/** URL path pattern (e.g., "/books", "/books/:id") */
|
|
63
|
+
readonly path: string;
|
|
64
|
+
/** Handler function to invoke for matching requests */
|
|
65
|
+
readonly handler: RestHandler;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create REST handlers for all collections in a database.
|
|
69
|
+
*
|
|
70
|
+
* Generates framework-agnostic route descriptors that can be adapted to any
|
|
71
|
+
* HTTP framework (Express, Hono, Bun.serve, etc.).
|
|
72
|
+
*
|
|
73
|
+
* Generated routes per collection:
|
|
74
|
+
* - GET /:collection — Query with filters, sort, pagination
|
|
75
|
+
* - GET /:collection/:id — Find by ID
|
|
76
|
+
* - POST /:collection — Create entity
|
|
77
|
+
* - PUT /:collection/:id — Update entity
|
|
78
|
+
* - DELETE /:collection/:id — Delete entity
|
|
79
|
+
* - POST /:collection/batch — Create multiple entities
|
|
80
|
+
* - GET /:collection/aggregate — Aggregation queries
|
|
81
|
+
*
|
|
82
|
+
* @param config - The database configuration defining collections
|
|
83
|
+
* @param db - An EffectDatabase or EffectDatabaseWithPersistence instance
|
|
84
|
+
* @returns Array of route descriptors with method, path, and handler
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { createRestHandlers } from "@proseql/rest"
|
|
89
|
+
* import { createEffectDatabase } from "@proseql/core"
|
|
90
|
+
*
|
|
91
|
+
* const config = {
|
|
92
|
+
* books: { schema: BookSchema, relationships: {} },
|
|
93
|
+
* } as const
|
|
94
|
+
*
|
|
95
|
+
* const db = await Effect.runPromise(createEffectDatabase(config, initialData))
|
|
96
|
+
* const routes = createRestHandlers(config, db)
|
|
97
|
+
*
|
|
98
|
+
* // Adapt to your framework:
|
|
99
|
+
* for (const { method, path, handler } of routes) {
|
|
100
|
+
* app[method.toLowerCase()](path, async (req, res) => {
|
|
101
|
+
* const response = await handler({
|
|
102
|
+
* params: req.params,
|
|
103
|
+
* query: req.query,
|
|
104
|
+
* body: req.body,
|
|
105
|
+
* })
|
|
106
|
+
* res.status(response.status).json(response.body)
|
|
107
|
+
* })
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare const createRestHandlers: <Config extends DatabaseConfig>(config: Config, db: GenerateDatabase<Config> | GenerateDatabaseWithPersistence<Config>) => ReadonlyArray<RouteDescriptor>;
|
|
112
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACX,cAAc,EACd,gBAAgB,EAChB,+BAA+B,EAC/B,MAAM,eAAe,CAAC;AAQvB;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC3B;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IAE/D;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB,kCAAkC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAEtE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAE5B,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC9B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,SAAS,cAAc,EAC/D,QAAQ,MAAM,EACd,IAAI,gBAAgB,CAAC,MAAM,CAAC,GAAG,+BAA+B,CAAC,MAAM,CAAC,KACpE,aAAa,CAAC,eAAe,CA4D/B,CAAC"}
|