rads-db 3.1.14 → 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 +13 -2
- package/dist/index.mjs +13 -2
- package/drivers/azureCosmos.cjs +72 -4
- package/drivers/azureCosmos.mjs +63 -4
- package/drivers/restApi.cjs +22 -2
- package/drivers/restApi.mjs +13 -2
- package/features/eventSourcing.cjs +10 -8
- package/features/eventSourcing.mjs +10 -8
- package/integrations/restEndpoints.cjs +3 -0
- package/integrations/restEndpoints.mjs +3 -0
- package/package.json +1 -1
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}`;
|
package/drivers/azureCosmos.cjs
CHANGED
|
@@ -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
|
|
192
|
+
let columns = "*";
|
|
159
193
|
if (args.include?._pick) {
|
|
160
|
-
|
|
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 ${
|
|
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
|
-
|
|
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) {
|
package/drivers/azureCosmos.mjs
CHANGED
|
@@ -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
|
|
139
|
+
let columns = "*";
|
|
115
140
|
if (args.include?._pick) {
|
|
116
|
-
|
|
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 ${
|
|
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
|
-
|
|
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) {
|
package/drivers/restApi.cjs
CHANGED
|
@@ -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}`;
|
package/drivers/restApi.mjs
CHANGED
|
@@ -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 ${
|
|
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 ${
|
|
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);
|
|
@@ -82,12 +82,14 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
|
|
|
82
82
|
aggDoc = newAggDoc;
|
|
83
83
|
if (!context.options.keepNulls) (0, _radsDb.cleanUndefinedAndNull)(aggDoc);
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
if (aggDoc) {
|
|
86
|
+
result.push({
|
|
87
|
+
aggDoc,
|
|
88
|
+
events,
|
|
89
|
+
updatedEvents: events.filter(ev => docsById[ev.id]),
|
|
90
|
+
oldAggDoc: existingAggregatesById[aggDoc.id]
|
|
91
|
+
});
|
|
92
|
+
}
|
|
91
93
|
}
|
|
92
94
|
await (0, _radsDb.handlePrecomputed)(
|
|
93
95
|
// @ts-expect-error TODO wrong types
|
|
@@ -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 ${
|
|
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 ${
|
|
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);
|
|
@@ -71,12 +71,14 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
|
|
|
71
71
|
aggDoc = newAggDoc;
|
|
72
72
|
if (!context.options.keepNulls) cleanUndefinedAndNull(aggDoc);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
if (aggDoc) {
|
|
75
|
+
result.push({
|
|
76
|
+
aggDoc,
|
|
77
|
+
events,
|
|
78
|
+
updatedEvents: events.filter((ev) => docsById[ev.id]),
|
|
79
|
+
oldAggDoc: existingAggregatesById[aggDoc.id]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
80
82
|
}
|
|
81
83
|
await handlePrecomputed(
|
|
82
84
|
// @ts-expect-error TODO wrong types
|
|
@@ -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
|
}
|