@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.
- package/dist/cjs/OINONoSql.js +157 -0
- package/dist/cjs/OINONoSqlApi.js +378 -0
- package/dist/cjs/OINONoSqlConstants.js +7 -0
- package/dist/cjs/OINONoSqlDataModel.js +65 -0
- package/dist/cjs/OINONoSqlFactory.js +71 -0
- package/dist/cjs/index.js +11 -0
- package/dist/esm/OINONoSql.js +153 -0
- package/dist/esm/OINONoSqlApi.js +374 -0
- package/dist/esm/OINONoSqlConstants.js +6 -0
- package/dist/esm/OINONoSqlDataModel.js +61 -0
- package/dist/esm/OINONoSqlFactory.js +67 -0
- package/dist/esm/index.js +4 -0
- package/dist/types/OINONoSql.d.ts +81 -0
- package/dist/types/OINONoSqlApi.d.ts +67 -0
- package/dist/types/OINONoSqlConstants.d.ts +34 -0
- package/dist/types/OINONoSqlDataModel.d.ts +29 -0
- package/dist/types/OINONoSqlFactory.d.ts +40 -0
- package/dist/types/index.d.ts +5 -0
- package/package.json +37 -0
- package/src/OINONoSql.ts +202 -0
- package/src/OINONoSqlApi.test.ts +483 -0
- package/src/OINONoSqlApi.ts +414 -0
- package/src/OINONoSqlConstants.ts +43 -0
- package/src/OINONoSqlDataModel.ts +65 -0
- package/src/OINONoSqlFactory.ts +79 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { OINODataSource, OINODataCell, OINOQueryFilter } from "@oino-ts/common";
|
|
2
|
+
import { OINONoSqlParams, OINONoSqlEntry } from "./OINONoSqlConstants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for NoSQL storage backends. Subclasses implement
|
|
5
|
+
* the core CRUD operations for a specific provider (e.g. Azure Table Storage).
|
|
6
|
+
*
|
|
7
|
+
* The data model exposed by the API has a fixed set of fields:
|
|
8
|
+
* 1. `partitionKey` – partition key (primary key component, string)
|
|
9
|
+
* 2. `rowKey` – row key (primary key component, string)
|
|
10
|
+
* 3. `timestamp` – last modification timestamp (datetime)
|
|
11
|
+
* 4. `etag` – entity tag (string)
|
|
12
|
+
* 5. `properties` – all custom entity properties as a JSON string
|
|
13
|
+
*
|
|
14
|
+
* The SQL-formatting methods inherited from `OINODataSource` are not used
|
|
15
|
+
* by nosql operations; they are implemented here as passthrough stubs.
|
|
16
|
+
*/
|
|
17
|
+
export declare abstract class OINONoSql extends OINODataSource {
|
|
18
|
+
protected readonly nosqlParams: OINONoSqlParams;
|
|
19
|
+
/** Table name */
|
|
20
|
+
readonly name: string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether this backend can auto-generate primary key values when none are
|
|
23
|
+
* supplied in a POST request. Defaults to `false`; override in subclasses
|
|
24
|
+
* that support server-side key generation (e.g. UUID on insert).
|
|
25
|
+
*/
|
|
26
|
+
readonly supportsAutoKey: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Constructor for `OINONoSql`.
|
|
29
|
+
* @param params nosql storage connection parameters
|
|
30
|
+
*/
|
|
31
|
+
constructor(params: OINONoSqlParams);
|
|
32
|
+
printTableName(name: string): string;
|
|
33
|
+
printColumnName(name: string): string;
|
|
34
|
+
printCellAsValue(cellValue: OINODataCell, nativeType: string): string;
|
|
35
|
+
printStringValue(s: string): string;
|
|
36
|
+
parseValueAsCell(v: OINODataCell, nativeType: string): OINODataCell;
|
|
37
|
+
/**
|
|
38
|
+
* Test whether a NoSQL entry matches an `OINOQueryFilter` predicate.
|
|
39
|
+
* Used for in-memory (result) filtering when the storage backend cannot
|
|
40
|
+
* translate the predicate to a native query.
|
|
41
|
+
*
|
|
42
|
+
* @param entry nosql entry to test
|
|
43
|
+
* @param filter filter predicate to evaluate
|
|
44
|
+
*/
|
|
45
|
+
protected static matchesEntry(entry: OINONoSqlEntry, filter: OINOQueryFilter): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* List all entities in the table, applying storage-level filtering where
|
|
48
|
+
* possible and in-memory result filtering for remaining predicates.
|
|
49
|
+
*
|
|
50
|
+
* @param filter optional query filter to apply
|
|
51
|
+
*/
|
|
52
|
+
abstract listEntries(filter?: OINOQueryFilter): Promise<OINONoSqlEntry[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Fetch a single entity by its primary key values.
|
|
55
|
+
*
|
|
56
|
+
* Returns `null` when no entity with the given primary key exists.
|
|
57
|
+
*
|
|
58
|
+
* @param primaryKey ordered primary key values as defined by the implementation's data model
|
|
59
|
+
*/
|
|
60
|
+
abstract getEntry(primaryKey: string[]): Promise<OINONoSqlEntry | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Upsert (insert or replace) an entity.
|
|
63
|
+
*
|
|
64
|
+
* @param entry entity to upsert
|
|
65
|
+
*/
|
|
66
|
+
abstract upsertEntry(entry: OINONoSqlEntry): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Upsert (insert or replace) multiple entities in the most efficient way
|
|
69
|
+
* the backend supports. The default implementation loops over
|
|
70
|
+
* `upsertEntry`; override in subclasses that support native batch writes.
|
|
71
|
+
*
|
|
72
|
+
* @param entries entities to upsert
|
|
73
|
+
*/
|
|
74
|
+
upsertEntries(entries: OINONoSqlEntry[]): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Delete an entity.
|
|
77
|
+
*
|
|
78
|
+
* @param primaryKey ordered primary key values as defined by the implementation's data model
|
|
79
|
+
*/
|
|
80
|
+
abstract deleteEntry(primaryKey: string[]): Promise<void>;
|
|
81
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { OINOApi, OINOApiParams, OINOApiRequest, OINOApiResult, OINOContentType, OINOQueryParams, OINOHttpRequest, type OINOApiData } from "@oino-ts/common";
|
|
2
|
+
import { OINONoSql } from "./OINONoSql.js";
|
|
3
|
+
import { OINONoSqlDataModel } from "./OINONoSqlDataModel.js";
|
|
4
|
+
/**
|
|
5
|
+
* REST API for NoSQL table storage.
|
|
6
|
+
*
|
|
7
|
+
* Supports the following HTTP methods:
|
|
8
|
+
* - **GET without id** – lists all entities and returns metadata as JSON.
|
|
9
|
+
* - **GET with id** – returns a single entity.
|
|
10
|
+
* - **POST / PUT with id** – upserts an entity; body must be a JSON object
|
|
11
|
+
* with a `properties` key containing the custom entity properties.
|
|
12
|
+
* - **DELETE with id** – deletes the named entity.
|
|
13
|
+
*
|
|
14
|
+
* The URL row ID format uses `OINOConfig.OINO_ID_SEPARATOR` to join the
|
|
15
|
+
* primary key field values, matching the number and order of primary key
|
|
16
|
+
* fields in the data model (same `_OINOID_` convention as `OINODbApi`).
|
|
17
|
+
*/
|
|
18
|
+
export declare class OINONoSqlApi extends OINOApi {
|
|
19
|
+
/** NoSQL storage backend */
|
|
20
|
+
readonly noSql: OINONoSql;
|
|
21
|
+
/** NoSQL-specific data model (populated by `initializeDatamodel`) */
|
|
22
|
+
noSqlDatamodel: OINONoSqlDataModel | null;
|
|
23
|
+
/**
|
|
24
|
+
* Constructor.
|
|
25
|
+
*
|
|
26
|
+
* NOTE: `initializeDatamodel` (or `OINONoSqlFactory.createApi`) must be
|
|
27
|
+
* called before the first request is dispatched.
|
|
28
|
+
*
|
|
29
|
+
* @param noSql nosql storage backend
|
|
30
|
+
* @param params API parameters
|
|
31
|
+
*/
|
|
32
|
+
constructor(noSql: OINONoSql, params: OINOApiParams);
|
|
33
|
+
/**
|
|
34
|
+
* Attach the static nosql data model and mark the API as initialised.
|
|
35
|
+
*
|
|
36
|
+
* @param datamodel `OINONoSqlDataModel` instance for this API
|
|
37
|
+
*/
|
|
38
|
+
initializeDatamodel(datamodel: OINONoSqlDataModel): void;
|
|
39
|
+
/**
|
|
40
|
+
* Parse a `_OINOID_`-formatted row ID into an ordered array of decoded
|
|
41
|
+
* primary key values using `OINOConfig.parseOINOId`. Returns `null` when
|
|
42
|
+
* the number of parts does not match the data model's primary key count.
|
|
43
|
+
*
|
|
44
|
+
* @param rowId `_OINOID_`-formatted row ID
|
|
45
|
+
*/
|
|
46
|
+
private _parseRowId;
|
|
47
|
+
/**
|
|
48
|
+
* Validate a data row against API parameters. Currently checks whether
|
|
49
|
+
* primary key fields are present when `requirePrimaryKey` is `true`.
|
|
50
|
+
*
|
|
51
|
+
* `requirePrimaryKey` is derived at the call-site from:
|
|
52
|
+
* - `this.params.failOnInsertWithoutKey` when explicitly set, or
|
|
53
|
+
* - `!this.noSql.supportsAutoKey` as the implementation-specific default.
|
|
54
|
+
*/
|
|
55
|
+
private _validateRow;
|
|
56
|
+
private _parseData;
|
|
57
|
+
private _rowToEntry;
|
|
58
|
+
private _doGet;
|
|
59
|
+
private _doPut;
|
|
60
|
+
private _doPost;
|
|
61
|
+
private _doDelete;
|
|
62
|
+
doApiRequest(request: OINOApiRequest): Promise<OINOApiResult>;
|
|
63
|
+
doBatchUpdate(method: string, rowId: string, rowData: OINOApiData, queryParams?: OINOQueryParams): Promise<OINOApiResult>;
|
|
64
|
+
doBatchApiRequest(request: OINOApiRequest): Promise<OINOApiResult>;
|
|
65
|
+
doHttpRequest(request: OINOHttpRequest, rowId: string, rowData: OINOApiData, queryParams: OINOQueryParams): Promise<OINOApiResult>;
|
|
66
|
+
doRequest(method: string, rowId: string, rowData: OINOApiData, queryParams: OINOQueryParams, contentType?: OINOContentType): Promise<OINOApiResult>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { OINONoSql } from "./OINONoSql.js";
|
|
2
|
+
/**
|
|
3
|
+
* NoSQL class (constructor) type
|
|
4
|
+
* @param params nosql parameters
|
|
5
|
+
*/
|
|
6
|
+
export type OINONoSqlConstructor = new (params: OINONoSqlParams) => OINONoSql;
|
|
7
|
+
/** NoSQL storage connection parameters */
|
|
8
|
+
export type OINONoSqlParams = {
|
|
9
|
+
/** Name of the nosql class (e.g. OINONoSqlAzureTable) */
|
|
10
|
+
type: string;
|
|
11
|
+
/** Service endpoint URL */
|
|
12
|
+
url: string;
|
|
13
|
+
/** Table name */
|
|
14
|
+
table: string;
|
|
15
|
+
/** Provider-specific connection string (e.g. Azure Storage connection string) */
|
|
16
|
+
connectionStr?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional static partition key. When set, all read/write operations are
|
|
19
|
+
* automatically scoped to this partition key, allowing multiple logical
|
|
20
|
+
* tables to share a single Azure Table Storage table.
|
|
21
|
+
*/
|
|
22
|
+
staticPartitionKey?: string;
|
|
23
|
+
};
|
|
24
|
+
/** A single NoSQL entity entry */
|
|
25
|
+
export type OINONoSqlEntry = {
|
|
26
|
+
/** Primary key values in the order defined by the implementation's data model */
|
|
27
|
+
primaryKey: string[];
|
|
28
|
+
/** Last modification timestamp */
|
|
29
|
+
timestamp: Date;
|
|
30
|
+
/** Entity tag */
|
|
31
|
+
etag: string;
|
|
32
|
+
/** All custom entity properties as a key-value map */
|
|
33
|
+
properties: Record<string, unknown>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { OINODataModel, OINOMemoryDataset } from "@oino-ts/common";
|
|
2
|
+
import { OINONoSqlApi } from "./OINONoSqlApi.js";
|
|
3
|
+
import { OINONoSqlEntry } from "./OINONoSqlConstants.js";
|
|
4
|
+
/**
|
|
5
|
+
* Static data model for NoSQL entity listings.
|
|
6
|
+
*
|
|
7
|
+
* The canonical field order is determined by the implementation's
|
|
8
|
+
* `initializeApiDatamodel` call. Primary key fields are mapped positionally
|
|
9
|
+
* to `OINONoSqlEntry.primaryKey`, while the remaining fields (`timestamp`,
|
|
10
|
+
* `etag`, `properties`) are matched by name.
|
|
11
|
+
*/
|
|
12
|
+
export declare class OINONoSqlDataModel extends OINODataModel {
|
|
13
|
+
/** Reference to the owning NoSQL API */
|
|
14
|
+
readonly noSqlApi: OINONoSqlApi;
|
|
15
|
+
/**
|
|
16
|
+
* Constructor. Fields are added externally by the nosql implementation
|
|
17
|
+
* via `initializeApiDatamodel`.
|
|
18
|
+
*
|
|
19
|
+
* @param api the `OINONoSqlApi` that owns this data model
|
|
20
|
+
*/
|
|
21
|
+
constructor(api: OINONoSqlApi);
|
|
22
|
+
/**
|
|
23
|
+
* Convert an array of NoSQL entries into an in-memory dataset whose
|
|
24
|
+
* columns match the fields present in this model.
|
|
25
|
+
*
|
|
26
|
+
* @param entries nosql entries from the storage backend
|
|
27
|
+
*/
|
|
28
|
+
entriesToDataset(entries: OINONoSqlEntry[]): OINOMemoryDataset;
|
|
29
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OINOApiParams } from "@oino-ts/common";
|
|
2
|
+
import { OINONoSqlParams, OINONoSqlConstructor } from "./OINONoSqlConstants.js";
|
|
3
|
+
import { OINONoSql } from "./OINONoSql.js";
|
|
4
|
+
import { OINONoSqlApi } from "./OINONoSqlApi.js";
|
|
5
|
+
/**
|
|
6
|
+
* Static factory for creating `OINONoSql` instances and `OINONoSqlApi` instances
|
|
7
|
+
* from registered provider classes.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* OINONoSqlFactory.registerNoSql("OINONoSqlAzureTable", OINONoSqlAzureTable)
|
|
12
|
+
* const nosql = await OINONoSqlFactory.createNoSql({ type: "OINONoSqlAzureTable", ... })
|
|
13
|
+
* const api = await OINONoSqlFactory.createApi(nosql, { apiName: "entities", tableName: "myTable" })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare class OINONoSqlFactory {
|
|
17
|
+
private static _registry;
|
|
18
|
+
/**
|
|
19
|
+
* Register a nosql provider class under the given name.
|
|
20
|
+
*
|
|
21
|
+
* @param name name used in `OINONoSqlParams.type`
|
|
22
|
+
* @param noSqlClass constructor of the provider
|
|
23
|
+
*/
|
|
24
|
+
static registerNoSql(name: string, noSqlClass: OINONoSqlConstructor): void;
|
|
25
|
+
/**
|
|
26
|
+
* Create and optionally connect/validate a nosql backend from params.
|
|
27
|
+
*
|
|
28
|
+
* @param params connection parameters
|
|
29
|
+
* @param connect if true, calls `connect()` on the backend
|
|
30
|
+
* @param validate if true, calls `validate()` on the backend
|
|
31
|
+
*/
|
|
32
|
+
static createNoSql(params: OINONoSqlParams, connect?: boolean, validate?: boolean): Promise<OINONoSql>;
|
|
33
|
+
/**
|
|
34
|
+
* Create an `OINONoSqlApi` and initialise its data model.
|
|
35
|
+
*
|
|
36
|
+
* @param noSql nosql backend to use
|
|
37
|
+
* @param params API parameters
|
|
38
|
+
*/
|
|
39
|
+
static createApi(noSql: OINONoSql, params: OINOApiParams): Promise<OINONoSqlApi>;
|
|
40
|
+
}
|
|
@@ -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";
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oino-ts/nosql",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OINO TS library package for publishing NoSQL storage as a REST API.",
|
|
5
|
+
"author": "Matias Kiviniemi (pragmatta)",
|
|
6
|
+
"license": "MPL-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/pragmatta/oino-ts.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"nosql",
|
|
13
|
+
"storage",
|
|
14
|
+
"rest-api",
|
|
15
|
+
"typescript",
|
|
16
|
+
"library"
|
|
17
|
+
],
|
|
18
|
+
"main": "./dist/cjs/index.js",
|
|
19
|
+
"module": "./dist/esm/index.js",
|
|
20
|
+
"types": "./dist/types/index.d.ts",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@oino-ts/common": "1.0.0",
|
|
23
|
+
"oino-ts": "file:.."
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@oino-ts/types": "1.0.0",
|
|
27
|
+
"@types/bun": "^1.1.14",
|
|
28
|
+
"@types/node": "^21.0.00",
|
|
29
|
+
"typescript": "~5.9.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src/*.ts",
|
|
33
|
+
"dist/cjs/*.js",
|
|
34
|
+
"dist/esm/*.js",
|
|
35
|
+
"dist/types/*.d.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|
package/src/OINONoSql.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
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 { OINODataSource, OINODataCell, OINOQueryFilter, OINOQueryBooleanOperation, OINOQueryComparison, OINOQueryNullCheck } from "@oino-ts/common"
|
|
8
|
+
import { OINONoSqlParams, OINONoSqlEntry } from "./OINONoSqlConstants.js"
|
|
9
|
+
|
|
10
|
+
const NOSQL_LIKE_ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g
|
|
11
|
+
const NOSQL_LIKE_PERCENT_REGEX = /%/g
|
|
12
|
+
const NOSQL_LIKE_UNDERSCORE_REGEX = /_/g
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Abstract base class for NoSQL storage backends. Subclasses implement
|
|
16
|
+
* the core CRUD operations for a specific provider (e.g. Azure Table Storage).
|
|
17
|
+
*
|
|
18
|
+
* The data model exposed by the API has a fixed set of fields:
|
|
19
|
+
* 1. `partitionKey` – partition key (primary key component, string)
|
|
20
|
+
* 2. `rowKey` – row key (primary key component, string)
|
|
21
|
+
* 3. `timestamp` – last modification timestamp (datetime)
|
|
22
|
+
* 4. `etag` – entity tag (string)
|
|
23
|
+
* 5. `properties` – all custom entity properties as a JSON string
|
|
24
|
+
*
|
|
25
|
+
* The SQL-formatting methods inherited from `OINODataSource` are not used
|
|
26
|
+
* by nosql operations; they are implemented here as passthrough stubs.
|
|
27
|
+
*/
|
|
28
|
+
export abstract class OINONoSql extends OINODataSource {
|
|
29
|
+
|
|
30
|
+
protected readonly nosqlParams: OINONoSqlParams
|
|
31
|
+
|
|
32
|
+
/** Table name */
|
|
33
|
+
readonly name: string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether this backend can auto-generate primary key values when none are
|
|
37
|
+
* supplied in a POST request. Defaults to `false`; override in subclasses
|
|
38
|
+
* that support server-side key generation (e.g. UUID on insert).
|
|
39
|
+
*/
|
|
40
|
+
readonly supportsAutoKey: boolean = false
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Constructor for `OINONoSql`.
|
|
44
|
+
* @param params nosql storage connection parameters
|
|
45
|
+
*/
|
|
46
|
+
constructor(params: OINONoSqlParams) {
|
|
47
|
+
super()
|
|
48
|
+
this.nosqlParams = { ...params }
|
|
49
|
+
this.name = this.nosqlParams.table
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── OINODataSource passthrough stubs ──────────────────────────────────
|
|
53
|
+
|
|
54
|
+
printTableName(name: string): string {
|
|
55
|
+
return name
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
printColumnName(name: string): string {
|
|
59
|
+
return name
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
printCellAsValue(cellValue: OINODataCell, nativeType: string): string {
|
|
63
|
+
if (cellValue === null || cellValue === undefined) {
|
|
64
|
+
return ""
|
|
65
|
+
}
|
|
66
|
+
if (cellValue instanceof Date) {
|
|
67
|
+
return cellValue.toISOString()
|
|
68
|
+
}
|
|
69
|
+
return String(cellValue)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
printStringValue(s: string): string {
|
|
73
|
+
return s
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
parseValueAsCell(v: OINODataCell, nativeType: string): OINODataCell {
|
|
77
|
+
if (nativeType === "DATETIME" && typeof v === "string" && v !== "") {
|
|
78
|
+
return new Date(v)
|
|
79
|
+
}
|
|
80
|
+
return v
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── NoSQL-specific filter helpers ─────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Test whether a NoSQL entry matches an `OINOQueryFilter` predicate.
|
|
87
|
+
* Used for in-memory (result) filtering when the storage backend cannot
|
|
88
|
+
* translate the predicate to a native query.
|
|
89
|
+
*
|
|
90
|
+
* @param entry nosql entry to test
|
|
91
|
+
* @param filter filter predicate to evaluate
|
|
92
|
+
*/
|
|
93
|
+
protected static matchesEntry(entry: OINONoSqlEntry, filter: OINOQueryFilter): boolean {
|
|
94
|
+
if (filter.isEmpty()) return true
|
|
95
|
+
|
|
96
|
+
const op = filter.operator
|
|
97
|
+
|
|
98
|
+
if (op === OINOQueryBooleanOperation.and) {
|
|
99
|
+
return OINONoSql.matchesEntry(entry, filter.leftSide as OINOQueryFilter) &&
|
|
100
|
+
OINONoSql.matchesEntry(entry, filter.rightSide as OINOQueryFilter)
|
|
101
|
+
}
|
|
102
|
+
if (op === OINOQueryBooleanOperation.or) {
|
|
103
|
+
return OINONoSql.matchesEntry(entry, filter.leftSide as OINOQueryFilter) ||
|
|
104
|
+
OINONoSql.matchesEntry(entry, filter.rightSide as OINOQueryFilter)
|
|
105
|
+
}
|
|
106
|
+
if (op === OINOQueryBooleanOperation.not) {
|
|
107
|
+
return !OINONoSql.matchesEntry(entry, filter.rightSide as OINOQueryFilter)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const field_name = filter.leftSide as string
|
|
111
|
+
const compare_value = filter.rightSide as string
|
|
112
|
+
|
|
113
|
+
let field_value: string | number | Date | null
|
|
114
|
+
switch (field_name) {
|
|
115
|
+
case "timestamp": field_value = entry.timestamp; break
|
|
116
|
+
case "etag": field_value = entry.etag; break
|
|
117
|
+
default: return true
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (op === OINOQueryNullCheck.isnull) return field_value === null
|
|
121
|
+
if (op === OINOQueryNullCheck.isNotNull) return field_value !== null
|
|
122
|
+
if (field_value === null) return false
|
|
123
|
+
|
|
124
|
+
if (field_value instanceof Date) {
|
|
125
|
+
const ms = field_value.getTime()
|
|
126
|
+
const cmp_ms = new Date(compare_value).getTime()
|
|
127
|
+
switch (op) {
|
|
128
|
+
case OINOQueryComparison.lt: return ms < cmp_ms
|
|
129
|
+
case OINOQueryComparison.le: return ms <= cmp_ms
|
|
130
|
+
case OINOQueryComparison.eq: return ms === cmp_ms
|
|
131
|
+
case OINOQueryComparison.ne: return ms !== cmp_ms
|
|
132
|
+
case OINOQueryComparison.ge: return ms >= cmp_ms
|
|
133
|
+
case OINOQueryComparison.gt: return ms > cmp_ms
|
|
134
|
+
default: return true
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const str_value = String(field_value)
|
|
139
|
+
switch (op) {
|
|
140
|
+
case OINOQueryComparison.lt: return str_value < compare_value
|
|
141
|
+
case OINOQueryComparison.le: return str_value <= compare_value
|
|
142
|
+
case OINOQueryComparison.eq: return str_value === compare_value
|
|
143
|
+
case OINOQueryComparison.ne: return str_value !== compare_value
|
|
144
|
+
case OINOQueryComparison.ge: return str_value >= compare_value
|
|
145
|
+
case OINOQueryComparison.gt: return str_value > compare_value
|
|
146
|
+
case OINOQueryComparison.like: {
|
|
147
|
+
const escaped = compare_value
|
|
148
|
+
.replace(NOSQL_LIKE_ESCAPE_REGEX, "\\$&")
|
|
149
|
+
.replace(NOSQL_LIKE_PERCENT_REGEX, ".*")
|
|
150
|
+
.replace(NOSQL_LIKE_UNDERSCORE_REGEX, ".")
|
|
151
|
+
return new RegExp("^" + escaped + "$", "i").test(str_value)
|
|
152
|
+
}
|
|
153
|
+
default: return true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Abstract NoSQL operations ─────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* List all entities in the table, applying storage-level filtering where
|
|
161
|
+
* possible and in-memory result filtering for remaining predicates.
|
|
162
|
+
*
|
|
163
|
+
* @param filter optional query filter to apply
|
|
164
|
+
*/
|
|
165
|
+
abstract listEntries(filter?: OINOQueryFilter): Promise<OINONoSqlEntry[]>
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Fetch a single entity by its primary key values.
|
|
169
|
+
*
|
|
170
|
+
* Returns `null` when no entity with the given primary key exists.
|
|
171
|
+
*
|
|
172
|
+
* @param primaryKey ordered primary key values as defined by the implementation's data model
|
|
173
|
+
*/
|
|
174
|
+
abstract getEntry(primaryKey: string[]): Promise<OINONoSqlEntry | null>
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Upsert (insert or replace) an entity.
|
|
178
|
+
*
|
|
179
|
+
* @param entry entity to upsert
|
|
180
|
+
*/
|
|
181
|
+
abstract upsertEntry(entry: OINONoSqlEntry): Promise<void>
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Upsert (insert or replace) multiple entities in the most efficient way
|
|
185
|
+
* the backend supports. The default implementation loops over
|
|
186
|
+
* `upsertEntry`; override in subclasses that support native batch writes.
|
|
187
|
+
*
|
|
188
|
+
* @param entries entities to upsert
|
|
189
|
+
*/
|
|
190
|
+
async upsertEntries(entries: OINONoSqlEntry[]): Promise<void> {
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
await this.upsertEntry(entry)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Delete an entity.
|
|
198
|
+
*
|
|
199
|
+
* @param primaryKey ordered primary key values as defined by the implementation's data model
|
|
200
|
+
*/
|
|
201
|
+
abstract deleteEntry(primaryKey: string[]): Promise<void>
|
|
202
|
+
}
|