@forestadmin-experimental/datasource-cosmos 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 (38) hide show
  1. package/README.md +512 -0
  2. package/dist/collection.d.ts +14 -0
  3. package/dist/collection.d.ts.map +1 -0
  4. package/dist/collection.js +90 -0
  5. package/dist/datasource.d.ts +26 -0
  6. package/dist/datasource.d.ts.map +1 -0
  7. package/dist/datasource.js +87 -0
  8. package/dist/index.d.ts +71 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +126 -0
  11. package/dist/introspection/builder.d.ts +68 -0
  12. package/dist/introspection/builder.d.ts.map +1 -0
  13. package/dist/introspection/builder.js +31 -0
  14. package/dist/introspection/container-introspector.d.ts +27 -0
  15. package/dist/introspection/container-introspector.d.ts.map +1 -0
  16. package/dist/introspection/container-introspector.js +168 -0
  17. package/dist/introspection/introspector.d.ts +18 -0
  18. package/dist/introspection/introspector.d.ts.map +1 -0
  19. package/dist/introspection/introspector.js +55 -0
  20. package/dist/model-builder/model.d.ts +79 -0
  21. package/dist/model-builder/model.d.ts.map +1 -0
  22. package/dist/model-builder/model.js +176 -0
  23. package/dist/utils/aggregation-converter.d.ts +30 -0
  24. package/dist/utils/aggregation-converter.d.ts.map +1 -0
  25. package/dist/utils/aggregation-converter.js +181 -0
  26. package/dist/utils/model-to-collection-schema-converter.d.ts +9 -0
  27. package/dist/utils/model-to-collection-schema-converter.d.ts.map +1 -0
  28. package/dist/utils/model-to-collection-schema-converter.js +72 -0
  29. package/dist/utils/query-converter.d.ts +38 -0
  30. package/dist/utils/query-converter.d.ts.map +1 -0
  31. package/dist/utils/query-converter.js +199 -0
  32. package/dist/utils/serializer.d.ts +6 -0
  33. package/dist/utils/serializer.d.ts.map +1 -0
  34. package/dist/utils/serializer.js +29 -0
  35. package/dist/utils/type-converter.d.ts +43 -0
  36. package/dist/utils/type-converter.d.ts.map +1 -0
  37. package/dist/utils/type-converter.js +157 -0
  38. package/package.json +36 -0
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const container_introspector_1 = __importDefault(require("./container-introspector"));
7
+ class Introspector {
8
+ /**
9
+ * Introspect all containers in a Cosmos DB database
10
+ */
11
+ static async introspect(cosmosClient, databaseName, logger) {
12
+ logger?.('Info', 'Introspector - Introspect Cosmos DB');
13
+ return Introspector.introspectAll(cosmosClient, databaseName, logger);
14
+ }
15
+ /**
16
+ * Introspect all containers in the specified database
17
+ */
18
+ static async introspectAll(cosmosClient, databaseName, logger) {
19
+ const results = [];
20
+ const database = cosmosClient.database(databaseName);
21
+ // Get all containers in the database
22
+ const { resources: containers } = await database.containers.readAll().fetchAll();
23
+ // Filter out system containers if needed
24
+ const userContainers = containers.filter(container => !container.id.startsWith('_'));
25
+ logger?.('Info', `Introspector - Found ${userContainers.length} containers in database '${databaseName}'`);
26
+ // Introspect each container
27
+ for (const containerInfo of userContainers) {
28
+ try {
29
+ const model = await (0, container_introspector_1.default)(cosmosClient, containerInfo.id, // Use container name as collection name
30
+ databaseName, containerInfo.id, undefined, // Let introspector determine partition key
31
+ 100);
32
+ results.push(model);
33
+ logger?.('Info', `Introspector - Successfully introspected container '${containerInfo.id}'`);
34
+ }
35
+ catch (error) {
36
+ logger?.('Warn', `Introspector - Failed to introspect container '${containerInfo.id}': ${error.message}`);
37
+ }
38
+ }
39
+ logger?.('Info', `Introspector - The following containers have been loaded: ${userContainers
40
+ .map(c => c.id)
41
+ .join(', ')}`);
42
+ return results;
43
+ }
44
+ /**
45
+ * Introspect a specific container
46
+ */
47
+ static async introspectContainer(cosmosClient, databaseName, containerName, sampleSize, logger) {
48
+ logger?.('Info', `Introspector - Introspecting container '${containerName}'`);
49
+ const model = await (0, container_introspector_1.default)(cosmosClient, containerName, databaseName, containerName, undefined, sampleSize || 100);
50
+ logger?.('Info', `Introspector - Successfully introspected container '${containerName}'`);
51
+ return model;
52
+ }
53
+ }
54
+ exports.default = Introspector;
55
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50cm9zcGVjdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2ludHJvc3BlY3Rpb24vaW50cm9zcGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBSUEsc0ZBQTJEO0FBRzNELE1BQXFCLFlBQVk7SUFDL0I7O09BRUc7SUFDSCxNQUFNLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FDckIsWUFBMEIsRUFDMUIsWUFBb0IsRUFDcEIsTUFBZTtRQUVmLE1BQU0sRUFBRSxDQUFDLE1BQU0sRUFBRSxxQ0FBcUMsQ0FBQyxDQUFDO1FBRXhELE9BQU8sWUFBWSxDQUFDLGFBQWEsQ0FBQyxZQUFZLEVBQUUsWUFBWSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ3hFLENBQUM7SUFFRDs7T0FFRztJQUNLLE1BQU0sQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUNoQyxZQUEwQixFQUMxQixZQUFvQixFQUNwQixNQUFlO1FBRWYsTUFBTSxPQUFPLEdBQWtCLEVBQUUsQ0FBQztRQUNsQyxNQUFNLFFBQVEsR0FBRyxZQUFZLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBRXJELHFDQUFxQztRQUNyQyxNQUFNLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sUUFBUSxDQUFDLFVBQVUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUVqRix5Q0FBeUM7UUFDekMsTUFBTSxjQUFjLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUVyRixNQUFNLEVBQUUsQ0FDTixNQUFNLEVBQ04sd0JBQXdCLGNBQWMsQ0FBQyxNQUFNLDRCQUE0QixZQUFZLEdBQUcsQ0FDekYsQ0FBQztRQUVGLDRCQUE0QjtRQUM1QixLQUFLLE1BQU0sYUFBYSxJQUFJLGNBQWMsRUFBRSxDQUFDO1lBQzNDLElBQUksQ0FBQztnQkFDSCxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUEsZ0NBQW1CLEVBQ3JDLFlBQVksRUFDWixhQUFhLENBQUMsRUFBRSxFQUFFLHdDQUF3QztnQkFDMUQsWUFBWSxFQUNaLGFBQWEsQ0FBQyxFQUFFLEVBQ2hCLFNBQVMsRUFBRSwyQ0FBMkM7Z0JBQ3RELEdBQUcsQ0FDSixDQUFDO2dCQUVGLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBRXBCLE1BQU0sRUFBRSxDQUNOLE1BQU0sRUFDTix1REFBdUQsYUFBYSxDQUFDLEVBQUUsR0FBRyxDQUMzRSxDQUFDO1lBQ0osQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsTUFBTSxFQUFFLENBQ04sTUFBTSxFQUNOLGtEQUFrRCxhQUFhLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FDeEYsQ0FBQztZQUNKLENBQUM7UUFDSCxDQUFDO1FBRUQsTUFBTSxFQUFFLENBQ04sTUFBTSxFQUNOLDZEQUE2RCxjQUFjO2FBQ3hFLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7YUFDZCxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FDaEIsQ0FBQztRQUVGLE9BQU8sT0FBTyxDQUFDO0lBQ2pCLENBQUM7SUFFRDs7T0FFRztJQUNILE1BQU0sQ0FBQyxLQUFLLENBQUMsbUJBQW1CLENBQzlCLFlBQTBCLEVBQzFCLFlBQW9CLEVBQ3BCLGFBQXFCLEVBQ3JCLFVBQW1CLEVBQ25CLE1BQWU7UUFFZixNQUFNLEVBQUUsQ0FBQyxNQUFNLEVBQUUsMkNBQTJDLGFBQWEsR0FBRyxDQUFDLENBQUM7UUFFOUUsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFBLGdDQUFtQixFQUNyQyxZQUFZLEVBQ1osYUFBYSxFQUNiLFlBQVksRUFDWixhQUFhLEVBQ2IsU0FBUyxFQUNULFVBQVUsSUFBSSxHQUFHLENBQ2xCLENBQUM7UUFFRixNQUFNLEVBQUUsQ0FBQyxNQUFNLEVBQUUsdURBQXVELGFBQWEsR0FBRyxDQUFDLENBQUM7UUFFMUYsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0NBQ0Y7QUFqR0QsK0JBaUdDIn0=
@@ -0,0 +1,79 @@
1
+ import { Container, CosmosClient, SqlQuerySpec } from '@azure/cosmos';
2
+ import { RecordData } from '@forestadmin/datasource-toolkit';
3
+ import { OverrideTypeConverter } from '../introspection/builder';
4
+ export interface CosmosSchema {
5
+ [key: string]: {
6
+ type: string;
7
+ nullable?: boolean;
8
+ indexed?: boolean;
9
+ };
10
+ }
11
+ export default class ModelCosmos {
12
+ name: string;
13
+ /**
14
+ * The Cosmos DB database name
15
+ */
16
+ private databaseName;
17
+ /**
18
+ * The Cosmos DB container name
19
+ */
20
+ private containerName;
21
+ /**
22
+ * The partition key path for this container
23
+ */
24
+ private partitionKeyPath;
25
+ /**
26
+ * Schema definition inferred from sample documents
27
+ */
28
+ private schema;
29
+ /**
30
+ * Cosmos DB client instance
31
+ */
32
+ private cosmosClient;
33
+ /**
34
+ * Container reference for direct operations
35
+ */
36
+ private container;
37
+ overrideTypeConverter?: OverrideTypeConverter;
38
+ enableCount?: boolean;
39
+ constructor(cosmosClient: CosmosClient, name: string, databaseName: string, containerName: string, partitionKeyPath: string, schema: CosmosSchema, overrideTypeConverter?: OverrideTypeConverter, enableCount?: boolean);
40
+ create(data: RecordData[]): Promise<RecordData[]>;
41
+ update(ids: string[], patch: RecordData): Promise<void>;
42
+ delete(ids: string[]): Promise<void>;
43
+ query(querySpec: SqlQuerySpec, offset?: number, limit?: number): Promise<RecordData[]>;
44
+ aggregateQuery(querySpec: SqlQuerySpec): Promise<RecordData[]>;
45
+ count(querySpec?: SqlQuerySpec): Promise<number>;
46
+ /**
47
+ * Get items by their IDs (requires scanning as we don't have partition keys)
48
+ */
49
+ private getItemsByIds;
50
+ /**
51
+ * Extract the partition key value from an item
52
+ */
53
+ private getPartitionKeyValue;
54
+ /**
55
+ * Generate a unique ID for new items
56
+ */
57
+ private generateId;
58
+ /**
59
+ * Return all fields from schema
60
+ */
61
+ getAttributes(): CosmosSchema;
62
+ /**
63
+ * Get container reference for advanced operations
64
+ */
65
+ getContainer(): Container;
66
+ /**
67
+ * Get partition key path
68
+ */
69
+ getPartitionKeyPath(): string;
70
+ /**
71
+ * Get database name
72
+ */
73
+ getDatabaseName(): string;
74
+ /**
75
+ * Get container name
76
+ */
77
+ getContainerName(): string;
78
+ }
79
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/model-builder/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAkB,YAAY,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAE7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,MAAM,CAAC,OAAO,OAAO,WAAW;IACvB,IAAI,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,OAAO,CAAC,YAAY,CAAS;IAE7B;;OAEG;IACH,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAS;IAEjC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAe;IAE7B;;OAEG;IACH,OAAO,CAAC,YAAY,CAAe;IAEnC;;OAEG;IACH,OAAO,CAAC,SAAS,CAAY;IAEtB,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAE9C,WAAW,CAAC,EAAE,OAAO,CAAC;gBAG3B,YAAY,EAAE,YAAY,EAC1B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,YAAY,EACpB,qBAAqB,CAAC,EAAE,qBAAqB,EAC7C,WAAW,CAAC,EAAE,OAAO;IAcV,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IA4BjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBvD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAapC,KAAK,CAChB,SAAS,EAAE,YAAY,EACvB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC;IAsBX,cAAc,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAM9D,KAAK,CAAC,SAAS,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAmB7D;;OAEG;YACW,aAAa;IAgB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;IACI,aAAa,IAAI,YAAY;IAIpC;;OAEG;IACI,YAAY,IAAI,SAAS;IAIhC;;OAEG;IACI,mBAAmB,IAAI,MAAM;IAIpC;;OAEG;IACI,eAAe,IAAI,MAAM;IAIhC;;OAEG;IACI,gBAAgB,IAAI,MAAM;CAGlC"}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const serializer_1 = __importDefault(require("../utils/serializer"));
7
+ class ModelCosmos {
8
+ constructor(cosmosClient, name, databaseName, containerName, partitionKeyPath, schema, overrideTypeConverter, enableCount) {
9
+ this.name = name;
10
+ this.databaseName = databaseName;
11
+ this.containerName = containerName;
12
+ this.partitionKeyPath = partitionKeyPath;
13
+ this.schema = schema;
14
+ this.overrideTypeConverter = overrideTypeConverter;
15
+ this.enableCount = enableCount;
16
+ this.cosmosClient = cosmosClient;
17
+ this.container = this.cosmosClient.database(databaseName).container(containerName);
18
+ }
19
+ async create(data) {
20
+ const createdRecords = [];
21
+ // Add timestamps if schema includes them
22
+ if (this.schema.createdAt) {
23
+ data.forEach(newRecord => {
24
+ newRecord.createdAt = new Date().toISOString();
25
+ });
26
+ }
27
+ // Cosmos DB doesn't have a native bulk create, so we'll create items individually
28
+ // In production, you might want to use stored procedures or batch operations
29
+ // eslint-disable-next-line no-restricted-syntax
30
+ for (const record of data) {
31
+ // Ensure the record has an id field (required by Cosmos DB)
32
+ const itemToCreate = {
33
+ id: record.id || this.generateId(),
34
+ ...record,
35
+ };
36
+ // eslint-disable-next-line no-await-in-loop
37
+ const { resource } = await this.container.items.create(itemToCreate);
38
+ createdRecords.push(serializer_1.default.serialize(resource));
39
+ }
40
+ return createdRecords;
41
+ }
42
+ async update(ids, patch) {
43
+ // Cosmos DB requires both id and partition key for updates
44
+ // We need to fetch the items first to get their partition keys
45
+ const itemsToUpdate = await this.getItemsByIds(ids);
46
+ // eslint-disable-next-line no-restricted-syntax
47
+ for (const item of itemsToUpdate) {
48
+ const partitionKeyValue = this.getPartitionKeyValue(item);
49
+ // Merge the patch with the existing item
50
+ const updatedItem = {
51
+ ...item,
52
+ ...patch,
53
+ };
54
+ // eslint-disable-next-line no-await-in-loop
55
+ await this.container.item(item.id, partitionKeyValue).replace(updatedItem);
56
+ }
57
+ }
58
+ async delete(ids) {
59
+ // Similar to update, we need partition keys for deletion
60
+ const itemsToDelete = await this.getItemsByIds(ids);
61
+ // eslint-disable-next-line no-restricted-syntax
62
+ for (const item of itemsToDelete) {
63
+ const partitionKeyValue = this.getPartitionKeyValue(item);
64
+ // eslint-disable-next-line no-await-in-loop
65
+ await this.container.item(item.id, partitionKeyValue).delete();
66
+ }
67
+ }
68
+ async query(querySpec, offset, limit) {
69
+ const options = {
70
+ maxItemCount: limit,
71
+ };
72
+ const { resources } = await this.container.items.query(querySpec, options).fetchAll();
73
+ // Apply offset and limit (Cosmos DB doesn't have native OFFSET)
74
+ let results = resources;
75
+ if (offset) {
76
+ results = results.slice(offset);
77
+ }
78
+ if (limit && offset) {
79
+ // If we already sliced with offset, limit the remaining results
80
+ results = results.slice(0, limit);
81
+ }
82
+ return results.map(item => serializer_1.default.serialize(item));
83
+ }
84
+ async aggregateQuery(querySpec) {
85
+ const { resources } = await this.container.items.query(querySpec).fetchAll();
86
+ return resources.map(item => serializer_1.default.serialize(item));
87
+ }
88
+ async count(querySpec) {
89
+ if (!querySpec) {
90
+ // Simple count without filters
91
+ const countQuery = {
92
+ query: 'SELECT VALUE COUNT(1) FROM c',
93
+ };
94
+ const { resources } = await this.container.items.query(countQuery).fetchAll();
95
+ return resources[0] || 0;
96
+ }
97
+ // Count with filters - we need to modify the query to use COUNT
98
+ const { resources } = await this.container.items.query(querySpec).fetchAll();
99
+ return resources.length;
100
+ }
101
+ // INTERNAL HELPER METHODS
102
+ /**
103
+ * Get items by their IDs (requires scanning as we don't have partition keys)
104
+ */
105
+ async getItemsByIds(ids) {
106
+ const querySpec = {
107
+ query: 'SELECT * FROM c WHERE ARRAY_CONTAINS(@ids, c.id)',
108
+ parameters: [
109
+ {
110
+ name: '@ids',
111
+ value: ids,
112
+ },
113
+ ],
114
+ };
115
+ const { resources } = await this.container.items.query(querySpec).fetchAll();
116
+ return resources;
117
+ }
118
+ /**
119
+ * Extract the partition key value from an item
120
+ */
121
+ getPartitionKeyValue(item) {
122
+ // Remove leading slash from partition key path
123
+ const keyPath = this.partitionKeyPath.replace(/^\//, '');
124
+ // Handle nested paths (e.g., "/address/city")
125
+ const keys = keyPath.split('/');
126
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
+ let value = item;
128
+ for (const key of keys) {
129
+ if (value && typeof value === 'object') {
130
+ value = value[key];
131
+ }
132
+ else {
133
+ break;
134
+ }
135
+ }
136
+ return value;
137
+ }
138
+ /**
139
+ * Generate a unique ID for new items
140
+ */
141
+ generateId() {
142
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
143
+ }
144
+ /**
145
+ * Return all fields from schema
146
+ */
147
+ getAttributes() {
148
+ return this.schema;
149
+ }
150
+ /**
151
+ * Get container reference for advanced operations
152
+ */
153
+ getContainer() {
154
+ return this.container;
155
+ }
156
+ /**
157
+ * Get partition key path
158
+ */
159
+ getPartitionKeyPath() {
160
+ return this.partitionKeyPath;
161
+ }
162
+ /**
163
+ * Get database name
164
+ */
165
+ getDatabaseName() {
166
+ return this.databaseName;
167
+ }
168
+ /**
169
+ * Get container name
170
+ */
171
+ getContainerName() {
172
+ return this.containerName;
173
+ }
174
+ }
175
+ exports.default = ModelCosmos;
176
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWwtYnVpbGRlci9tb2RlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUtBLHFFQUE2QztBQVU3QyxNQUFxQixXQUFXO0lBcUM5QixZQUNFLFlBQTBCLEVBQzFCLElBQVksRUFDWixZQUFvQixFQUNwQixhQUFxQixFQUNyQixnQkFBd0IsRUFDeEIsTUFBb0IsRUFDcEIscUJBQTZDLEVBQzdDLFdBQXFCO1FBRXJCLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ2pCLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFDO1FBQ25DLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQztRQUN6QyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMscUJBQXFCLEdBQUcscUJBQXFCLENBQUM7UUFDbkQsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7UUFFL0IsSUFBSSxDQUFDLFlBQVksR0FBRyxZQUFZLENBQUM7UUFDakMsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQyxTQUFTLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDckYsQ0FBQztJQUVNLEtBQUssQ0FBQyxNQUFNLENBQUMsSUFBa0I7UUFDcEMsTUFBTSxjQUFjLEdBQWlCLEVBQUUsQ0FBQztRQUV4Qyx5Q0FBeUM7UUFDekMsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQzFCLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUU7Z0JBQ3ZCLFNBQVMsQ0FBQyxTQUFTLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNqRCxDQUFDLENBQUMsQ0FB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
@@ -0,0 +1,30 @@
1
+ import { SqlQuerySpec } from '@azure/cosmos';
2
+ import { AggregateResult, Aggregation, AggregationOperation, ConditionTree } from '@forestadmin/datasource-toolkit';
3
+ export default class AggregationConverter {
4
+ private static AGGREGATION_OPERATION;
5
+ /**
6
+ * Build SQL aggregation query for Cosmos DB
7
+ */
8
+ static buildAggregationQuery(aggregation: Aggregation, conditionTree?: ConditionTree, limit?: number): SqlQuerySpec;
9
+ /**
10
+ * Build SELECT clause for aggregation expression
11
+ */
12
+ private static buildAggregateExpression;
13
+ /**
14
+ * Build aggregation query with grouping
15
+ */
16
+ private static buildGroupedAggregationQuery;
17
+ /**
18
+ * Build date grouping expression
19
+ */
20
+ private static buildDateGroupExpression;
21
+ /**
22
+ * Process raw aggregation results into Forest Admin format
23
+ */
24
+ static processAggregationResults(rawResults: any[], aggregation: Aggregation): AggregateResult[];
25
+ /**
26
+ * Build a simpler aggregation query for single-group scenarios
27
+ */
28
+ static buildSimpleAggregationQuery(operation: AggregationOperation, field: string | null, groupByField: string | null, conditionTree?: ConditionTree, limit?: number): SqlQuerySpec;
29
+ }
30
+ //# sourceMappingURL=aggregation-converter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregation-converter.d.ts","sourceRoot":"","sources":["../../src/utils/aggregation-converter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EACL,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,aAAa,EAEd,MAAM,iCAAiC,CAAC;AAKzC,MAAM,CAAC,OAAO,OAAO,oBAAoB;IACvC,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAMlC;IAEF;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAC1B,WAAW,EAAE,WAAW,EACxB,aAAa,CAAC,EAAE,aAAa,EAC7B,KAAK,CAAC,EAAE,MAAM,GACb,YAAY;IAiBf;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAYvC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,4BAA4B;IA6D3C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAyBvC;;OAEG;IACH,MAAM,CAAC,yBAAyB,CAE9B,UAAU,EAAE,GAAG,EAAE,EACjB,WAAW,EAAE,WAAW,GACvB,eAAe,EAAE;IAkCpB;;OAEG;IACH,MAAM,CAAC,2BAA2B,CAChC,SAAS,EAAE,oBAAoB,EAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,aAAa,CAAC,EAAE,aAAa,EAC7B,KAAK,CAAC,EAAE,MAAM,GACb,YAAY;CAoChB"}
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const query_converter_1 = __importDefault(require("./query-converter"));
7
+ const serializer_1 = __importDefault(require("./serializer"));
8
+ class AggregationConverter {
9
+ /**
10
+ * Build SQL aggregation query for Cosmos DB
11
+ */
12
+ static buildAggregationQuery(aggregation, conditionTree, limit) {
13
+ const queryConverter = new query_converter_1.default();
14
+ const { where, parameters } = queryConverter.getWhereClause(conditionTree);
15
+ const whereFragment = where ? `WHERE ${where}` : '';
16
+ // Handle simple aggregation without grouping
17
+ if (!aggregation.groups || aggregation.groups.length === 0) {
18
+ const selectClause = this.buildAggregateExpression(aggregation);
19
+ const query = `SELECT ${selectClause} as value FROM c ${whereFragment}`;
20
+ return { query, parameters };
21
+ }
22
+ // Handle aggregation with grouping
23
+ return this.buildGroupedAggregationQuery(aggregation, whereFragment, parameters, limit);
24
+ }
25
+ /**
26
+ * Build SELECT clause for aggregation expression
27
+ */
28
+ static buildAggregateExpression(aggregation) {
29
+ if (!aggregation.field) {
30
+ // COUNT(*) case
31
+ return 'COUNT(1)';
32
+ }
33
+ const operation = this.AGGREGATION_OPERATION[aggregation.operation];
34
+ const fieldPath = `c.${aggregation.field}`;
35
+ return `${operation}(${fieldPath})`;
36
+ }
37
+ /**
38
+ * Build aggregation query with grouping
39
+ */
40
+ static buildGroupedAggregationQuery(aggregation, whereFragment, parameters, limit) {
41
+ const groups = aggregation.groups || [];
42
+ // Build the aggregate expression
43
+ const aggregateExpr = this.buildAggregateExpression(aggregation);
44
+ // Build the full query
45
+ // Note: Cosmos DB doesn't support GROUP BY directly in SQL API
46
+ // We need to use a different approach with subqueries or application-level grouping
47
+ // For now, we'll use VALUE syntax with DISTINCT for simple grouping
48
+ if (groups.length === 1 && !groups[0].operation) {
49
+ // Simple single field grouping
50
+ const groupField = `c.${groups[0].field}`;
51
+ // For Count operation without field, we need a different approach
52
+ if (aggregation.operation === 'Count' && !aggregation.field) {
53
+ const query = `
54
+ SELECT ${groupField} as groupKey, COUNT(1) as value
55
+ FROM c
56
+ ${whereFragment}
57
+ GROUP BY ${groupField}
58
+ ORDER BY ${groupField}
59
+ ${limit ? `OFFSET 0 LIMIT ${limit}` : ''}
60
+ `
61
+ .trim()
62
+ .replace(/\s+/g, ' ');
63
+ return { query, parameters };
64
+ }
65
+ // For other aggregations
66
+ const query = `
67
+ SELECT ${groupField} as groupKey, ${aggregateExpr} as value
68
+ FROM c
69
+ ${whereFragment}
70
+ GROUP BY ${groupField}
71
+ ORDER BY ${groupField}
72
+ ${limit ? `OFFSET 0 LIMIT ${limit}` : ''}
73
+ `
74
+ .trim()
75
+ .replace(/\s+/g, ' ');
76
+ return { query, parameters };
77
+ }
78
+ // Multiple groups or date operations - more complex, needs special handling
79
+ // For now, throw an error as this requires more complex implementation
80
+ const errorMessage = 'Complex grouping with multiple fields or date operations requires ' +
81
+ 'application-level processing. Please implement this using the raw query ' +
82
+ 'results and post-processing.';
83
+ throw new Error(errorMessage);
84
+ }
85
+ /**
86
+ * Build date grouping expression
87
+ */
88
+ static buildDateGroupExpression(field, operation, alias) {
89
+ const fieldPath = `c.${field}`;
90
+ switch (operation) {
91
+ case 'Year':
92
+ return `DateTimeYear(${fieldPath}) as ${alias}`;
93
+ case 'Month':
94
+ return `CONCAT(DateTimeYear(${fieldPath}), '-', DateTimeMonth(${fieldPath})) as ${alias}`;
95
+ case 'Week':
96
+ // Cosmos DB doesn't have built-in week function, we'll approximate
97
+ return `DateTimeDay(${fieldPath}) as ${alias}`;
98
+ case 'Day':
99
+ return (`CONCAT(DateTimeYear(${fieldPath}), '-', DateTimeMonth(${fieldPath}), '-', ` +
100
+ `DateTimeDay(${fieldPath})) as ${alias}`);
101
+ default:
102
+ throw new Error(`Unsupported date operation: ${operation}`);
103
+ }
104
+ }
105
+ /**
106
+ * Process raw aggregation results into Forest Admin format
107
+ */
108
+ static processAggregationResults(
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ rawResults, aggregation) {
111
+ // Handle simple aggregation without grouping
112
+ if (!aggregation.groups || aggregation.groups.length === 0) {
113
+ if (rawResults.length === 0) {
114
+ return [{ value: 0, group: {} }];
115
+ }
116
+ return [
117
+ {
118
+ value: serializer_1.default.serializeValue(rawResults[0].value),
119
+ group: {},
120
+ },
121
+ ];
122
+ }
123
+ // Handle grouped aggregation
124
+ return rawResults.map(result => {
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ const group = {};
127
+ if (aggregation.groups) {
128
+ aggregation.groups.forEach((groupDef, index) => {
129
+ const groupKey = result.groupKey || result[`group${index}`];
130
+ group[groupDef.field] = serializer_1.default.serializeValue(groupKey);
131
+ });
132
+ }
133
+ return {
134
+ value: serializer_1.default.serializeValue(result.value),
135
+ group,
136
+ };
137
+ });
138
+ }
139
+ /**
140
+ * Build a simpler aggregation query for single-group scenarios
141
+ */
142
+ static buildSimpleAggregationQuery(operation, field, groupByField, conditionTree, limit) {
143
+ const queryConverter = new query_converter_1.default();
144
+ const { where, parameters } = queryConverter.getWhereClause(conditionTree);
145
+ const whereFragment = where ? `WHERE ${where}` : '';
146
+ let selectClause;
147
+ let groupByClause = '';
148
+ let orderByClause = '';
149
+ if (!field) {
150
+ // COUNT(*) case
151
+ selectClause = groupByField
152
+ ? `c.${groupByField} as groupKey, COUNT(1) as value`
153
+ : 'COUNT(1) as value';
154
+ }
155
+ else {
156
+ const op = this.AGGREGATION_OPERATION[operation];
157
+ const fieldPath = `c.${field}`;
158
+ selectClause = groupByField
159
+ ? `c.${groupByField} as groupKey, ${op}(${fieldPath}) as value`
160
+ : `${op}(${fieldPath}) as value`;
161
+ }
162
+ if (groupByField) {
163
+ groupByClause = `GROUP BY c.${groupByField}`;
164
+ orderByClause = `ORDER BY c.${groupByField}`;
165
+ }
166
+ const limitClause = limit && groupByField ? `OFFSET 0 LIMIT ${limit}` : '';
167
+ const queryString = `SELECT ${selectClause} FROM c ${whereFragment} ${groupByClause} ` +
168
+ `${orderByClause} ${limitClause}`;
169
+ const query = queryString.trim().replace(/\s+/g, ' ');
170
+ return { query, parameters };
171
+ }
172
+ }
173
+ AggregationConverter.AGGREGATION_OPERATION = {
174
+ Sum: 'SUM',
175
+ Avg: 'AVG',
176
+ Count: 'COUNT',
177
+ Max: 'MAX',
178
+ Min: 'MIN',
179
+ };
180
+ exports.default = AggregationConverter;
181
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWdncmVnYXRpb24tY29udmVydGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL2FnZ3JlZ2F0aW9uLWNvbnZlcnRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQVNBLHdFQUErQztBQUMvQyw4REFBc0M7QUFFdEMsTUFBcUIsb0JBQW9CO0lBU3ZDOztPQUVHO0lBQ0gsTUFBTSxDQUFDLHFCQUFxQixDQUMxQixXQUF3QixFQUN4QixhQUE2QixFQUM3QixLQUFjO1FBRWQsTUFBTSxjQUFjLEdBQUcsSUFBSSx5QkFBYyxFQUFFLENBQUM7UUFDNUMsTUFBTSxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsR0FBRyxjQUFjLENBQUMsY0FBYyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQzNFLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBRXBELDZDQUE2QztRQUM3QyxJQUFJLENBQUMsV0FBVyxDQUFDLE1BQU0sSUFBSSxXQUFXLENBQUMsTUFBTSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUMzRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsd0JBQXdCLENBQUMsV0FBVyxDQUFDLENBQUM7WUFDaEUsTUFBTSxLQUFLLEdBQUcsVUFBVSxZQUFZLG9CQUFvQixhQUFhLEVBQUUsQ0FBQztZQUV4RSxPQUFPLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxDQUFDO1FBQy9CLENBQUM7UUFFRCxtQ0FBbUM7UUFDbkMsT0FBTyxJQUFJLENBQUMsNEJBQTRCLENBQUMsV0FBVyxFQUFFLGFBQWEsRUFBRSxVQUFVLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDMUYsQ0FBQztJQUVEOztPQUVHO0lBQ0ssTUFBTSxDQUFDLHdCQUF3QixDQUFDLFdBQXdCO1FBQzlELElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLENBQUM7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
@@ -0,0 +1,9 @@
1
+ import { CollectionSchema, Logger } from '@forestadmin/datasource-toolkit';
2
+ import ModelCosmos from '../model-builder/model';
3
+ export default class ModelToCollectionSchemaConverter {
4
+ private static convertAttribute;
5
+ private static convertAttributes;
6
+ private static getFieldSchemaOrOverride;
7
+ static convert(model: ModelCosmos, logger?: Logger): CollectionSchema;
8
+ }
9
+ //# sourceMappingURL=model-to-collection-schema-converter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-to-collection-schema-converter.d.ts","sourceRoot":"","sources":["../../src/utils/model-to-collection-schema-converter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAGhB,MAAM,EACP,MAAM,iCAAiC,CAAC;AAIzC,OAAO,WAA6B,MAAM,wBAAwB,CAAC;AAEnE,MAAM,CAAC,OAAO,OAAO,gCAAgC;IACnD,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAqB/B,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAoChC,OAAO,CAAC,MAAM,CAAC,wBAAwB;WAgBzB,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,gBAAgB;CAmB7E"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const type_converter_1 = __importDefault(require("./type-converter"));
7
+ class ModelToCollectionSchemaConverter {
8
+ static convertAttribute(fieldName, fieldSchema) {
9
+ const cosmosType = fieldSchema.type;
10
+ const columnType = type_converter_1.default.fromDataType(cosmosType);
11
+ const filterOperators = type_converter_1.default.operatorsForColumnType(columnType);
12
+ const isSortable = type_converter_1.default.isSortable(cosmosType);
13
+ const column = {
14
+ columnType,
15
+ filterOperators,
16
+ type: 'Column',
17
+ validation: [],
18
+ isReadOnly: false, // Cosmos DB fields are generally writable
19
+ isSortable,
20
+ };
21
+ return column;
22
+ }
23
+ static convertAttributes(modelName, schema, overrideTypeConverter, logger) {
24
+ const fields = {};
25
+ Object.entries(schema).forEach(([fieldName, fieldSchema]) => {
26
+ try {
27
+ fields[fieldName] = this.getFieldSchemaOrOverride(fieldName, fieldSchema, overrideTypeConverter);
28
+ }
29
+ catch (error) {
30
+ logger?.('Warn', `Skipping column '${modelName}.${fieldName}' (${error.message})`);
31
+ }
32
+ });
33
+ // Add the 'id' field which is required by Cosmos DB
34
+ const idColumn = {
35
+ columnType: 'String',
36
+ type: 'Column',
37
+ filterOperators: type_converter_1.default.operatorsForId(),
38
+ validation: [],
39
+ isReadOnly: false, // Can be set when creating documents
40
+ isSortable: true,
41
+ isPrimaryKey: true,
42
+ };
43
+ fields.id = idColumn;
44
+ return fields;
45
+ }
46
+ static getFieldSchemaOrOverride(fieldName, fieldSchema, overrideTypeConverter) {
47
+ const field = ModelToCollectionSchemaConverter.convertAttribute(fieldName, fieldSchema);
48
+ return overrideTypeConverter
49
+ ? overrideTypeConverter({
50
+ fieldName,
51
+ attribute: fieldSchema,
52
+ generatedFieldSchema: field,
53
+ }) || field
54
+ : field;
55
+ }
56
+ static convert(model, logger) {
57
+ if (!model)
58
+ throw new Error('Invalid (null) model.');
59
+ return {
60
+ actions: {},
61
+ charts: [],
62
+ countable: model.enableCount ?? true,
63
+ fields: {
64
+ ...this.convertAttributes(model.name, model.getAttributes(), model.overrideTypeConverter, logger),
65
+ },
66
+ searchable: false, // Cosmos DB doesn't have built-in full-text search like Elasticsearch
67
+ segments: [],
68
+ };
69
+ }
70
+ }
71
+ exports.default = ModelToCollectionSchemaConverter;
72
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kZWwtdG8tY29sbGVjdGlvbi1zY2hlbWEtY29udmVydGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL21vZGVsLXRvLWNvbGxlY3Rpb24tc2NoZW1hLWNvbnZlcnRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQU9BLHNFQUFpRTtBQUlqRSxNQUFxQixnQ0FBZ0M7SUFDM0MsTUFBTSxDQUFDLGdCQUFnQixDQUM3QixTQUFpQixFQUNqQixXQUFvRTtRQUVwRSxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsSUFBc0IsQ0FBQztRQUN0RCxNQUFNLFVBQVUsR0FBRyx3QkFBYSxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUMxRCxNQUFNLGVBQWUsR0FBRyx3QkFBYSxDQUFDLHNCQUFzQixDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3pFLE1BQU0sVUFBVSxHQUFHLHdCQUFhLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBRXhELE1BQU0sTUFBTSxHQUFpQjtZQUMzQixVQUFVO1lBQ1YsZUFBZTtZQUNmLElBQUksRUFBRSxRQUFRO1lBQ2QsVUFBVSxFQUFFLEVBQUU7WUFDZCxVQUFVLEVBQUUsS0FBSyxFQUFFLDBDQUEwQztZQUM3RCxVQUFVO1NBQ1gsQ0FBQztRQUVGLE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFTyxNQUFNLENBQUMsaUJBQWlCLENBQzlCLFNBQWlCLEVBQ2pCLE1BQW9CLEVBQ3BCLHFCQUE2QyxFQUM3QyxNQUFlO1FBRWYsTUFBTSxNQUFNLEdBQStCLEVBQUUsQ0FBQztRQUU5QyxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsU0FBUyxFQUFFLFdBQVcsQ0FBQyxFQUFFLEVBQUU7WUFDMUQsSUFBSSxDQUFDO2dCQUNILE1BQU0sQ0FBQyxTQUFTLENBQUMsR0FBRyxJQUFJLENBQUMsd0JBQXdCLENBQy9DLFNBQVMsRUFDVCxXQUFXLEVBQ1gscUJBQXFCLENBQ3RCLENBQUM7WUFDSixDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixNQUFNLEVBQUUsQ0FBQyxNQUFNLEVBQUUsb0JBQW9CLFNBQVMsSUFBSSxTQUFTLE1BQU0sS0FBSyxDQUFDLE9BQU8sR0FBRyxDQUFDLENBQUM7WUFDckYsQ0FBQztRQUNILENBQUMsQ0FBQyxDQUFDO1FBRUgsb0RBQW9EO1FBQ3BELE1BQU0sUUFBUSxHQUFpQjtZQUM3QixVQUFVLEVBQUUsUUFBUTtZQUNwQixJQUFJLEVBQUUsUUFBUTtZQUNkLGVBQWUsRUFBRSx3QkFBYSxDQUFDLGNBQWMsRUFBRTtZQUMvQyxVQUFVLEVBQUUsRUFBRTtZQUNkLFVBQVUsRUFBRSxLQUFLLEVBQUUscUNBQXFDO1lBQ3hELFVBQVUsRUFBRSxJQUFJO1lBQ2hCLFlBQVksRUFBRSxJQUFJO1NBQ25CLENBQUM7UUFFRixNQUFNLENBQUMsRUFBRSxHQUFHLFFBQVEsQ0FBQztRQUVyQixPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRU8sTUFBTSxDQUFDLHdCQUF3QixDQUNyQyxTQUFpQixFQUNqQixXQUFvRSxFQUNwRSxxQkFBNkM7UUFFN0MsTUFBTSxLQUFLLEdBQUcsZ0NBQWdDLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLFdBQVcsQ0FBQyxDQUFDO1FBRXhGLE9BQU8scUJBQXFCO1lBQzFCLENBQUMsQ0FBQyxxQkFBcUIsQ0FBQztnQkFDcEIsU0FBUztnQkFDVCxTQUFTLEVBQUUsV0FBVztnQkFDdEIsb0JBQW9CLEVBQUUsS0FBSzthQUM1QixDQUFDLElBQUksS0FBSztZQUNiLENBQUMsQ0FBQyxLQUFLLENBQUM7SUFDWixDQUFDO0lBRU0sTUFBTSxDQUFDLE9BQU8sQ0FBQyxLQUFrQixFQUFFLE1BQWU7UUFDdkQsSUFBSSxDQUFDLEtBQUs7WUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixDQUFDLENBQUM7UUFFckQsT0FBTztZQUNMLE9BQU8sRUFBRSxFQUFFO1lBQ1gsTUFBTSxFQUFFLEVBQUU7WUFDVixTQUFTLEVBQUUsS0FBSyxDQUFDLFdBQVcsSUFBSSxJQUFJO1lBQ3BDLE1BQU0sRUFBRTtnQkFDTixHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FDdkIsS0FBSyxDQUFDLElBQUksRUFDVixLQUFLLENBQUMsYUFBYSxFQUFFLEVBQ3JCLEtBQUssQ0FBQyxxQkFBcUIsRUFDM0IsTUFBTSxDQUNQO2FBQ0Y7WUFDRCxVQUFVLEVBQUUsS0FBSyxFQUFFLHNFQUFzRTtZQUN6RixRQUFRLEVBQUUsRUFBRTtTQUNiLENBQUM7SUFDSixDQUFDO0NBQ0Y7QUE3RkQsbURBNkZDIn0=