@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
package/README.md ADDED
@@ -0,0 +1,512 @@
1
+ # Forest Admin Cosmos DB NoSQL Datasource
2
+
3
+ [![npm version](https://badge.fury.io/js/@forestadmin-experimental%2Fdatasource-cosmos.svg)](https://www.npmjs.com/package/@forestadmin-experimental/datasource-cosmos)
4
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
5
+
6
+ A Forest Admin datasource for Azure Cosmos DB NoSQL API with full support for:
7
+ - 🔍 **Introspection** - Automatic schema detection from sample documents
8
+ - 📝 **CRUD Operations** - Create, Read, Update, Delete
9
+ - 📊 **Aggregations** - Sum, Count, Avg, Min, Max with grouping
10
+ - 🔎 **Advanced Filtering** - Complex queries with AND/OR conditions
11
+ - 🔢 **Sorting & Pagination** - Efficient data retrieval
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @forestadmin-experimental/datasource-cosmos @azure/cosmos
17
+ # or
18
+ yarn add @forestadmin-experimental/datasource-cosmos @azure/cosmos
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Using the Cosmos DB Emulator (for development)
24
+
25
+ ```typescript
26
+ import { createAgent } from '@forestadmin/agent';
27
+ import { createCosmosDataSourceForEmulator } from '@forestadmin-experimental/datasource-cosmos';
28
+
29
+ const agent = createAgent({
30
+ authSecret: process.env.FOREST_AUTH_SECRET,
31
+ envSecret: process.env.FOREST_ENV_SECRET,
32
+ isProduction: false,
33
+ });
34
+
35
+ agent.addDataSource(
36
+ createCosmosDataSourceForEmulator('myDatabase', {
37
+ builder: configurator =>
38
+ configurator
39
+ .addCollectionFromContainer({
40
+ name: 'Users',
41
+ databaseName: 'myDatabase',
42
+ containerName: 'users',
43
+ })
44
+ .addCollectionFromContainer({
45
+ name: 'Products',
46
+ databaseName: 'myDatabase',
47
+ containerName: 'products',
48
+ }),
49
+ }),
50
+ );
51
+
52
+ agent.start();
53
+ ```
54
+
55
+ ### Using Azure Cosmos DB
56
+
57
+ ```typescript
58
+ import { createAgent } from '@forestadmin/agent';
59
+ import { createCosmosDataSource } from '@forestadmin-experimental/datasource-cosmos';
60
+
61
+ const agent = createAgent({
62
+ authSecret: process.env.FOREST_AUTH_SECRET,
63
+ envSecret: process.env.FOREST_ENV_SECRET,
64
+ isProduction: true,
65
+ });
66
+
67
+ agent.addDataSource(
68
+ createCosmosDataSource(
69
+ process.env.COSMOS_ENDPOINT,
70
+ process.env.COSMOS_KEY,
71
+ 'myDatabase',
72
+ {
73
+ builder: configurator =>
74
+ configurator
75
+ .addCollectionFromContainer({
76
+ name: 'Users',
77
+ databaseName: 'myDatabase',
78
+ containerName: 'users',
79
+ partitionKeyPath: '/userId',
80
+ })
81
+ .addCollectionFromContainer({
82
+ name: 'Orders',
83
+ databaseName: 'myDatabase',
84
+ containerName: 'orders',
85
+ partitionKeyPath: '/customerId',
86
+ sampleSize: 200, // Analyze 200 documents for schema inference
87
+ }),
88
+ },
89
+ ),
90
+ );
91
+
92
+ agent.start();
93
+ ```
94
+
95
+ ## Configuration Options
96
+
97
+ ### Factory Functions
98
+
99
+ #### `createCosmosDataSource(endpoint, key, databaseName?, options?)`
100
+
101
+ Create a datasource with Azure Cosmos DB connection details.
102
+
103
+ **Parameters:**
104
+ - `endpoint` (string): Cosmos DB endpoint URL
105
+ - `key` (string): Cosmos DB access key
106
+ - `databaseName` (string, optional): Database name for auto-introspection
107
+ - `options` (object, optional):
108
+ - `builder`: Configuration function for manual collection setup
109
+ - `liveQueryConnections`: Name for native query connection
110
+ - `liveQueryDatabase`: Database for native queries
111
+ - `clientOptions`: Additional CosmosClient options
112
+
113
+ **Example:**
114
+ ```typescript
115
+ createCosmosDataSource(
116
+ 'https://myaccount.documents.azure.com:443/',
117
+ 'myAccessKey',
118
+ 'myDatabase',
119
+ {
120
+ builder: configurator =>
121
+ configurator.addCollectionFromContainer({
122
+ name: 'Users',
123
+ databaseName: 'myDatabase',
124
+ containerName: 'users',
125
+ }),
126
+ liveQueryConnections: 'cosmos',
127
+ liveQueryDatabase: 'myDatabase',
128
+ },
129
+ )
130
+ ```
131
+
132
+ #### `createCosmosDataSourceForEmulator(databaseName?, options?)`
133
+
134
+ Create a datasource for the Cosmos DB Emulator (localhost:8081).
135
+
136
+ **Parameters:**
137
+ - `databaseName` (string, optional): Database name for auto-introspection
138
+ - `options` (object, optional): Same as `createCosmosDataSource`
139
+
140
+ **Example:**
141
+ ```typescript
142
+ createCosmosDataSourceForEmulator('testDatabase', {
143
+ builder: configurator =>
144
+ configurator.addCollectionFromContainer({
145
+ name: 'TestCollection',
146
+ databaseName: 'testDatabase',
147
+ containerName: 'test-container',
148
+ }),
149
+ })
150
+ ```
151
+
152
+ #### `createCosmosDataSourceWithExistingClient(client, databaseName?)`
153
+
154
+ Create a datasource with an existing CosmosClient instance.
155
+
156
+ **Parameters:**
157
+ - `client` (CosmosClient): Existing Cosmos DB client
158
+ - `databaseName` (string, optional): Database name for auto-introspection
159
+
160
+ **Example:**
161
+ ```typescript
162
+ import { CosmosClient } from '@azure/cosmos';
163
+ import { createCosmosDataSourceWithExistingClient } from '@forestadmin-experimental/datasource-cosmos';
164
+
165
+ const client = new CosmosClient({
166
+ endpoint: 'https://myaccount.documents.azure.com:443/',
167
+ key: 'myAccessKey',
168
+ });
169
+
170
+ createCosmosDataSourceWithExistingClient(client, 'myDatabase')
171
+ ```
172
+
173
+ ### Collection Configuration
174
+
175
+ When using the builder pattern, you can configure collections with these options:
176
+
177
+ ```typescript
178
+ configurator.addCollectionFromContainer({
179
+ // Required
180
+ name: 'Users', // Forest Admin collection name
181
+ databaseName: 'myDatabase', // Cosmos DB database name
182
+ containerName: 'users', // Cosmos DB container name
183
+
184
+ // Optional
185
+ partitionKeyPath: '/userId', // Partition key (auto-detected if not provided)
186
+ sampleSize: 100, // Number of documents to analyze for schema (default: 100)
187
+ enableCount: true, // Enable total count in pagination (default: true)
188
+
189
+ overrideTypeConverter: (field) => {
190
+ // Custom type conversion logic
191
+ if (field.fieldName === 'customField') {
192
+ return {
193
+ ...field.generatedFieldSchema,
194
+ columnType: 'String',
195
+ };
196
+ }
197
+ },
198
+ })
199
+ ```
200
+
201
+ ## Features
202
+
203
+ ### Schema Introspection
204
+
205
+ The datasource automatically analyzes sample documents to infer the schema:
206
+
207
+ ```typescript
208
+ // Automatic introspection of all containers in a database
209
+ createCosmosDataSource(endpoint, key, 'myDatabase')
210
+
211
+ // Manual configuration for specific containers
212
+ createCosmosDataSource(endpoint, key, undefined, {
213
+ builder: configurator =>
214
+ configurator
215
+ .addCollectionFromContainer({
216
+ name: 'Users',
217
+ databaseName: 'myDatabase',
218
+ containerName: 'users',
219
+ sampleSize: 200, // Analyze 200 documents for better accuracy
220
+ }),
221
+ })
222
+ ```
223
+
224
+ **Supported Data Types:**
225
+ - `string` → Forest Admin `String`
226
+ - `number` → Forest Admin `Number`
227
+ - `boolean` → Forest Admin `Boolean`
228
+ - `date` (ISO 8601 strings or Date objects) → Forest Admin `Date`
229
+ - `object` → Forest Admin `Json`
230
+ - `array` → Forest Admin `Json`
231
+ - GeoJSON Point → Forest Admin `Point`
232
+
233
+ ### CRUD Operations
234
+
235
+ All standard CRUD operations are fully supported:
236
+
237
+ ```typescript
238
+ // Create
239
+ await collection.create(caller, [
240
+ { name: 'John Doe', email: 'john@example.com', age: 30 },
241
+ { name: 'Jane Doe', email: 'jane@example.com', age: 28 },
242
+ ]);
243
+
244
+ // Read with filters
245
+ await collection.list(
246
+ caller,
247
+ new PaginatedFilter({
248
+ conditionTree: {
249
+ field: 'age',
250
+ operator: 'GreaterThan',
251
+ value: 25,
252
+ },
253
+ page: { limit: 10, skip: 0 },
254
+ }),
255
+ new Projection('id', 'name', 'email'),
256
+ );
257
+
258
+ // Update
259
+ await collection.update(
260
+ caller,
261
+ new Filter({
262
+ conditionTree: {
263
+ field: 'status',
264
+ operator: 'Equal',
265
+ value: 'pending',
266
+ },
267
+ }),
268
+ { status: 'active' },
269
+ );
270
+
271
+ // Delete
272
+ await collection.delete(
273
+ caller,
274
+ new Filter({
275
+ conditionTree: {
276
+ field: 'status',
277
+ operator: 'Equal',
278
+ value: 'archived',
279
+ },
280
+ }),
281
+ );
282
+ ```
283
+
284
+ ### Filtering
285
+
286
+ **Supported Operators:**
287
+ - Presence: `Present`, `Missing`
288
+ - Equality: `Equal`, `NotEqual`, `In`, `NotIn`
289
+ - Comparison: `LessThan`, `GreaterThan`
290
+ - Strings: `Like`, `ILike`, `Contains`, `NotContains`, `StartsWith`, `EndsWith`
291
+ - Arrays: `IncludesAll`
292
+
293
+ **Complex Queries:**
294
+ ```typescript
295
+ // AND condition
296
+ const filter = new PaginatedFilter({
297
+ conditionTree: {
298
+ aggregator: 'And',
299
+ conditions: [
300
+ { field: 'status', operator: 'Equal', value: 'active' },
301
+ { field: 'age', operator: 'GreaterThan', value: 18 },
302
+ { field: 'role', operator: 'In', value: ['admin', 'moderator'] },
303
+ ],
304
+ },
305
+ });
306
+
307
+ // OR condition
308
+ const filter = new PaginatedFilter({
309
+ conditionTree: {
310
+ aggregator: 'Or',
311
+ conditions: [
312
+ { field: 'priority', operator: 'Equal', value: 'high' },
313
+ { field: 'urgent', operator: 'Equal', value: true },
314
+ ],
315
+ },
316
+ });
317
+
318
+ // Nested conditions
319
+ const filter = new PaginatedFilter({
320
+ conditionTree: {
321
+ aggregator: 'And',
322
+ conditions: [
323
+ { field: 'status', operator: 'Equal', value: 'active' },
324
+ {
325
+ aggregator: 'Or',
326
+ conditions: [
327
+ { field: 'role', operator: 'Equal', value: 'admin' },
328
+ { field: 'role', operator: 'Equal', value: 'owner' },
329
+ ],
330
+ },
331
+ ],
332
+ },
333
+ });
334
+ ```
335
+
336
+ ### Aggregations
337
+
338
+ Full support for aggregations with grouping:
339
+
340
+ ```typescript
341
+ // Simple count
342
+ await collection.aggregate(
343
+ caller,
344
+ new Filter({}),
345
+ {
346
+ operation: 'Count',
347
+ field: null,
348
+ },
349
+ );
350
+
351
+ // Sum with grouping
352
+ await collection.aggregate(
353
+ caller,
354
+ new Filter({}),
355
+ {
356
+ operation: 'Sum',
357
+ field: 'revenue',
358
+ groups: [{ field: 'category' }],
359
+ },
360
+ 10, // limit to 10 groups
361
+ );
362
+
363
+ // Average by date
364
+ await collection.aggregate(
365
+ caller,
366
+ new Filter({}),
367
+ {
368
+ operation: 'Avg',
369
+ field: 'score',
370
+ groups: [{ field: 'createdAt', operation: 'Month' }],
371
+ },
372
+ );
373
+ ```
374
+
375
+ **Supported Aggregation Operations:**
376
+ - `Count` - Count records
377
+ - `Sum` - Sum numeric field
378
+ - `Avg` - Average of numeric field
379
+ - `Min` - Minimum value
380
+ - `Max` - Maximum value
381
+
382
+ **Supported Date Grouping:**
383
+ - `Year`
384
+ - `Month`
385
+ - `Week` (approximation)
386
+ - `Day`
387
+
388
+ ### Sorting & Pagination
389
+
390
+ ```typescript
391
+ // Sort by single field
392
+ const filter = new PaginatedFilter({
393
+ sort: [{ field: 'createdAt', ascending: false }],
394
+ page: { limit: 20, skip: 0 },
395
+ });
396
+
397
+ // Sort by multiple fields
398
+ const filter = new PaginatedFilter({
399
+ sort: [
400
+ { field: 'status', ascending: true },
401
+ { field: 'priority', ascending: false },
402
+ { field: 'createdAt', ascending: false },
403
+ ],
404
+ page: { limit: 50, skip: 100 },
405
+ });
406
+ ```
407
+
408
+ ### Native SQL Queries
409
+
410
+ Execute native Cosmos DB SQL queries:
411
+
412
+ ```typescript
413
+ createCosmosDataSource(endpoint, key, 'myDatabase', {
414
+ liveQueryConnections: 'cosmos',
415
+ liveQueryDatabase: 'myDatabase',
416
+ });
417
+
418
+ // In your Forest Admin dashboard, you can now execute:
419
+ // SELECT * FROM users WHERE users.age > $minAge AND users.status = $status
420
+ // With parameters: { minAge: 18, status: 'active' }
421
+ ```
422
+
423
+ ## Development
424
+
425
+ ### Running Tests
426
+
427
+ ```bash
428
+ # Unit tests
429
+ yarn test
430
+
431
+ # Integration tests (requires Cosmos DB Emulator)
432
+ docker compose up -d
433
+ yarn test
434
+ ```
435
+
436
+ ### Cosmos DB Emulator
437
+
438
+ For local development, use the Azure Cosmos DB Emulator:
439
+
440
+ **Docker:**
441
+ ```bash
442
+ docker compose up -d
443
+ ```
444
+
445
+ **Connection Details:**
446
+ - Endpoint: `https://localhost:8081`
447
+ - Key: `C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==`
448
+
449
+ ## Architecture
450
+
451
+ The datasource follows the Forest Admin datasource architecture:
452
+
453
+ ```
454
+ src/
455
+ ├── datasource.ts # Main CosmosDataSource class
456
+ ├── collection.ts # CosmosCollection with CRUD operations
457
+ ├── model-builder/
458
+ │ └── model.ts # ModelCosmos - Cosmos DB client wrapper
459
+ ├── introspection/
460
+ │ ├── introspector.ts # Auto-discovery of containers
461
+ │ ├── container-introspector.ts # Schema inference from documents
462
+ │ └── builder.ts # Configuration builder pattern
463
+ ├── utils/
464
+ │ ├── type-converter.ts # Cosmos to Forest Admin type mapping
465
+ │ ├── query-converter.ts # Filter to Cosmos SQL conversion
466
+ │ ├── aggregation-converter.ts # Aggregation query builder
467
+ │ ├── model-to-collection-schema-converter.ts # Schema generation
468
+ │ └── serializer.ts # Result serialization
469
+ └── index.ts # Public API and factory functions
470
+ ```
471
+
472
+ ## Limitations
473
+
474
+ 1. **Cosmos DB SQL API Limitations:**
475
+ - GROUP BY with multiple fields requires complex implementation
476
+ - No native JOIN support (use nested objects instead)
477
+ - Partition key required for efficient queries
478
+
479
+ 2. **Performance Considerations:**
480
+ - Schema introspection analyzes sample documents (configurable sample size)
481
+ - Large result sets should use pagination
482
+ - Consider indexing policies for optimal query performance
483
+
484
+ 3. **Type Inference:**
485
+ - Schema is inferred from sample documents
486
+ - Mixed types in the same field are treated as Json
487
+ - Nested objects are flattened or treated as Json
488
+
489
+ ## Contributing
490
+
491
+ Contributions are welcome! Please see the main repository for contribution guidelines.
492
+
493
+ ## License
494
+
495
+ GPL-3.0 - See LICENSE file for details
496
+
497
+ ## Support
498
+
499
+ - 📚 [Forest Admin Documentation](https://docs.forestadmin.com)
500
+ - 💬 [Community Forum](https://community.forestadmin.com)
501
+ - 🐛 [Issue Tracker](https://github.com/ForestAdmin/forestadmin-experimental/issues)
502
+ - 📧 [Email Support](mailto:support@forestadmin.com)
503
+
504
+ ## Related Packages
505
+
506
+ - [@forestadmin/agent](https://www.npmjs.com/package/@forestadmin/agent) - Forest Admin Agent
507
+ - [@forestadmin/datasource-toolkit](https://www.npmjs.com/package/@forestadmin/datasource-toolkit) - Base toolkit
508
+ - [@azure/cosmos](https://www.npmjs.com/package/@azure/cosmos) - Azure Cosmos DB SDK
509
+
510
+ ---
511
+
512
+ Made with ❤️ by [Forest Admin](https://www.forestadmin.com)
@@ -0,0 +1,14 @@
1
+ import { CosmosClient } from '@azure/cosmos';
2
+ import { AggregateResult, Aggregation, BaseCollection, Caller, DataSource, Filter, Logger, PaginatedFilter, Projection, RecordData } from '@forestadmin/datasource-toolkit';
3
+ import ModelCosmos from './model-builder/model';
4
+ export default class CosmosCollection extends BaseCollection {
5
+ protected internalModel: ModelCosmos;
6
+ private queryConverter;
7
+ constructor(datasource: DataSource, model: ModelCosmos, logger: Logger, nativeDriver: CosmosClient);
8
+ create(caller: Caller, data: RecordData[]): Promise<RecordData[]>;
9
+ list(caller: Caller, filter: PaginatedFilter, projection: Projection): Promise<RecordData[]>;
10
+ update(caller: Caller, filter: Filter, patch: RecordData): Promise<void>;
11
+ delete(caller: Caller, filter: Filter): Promise<void>;
12
+ aggregate(caller: Caller, filter: Filter, aggregation: Aggregation, limit?: number): Promise<AggregateResult[]>;
13
+ }
14
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EACL,eAAe,EACf,WAAW,EACX,cAAc,EACd,MAAM,EACN,UAAU,EACV,MAAM,EACN,MAAM,EACN,eAAe,EACf,UAAU,EACV,UAAU,EACX,MAAM,iCAAiC,CAAC;AAEzC,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAKhD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,cAAc;IAC1D,SAAS,CAAC,aAAa,EAAE,WAAW,CAAC;IAErC,OAAO,CAAC,cAAc,CAAiB;gBAGrC,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,YAAY;IAmBtB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAUjE,IAAI,CACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,UAAU,EAAE,CAAC;IAyBlB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBrD,SAAS,CACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,EAAE,CAAC;CAkB9B"}
@@ -0,0 +1,90 @@
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 datasource_toolkit_1 = require("@forestadmin/datasource-toolkit");
7
+ const aggregation_converter_1 = __importDefault(require("./utils/aggregation-converter"));
8
+ const model_to_collection_schema_converter_1 = __importDefault(require("./utils/model-to-collection-schema-converter"));
9
+ const query_converter_1 = __importDefault(require("./utils/query-converter"));
10
+ class CosmosCollection extends datasource_toolkit_1.BaseCollection {
11
+ constructor(datasource, model, logger, nativeDriver) {
12
+ if (!model)
13
+ throw new Error('Invalid (null) model instance.');
14
+ super(model.name, datasource, nativeDriver);
15
+ this.internalModel = model;
16
+ this.queryConverter = new query_converter_1.default();
17
+ const modelSchema = model_to_collection_schema_converter_1.default.convert(this.internalModel, logger);
18
+ if (this.internalModel.enableCount !== false)
19
+ this.enableCount();
20
+ this.addFields(modelSchema.fields);
21
+ this.addSegments(modelSchema.segments);
22
+ logger('Debug', `CosmosCollection - ${this.name} added`);
23
+ }
24
+ async create(caller, data) {
25
+ try {
26
+ const recordsResponse = await this.internalModel.create(data);
27
+ return recordsResponse;
28
+ }
29
+ catch (error) {
30
+ throw new Error(`Failed to create records: ${error.message}`);
31
+ }
32
+ }
33
+ async list(caller, filter, projection) {
34
+ try {
35
+ // Build the SQL query from the filter
36
+ const projectionFields = projection.columns.length > 0 ? projection.columns : undefined;
37
+ const querySpec = this.queryConverter.getSqlQuerySpec(filter.conditionTree, filter.sort, projectionFields);
38
+ // Execute the query with pagination
39
+ const recordsResponse = await this.internalModel.query(querySpec, filter.page?.skip, filter.page?.limit);
40
+ // Apply projection to only return projected fields
41
+ return projection.apply(recordsResponse);
42
+ }
43
+ catch (error) {
44
+ throw new Error(`Failed to list records: ${error.message}`);
45
+ }
46
+ }
47
+ async update(caller, filter, patch) {
48
+ try {
49
+ // First, get the IDs of records to update
50
+ const records = await this.list(caller, filter, new datasource_toolkit_1.Projection('id'));
51
+ const ids = records.map(record => record.id);
52
+ if (ids.length === 0) {
53
+ return; // Nothing to update
54
+ }
55
+ await this.internalModel.update(ids, patch);
56
+ }
57
+ catch (error) {
58
+ throw new Error(`Failed to update records: ${error.message}`);
59
+ }
60
+ }
61
+ async delete(caller, filter) {
62
+ try {
63
+ // First, get the IDs of records to delete
64
+ const records = await this.list(caller, filter, new datasource_toolkit_1.Projection('id'));
65
+ const ids = records.map(record => record.id);
66
+ if (ids.length === 0) {
67
+ return; // Nothing to delete
68
+ }
69
+ await this.internalModel.delete(ids);
70
+ }
71
+ catch (error) {
72
+ throw new Error(`Failed to delete records: ${error.message}`);
73
+ }
74
+ }
75
+ async aggregate(caller, filter, aggregation, limit) {
76
+ try {
77
+ // Build aggregation query
78
+ const querySpec = aggregation_converter_1.default.buildAggregationQuery(aggregation, filter.conditionTree, limit);
79
+ // Execute aggregation query
80
+ const rawResults = await this.internalModel.aggregateQuery(querySpec);
81
+ // Process results into Forest Admin format
82
+ return aggregation_converter_1.default.processAggregationResults(rawResults, aggregation);
83
+ }
84
+ catch (error) {
85
+ throw new Error(`Failed to aggregate records: ${error.message}`);
86
+ }
87
+ }
88
+ }
89
+ exports.default = CosmosCollection;
90
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29sbGVjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb2xsZWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBR0Esd0VBV3lDO0FBR3pDLDBGQUFpRTtBQUNqRSx3SEFBMEU7QUFDMUUsOEVBQXFEO0FBRXJELE1BQXFCLGdCQUFpQixTQUFRLG1DQUFjO0lBSzFELFlBQ0UsVUFBc0IsRUFDdEIsS0FBa0IsRUFDbEIsTUFBYyxFQUNkLFlBQTBCO1FBRTFCLElBQUksQ0FBQyxLQUFLO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFDO1FBRTlELEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLFVBQVUsRUFBRSxZQUFZLENBQUMsQ0FBQztRQUU1QyxJQUFJLENBQUMsYUFBYSxHQUFHLEtBQUssQ0FBQztRQUUzQixJQUFJLENBQUMsY0FBYyxHQUFHLElBQUkseUJBQWMsRUFBRSxDQUFDO1FBRTNDLE1BQU0sV0FBVyxHQUFHLDhDQUFjLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxhQUFhLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFdkUsSUFBSSxJQUFJLENBQUMsYUFBYSxDQUFDLFdBQVcsS0FBSyxLQUFLO1lBQUUsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ2pFLElBQUksQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ25DLElBQUksQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRXZDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsc0JBQXNCLElBQUksQ0FBQyxJQUFJLFFBQVEsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRCxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQWMsRUFBRSxJQUFrQjtRQUM3QyxJQUFJLENBQUM7WUFDSCxNQUFNLGVBQWUsR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLElBQUks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
@@ -0,0 +1,26 @@
1
+ import { CosmosClient } from '@azure/cosmos';
2
+ import { BaseDataSource, Logger } from '@forestadmin/datasource-toolkit';
3
+ import CosmosCollection from './collection';
4
+ import ModelCosmos from './model-builder/model';
5
+ export default class CosmosDataSource extends BaseDataSource<CosmosCollection> {
6
+ /**
7
+ * Cosmos DB client instance
8
+ */
9
+ protected cosmosClient: CosmosClient;
10
+ constructor(cosmosClient: CosmosClient, collectionModels: ModelCosmos[], logger: Logger, options?: {
11
+ liveQueryConnections?: string;
12
+ liveQueryDatabase?: string;
13
+ });
14
+ protected createCollections(collectionModels: ModelCosmos[], logger: Logger): Promise<void>;
15
+ executeNativeQuery(connectionName: string, query: string, contextVariables?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
16
+ /**
17
+ * Parse the query string and extract container name and build SqlQuerySpec
18
+ * Expected format: "SELECT * FROM containerName WHERE ..."
19
+ */
20
+ private parseQuery;
21
+ /**
22
+ * Replace placeholders ($variableName) with Cosmos DB parameters (@paramN)
23
+ */
24
+ private replacePlaceholders;
25
+ }
26
+ //# sourceMappingURL=datasource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datasource.d.ts","sourceRoot":"","sources":["../src/datasource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,iCAAiC,CAAC;AAEzE,OAAO,gBAAgB,MAAM,cAAc,CAAC;AAC5C,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAOhD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,cAAc,CAAC,gBAAgB,CAAC;IAC5E;;OAEG;IACH,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC;gBAGnC,YAAY,EAAE,YAAY,EAC1B,gBAAgB,EAAE,WAAW,EAAE,EAC/B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE;cAoBzD,iBAAiB,CAAC,gBAAgB,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM;IAUlE,kBAAkB,CAC/B,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAC7C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAiBrC;;;OAGG;IACH,OAAO,CAAC,UAAU;IAwBlB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CA0B5B"}