rads-db 3.1.15 → 3.1.16

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/dist/index.cjs CHANGED
@@ -1275,10 +1275,21 @@ const restApi = (options) => (schema, entity) => {
1275
1275
  }
1276
1276
  return responseJson;
1277
1277
  }
1278
- const { handlePlural } = schema[entity] || {};
1279
- if (!handlePlural) throw new Error(`Entity ${entity} was not found in schema`);
1278
+ const { handlePlural, handle } = schema[entity] || {};
1279
+ if (!handlePlural || !handle) throw new Error(`Entity ${entity} was not found in schema`);
1280
1280
  const instance = {
1281
1281
  driverName: "restApi",
1282
+ async getAgg(args, ctx) {
1283
+ args = args || {};
1284
+ const url = `${opts.baseUrl}/${handle}/agg`;
1285
+ const fetchOptions = {
1286
+ method: "POST",
1287
+ body: JSON.stringify(args),
1288
+ headers: { "Content-Type": "application/json" }
1289
+ };
1290
+ fetchOptions.headers = { ...fetchOptions.headers, ...opts.getHeaders({ args, context: ctx }) };
1291
+ return await fetchInner(url, fetchOptions);
1292
+ },
1282
1293
  async getMany(args, ctx) {
1283
1294
  args = args || {};
1284
1295
  const url = `${opts.baseUrl}/${handlePlural}`;
package/dist/index.mjs CHANGED
@@ -1268,10 +1268,21 @@ const restApi = (options) => (schema, entity) => {
1268
1268
  }
1269
1269
  return responseJson;
1270
1270
  }
1271
- const { handlePlural } = schema[entity] || {};
1272
- if (!handlePlural) throw new Error(`Entity ${entity} was not found in schema`);
1271
+ const { handlePlural, handle } = schema[entity] || {};
1272
+ if (!handlePlural || !handle) throw new Error(`Entity ${entity} was not found in schema`);
1273
1273
  const instance = {
1274
1274
  driverName: "restApi",
1275
+ async getAgg(args, ctx) {
1276
+ args = args || {};
1277
+ const url = `${opts.baseUrl}/${handle}/agg`;
1278
+ const fetchOptions = {
1279
+ method: "POST",
1280
+ body: JSON.stringify(args),
1281
+ headers: { "Content-Type": "application/json" }
1282
+ };
1283
+ fetchOptions.headers = { ...fetchOptions.headers, ...opts.getHeaders({ args, context: ctx }) };
1284
+ return await fetchInner(url, fetchOptions);
1285
+ },
1275
1286
  async getMany(args, ctx) {
1276
1287
  args = args || {};
1277
1288
  const url = `${opts.baseUrl}/${handlePlural}`;
@@ -97,6 +97,40 @@ var _default = options => (schema, entity) => {
97
97
  driverName: "azureCosmos",
98
98
  client,
99
99
  getMany,
100
+ async getAgg(args, ctx) {
101
+ args = args || {};
102
+ const where = args.where || {};
103
+ const {
104
+ query,
105
+ parameters
106
+ } = getCosmosQuery(schema, entity, args);
107
+ let cursor;
108
+ const response = client.items.query({
109
+ query,
110
+ parameters: Object.keys(parameters).map(k => ({
111
+ name: `@${k}`,
112
+ value: parameters[k]
113
+ }))
114
+ }, {
115
+ continuationToken: args.cursor || void 0,
116
+ continuationTokenLimitInKB: 4,
117
+ maxItemCount: args.maxItemCount
118
+ });
119
+ const page = await response.fetchNext();
120
+ let result = {};
121
+ for (const r of page.resources) {
122
+ result = {
123
+ ...result,
124
+ ...r
125
+ };
126
+ }
127
+ ctx?.log?.({
128
+ charge: page.requestCharge,
129
+ request: query
130
+ });
131
+ if (page.continuationToken) result.cursor = page.continuationToken;
132
+ return result;
133
+ },
100
134
  async deleteMany(args, ctx) {
101
135
  const {
102
136
  nodes,
@@ -155,11 +189,32 @@ function getCosmosQuery(schema, entity, args) {
155
189
  entity
156
190
  }, parameters, where)].filter(x => x);
157
191
  const orderByClause = getCosmosOrderBy(args);
158
- let colums = "*";
192
+ let columns = "*";
159
193
  if (args.include?._pick) {
160
- colums = getCosmosSelectValue(args.include);
194
+ columns = getCosmosSelectValue(args.include);
195
+ }
196
+ if (args.agg) {
197
+ const columnsArray = [];
198
+ for (const key of args.agg) {
199
+ const [field, aggOp] = key.split("_");
200
+ if (aggOp === "count") {
201
+ columnsArray.push("COUNT(1) AS _count");
202
+ }
203
+ if (aggOp === "max" && field) {
204
+ columnsArray.push(`MAX(r["${field}"]) AS ${field}_max`);
205
+ }
206
+ if (aggOp === "min" && field) {
207
+ columnsArray.push(`MIN(r["${field}"]) AS ${field}_min`);
208
+ }
209
+ if (aggOp === "sum" && field) {
210
+ columnsArray.push(`SUM(r["${field}"]) AS ${field}_sum`);
211
+ }
212
+ }
213
+ if (columnsArray.length) {
214
+ columns = columnsArray.join(", ");
215
+ }
161
216
  }
162
- const query = `select ${colums} from r where ${whereClauses.join(" AND ")} ${orderByClause}`;
217
+ const query = `select ${columns} from r where ${whereClauses.join(" AND ")} ${orderByClause}`;
163
218
  return {
164
219
  query,
165
220
  parameters
@@ -588,7 +643,20 @@ async function createDatabaseIfNotExists(options, client) {
588
643
  async function bulkSendWithRetry(client, chunkToSend) {
589
644
  const responses = [];
590
645
  for (let i = 0; i < 20; i++) {
591
- const response = await client.items.bulk(chunkToSend);
646
+ let response;
647
+ try {
648
+ response = await client.items.bulk(chunkToSend);
649
+ } catch (e) {
650
+ const is429 = e?.message?.includes("cosmosdb-error-429");
651
+ if (is429) {
652
+ const delay2 = 50 + i * 100;
653
+ console.warn(`Db overloaded. Retrying after ${delay2}ms...`);
654
+ await new Promise(resolve => setTimeout(resolve, delay2));
655
+ continue;
656
+ } else {
657
+ throw e;
658
+ }
659
+ }
592
660
  const newChunkToSend = [];
593
661
  for (let i2 = 0; i2 < chunkToSend.length; i2++) {
594
662
  if (response[i2].statusCode !== 429) {
@@ -64,6 +64,31 @@ export default (options) => (schema, entity) => {
64
64
  driverName: "azureCosmos",
65
65
  client,
66
66
  getMany,
67
+ async getAgg(args, ctx) {
68
+ args = args || {};
69
+ const where = args.where || {};
70
+ const { query, parameters } = getCosmosQuery(schema, entity, args);
71
+ let cursor;
72
+ const response = client.items.query(
73
+ {
74
+ query,
75
+ parameters: Object.keys(parameters).map((k) => ({ name: `@${k}`, value: parameters[k] }))
76
+ },
77
+ {
78
+ continuationToken: args.cursor || void 0,
79
+ continuationTokenLimitInKB: 4,
80
+ maxItemCount: args.maxItemCount
81
+ }
82
+ );
83
+ const page = await response.fetchNext();
84
+ let result = {};
85
+ for (const r of page.resources) {
86
+ result = { ...result, ...r };
87
+ }
88
+ ctx?.log?.({ charge: page.requestCharge, request: query });
89
+ if (page.continuationToken) result.cursor = page.continuationToken;
90
+ return result;
91
+ },
67
92
  async deleteMany(args, ctx) {
68
93
  const { nodes, cursor } = await getMany(args, ctx);
69
94
  for (const chunk of _.chunk(nodes, 100)) {
@@ -111,11 +136,32 @@ function getCosmosQuery(schema, entity, args) {
111
136
  getCosmosQueryWhere({ schema, entity }, parameters, where)
112
137
  ].filter((x) => x);
113
138
  const orderByClause = getCosmosOrderBy(args);
114
- let colums = "*";
139
+ let columns = "*";
115
140
  if (args.include?._pick) {
116
- colums = getCosmosSelectValue(args.include);
141
+ columns = getCosmosSelectValue(args.include);
142
+ }
143
+ if (args.agg) {
144
+ const columnsArray = [];
145
+ for (const key of args.agg) {
146
+ const [field, aggOp] = key.split("_");
147
+ if (aggOp === "count") {
148
+ columnsArray.push("COUNT(1) AS _count");
149
+ }
150
+ if (aggOp === "max" && field) {
151
+ columnsArray.push(`MAX(r["${field}"]) AS ${field}_max`);
152
+ }
153
+ if (aggOp === "min" && field) {
154
+ columnsArray.push(`MIN(r["${field}"]) AS ${field}_min`);
155
+ }
156
+ if (aggOp === "sum" && field) {
157
+ columnsArray.push(`SUM(r["${field}"]) AS ${field}_sum`);
158
+ }
159
+ }
160
+ if (columnsArray.length) {
161
+ columns = columnsArray.join(", ");
162
+ }
117
163
  }
118
- const query = `select ${colums} from r where ${whereClauses.join(" AND ")} ${orderByClause}`;
164
+ const query = `select ${columns} from r where ${whereClauses.join(" AND ")} ${orderByClause}`;
119
165
  return { query, parameters };
120
166
  }
121
167
  const operatorHandlers = {
@@ -385,7 +431,20 @@ async function createDatabaseIfNotExists(options, client) {
385
431
  async function bulkSendWithRetry(client, chunkToSend) {
386
432
  const responses = [];
387
433
  for (let i = 0; i < 20; i++) {
388
- const response = await client.items.bulk(chunkToSend);
434
+ let response;
435
+ try {
436
+ response = await client.items.bulk(chunkToSend);
437
+ } catch (e) {
438
+ const is429 = e?.message?.includes("cosmosdb-error-429");
439
+ if (is429) {
440
+ const delay2 = 50 + i * 100;
441
+ console.warn(`Db overloaded. Retrying after ${delay2}ms...`);
442
+ await new Promise((resolve) => setTimeout(resolve, delay2));
443
+ continue;
444
+ } else {
445
+ throw e;
446
+ }
447
+ }
389
448
  const newChunkToSend = [];
390
449
  for (let i2 = 0; i2 < chunkToSend.length; i2++) {
391
450
  if (response[i2].statusCode !== 429) {
@@ -28,11 +28,31 @@ var _default = options => (schema, entity) => {
28
28
  return responseJson;
29
29
  }
30
30
  const {
31
- handlePlural
31
+ handlePlural,
32
+ handle
32
33
  } = schema[entity] || {};
33
- if (!handlePlural) throw new Error(`Entity ${entity} was not found in schema`);
34
+ if (!handlePlural || !handle) throw new Error(`Entity ${entity} was not found in schema`);
34
35
  const instance = {
35
36
  driverName: "restApi",
37
+ async getAgg(args, ctx) {
38
+ args = args || {};
39
+ const url = `${opts.baseUrl}/${handle}/agg`;
40
+ const fetchOptions = {
41
+ method: "POST",
42
+ body: JSON.stringify(args),
43
+ headers: {
44
+ "Content-Type": "application/json"
45
+ }
46
+ };
47
+ fetchOptions.headers = {
48
+ ...fetchOptions.headers,
49
+ ...opts.getHeaders({
50
+ args,
51
+ context: ctx
52
+ })
53
+ };
54
+ return await fetchInner(url, fetchOptions);
55
+ },
36
56
  async getMany(args, ctx) {
37
57
  args = args || {};
38
58
  const url = `${opts.baseUrl}/${handlePlural}`;
@@ -21,10 +21,21 @@ export default (options) => (schema, entity) => {
21
21
  }
22
22
  return responseJson;
23
23
  }
24
- const { handlePlural } = schema[entity] || {};
25
- if (!handlePlural) throw new Error(`Entity ${entity} was not found in schema`);
24
+ const { handlePlural, handle } = schema[entity] || {};
25
+ if (!handlePlural || !handle) throw new Error(`Entity ${entity} was not found in schema`);
26
26
  const instance = {
27
27
  driverName: "restApi",
28
+ async getAgg(args, ctx) {
29
+ args = args || {};
30
+ const url = `${opts.baseUrl}/${handle}/agg`;
31
+ const fetchOptions = {
32
+ method: "POST",
33
+ body: JSON.stringify(args),
34
+ headers: { "Content-Type": "application/json" }
35
+ };
36
+ fetchOptions.headers = { ...fetchOptions.headers, ...opts.getHeaders({ args, context: ctx }) };
37
+ return await fetchInner(url, fetchOptions);
38
+ },
28
39
  async getMany(args, ctx) {
29
40
  args = args || {};
30
41
  const url = `${opts.baseUrl}/${handlePlural}`;
@@ -36,9 +36,9 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
36
36
  if (!d.doc.id || docsById[d.doc.id]) continue;
37
37
  docsById[d.doc.id] = d.doc;
38
38
  const aggId = d.doc[aggregateRelationField].id;
39
- if (!aggId) throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
39
+ if (!aggId) throw new Error(`Missing ${eventEntityName}.${aggregateRelationField}.id`);
40
40
  if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
41
- throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
41
+ throw new Error(`Field ${eventEntityName}.${aggregateRelationField}.id cannot be changed`);
42
42
  }
43
43
  if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
44
44
  docsByAggId[aggId].push(d.doc);
@@ -28,9 +28,9 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
28
28
  if (!d.doc.id || docsById[d.doc.id]) continue;
29
29
  docsById[d.doc.id] = d.doc;
30
30
  const aggId = d.doc[aggregateRelationField].id;
31
- if (!aggId) throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
31
+ if (!aggId) throw new Error(`Missing ${eventEntityName}.${aggregateRelationField}.id`);
32
32
  if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
33
- throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
33
+ throw new Error(`Field ${eventEntityName}.${aggregateRelationField}.id cannot be changed`);
34
34
  }
35
35
  if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
36
36
  docsByAggId[aggId].push(d.doc);
@@ -68,6 +68,9 @@ function getRestRoutes(options) {
68
68
  POST: (args, ctx) => db[entity.handle].getMany(args.body, ctx),
69
69
  PUT: (args, ctx) => db[entity.handle].putMany(args.body?.data, ctx)
70
70
  };
71
+ routes[`${prefix}${entity.handle}/agg`] = {
72
+ POST: (args, ctx) => db[entity.handle].getAgg(args.body, ctx)
73
+ };
71
74
  }
72
75
  return routes;
73
76
  }
@@ -38,6 +38,9 @@ export function getRestRoutes(options) {
38
38
  POST: (args, ctx) => db[entity.handle].getMany(args.body, ctx),
39
39
  PUT: (args, ctx) => db[entity.handle].putMany(args.body?.data, ctx)
40
40
  };
41
+ routes[`${prefix}${entity.handle}/agg`] = {
42
+ POST: (args, ctx) => db[entity.handle].getAgg(args.body, ctx)
43
+ };
41
44
  }
42
45
  return routes;
43
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rads-db",
3
- "version": "3.1.15",
3
+ "version": "3.1.16",
4
4
  "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
5
5
  "author": "",
6
6
  "license": "ISC",