@oino-ts/nosql 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.
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OINONoSqlApi = exports.OINONoSqlFactory = exports.OINONoSqlDataModel = exports.OINONoSql = void 0;
4
+ var OINONoSql_js_1 = require("./OINONoSql.js");
5
+ Object.defineProperty(exports, "OINONoSql", { enumerable: true, get: function () { return OINONoSql_js_1.OINONoSql; } });
6
+ var OINONoSqlDataModel_js_1 = require("./OINONoSqlDataModel.js");
7
+ Object.defineProperty(exports, "OINONoSqlDataModel", { enumerable: true, get: function () { return OINONoSqlDataModel_js_1.OINONoSqlDataModel; } });
8
+ var OINONoSqlFactory_js_1 = require("./OINONoSqlFactory.js");
9
+ Object.defineProperty(exports, "OINONoSqlFactory", { enumerable: true, get: function () { return OINONoSqlFactory_js_1.OINONoSqlFactory; } });
10
+ var OINONoSqlApi_js_1 = require("./OINONoSqlApi.js");
11
+ Object.defineProperty(exports, "OINONoSqlApi", { enumerable: true, get: function () { return OINONoSqlApi_js_1.OINONoSqlApi; } });
@@ -0,0 +1,153 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { OINODataSource, OINOQueryBooleanOperation, OINOQueryComparison, OINOQueryNullCheck } from "@oino-ts/common";
7
+ const NOSQL_LIKE_ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g;
8
+ const NOSQL_LIKE_PERCENT_REGEX = /%/g;
9
+ const NOSQL_LIKE_UNDERSCORE_REGEX = /_/g;
10
+ /**
11
+ * Abstract base class for NoSQL storage backends. Subclasses implement
12
+ * the core CRUD operations for a specific provider (e.g. Azure Table Storage).
13
+ *
14
+ * The data model exposed by the API has a fixed set of fields:
15
+ * 1. `partitionKey` – partition key (primary key component, string)
16
+ * 2. `rowKey` – row key (primary key component, string)
17
+ * 3. `timestamp` – last modification timestamp (datetime)
18
+ * 4. `etag` – entity tag (string)
19
+ * 5. `properties` – all custom entity properties as a JSON string
20
+ *
21
+ * The SQL-formatting methods inherited from `OINODataSource` are not used
22
+ * by nosql operations; they are implemented here as passthrough stubs.
23
+ */
24
+ export class OINONoSql extends OINODataSource {
25
+ nosqlParams;
26
+ /** Table name */
27
+ name;
28
+ /**
29
+ * Whether this backend can auto-generate primary key values when none are
30
+ * supplied in a POST request. Defaults to `false`; override in subclasses
31
+ * that support server-side key generation (e.g. UUID on insert).
32
+ */
33
+ supportsAutoKey = false;
34
+ /**
35
+ * Constructor for `OINONoSql`.
36
+ * @param params nosql storage connection parameters
37
+ */
38
+ constructor(params) {
39
+ super();
40
+ this.nosqlParams = { ...params };
41
+ this.name = this.nosqlParams.table;
42
+ }
43
+ // ── OINODataSource passthrough stubs ──────────────────────────────────
44
+ printTableName(name) {
45
+ return name;
46
+ }
47
+ printColumnName(name) {
48
+ return name;
49
+ }
50
+ printCellAsValue(cellValue, nativeType) {
51
+ if (cellValue === null || cellValue === undefined) {
52
+ return "";
53
+ }
54
+ if (cellValue instanceof Date) {
55
+ return cellValue.toISOString();
56
+ }
57
+ return String(cellValue);
58
+ }
59
+ printStringValue(s) {
60
+ return s;
61
+ }
62
+ parseValueAsCell(v, nativeType) {
63
+ if (nativeType === "DATETIME" && typeof v === "string" && v !== "") {
64
+ return new Date(v);
65
+ }
66
+ return v;
67
+ }
68
+ // ── NoSQL-specific filter helpers ─────────────────────────────────────
69
+ /**
70
+ * Test whether a NoSQL entry matches an `OINOQueryFilter` predicate.
71
+ * Used for in-memory (result) filtering when the storage backend cannot
72
+ * translate the predicate to a native query.
73
+ *
74
+ * @param entry nosql entry to test
75
+ * @param filter filter predicate to evaluate
76
+ */
77
+ static matchesEntry(entry, filter) {
78
+ if (filter.isEmpty())
79
+ return true;
80
+ const op = filter.operator;
81
+ if (op === OINOQueryBooleanOperation.and) {
82
+ return OINONoSql.matchesEntry(entry, filter.leftSide) &&
83
+ OINONoSql.matchesEntry(entry, filter.rightSide);
84
+ }
85
+ if (op === OINOQueryBooleanOperation.or) {
86
+ return OINONoSql.matchesEntry(entry, filter.leftSide) ||
87
+ OINONoSql.matchesEntry(entry, filter.rightSide);
88
+ }
89
+ if (op === OINOQueryBooleanOperation.not) {
90
+ return !OINONoSql.matchesEntry(entry, filter.rightSide);
91
+ }
92
+ const field_name = filter.leftSide;
93
+ const compare_value = filter.rightSide;
94
+ let field_value;
95
+ switch (field_name) {
96
+ case "timestamp":
97
+ field_value = entry.timestamp;
98
+ break;
99
+ case "etag":
100
+ field_value = entry.etag;
101
+ break;
102
+ default: return true;
103
+ }
104
+ if (op === OINOQueryNullCheck.isnull)
105
+ return field_value === null;
106
+ if (op === OINOQueryNullCheck.isNotNull)
107
+ return field_value !== null;
108
+ if (field_value === null)
109
+ return false;
110
+ if (field_value instanceof Date) {
111
+ const ms = field_value.getTime();
112
+ const cmp_ms = new Date(compare_value).getTime();
113
+ switch (op) {
114
+ case OINOQueryComparison.lt: return ms < cmp_ms;
115
+ case OINOQueryComparison.le: return ms <= cmp_ms;
116
+ case OINOQueryComparison.eq: return ms === cmp_ms;
117
+ case OINOQueryComparison.ne: return ms !== cmp_ms;
118
+ case OINOQueryComparison.ge: return ms >= cmp_ms;
119
+ case OINOQueryComparison.gt: return ms > cmp_ms;
120
+ default: return true;
121
+ }
122
+ }
123
+ const str_value = String(field_value);
124
+ switch (op) {
125
+ case OINOQueryComparison.lt: return str_value < compare_value;
126
+ case OINOQueryComparison.le: return str_value <= compare_value;
127
+ case OINOQueryComparison.eq: return str_value === compare_value;
128
+ case OINOQueryComparison.ne: return str_value !== compare_value;
129
+ case OINOQueryComparison.ge: return str_value >= compare_value;
130
+ case OINOQueryComparison.gt: return str_value > compare_value;
131
+ case OINOQueryComparison.like: {
132
+ const escaped = compare_value
133
+ .replace(NOSQL_LIKE_ESCAPE_REGEX, "\\$&")
134
+ .replace(NOSQL_LIKE_PERCENT_REGEX, ".*")
135
+ .replace(NOSQL_LIKE_UNDERSCORE_REGEX, ".");
136
+ return new RegExp("^" + escaped + "$", "i").test(str_value);
137
+ }
138
+ default: return true;
139
+ }
140
+ }
141
+ /**
142
+ * Upsert (insert or replace) multiple entities in the most efficient way
143
+ * the backend supports. The default implementation loops over
144
+ * `upsertEntry`; override in subclasses that support native batch writes.
145
+ *
146
+ * @param entries entities to upsert
147
+ */
148
+ async upsertEntries(entries) {
149
+ for (const entry of entries) {
150
+ await this.upsertEntry(entry);
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,374 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { OINOApi, OINOApiRequest, OINOApiResult, OINOModelSet, OINOContentType, OINOLog, OINOConfig, OINOParser, OINOBenchmark, OINO_ERROR_PREFIX } from "@oino-ts/common";
7
+ /**
8
+ * REST API for NoSQL table storage.
9
+ *
10
+ * Supports the following HTTP methods:
11
+ * - **GET without id** – lists all entities and returns metadata as JSON.
12
+ * - **GET with id** – returns a single entity.
13
+ * - **POST / PUT with id** – upserts an entity; body must be a JSON object
14
+ * with a `properties` key containing the custom entity properties.
15
+ * - **DELETE with id** – deletes the named entity.
16
+ *
17
+ * The URL row ID format uses `OINOConfig.OINO_ID_SEPARATOR` to join the
18
+ * primary key field values, matching the number and order of primary key
19
+ * fields in the data model (same `_OINOID_` convention as `OINODbApi`).
20
+ */
21
+ export class OINONoSqlApi extends OINOApi {
22
+ /** NoSQL storage backend */
23
+ noSql;
24
+ /** NoSQL-specific data model (populated by `initializeDatamodel`) */
25
+ noSqlDatamodel = null;
26
+ /**
27
+ * Constructor.
28
+ *
29
+ * NOTE: `initializeDatamodel` (or `OINONoSqlFactory.createApi`) must be
30
+ * called before the first request is dispatched.
31
+ *
32
+ * @param noSql nosql storage backend
33
+ * @param params API parameters
34
+ */
35
+ constructor(noSql, params) {
36
+ if (params.hashidKey) {
37
+ throw new Error(OINO_ERROR_PREFIX + ": hashid is not supported by OINONoSqlApi (primary keys are strings, not numeric IDs)");
38
+ }
39
+ if (params.failOnUpdateOnAutoinc) {
40
+ throw new Error(OINO_ERROR_PREFIX + ": failOnUpdateOnAutoinc is not supported by OINONoSqlApi (no autoinc fields in NoSQL)");
41
+ }
42
+ if (params.returnInsertedIds) {
43
+ throw new Error(OINO_ERROR_PREFIX + ": returnInsertedIds is not supported by OINONoSqlApi");
44
+ }
45
+ super(noSql, params);
46
+ this.noSql = noSql;
47
+ }
48
+ /**
49
+ * Attach the static nosql data model and mark the API as initialised.
50
+ *
51
+ * @param datamodel `OINONoSqlDataModel` instance for this API
52
+ */
53
+ initializeDatamodel(datamodel) {
54
+ this.noSqlDatamodel = datamodel;
55
+ this.datamodel = datamodel;
56
+ this.initialized = true;
57
+ }
58
+ /**
59
+ * Parse a `_OINOID_`-formatted row ID into an ordered array of decoded
60
+ * primary key values using `OINOConfig.parseOINOId`. Returns `null` when
61
+ * the number of parts does not match the data model's primary key count.
62
+ *
63
+ * @param rowId `_OINOID_`-formatted row ID
64
+ */
65
+ _parseRowId(rowId) {
66
+ if (!this.noSqlDatamodel)
67
+ return null;
68
+ const pk_count = this.noSqlDatamodel.filterFields((f) => f.fieldParams.isPrimaryKey).length;
69
+ const parts = OINOConfig.parseOINOId(rowId);
70
+ if (parts.length !== pk_count)
71
+ return null;
72
+ return parts;
73
+ }
74
+ // ── Private helpers ───────────────────────────────────────────────────
75
+ /**
76
+ * Validate a data row against API parameters. Currently checks whether
77
+ * primary key fields are present when `requirePrimaryKey` is `true`.
78
+ *
79
+ * `requirePrimaryKey` is derived at the call-site from:
80
+ * - `this.params.failOnInsertWithoutKey` when explicitly set, or
81
+ * - `!this.noSql.supportsAutoKey` as the implementation-specific default.
82
+ */
83
+ _validateRow(result, row, requirePrimaryKey) {
84
+ if (!requirePrimaryKey)
85
+ return;
86
+ const pk_fields = this.noSqlDatamodel.filterFields((f) => f.fieldParams.isPrimaryKey);
87
+ for (let i = 0; i < pk_fields.length; i++) {
88
+ const field_idx = this.noSqlDatamodel.fields.indexOf(pk_fields[i]);
89
+ const val = row[field_idx];
90
+ if (val === undefined || val === null || String(val) === "") {
91
+ result.setError(405, `Primary key '${pk_fields[i].name}' is missing from the data!`, "_validateRow");
92
+ return;
93
+ }
94
+ }
95
+ }
96
+ _parseData(result, request) {
97
+ let rows = [];
98
+ const data = request.rowData ?? request.body;
99
+ try {
100
+ if (Array.isArray(data)) {
101
+ rows = data;
102
+ }
103
+ else if (data != null) {
104
+ rows = OINOParser.createRows(this.datamodel, data, request.requestType, request.multipartBoundary);
105
+ }
106
+ }
107
+ catch (e) {
108
+ result.setError(400, "Invalid data: " + e.message, "_parseData");
109
+ }
110
+ return rows;
111
+ }
112
+ _rowToEntry(row, pkOverride) {
113
+ const pk_fields = this.noSqlDatamodel.fields.filter(f => f.fieldParams.isPrimaryKey);
114
+ const primary_key = pkOverride ?? pk_fields.map(f => {
115
+ const idx = this.noSqlDatamodel.fields.indexOf(f);
116
+ return String(row[idx] ?? "");
117
+ });
118
+ const properties_idx = this.noSqlDatamodel.fields.findIndex(f => f.name === "properties");
119
+ const raw = properties_idx >= 0 ? row[properties_idx] : undefined;
120
+ let properties = {};
121
+ if (typeof raw === "string") {
122
+ properties = JSON.parse(raw);
123
+ }
124
+ else if ((raw != null) && (typeof raw === "object") && !Array.isArray(raw) && !(raw instanceof Date) && !(raw instanceof Uint8Array)) {
125
+ properties = raw;
126
+ }
127
+ return { primaryKey: primary_key, timestamp: new Date(), etag: "", properties };
128
+ }
129
+ // ── Private HTTP method handlers ──────────────────────────────────────
130
+ async _doGet(result, pkValues, request) {
131
+ if (!pkValues) {
132
+ try {
133
+ const entries = await this.noSql.listEntries(request.queryParams?.filter);
134
+ const dataset = this.noSqlDatamodel.entriesToDataset(entries);
135
+ result.data = new OINOModelSet(this.datamodel, dataset, request.queryParams);
136
+ }
137
+ catch (e) {
138
+ result.setError(500, "Error listing nosql entries: " + e.message, "DoGet");
139
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doGet", "exception in list request", { message: e.message, stack: e.stack });
140
+ }
141
+ }
142
+ else {
143
+ try {
144
+ const entry = await this.noSql.getEntry(pkValues);
145
+ if (entry === null) {
146
+ result.setError(404, `Entry '${pkValues.join("/")}' not found`, "DoGet");
147
+ }
148
+ else {
149
+ const dataset = this.noSqlDatamodel.entriesToDataset([entry]);
150
+ result.data = new OINOModelSet(this.datamodel, dataset, request.queryParams);
151
+ }
152
+ }
153
+ catch (e) {
154
+ result.setError(500, "Error fetching nosql entry: " + e.message, "DoGet");
155
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doGet", "exception in get request", { message: e.message, stack: e.stack });
156
+ }
157
+ }
158
+ }
159
+ async _doPut(result, pkValues, row) {
160
+ try {
161
+ await this.noSql.upsertEntry(this._rowToEntry(row, pkValues));
162
+ }
163
+ catch (e) {
164
+ result.setError(500, "Error upserting nosql entry: " + e.message, "DoPut");
165
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doPut", "exception in put request", { message: e.message, stack: e.stack });
166
+ }
167
+ }
168
+ async _doPost(result, rows, pkOverride) {
169
+ // Validate all rows first and collect valid entries
170
+ const entries = [];
171
+ const require_pk = !pkOverride && (this.params.failOnInsertWithoutKey ?? !this.noSql.supportsAutoKey);
172
+ for (const row of rows) {
173
+ if (require_pk) {
174
+ this._validateRow(result, row, true);
175
+ if (!result.success) {
176
+ if (this.params.failOnAnyInvalidRows === false) {
177
+ result.setOk();
178
+ continue;
179
+ }
180
+ return;
181
+ }
182
+ }
183
+ entries.push(this._rowToEntry(row, pkOverride));
184
+ }
185
+ if (entries.length === 0 && result.success) {
186
+ result.setError(405, "No valid rows for POST!", "DoPost");
187
+ return;
188
+ }
189
+ // Single batch call — implementations use native bulk APIs where possible
190
+ try {
191
+ await this.noSql.upsertEntries(entries);
192
+ }
193
+ catch (e) {
194
+ result.setError(500, "Error upserting nosql entries: " + e.message, "DoPost");
195
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doPost", "exception in post request", { message: e.message, stack: e.stack });
196
+ }
197
+ }
198
+ async _doDelete(result, pkValues) {
199
+ try {
200
+ await this.noSql.deleteEntry(pkValues);
201
+ }
202
+ catch (e) {
203
+ result.setError(500, "Error deleting nosql entry: " + e.message, "DoDelete");
204
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doDelete", "exception in delete request", { message: e.message, stack: e.stack });
205
+ }
206
+ }
207
+ // ── OINOApi abstract implementations ─────────────────────────────────
208
+ async doApiRequest(request) {
209
+ if (!this.initialized) {
210
+ throw new Error(OINO_ERROR_PREFIX + ": OINONoSqlApi is not initialized yet!");
211
+ }
212
+ OINOLog.debug("@oino-ts/nosql", "OINONoSqlApi", "doApiRequest", "Request", { method: request.method, id: request.rowId });
213
+ const result = new OINOApiResult(request);
214
+ let rows = [];
215
+ if (request.method === "PUT" || request.method === "POST") {
216
+ rows = this._parseData(result, request);
217
+ }
218
+ if (request.method === "GET") {
219
+ if (request.rowId) {
220
+ const pk_values = this._parseRowId(request.rowId);
221
+ if (!pk_values) {
222
+ const pk_count = this.noSqlDatamodel.filterFields((f) => f.fieldParams.isPrimaryKey).length;
223
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest");
224
+ }
225
+ else {
226
+ await this._doGet(result, pk_values, request);
227
+ }
228
+ }
229
+ else {
230
+ await this._doGet(result, null, request);
231
+ }
232
+ }
233
+ else if (request.method === "PUT") {
234
+ if (!request.rowId) {
235
+ result.setError(400, "HTTP PUT method requires a URL ID!", "DoRequest");
236
+ }
237
+ else if (rows.length !== 1) {
238
+ result.setError(400, "HTTP PUT method requires exactly one row in the body data!", "DoRequest");
239
+ }
240
+ else {
241
+ const pk_values = this._parseRowId(request.rowId);
242
+ if (!pk_values) {
243
+ const pk_count = this.noSqlDatamodel.filterFields((f) => f.fieldParams.isPrimaryKey).length;
244
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest");
245
+ }
246
+ else {
247
+ await this._doPut(result, pk_values, rows[0]);
248
+ }
249
+ }
250
+ }
251
+ else if (request.method === "POST") {
252
+ if (rows.length === 0) {
253
+ result.setError(400, "HTTP POST method requires at least one row in the body data!", "DoRequest");
254
+ }
255
+ else {
256
+ let pk_override;
257
+ if (request.rowId) {
258
+ if (rows.length !== 1) {
259
+ result.setError(400, "HTTP POST with a URL ID requires exactly one row in the body data!", "DoRequest");
260
+ }
261
+ else {
262
+ const pk_values = this._parseRowId(request.rowId);
263
+ if (!pk_values) {
264
+ const pk_count = this.noSqlDatamodel.filterFields((f) => f.fieldParams.isPrimaryKey).length;
265
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest");
266
+ }
267
+ else {
268
+ pk_override = pk_values;
269
+ }
270
+ }
271
+ }
272
+ if (result.success) {
273
+ await this._doPost(result, rows, pk_override);
274
+ }
275
+ }
276
+ }
277
+ else if (request.method === "DELETE") {
278
+ if (!request.rowId) {
279
+ result.setError(400, "HTTP DELETE method requires a URL ID!", "DoRequest");
280
+ }
281
+ else {
282
+ const pk_values = this._parseRowId(request.rowId);
283
+ if (!pk_values) {
284
+ const pk_count = this.noSqlDatamodel.filterFields((f) => f.fieldParams.isPrimaryKey).length;
285
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest");
286
+ }
287
+ else {
288
+ await this._doDelete(result, pk_values);
289
+ }
290
+ }
291
+ }
292
+ else {
293
+ result.setError(405, "Unsupported HTTP method '" + request.method + "' for OINONoSqlApi", "DoRequest");
294
+ }
295
+ return result;
296
+ }
297
+ async doBatchUpdate(method, rowId, rowData, queryParams) {
298
+ return this.doBatchApiRequest(new OINOApiRequest({ method, rowId, rowData, queryParams }));
299
+ }
300
+ async doBatchApiRequest(request) {
301
+ if (!this.initialized) {
302
+ throw new Error(OINO_ERROR_PREFIX + ": OINONoSqlApi is not initialized yet!");
303
+ }
304
+ OINOLog.debug("@oino-ts/nosql", "OINONoSqlApi", "doBatchApiRequest", "Request", { method: request.method });
305
+ const result = new OINOApiResult(request);
306
+ if (request.method !== "PUT" && request.method !== "DELETE") {
307
+ result.setError(405, "Batch API only supports PUT and DELETE methods!", "DoBatchApiRequest");
308
+ return result;
309
+ }
310
+ OINOBenchmark.startMetric("OINONoSqlApi", "doBatchApiRequest." + request.method);
311
+ const rows = this._parseData(result, request);
312
+ if (!result.success) {
313
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false);
314
+ return result;
315
+ }
316
+ if (request.method === "PUT") {
317
+ const entries = [];
318
+ const require_pk = this.params.failOnInsertWithoutKey ?? !this.noSql.supportsAutoKey;
319
+ for (const row of rows) {
320
+ this._validateRow(result, row, require_pk);
321
+ if (!result.success) {
322
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false);
323
+ return result;
324
+ }
325
+ entries.push(this._rowToEntry(row));
326
+ }
327
+ if (entries.length === 0) {
328
+ result.setError(405, "No valid rows for batch PUT!", "DoBatchApiRequest");
329
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false);
330
+ return result;
331
+ }
332
+ try {
333
+ await this.noSql.upsertEntries(entries);
334
+ }
335
+ catch (e) {
336
+ result.setError(500, "Error batch upserting nosql entries: " + e.message, "DoBatchApiRequest");
337
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "doBatchApiRequest", "exception in batch put request", { message: e.message, stack: e.stack });
338
+ }
339
+ }
340
+ else {
341
+ const pk_fields = this.noSqlDatamodel.fields.filter(f => f.fieldParams.isPrimaryKey);
342
+ for (const row of rows) {
343
+ const pk_values = pk_fields.map(f => {
344
+ const idx = this.noSqlDatamodel.fields.indexOf(f);
345
+ return String(row[idx] ?? "");
346
+ });
347
+ try {
348
+ await this.noSql.deleteEntry(pk_values);
349
+ }
350
+ catch (e) {
351
+ result.setError(500, "Error batch deleting nosql entry: " + e.message, "DoBatchApiRequest");
352
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "doBatchApiRequest", "exception in batch delete request", { message: e.message, stack: e.stack });
353
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false);
354
+ return result;
355
+ }
356
+ }
357
+ }
358
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, result.success);
359
+ return result;
360
+ }
361
+ async doHttpRequest(request, rowId, rowData, queryParams) {
362
+ const api_request = OINOApiRequest.fromHttpRequest(request, rowId, rowData, queryParams);
363
+ return this.doApiRequest(api_request);
364
+ }
365
+ async doRequest(method, rowId, rowData, queryParams, contentType = OINOContentType.json) {
366
+ return this.doApiRequest(new OINOApiRequest({
367
+ method,
368
+ rowId,
369
+ rowData,
370
+ queryParams,
371
+ requestType: contentType
372
+ }));
373
+ }
374
+ }
@@ -0,0 +1,6 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ export {};
@@ -0,0 +1,61 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { OINODataModel, OINOMemoryDataset, } from "@oino-ts/common";
7
+ /**
8
+ * Static data model for NoSQL entity listings.
9
+ *
10
+ * The canonical field order is determined by the implementation's
11
+ * `initializeApiDatamodel` call. Primary key fields are mapped positionally
12
+ * to `OINONoSqlEntry.primaryKey`, while the remaining fields (`timestamp`,
13
+ * `etag`, `properties`) are matched by name.
14
+ */
15
+ export class OINONoSqlDataModel extends OINODataModel {
16
+ /** Reference to the owning NoSQL API */
17
+ noSqlApi;
18
+ /**
19
+ * Constructor. Fields are added externally by the nosql implementation
20
+ * via `initializeApiDatamodel`.
21
+ *
22
+ * @param api the `OINONoSqlApi` that owns this data model
23
+ */
24
+ constructor(api) {
25
+ super(api);
26
+ this.noSqlApi = api;
27
+ }
28
+ /**
29
+ * Convert an array of NoSQL entries into an in-memory dataset whose
30
+ * columns match the fields present in this model.
31
+ *
32
+ * @param entries nosql entries from the storage backend
33
+ */
34
+ entriesToDataset(entries) {
35
+ const pk_fields = this.fields.filter(f => f.fieldParams.isPrimaryKey);
36
+ const rows = entries.map(e => {
37
+ const row = [];
38
+ for (const field of this.fields) {
39
+ const pk_idx = pk_fields.indexOf(field);
40
+ if (pk_idx >= 0) {
41
+ row.push(e.primaryKey[pk_idx] ?? "");
42
+ }
43
+ else {
44
+ switch (field.name) {
45
+ case "timestamp":
46
+ row.push(e.timestamp);
47
+ break;
48
+ case "etag":
49
+ row.push(e.etag);
50
+ break;
51
+ case "properties":
52
+ row.push(JSON.stringify(e.properties));
53
+ break;
54
+ }
55
+ }
56
+ }
57
+ return row;
58
+ });
59
+ return new OINOMemoryDataset(rows);
60
+ }
61
+ }
@@ -0,0 +1,67 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { OINONoSqlApi } from "./OINONoSqlApi.js";
7
+ /**
8
+ * Static factory for creating `OINONoSql` instances and `OINONoSqlApi` instances
9
+ * from registered provider classes.
10
+ *
11
+ * Usage:
12
+ * ```ts
13
+ * OINONoSqlFactory.registerNoSql("OINONoSqlAzureTable", OINONoSqlAzureTable)
14
+ * const nosql = await OINONoSqlFactory.createNoSql({ type: "OINONoSqlAzureTable", ... })
15
+ * const api = await OINONoSqlFactory.createApi(nosql, { apiName: "entities", tableName: "myTable" })
16
+ * ```
17
+ */
18
+ export class OINONoSqlFactory {
19
+ static _registry = {};
20
+ /**
21
+ * Register a nosql provider class under the given name.
22
+ *
23
+ * @param name name used in `OINONoSqlParams.type`
24
+ * @param noSqlClass constructor of the provider
25
+ */
26
+ static registerNoSql(name, noSqlClass) {
27
+ this._registry[name] = noSqlClass;
28
+ }
29
+ /**
30
+ * Create and optionally connect/validate a nosql backend from params.
31
+ *
32
+ * @param params connection parameters
33
+ * @param connect if true, calls `connect()` on the backend
34
+ * @param validate if true, calls `validate()` on the backend
35
+ */
36
+ static async createNoSql(params, connect = true, validate = true) {
37
+ const no_sql_class = this._registry[params.type];
38
+ if (!no_sql_class) {
39
+ throw new Error("Unsupported nosql type: " + params.type);
40
+ }
41
+ const nosql = new no_sql_class(params);
42
+ if (connect) {
43
+ const connect_res = await nosql.connect();
44
+ if (!connect_res.success) {
45
+ throw new Error("NoSql connection failed: " + connect_res.statusText);
46
+ }
47
+ }
48
+ if (validate) {
49
+ const validate_res = await nosql.validate();
50
+ if (!validate_res.success) {
51
+ throw new Error("NoSql validation failed: " + validate_res.statusText);
52
+ }
53
+ }
54
+ return nosql;
55
+ }
56
+ /**
57
+ * Create an `OINONoSqlApi` and initialise its data model.
58
+ *
59
+ * @param noSql nosql backend to use
60
+ * @param params API parameters
61
+ */
62
+ static async createApi(noSql, params) {
63
+ const api = new OINONoSqlApi(noSql, params);
64
+ await noSql.initializeApiDatamodel(api);
65
+ return api;
66
+ }
67
+ }
@@ -0,0 +1,4 @@
1
+ export { OINONoSql } from "./OINONoSql.js";
2
+ export { OINONoSqlDataModel } from "./OINONoSqlDataModel.js";
3
+ export { OINONoSqlFactory } from "./OINONoSqlFactory.js";
4
+ export { OINONoSqlApi } from "./OINONoSqlApi.js";