@mxtommy/kip 4.5.0 → 4.5.1
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/CHANGELOG.md +5 -0
- package/package.json +16 -6
- package/plugin/duckdb-parquet-storage.service.js +78 -102
- package/plugin/index.js +60 -40
- package/public/{chunk-6XFWUUDD.js → chunk-EQ2N7KDA.js} +1 -1
- package/public/{chunk-EDNYYQIZ.js → chunk-IYRLINL7.js} +1 -1
- package/public/{chunk-UYIJND2R.js → chunk-JGGMFMY5.js} +1 -1
- package/public/{chunk-DD4F6F4S.js → chunk-RONXIZ2U.js} +8 -8
- package/public/{chunk-2ICAVOT2.js → chunk-VCY32MWT.js} +1 -1
- package/public/{chunk-J3LDKVIS.js → chunk-ZV7IYYEQ.js} +1 -1
- package/public/index.html +1 -1
- package/public/{main-EG2WF4EO.js → main-FQESQQV6.js} +1 -1
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -84
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -35
- package/.github/copilot-instructions.md +0 -218
- package/.github/instructions/angular.instructions.md +0 -123
- package/.github/instructions/best-practices.instructions.md +0 -59
- package/.github/instructions/project.instructions.md +0 -468
- package/.github/workflows/ci.yml +0 -37
- package/docs/widget-schematic.md +0 -102
- package/images/ActionSidenav.png +0 -0
- package/images/ChartplotterMode.png +0 -0
- package/images/KIPDemo.png +0 -0
- package/images/KipBrightness-1024.png +0 -0
- package/images/KipConfig-Units-1024.png +0 -0
- package/images/KipConfig-display-1024x488.png +0 -0
- package/images/KipFreeboard-SK-1024.png +0 -0
- package/images/KipGaugeSample1-1024x545.png +0 -0
- package/images/KipGaugeSample2-1024x488.png +0 -0
- package/images/KipGaugeSample3-1024x508.png +0 -0
- package/images/KipNightMode-1024.png +0 -0
- package/images/KipWidgetConfig-layout-1024.png +0 -0
- package/images/KipWidgetConfig-paths-1024x488.png +0 -0
- package/images/Options.png +0 -0
- package/images/exterior_user_installs.png +0 -0
- package/images/formfactor.png +0 -0
- package/plugin-config-data/kip/historicalData/kip-history.duckdb +0 -0
- package/plugin-config-data/kip/historicalData/parquet/chart-1/1772344583976-1772344583976.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-1/1771408800000-1771408890000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-1/1771412400000-1771412490000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-1/1771419600000-1771419650000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-1/1772344584154-1772344584154.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-1/1772344584191-1772344584191.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-1/1772344584268-1772344584268.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-2/1771502400000-1771502400000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-3/1771408800000-1771408890000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-3/1771412400000-1771412490000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-3/1771419600000-1771419650000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-3/1772344584268-1772344584268.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-4/1771408800000-1771408890000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-4/1771412400000-1771412490000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-4/1771419600000-1771419650000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-5/1771412400000-1771412490000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-5/1771419600000-1771419650000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-6/1771419600000-1771419650000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1771408800000-1771408890000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1771412400000-1771412490000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1771419600000-1771419650000.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1772344584191-1772344584191.parquet +0 -0
- package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1772344584268-1772344584268.parquet +0 -0
- package/tools/schematics/collection.json +0 -9
- package/tools/schematics/create-host2-widget/files/readme/README.md.template +0 -109
- package/tools/schematics/create-host2-widget/files/spec/widget-__name@dasherize__.component.spec.ts +0 -38
- package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.html +0 -6
- package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.scss +0 -5
- package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.ts.template +0 -94
- package/tools/schematics/create-host2-widget/index.js +0 -138
- package/tools/schematics/create-host2-widget/schema.json +0 -89
- package/tools/schematics/create-host2-widget/test/create-host2-widget.spec.ts +0 -70
- package/tools/schematics/create-host2-widget/utils/formatting.js +0 -119
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# v4.5.1
|
|
2
|
+
## Fixes
|
|
3
|
+
* DuckDB dependency causing build and installation errors. Fixes #979
|
|
4
|
+
* Reduced installation size. Fixes #980
|
|
1
5
|
# v4.5.0
|
|
2
6
|
## New Features
|
|
3
7
|
* Effortlessly review your vessel’s history with the new Widget Historical Charts—automatically track, store, and visualize key data. Instantly access up to the last full day of performance: just two-finger tap or right-click any widget to open a seamless history dialog—no setup, no clutter, just the trends you need. (Requires Signal K v2.22.1)
|
|
@@ -7,6 +11,7 @@
|
|
|
7
11
|
* Added "Days" as a selectable time scale in the Data Chart widget.
|
|
8
12
|
* Improved integration by validating server plugin presence, plugin state, and configuration.
|
|
9
13
|
* Added a Node-RED introduction guide to the Help section.
|
|
14
|
+
* Migrated KIP plugin historical storage internals from `duckdb` to `@duckdb/node-api` and Parquet export writing to `@dsnp/parquetjs`.
|
|
10
15
|
## Fixes
|
|
11
16
|
* Improved KIP plugin OpenAPI compatibility.
|
|
12
17
|
* Resolved slow Data Inspector performance caused by high resource usage in deep loop logic.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxtommy/kip",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"description": "An advanced and versatile marine instrumentation package to display Signal K data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -40,6 +40,15 @@
|
|
|
40
40
|
"displayName": "KIP Instrument MFD"
|
|
41
41
|
},
|
|
42
42
|
"main": "plugin/index.js",
|
|
43
|
+
"files": [
|
|
44
|
+
"plugin/**",
|
|
45
|
+
"public/**",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"CHANGELOG.md",
|
|
49
|
+
"CONTRIBUTORS.md",
|
|
50
|
+
"package.json"
|
|
51
|
+
],
|
|
43
52
|
"signalk-plugin-enabled-by-default": true,
|
|
44
53
|
"scripts": {
|
|
45
54
|
"test": "CI=1 ng test --watch=false",
|
|
@@ -87,9 +96,7 @@
|
|
|
87
96
|
"pwa-asset-generator": "^8.1.1",
|
|
88
97
|
"sass": "^1.49.9",
|
|
89
98
|
"ts-node": "^10.9.2",
|
|
90
|
-
"typescript": "^5.9.3"
|
|
91
|
-
},
|
|
92
|
-
"dependencies": {
|
|
99
|
+
"typescript": "^5.9.3",
|
|
93
100
|
"@angular/animations": "21.1.4",
|
|
94
101
|
"@angular/cdk": "21.1.4",
|
|
95
102
|
"@angular/common": "21.1.4",
|
|
@@ -102,7 +109,6 @@
|
|
|
102
109
|
"@angular/router": "21.1.4",
|
|
103
110
|
"@aziham/chartjs-plugin-streaming": "^3.5.1",
|
|
104
111
|
"@godind/ng-canvas-gauges": "^6.2.1",
|
|
105
|
-
"@signalk/server-api": "^2.22.0",
|
|
106
112
|
"@zakj/no-sleep": "^0.13.5",
|
|
107
113
|
"chart.js": "^4.5.1",
|
|
108
114
|
"chartjs-adapter-date-fns": "^3.0.0",
|
|
@@ -112,7 +118,6 @@
|
|
|
112
118
|
"core-js": "^3.13.1",
|
|
113
119
|
"d3": "^7.9.0",
|
|
114
120
|
"date-fns": "^2.30.0",
|
|
115
|
-
"duckdb": "^1.4.4",
|
|
116
121
|
"gridstack": "^12.3.3",
|
|
117
122
|
"js-quantities": "^1.8.0",
|
|
118
123
|
"lodash-es": "^4.17.23",
|
|
@@ -124,5 +129,10 @@
|
|
|
124
129
|
"steelseries": "^2.0.9",
|
|
125
130
|
"tslib": "^2.6.2",
|
|
126
131
|
"zone.js": "^0.15.1"
|
|
132
|
+
},
|
|
133
|
+
"dependencies": {
|
|
134
|
+
"@dsnp/parquetjs": "^1.8.7",
|
|
135
|
+
"@duckdb/node-api": "^1.4.4-r.2",
|
|
136
|
+
"@signalk/server-api": "^2.22.0"
|
|
127
137
|
}
|
|
128
138
|
}
|
|
@@ -1,42 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.DuckDbParquetStorageService = void 0;
|
|
37
4
|
const fs_1 = require("fs");
|
|
38
5
|
const path_1 = require("path");
|
|
39
|
-
const
|
|
6
|
+
const node_api_1 = require("@duckdb/node-api");
|
|
7
|
+
const parquetjs_1 = require("@dsnp/parquetjs");
|
|
40
8
|
/**
|
|
41
9
|
* Provides DuckDB storage and Parquet flush support for captured history samples.
|
|
42
10
|
*/
|
|
@@ -83,17 +51,15 @@ class DuckDbParquetStorageService {
|
|
|
83
51
|
this.logger = logger;
|
|
84
52
|
}
|
|
85
53
|
/**
|
|
86
|
-
* Applies
|
|
54
|
+
* Applies the fixed storage backend configuration.
|
|
87
55
|
*
|
|
88
|
-
* @param {unknown} settings Plugin settings payload from Signal K (ignored for fixed storage defaults).
|
|
89
56
|
* @returns {IDuckDbParquetStorageConfig} Fixed storage configuration.
|
|
90
57
|
*
|
|
91
58
|
* @example
|
|
92
|
-
* const cfg = storage.configure(
|
|
59
|
+
* const cfg = storage.configure();
|
|
93
60
|
* console.log(cfg.engine);
|
|
94
61
|
*/
|
|
95
|
-
configure(
|
|
96
|
-
void settings;
|
|
62
|
+
configure() {
|
|
97
63
|
this.initialized = false;
|
|
98
64
|
this.config = {
|
|
99
65
|
engine: 'duckdb-parquet',
|
|
@@ -118,21 +84,11 @@ class DuckDbParquetStorageService {
|
|
|
118
84
|
this.initialized = false;
|
|
119
85
|
this.lifecycleToken += 1;
|
|
120
86
|
try {
|
|
121
|
-
const duckdbModule = duckdb;
|
|
122
87
|
const dbPath = (0, path_1.resolve)(this.config.databaseFile);
|
|
123
88
|
(0, fs_1.mkdirSync)((0, path_1.dirname)(dbPath), { recursive: true });
|
|
124
89
|
(0, fs_1.mkdirSync)((0, path_1.resolve)(this.config.parquetDirectory), { recursive: true });
|
|
125
|
-
this.db =
|
|
126
|
-
|
|
127
|
-
? this.db.connect()
|
|
128
|
-
: this.db;
|
|
129
|
-
if (!maybeConnection
|
|
130
|
-
|| typeof maybeConnection.run !== 'function'
|
|
131
|
-
|| typeof maybeConnection.all !== 'function'
|
|
132
|
-
|| typeof maybeConnection.close !== 'function') {
|
|
133
|
-
throw new Error('DuckDB connection API is unavailable in this runtime');
|
|
134
|
-
}
|
|
135
|
-
this.connection = maybeConnection;
|
|
90
|
+
this.db = await node_api_1.DuckDBInstance.create(dbPath);
|
|
91
|
+
this.connection = await this.db.connect();
|
|
136
92
|
await this.createCoreTables();
|
|
137
93
|
await this.runSql('CREATE INDEX IF NOT EXISTS idx_history_series_scope_ts ON history_samples(series_id, ts_ms)');
|
|
138
94
|
await this.runSql('CREATE INDEX IF NOT EXISTS idx_history_series_scope_id ON history_series(series_id)');
|
|
@@ -154,7 +110,7 @@ class DuckDbParquetStorageService {
|
|
|
154
110
|
const message = error?.message ?? String(error);
|
|
155
111
|
this.lastInitError = message;
|
|
156
112
|
this.logger.error(`[SERIES STORAGE] DuckDB initialization failed: ${message}`);
|
|
157
|
-
this.logger.error('[SERIES STORAGE] DuckDB is required. Install runtime dependency with: npm i duckdb in the installed plugin directory, then restart Signal K.');
|
|
113
|
+
this.logger.error('[SERIES STORAGE] DuckDB Node API is required. Install runtime dependency with: npm i @duckdb/node-api in the installed plugin directory, then restart Signal K.');
|
|
158
114
|
this.connection = null;
|
|
159
115
|
this.db = null;
|
|
160
116
|
this.pendingRows = [];
|
|
@@ -811,10 +767,20 @@ class DuckDbParquetStorageService {
|
|
|
811
767
|
return;
|
|
812
768
|
}
|
|
813
769
|
const connection = this.connection;
|
|
770
|
+
const db = this.db;
|
|
814
771
|
this.connection = null;
|
|
815
|
-
|
|
816
|
-
connection.
|
|
817
|
-
}
|
|
772
|
+
try {
|
|
773
|
+
connection.disconnectSync();
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
// ignore disconnect failures during shutdown
|
|
777
|
+
}
|
|
778
|
+
try {
|
|
779
|
+
db?.closeSync();
|
|
780
|
+
}
|
|
781
|
+
catch {
|
|
782
|
+
// ignore close failures during shutdown
|
|
783
|
+
}
|
|
818
784
|
this.db = null;
|
|
819
785
|
}
|
|
820
786
|
async createCoreTables() {
|
|
@@ -850,10 +816,6 @@ class DuckDbParquetStorageService {
|
|
|
850
816
|
)
|
|
851
817
|
`);
|
|
852
818
|
}
|
|
853
|
-
async countRows(tableName) {
|
|
854
|
-
const rows = await this.querySql(`SELECT COUNT(*) AS removed_rows FROM ${tableName}`);
|
|
855
|
-
return this.toNumberOrUndefined(rows[0]?.removed_rows) ?? 0;
|
|
856
|
-
}
|
|
857
819
|
async insertRows(rows) {
|
|
858
820
|
if (rows.length === 0) {
|
|
859
821
|
return;
|
|
@@ -880,56 +842,73 @@ class DuckDbParquetStorageService {
|
|
|
880
842
|
const seriesDir = (0, path_1.join)(baseDir, this.safePath(seriesId));
|
|
881
843
|
(0, fs_1.mkdirSync)(seriesDir, { recursive: true });
|
|
882
844
|
const filePath = (0, path_1.join)(seriesDir, `${fromMs}-${toMs}.parquet`);
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
845
|
+
const rows = await this.querySql(`
|
|
846
|
+
SELECT
|
|
847
|
+
series_id,
|
|
848
|
+
dataset_uuid,
|
|
849
|
+
owner_widget_uuid,
|
|
850
|
+
path,
|
|
851
|
+
context,
|
|
852
|
+
source,
|
|
853
|
+
ts_ms,
|
|
854
|
+
value
|
|
855
|
+
FROM history_samples
|
|
856
|
+
WHERE series_id = ${this.escape(seriesId)}
|
|
857
|
+
AND ts_ms >= ${Math.trunc(fromMs)}
|
|
858
|
+
AND ts_ms <= ${Math.trunc(toMs)}
|
|
859
|
+
ORDER BY ts_ms
|
|
860
|
+
`);
|
|
861
|
+
if (rows.length === 0) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const schema = new parquetjs_1.ParquetSchema({
|
|
865
|
+
series_id: { type: 'UTF8' },
|
|
866
|
+
dataset_uuid: { type: 'UTF8' },
|
|
867
|
+
owner_widget_uuid: { type: 'UTF8' },
|
|
868
|
+
path: { type: 'UTF8' },
|
|
869
|
+
context: { type: 'UTF8' },
|
|
870
|
+
source: { type: 'UTF8', optional: true },
|
|
871
|
+
ts_ms: { type: 'INT64' },
|
|
872
|
+
ts: { type: 'TIMESTAMP_MILLIS' },
|
|
873
|
+
value: { type: 'DOUBLE' }
|
|
874
|
+
});
|
|
875
|
+
const writer = await parquetjs_1.ParquetWriter.openFile(schema, filePath);
|
|
876
|
+
try {
|
|
877
|
+
for (const row of rows) {
|
|
878
|
+
const timestampMs = this.toNumberOrUndefined(row.ts_ms);
|
|
879
|
+
const numericValue = this.toNumberOrUndefined(row.value);
|
|
880
|
+
if (timestampMs === undefined || numericValue === undefined) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
await writer.appendRow({
|
|
884
|
+
series_id: row.series_id,
|
|
885
|
+
dataset_uuid: row.dataset_uuid,
|
|
886
|
+
owner_widget_uuid: row.owner_widget_uuid,
|
|
887
|
+
path: row.path,
|
|
888
|
+
context: row.context,
|
|
889
|
+
source: row.source ?? undefined,
|
|
890
|
+
ts_ms: Math.trunc(timestampMs),
|
|
891
|
+
ts: new Date(timestampMs),
|
|
892
|
+
value: numericValue
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
finally {
|
|
897
|
+
await writer.close();
|
|
898
|
+
}
|
|
905
899
|
}
|
|
906
900
|
async runSql(sql) {
|
|
907
901
|
if (!this.connection) {
|
|
908
902
|
throw new Error('DuckDB connection is not initialized');
|
|
909
903
|
}
|
|
910
|
-
await
|
|
911
|
-
this.connection?.run(sql, (error) => {
|
|
912
|
-
if (error) {
|
|
913
|
-
rejectPromise(error);
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
resolvePromise();
|
|
917
|
-
});
|
|
918
|
-
});
|
|
904
|
+
await this.connection.run(sql);
|
|
919
905
|
}
|
|
920
906
|
async querySql(sql) {
|
|
921
907
|
if (!this.connection) {
|
|
922
908
|
throw new Error('DuckDB connection is not initialized');
|
|
923
909
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (error) {
|
|
927
|
-
rejectPromise(error);
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
resolvePromise((rows ?? []));
|
|
931
|
-
});
|
|
932
|
-
});
|
|
910
|
+
const result = await this.connection.runAndReadAll(sql);
|
|
911
|
+
return result.getRowObjectsJson();
|
|
933
912
|
}
|
|
934
913
|
async selectRowsForPaths(paths, context, fromMs, toMs) {
|
|
935
914
|
const rowsByPath = new Map();
|
|
@@ -1142,9 +1121,6 @@ class DuckDbParquetStorageService {
|
|
|
1142
1121
|
escape(value) {
|
|
1143
1122
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
1144
1123
|
}
|
|
1145
|
-
escapePath(value) {
|
|
1146
|
-
return String(value).replace(/'/g, "''");
|
|
1147
|
-
}
|
|
1148
1124
|
nullableString(value) {
|
|
1149
1125
|
if (value === undefined || value === null || value === '') {
|
|
1150
1126
|
return 'NULL';
|