@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,414 @@
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
+
7
+ import {
8
+ OINOApi,
9
+ OINOApiParams,
10
+ OINOApiRequest,
11
+ OINOApiResult,
12
+ OINOModelSet,
13
+ OINOContentType,
14
+ OINOQueryParams,
15
+ OINOHttpRequest,
16
+ type OINOApiData,
17
+ type OINODataField,
18
+ type OINODataRow,
19
+ OINOLog,
20
+ OINOConfig,
21
+ OINOParser,
22
+ OINOBenchmark,
23
+ OINO_ERROR_PREFIX
24
+ } from "@oino-ts/common"
25
+ import { OINONoSql } from "./OINONoSql.js"
26
+ import { OINONoSqlDataModel } from "./OINONoSqlDataModel.js"
27
+ import { OINONoSqlEntry } from "./OINONoSqlConstants.js"
28
+
29
+ /**
30
+ * REST API for NoSQL table storage.
31
+ *
32
+ * Supports the following HTTP methods:
33
+ * - **GET without id** – lists all entities and returns metadata as JSON.
34
+ * - **GET with id** – returns a single entity.
35
+ * - **POST / PUT with id** – upserts an entity; body must be a JSON object
36
+ * with a `properties` key containing the custom entity properties.
37
+ * - **DELETE with id** – deletes the named entity.
38
+ *
39
+ * The URL row ID format uses `OINOConfig.OINO_ID_SEPARATOR` to join the
40
+ * primary key field values, matching the number and order of primary key
41
+ * fields in the data model (same `_OINOID_` convention as `OINODbApi`).
42
+ */
43
+ export class OINONoSqlApi extends OINOApi {
44
+
45
+ /** NoSQL storage backend */
46
+ readonly noSql: OINONoSql
47
+
48
+ /** NoSQL-specific data model (populated by `initializeDatamodel`) */
49
+ noSqlDatamodel: OINONoSqlDataModel | null = null
50
+
51
+ /**
52
+ * Constructor.
53
+ *
54
+ * NOTE: `initializeDatamodel` (or `OINONoSqlFactory.createApi`) must be
55
+ * called before the first request is dispatched.
56
+ *
57
+ * @param noSql nosql storage backend
58
+ * @param params API parameters
59
+ */
60
+ constructor(noSql: OINONoSql, params: OINOApiParams) {
61
+ if (params.hashidKey) {
62
+ throw new Error(OINO_ERROR_PREFIX + ": hashid is not supported by OINONoSqlApi (primary keys are strings, not numeric IDs)")
63
+ }
64
+ if (params.failOnUpdateOnAutoinc) {
65
+ throw new Error(OINO_ERROR_PREFIX + ": failOnUpdateOnAutoinc is not supported by OINONoSqlApi (no autoinc fields in NoSQL)")
66
+ }
67
+ if (params.returnInsertedIds) {
68
+ throw new Error(OINO_ERROR_PREFIX + ": returnInsertedIds is not supported by OINONoSqlApi")
69
+ }
70
+ super(noSql, params)
71
+ this.noSql = noSql
72
+ }
73
+
74
+ /**
75
+ * Attach the static nosql data model and mark the API as initialised.
76
+ *
77
+ * @param datamodel `OINONoSqlDataModel` instance for this API
78
+ */
79
+ initializeDatamodel(datamodel: OINONoSqlDataModel): void {
80
+ this.noSqlDatamodel = datamodel
81
+ this.datamodel = datamodel
82
+ this.initialized = true
83
+ }
84
+
85
+ /**
86
+ * Parse a `_OINOID_`-formatted row ID into an ordered array of decoded
87
+ * primary key values using `OINOConfig.parseOINOId`. Returns `null` when
88
+ * the number of parts does not match the data model's primary key count.
89
+ *
90
+ * @param rowId `_OINOID_`-formatted row ID
91
+ */
92
+ private _parseRowId(rowId: string): string[] | null {
93
+ if (!this.noSqlDatamodel) return null
94
+ const pk_count = this.noSqlDatamodel.filterFields((f: OINODataField) => f.fieldParams.isPrimaryKey).length
95
+ const parts = OINOConfig.parseOINOId(rowId)
96
+ if (parts.length !== pk_count) return null
97
+ return parts
98
+ }
99
+
100
+ // ── Private helpers ───────────────────────────────────────────────────
101
+
102
+ /**
103
+ * Validate a data row against API parameters. Currently checks whether
104
+ * primary key fields are present when `requirePrimaryKey` is `true`.
105
+ *
106
+ * `requirePrimaryKey` is derived at the call-site from:
107
+ * - `this.params.failOnInsertWithoutKey` when explicitly set, or
108
+ * - `!this.noSql.supportsAutoKey` as the implementation-specific default.
109
+ */
110
+ private _validateRow(result: OINOApiResult, row: OINODataRow, requirePrimaryKey: boolean): void {
111
+ if (!requirePrimaryKey) return
112
+ const pk_fields = this.noSqlDatamodel!.filterFields((f: OINODataField) => f.fieldParams.isPrimaryKey)
113
+ for (let i = 0; i < pk_fields.length; i++) {
114
+ const field_idx = this.noSqlDatamodel!.fields.indexOf(pk_fields[i])
115
+ const val = row[field_idx]
116
+ if (val === undefined || val === null || String(val) === "") {
117
+ result.setError(405, `Primary key '${pk_fields[i].name}' is missing from the data!`, "_validateRow")
118
+ return
119
+ }
120
+ }
121
+ }
122
+
123
+ private _parseData(result: OINOApiResult, request: OINOApiRequest): OINODataRow[] {
124
+ let rows: OINODataRow[] = []
125
+ const data = request.rowData ?? request.body
126
+ try {
127
+ if (Array.isArray(data)) {
128
+ rows = data as OINODataRow[]
129
+ } else if (data != null) {
130
+ rows = OINOParser.createRows(this.datamodel!, data, request.requestType, request.multipartBoundary)
131
+ }
132
+ } catch (e: any) {
133
+ result.setError(400, "Invalid data: " + e.message, "_parseData")
134
+ }
135
+ return rows
136
+ }
137
+
138
+ private _rowToEntry(row: OINODataRow, pkOverride?: string[]): OINONoSqlEntry {
139
+ const pk_fields = this.noSqlDatamodel!.fields.filter(f => f.fieldParams.isPrimaryKey)
140
+ const primary_key = pkOverride ?? pk_fields.map(f => {
141
+ const idx = this.noSqlDatamodel!.fields.indexOf(f)
142
+ return String(row[idx] ?? "")
143
+ })
144
+ const properties_idx = this.noSqlDatamodel!.fields.findIndex(f => f.name === "properties")
145
+ const raw = properties_idx >= 0 ? row[properties_idx] : undefined
146
+ let properties: Record<string, unknown> = {}
147
+ if (typeof raw === "string") {
148
+ properties = JSON.parse(raw) as Record<string, unknown>
149
+ } else if ((raw != null) && (typeof raw === "object") && !Array.isArray(raw) && !(raw instanceof Date) && !(raw instanceof Uint8Array)) {
150
+ properties = raw as Record<string, unknown>
151
+ }
152
+ return { primaryKey: primary_key, timestamp: new Date(), etag: "", properties }
153
+ }
154
+
155
+ // ── Private HTTP method handlers ──────────────────────────────────────
156
+
157
+ private async _doGet(result: OINOApiResult, pkValues: string[] | null, request: OINOApiRequest): Promise<void> {
158
+ if (!pkValues) {
159
+ try {
160
+ const entries = await this.noSql.listEntries(request.queryParams?.filter)
161
+ const dataset = this.noSqlDatamodel!.entriesToDataset(entries)
162
+ result.data = new OINOModelSet(this.datamodel!, dataset, request.queryParams)
163
+ } catch (e: any) {
164
+ result.setError(500, "Error listing nosql entries: " + e.message, "DoGet")
165
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doGet",
166
+ "exception in list request", { message: e.message, stack: e.stack })
167
+ }
168
+ } else {
169
+ try {
170
+ const entry = await this.noSql.getEntry(pkValues)
171
+ if (entry === null) {
172
+ result.setError(404, `Entry '${pkValues.join("/")}' not found`, "DoGet")
173
+ } else {
174
+ const dataset = this.noSqlDatamodel!.entriesToDataset([entry])
175
+ result.data = new OINOModelSet(this.datamodel!, dataset, request.queryParams)
176
+ }
177
+ } catch (e: any) {
178
+ result.setError(500, "Error fetching nosql entry: " + e.message, "DoGet")
179
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doGet",
180
+ "exception in get request", { message: e.message, stack: e.stack })
181
+ }
182
+ }
183
+ }
184
+
185
+ private async _doPut(result: OINOApiResult, pkValues: string[], row: OINODataRow): Promise<void> {
186
+ try {
187
+ await this.noSql.upsertEntry(this._rowToEntry(row, pkValues))
188
+ } catch (e: any) {
189
+ result.setError(500, "Error upserting nosql entry: " + e.message, "DoPut")
190
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doPut",
191
+ "exception in put request", { message: e.message, stack: e.stack })
192
+ }
193
+ }
194
+
195
+ private async _doPost(result: OINOApiResult, rows: OINODataRow[], pkOverride?: string[]): Promise<void> {
196
+ // Validate all rows first and collect valid entries
197
+ const entries: OINONoSqlEntry[] = []
198
+ const require_pk = !pkOverride && (this.params.failOnInsertWithoutKey ?? !this.noSql.supportsAutoKey)
199
+ for (const row of rows) {
200
+ if (require_pk) {
201
+ this._validateRow(result, row, true)
202
+ if (!result.success) {
203
+ if (this.params.failOnAnyInvalidRows === false) {
204
+ result.setOk()
205
+ continue
206
+ }
207
+ return
208
+ }
209
+ }
210
+ entries.push(this._rowToEntry(row, pkOverride))
211
+ }
212
+ if (entries.length === 0 && result.success) {
213
+ result.setError(405, "No valid rows for POST!", "DoPost")
214
+ return
215
+ }
216
+ // Single batch call — implementations use native bulk APIs where possible
217
+ try {
218
+ await this.noSql.upsertEntries(entries)
219
+ } catch (e: any) {
220
+ result.setError(500, "Error upserting nosql entries: " + e.message, "DoPost")
221
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doPost",
222
+ "exception in post request", { message: e.message, stack: e.stack })
223
+ }
224
+ }
225
+
226
+ private async _doDelete(result: OINOApiResult, pkValues: string[]): Promise<void> {
227
+ try {
228
+ await this.noSql.deleteEntry(pkValues)
229
+ } catch (e: any) {
230
+ result.setError(500, "Error deleting nosql entry: " + e.message, "DoDelete")
231
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "_doDelete",
232
+ "exception in delete request", { message: e.message, stack: e.stack })
233
+ }
234
+ }
235
+
236
+ // ── OINOApi abstract implementations ─────────────────────────────────
237
+
238
+ async doApiRequest(request: OINOApiRequest): Promise<OINOApiResult> {
239
+ if (!this.initialized) {
240
+ throw new Error(OINO_ERROR_PREFIX + ": OINONoSqlApi is not initialized yet!")
241
+ }
242
+ OINOLog.debug("@oino-ts/nosql", "OINONoSqlApi", "doApiRequest", "Request",
243
+ { method: request.method, id: request.rowId })
244
+
245
+ const result = new OINOApiResult(request)
246
+ let rows: OINODataRow[] = []
247
+
248
+ if (request.method === "PUT" || request.method === "POST") {
249
+ rows = this._parseData(result, request)
250
+ }
251
+
252
+ if (request.method === "GET") {
253
+ if (request.rowId) {
254
+ const pk_values = this._parseRowId(request.rowId)
255
+ if (!pk_values) {
256
+ const pk_count = this.noSqlDatamodel!.filterFields((f: OINODataField) => f.fieldParams.isPrimaryKey).length
257
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest")
258
+ } else {
259
+ await this._doGet(result, pk_values, request)
260
+ }
261
+ } else {
262
+ await this._doGet(result, null, request)
263
+ }
264
+
265
+ } else if (request.method === "PUT") {
266
+ if (!request.rowId) {
267
+ result.setError(400, "HTTP PUT method requires a URL ID!", "DoRequest")
268
+ } else if (rows.length !== 1) {
269
+ result.setError(400, "HTTP PUT method requires exactly one row in the body data!", "DoRequest")
270
+ } else {
271
+ const pk_values = this._parseRowId(request.rowId)
272
+ if (!pk_values) {
273
+ const pk_count = this.noSqlDatamodel!.filterFields((f: OINODataField) => f.fieldParams.isPrimaryKey).length
274
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest")
275
+ } else {
276
+ await this._doPut(result, pk_values, rows[0])
277
+ }
278
+ }
279
+
280
+ } else if (request.method === "POST") {
281
+ if (rows.length === 0) {
282
+ result.setError(400, "HTTP POST method requires at least one row in the body data!", "DoRequest")
283
+ } else {
284
+ let pk_override: string[] | undefined
285
+ if (request.rowId) {
286
+ if (rows.length !== 1) {
287
+ result.setError(400, "HTTP POST with a URL ID requires exactly one row in the body data!", "DoRequest")
288
+ } else {
289
+ const pk_values = this._parseRowId(request.rowId)
290
+ if (!pk_values) {
291
+ const pk_count = this.noSqlDatamodel!.filterFields((f: OINODataField) => f.fieldParams.isPrimaryKey).length
292
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest")
293
+ } else {
294
+ pk_override = pk_values
295
+ }
296
+ }
297
+ }
298
+ if (result.success) {
299
+ await this._doPost(result, rows, pk_override)
300
+ }
301
+ }
302
+
303
+ } else if (request.method === "DELETE") {
304
+ if (!request.rowId) {
305
+ result.setError(400, "HTTP DELETE method requires a URL ID!", "DoRequest")
306
+ } else {
307
+ const pk_values = this._parseRowId(request.rowId)
308
+ if (!pk_values) {
309
+ const pk_count = this.noSqlDatamodel!.filterFields((f: OINODataField) => f.fieldParams.isPrimaryKey).length
310
+ result.setError(400, `Invalid row ID; expected ${pk_count} key part(s) separated by '${OINOConfig.OINO_ID_SEPARATOR}'`, "DoRequest")
311
+ } else {
312
+ await this._doDelete(result, pk_values)
313
+ }
314
+ }
315
+
316
+ } else {
317
+ result.setError(405, "Unsupported HTTP method '" + request.method + "' for OINONoSqlApi", "DoRequest")
318
+ }
319
+
320
+ return result
321
+ }
322
+
323
+ async doBatchUpdate(method: string, rowId: string, rowData: OINOApiData, queryParams?: OINOQueryParams): Promise<OINOApiResult> {
324
+ return this.doBatchApiRequest(new OINOApiRequest({ method, rowId, rowData, queryParams }))
325
+ }
326
+
327
+ async doBatchApiRequest(request: OINOApiRequest): Promise<OINOApiResult> {
328
+ if (!this.initialized) {
329
+ throw new Error(OINO_ERROR_PREFIX + ": OINONoSqlApi is not initialized yet!")
330
+ }
331
+ OINOLog.debug("@oino-ts/nosql", "OINONoSqlApi", "doBatchApiRequest", "Request",
332
+ { method: request.method })
333
+ const result = new OINOApiResult(request)
334
+ if (request.method !== "PUT" && request.method !== "DELETE") {
335
+ result.setError(405, "Batch API only supports PUT and DELETE methods!", "DoBatchApiRequest")
336
+ return result
337
+ }
338
+ OINOBenchmark.startMetric("OINONoSqlApi", "doBatchApiRequest." + request.method)
339
+ const rows = this._parseData(result, request)
340
+ if (!result.success) {
341
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false)
342
+ return result
343
+ }
344
+ if (request.method === "PUT") {
345
+ const entries: OINONoSqlEntry[] = []
346
+ const require_pk = this.params.failOnInsertWithoutKey ?? !this.noSql.supportsAutoKey
347
+ for (const row of rows) {
348
+ this._validateRow(result, row, require_pk)
349
+ if (!result.success) {
350
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false)
351
+ return result
352
+ }
353
+ entries.push(this._rowToEntry(row))
354
+ }
355
+ if (entries.length === 0) {
356
+ result.setError(405, "No valid rows for batch PUT!", "DoBatchApiRequest")
357
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false)
358
+ return result
359
+ }
360
+ try {
361
+ await this.noSql.upsertEntries(entries)
362
+ } catch (e: any) {
363
+ result.setError(500, "Error batch upserting nosql entries: " + e.message, "DoBatchApiRequest")
364
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "doBatchApiRequest",
365
+ "exception in batch put request", { message: e.message, stack: e.stack })
366
+ }
367
+ } else {
368
+ const pk_fields = this.noSqlDatamodel!.fields.filter(f => f.fieldParams.isPrimaryKey)
369
+ for (const row of rows) {
370
+ const pk_values = pk_fields.map(f => {
371
+ const idx = this.noSqlDatamodel!.fields.indexOf(f)
372
+ return String(row[idx] ?? "")
373
+ })
374
+ try {
375
+ await this.noSql.deleteEntry(pk_values)
376
+ } catch (e: any) {
377
+ result.setError(500, "Error batch deleting nosql entry: " + e.message, "DoBatchApiRequest")
378
+ OINOLog.exception("@oino-ts/nosql", "OINONoSqlApi", "doBatchApiRequest",
379
+ "exception in batch delete request", { message: e.message, stack: e.stack })
380
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, false)
381
+ return result
382
+ }
383
+ }
384
+ }
385
+ OINOBenchmark.endMetric("OINONoSqlApi", "doBatchApiRequest." + request.method, result.success)
386
+ return result
387
+ }
388
+
389
+ async doHttpRequest(
390
+ request: OINOHttpRequest,
391
+ rowId: string,
392
+ rowData: OINOApiData,
393
+ queryParams: OINOQueryParams
394
+ ): Promise<OINOApiResult> {
395
+ const api_request = OINOApiRequest.fromHttpRequest(request, rowId, rowData, queryParams)
396
+ return this.doApiRequest(api_request)
397
+ }
398
+
399
+ async doRequest(
400
+ method: string,
401
+ rowId: string,
402
+ rowData: OINOApiData,
403
+ queryParams: OINOQueryParams,
404
+ contentType: OINOContentType = OINOContentType.json
405
+ ): Promise<OINOApiResult> {
406
+ return this.doApiRequest(new OINOApiRequest({
407
+ method,
408
+ rowId,
409
+ rowData,
410
+ queryParams,
411
+ requestType: contentType
412
+ }))
413
+ }
414
+ }
@@ -0,0 +1,43 @@
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
+
7
+ import type { OINONoSql } from "./OINONoSql.js"
8
+
9
+ /**
10
+ * NoSQL class (constructor) type
11
+ * @param params nosql parameters
12
+ */
13
+ export type OINONoSqlConstructor = new (params: OINONoSqlParams) => OINONoSql
14
+
15
+ /** NoSQL storage connection parameters */
16
+ export type OINONoSqlParams = {
17
+ /** Name of the nosql class (e.g. OINONoSqlAzureTable) */
18
+ type: string
19
+ /** Service endpoint URL */
20
+ url: string
21
+ /** Table name */
22
+ table: string
23
+ /** Provider-specific connection string (e.g. Azure Storage connection string) */
24
+ connectionStr?: string
25
+ /**
26
+ * Optional static partition key. When set, all read/write operations are
27
+ * automatically scoped to this partition key, allowing multiple logical
28
+ * tables to share a single Azure Table Storage table.
29
+ */
30
+ staticPartitionKey?: string
31
+ }
32
+
33
+ /** A single NoSQL entity entry */
34
+ export type OINONoSqlEntry = {
35
+ /** Primary key values in the order defined by the implementation's data model */
36
+ primaryKey: string[]
37
+ /** Last modification timestamp */
38
+ timestamp: Date
39
+ /** Entity tag */
40
+ etag: string
41
+ /** All custom entity properties as a key-value map */
42
+ properties: Record<string, unknown>
43
+ }
@@ -0,0 +1,65 @@
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
+
7
+ import {
8
+ OINODataModel,
9
+ OINOMemoryDataset,
10
+ OINODataRow,
11
+ } from "@oino-ts/common"
12
+ import { OINONoSqlApi } from "./OINONoSqlApi.js"
13
+ import { OINONoSqlEntry } from "./OINONoSqlConstants.js"
14
+
15
+ /**
16
+ * Static data model for NoSQL entity listings.
17
+ *
18
+ * The canonical field order is determined by the implementation's
19
+ * `initializeApiDatamodel` call. Primary key fields are mapped positionally
20
+ * to `OINONoSqlEntry.primaryKey`, while the remaining fields (`timestamp`,
21
+ * `etag`, `properties`) are matched by name.
22
+ */
23
+ export class OINONoSqlDataModel extends OINODataModel {
24
+
25
+ /** Reference to the owning NoSQL API */
26
+ readonly noSqlApi: OINONoSqlApi
27
+
28
+ /**
29
+ * Constructor. Fields are added externally by the nosql implementation
30
+ * via `initializeApiDatamodel`.
31
+ *
32
+ * @param api the `OINONoSqlApi` that owns this data model
33
+ */
34
+ constructor(api: OINONoSqlApi) {
35
+ super(api)
36
+ this.noSqlApi = api
37
+ }
38
+
39
+ /**
40
+ * Convert an array of NoSQL entries into an in-memory dataset whose
41
+ * columns match the fields present in this model.
42
+ *
43
+ * @param entries nosql entries from the storage backend
44
+ */
45
+ entriesToDataset(entries: OINONoSqlEntry[]): OINOMemoryDataset {
46
+ const pk_fields = this.fields.filter(f => f.fieldParams.isPrimaryKey)
47
+ const rows: OINODataRow[] = entries.map(e => {
48
+ const row: OINODataRow = []
49
+ for (const field of this.fields) {
50
+ const pk_idx = pk_fields.indexOf(field)
51
+ if (pk_idx >= 0) {
52
+ row.push(e.primaryKey[pk_idx] ?? "")
53
+ } else {
54
+ switch (field.name) {
55
+ case "timestamp": row.push(e.timestamp); break
56
+ case "etag": row.push(e.etag); break
57
+ case "properties": row.push(JSON.stringify(e.properties)); break
58
+ }
59
+ }
60
+ }
61
+ return row
62
+ })
63
+ return new OINOMemoryDataset(rows)
64
+ }
65
+ }
@@ -0,0 +1,79 @@
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
+
7
+ import { OINOApiParams } from "@oino-ts/common"
8
+ import { OINONoSqlParams, OINONoSqlConstructor } from "./OINONoSqlConstants.js"
9
+ import { OINONoSql } from "./OINONoSql.js"
10
+ import { OINONoSqlApi } from "./OINONoSqlApi.js"
11
+
12
+ /**
13
+ * Static factory for creating `OINONoSql` instances and `OINONoSqlApi` instances
14
+ * from registered provider classes.
15
+ *
16
+ * Usage:
17
+ * ```ts
18
+ * OINONoSqlFactory.registerNoSql("OINONoSqlAzureTable", OINONoSqlAzureTable)
19
+ * const nosql = await OINONoSqlFactory.createNoSql({ type: "OINONoSqlAzureTable", ... })
20
+ * const api = await OINONoSqlFactory.createApi(nosql, { apiName: "entities", tableName: "myTable" })
21
+ * ```
22
+ */
23
+ export class OINONoSqlFactory {
24
+ private static _registry: Record<string, OINONoSqlConstructor> = {}
25
+
26
+ /**
27
+ * Register a nosql provider class under the given name.
28
+ *
29
+ * @param name name used in `OINONoSqlParams.type`
30
+ * @param noSqlClass constructor of the provider
31
+ */
32
+ static registerNoSql(name: string, noSqlClass: OINONoSqlConstructor): void {
33
+ this._registry[name] = noSqlClass
34
+ }
35
+
36
+ /**
37
+ * Create and optionally connect/validate a nosql backend from params.
38
+ *
39
+ * @param params connection parameters
40
+ * @param connect if true, calls `connect()` on the backend
41
+ * @param validate if true, calls `validate()` on the backend
42
+ */
43
+ static async createNoSql(
44
+ params: OINONoSqlParams,
45
+ connect: boolean = true,
46
+ validate: boolean = true
47
+ ): Promise<OINONoSql> {
48
+ const no_sql_class = this._registry[params.type]
49
+ if (!no_sql_class) {
50
+ throw new Error("Unsupported nosql type: " + params.type)
51
+ }
52
+ const nosql: OINONoSql = new no_sql_class(params)
53
+ if (connect) {
54
+ const connect_res = await nosql.connect()
55
+ if (!connect_res.success) {
56
+ throw new Error("NoSql connection failed: " + connect_res.statusText)
57
+ }
58
+ }
59
+ if (validate) {
60
+ const validate_res = await nosql.validate()
61
+ if (!validate_res.success) {
62
+ throw new Error("NoSql validation failed: " + validate_res.statusText)
63
+ }
64
+ }
65
+ return nosql
66
+ }
67
+
68
+ /**
69
+ * Create an `OINONoSqlApi` and initialise its data model.
70
+ *
71
+ * @param noSql nosql backend to use
72
+ * @param params API parameters
73
+ */
74
+ static async createApi(noSql: OINONoSql, params: OINOApiParams): Promise<OINONoSqlApi> {
75
+ const api = new OINONoSqlApi(noSql, params)
76
+ await noSql.initializeApiDatamodel(api)
77
+ return api
78
+ }
79
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { OINONoSql } from "./OINONoSql.js"
2
+ export { OINONoSqlDataModel } from "./OINONoSqlDataModel.js"
3
+ export { OINONoSqlFactory } from "./OINONoSqlFactory.js"
4
+ export { OINONoSqlApi } from "./OINONoSqlApi.js"
5
+ export { type OINONoSqlConstructor, type OINONoSqlParams, type OINONoSqlEntry } from "./OINONoSqlConstants.js"