lemon-core 3.0.0 → 3.1.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/README.md +13 -21
- package/dist/common/test-helper.js +19 -11
- package/dist/common/test-helper.js.map +1 -1
- package/dist/controllers/dummy-controller.d.ts +1 -1
- package/dist/controllers/dummy-controller.js +2 -2
- package/dist/controllers/dummy-controller.js.map +1 -1
- package/dist/controllers/general-api-controller.d.ts +1 -1
- package/dist/controllers/general-api-controller.js +5 -5
- package/dist/controllers/general-api-controller.js.map +1 -1
- package/dist/controllers/general-controller.js +2 -1
- package/dist/controllers/general-controller.js.map +1 -1
- package/dist/controllers/index.js +6 -2
- package/dist/controllers/index.js.map +1 -1
- package/dist/cores/api/api-service.d.ts +239 -0
- package/dist/cores/api/api-service.js +674 -0
- package/dist/cores/api/api-service.js.map +1 -0
- package/dist/cores/api/index.d.ts +10 -0
- package/dist/cores/api/index.js +27 -0
- package/dist/cores/api/index.js.map +1 -0
- package/dist/cores/api-service.js +19 -18
- package/dist/cores/api-service.js.map +1 -1
- package/dist/cores/aws/aws-kms-service.d.ts +53 -2
- package/dist/cores/aws/aws-kms-service.js +112 -29
- package/dist/cores/aws/aws-kms-service.js.map +1 -1
- package/dist/cores/aws/aws-s3-service.js +19 -18
- package/dist/cores/aws/aws-s3-service.js.map +1 -1
- package/dist/cores/aws/aws-sns-service.js +10 -10
- package/dist/cores/aws/aws-sns-service.js.map +1 -1
- package/dist/cores/aws/aws-sqs-service.js +12 -12
- package/dist/cores/aws/aws-sqs-service.js.map +1 -1
- package/dist/cores/aws/index.js +1 -1
- package/dist/cores/cache/cache-service.d.ts +440 -0
- package/dist/cores/cache/cache-service.js +967 -0
- package/dist/cores/cache/cache-service.js.map +1 -0
- package/dist/cores/cache/index.d.ts +10 -0
- package/dist/cores/cache/index.js +27 -0
- package/dist/cores/cache/index.js.map +1 -0
- package/dist/cores/cache-service.d.ts +54 -18
- package/dist/cores/cache-service.js +56 -45
- package/dist/cores/cache-service.js.map +1 -1
- package/dist/cores/config/config-service.js +11 -10
- package/dist/cores/config/config-service.js.map +1 -1
- package/dist/cores/core-services.js +6 -2
- package/dist/cores/core-services.js.map +1 -1
- package/dist/cores/core-types.d.ts +42 -12
- package/dist/cores/dynamo/dynamo-query-service.d.ts +52 -0
- package/dist/cores/dynamo/dynamo-query-service.js +127 -0
- package/dist/cores/dynamo/dynamo-query-service.js.map +1 -0
- package/dist/cores/dynamo/dynamo-scan-service.d.ts +70 -0
- package/dist/cores/dynamo/dynamo-scan-service.js +164 -0
- package/dist/cores/dynamo/dynamo-scan-service.js.map +1 -0
- package/dist/cores/dynamo/dynamo-service.d.ts +192 -0
- package/dist/cores/dynamo/dynamo-service.js +525 -0
- package/dist/cores/dynamo/dynamo-service.js.map +1 -0
- package/dist/cores/dynamo/index.d.ts +12 -0
- package/dist/cores/dynamo/index.js +29 -0
- package/dist/cores/dynamo/index.js.map +1 -0
- package/dist/cores/dynamo-query-service.js +7 -7
- package/dist/cores/dynamo-query-service.js.map +1 -1
- package/dist/cores/dynamo-scan-service.js +8 -8
- package/dist/cores/dynamo-scan-service.js.map +1 -1
- package/dist/cores/dynamo-service.js +19 -19
- package/dist/cores/dynamo-service.js.map +1 -1
- package/dist/cores/elastic/elastic6-query-service.d.ts +104 -0
- package/dist/cores/elastic/elastic6-query-service.js +510 -0
- package/dist/cores/elastic/elastic6-query-service.js.map +1 -0
- package/dist/cores/elastic/elastic6-service.d.ts +273 -0
- package/dist/cores/elastic/elastic6-service.js +903 -0
- package/dist/cores/elastic/elastic6-service.js.map +1 -0
- package/dist/cores/elastic/hangul-service.d.ts +102 -0
- package/dist/cores/elastic/hangul-service.js +205 -0
- package/dist/cores/elastic/hangul-service.js.map +1 -0
- package/dist/cores/elastic/index.d.ts +12 -0
- package/dist/cores/elastic/index.js +29 -0
- package/dist/cores/elastic/index.js.map +1 -0
- package/dist/cores/elastic6-query-service.js +7 -7
- package/dist/cores/elastic6-query-service.js.map +1 -1
- package/dist/cores/elastic6-service.js +57 -65
- package/dist/cores/elastic6-service.js.map +1 -1
- package/dist/cores/hangul-service.d.ts +17 -3
- package/dist/cores/hangul-service.js +17 -8
- package/dist/cores/hangul-service.js.map +1 -1
- package/dist/cores/http-storage-service.js +1 -1
- package/dist/cores/http-storage-service.js.map +1 -1
- package/dist/cores/index.d.ts +9 -15
- package/dist/cores/index.js +14 -15
- package/dist/cores/index.js.map +1 -1
- package/dist/cores/lambda/index.js +8 -4
- package/dist/cores/lambda/index.js.map +1 -1
- package/dist/cores/lambda/lambda-cognito-handler.js +2 -2
- package/dist/cores/lambda/lambda-cognito-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-cron-handler.js +2 -2
- package/dist/cores/lambda/lambda-cron-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-dynamo-stream-handler.d.ts +2 -2
- package/dist/cores/lambda/lambda-dynamo-stream-handler.js +20 -20
- package/dist/cores/lambda/lambda-dynamo-stream-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-handler.js +12 -11
- package/dist/cores/lambda/lambda-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-notification-handler.js +10 -10
- package/dist/cores/lambda/lambda-notification-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-sns-handler.js +13 -13
- package/dist/cores/lambda/lambda-sns-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-sqs-handler.js +13 -13
- package/dist/cores/lambda/lambda-sqs-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-web-handler.d.ts +158 -8
- package/dist/cores/lambda/lambda-web-handler.js +330 -119
- package/dist/cores/lambda/lambda-web-handler.js.map +1 -1
- package/dist/cores/lambda/lambda-wss-handler.js +16 -12
- package/dist/cores/lambda/lambda-wss-handler.js.map +1 -1
- package/dist/cores/model-manager.js +11 -11
- package/dist/cores/model-manager.js.map +1 -1
- package/dist/cores/protocol/protocol-service.d.ts +4 -0
- package/dist/cores/protocol/protocol-service.js +46 -40
- package/dist/cores/protocol/protocol-service.js.map +1 -1
- package/dist/cores/proxy-storage-service.js +7 -10
- package/dist/cores/proxy-storage-service.js.map +1 -1
- package/dist/cores/redis-storage-service.js +13 -13
- package/dist/cores/redis-storage-service.js.map +1 -1
- package/dist/cores/storage/http-storage-service.d.ts +22 -0
- package/dist/cores/storage/http-storage-service.js +129 -0
- package/dist/cores/storage/http-storage-service.js.map +1 -0
- package/dist/cores/storage/index.d.ts +14 -0
- package/dist/cores/storage/index.js +31 -0
- package/dist/cores/storage/index.js.map +1 -0
- package/dist/cores/storage/model-manager.d.ts +93 -0
- package/dist/cores/storage/model-manager.js +192 -0
- package/dist/cores/storage/model-manager.js.map +1 -0
- package/dist/cores/storage/proxy-storage-service.d.ts +573 -0
- package/dist/cores/storage/proxy-storage-service.js +913 -0
- package/dist/cores/storage/proxy-storage-service.js.map +1 -0
- package/dist/cores/storage/redis-storage-service.d.ts +183 -0
- package/dist/cores/storage/redis-storage-service.js +391 -0
- package/dist/cores/storage/redis-storage-service.js.map +1 -0
- package/dist/cores/storage/storage-service.d.ts +169 -0
- package/dist/cores/storage/storage-service.js +374 -0
- package/dist/cores/storage/storage-service.js.map +1 -0
- package/dist/cores/storage-service.d.ts +1 -1
- package/dist/cores/storage-service.js +10 -8
- package/dist/cores/storage-service.js.map +1 -1
- package/dist/engine/builder.js +27 -20
- package/dist/engine/builder.js.map +1 -1
- package/dist/engine/engine.js +53 -44
- package/dist/engine/engine.js.map +1 -1
- package/dist/engine/index.js +8 -4
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/utilities.d.ts +4 -3
- package/dist/engine/utilities.js +32 -37
- package/dist/engine/utilities.js.map +1 -1
- package/dist/environ.d.ts +2 -2
- package/dist/environ.js +20 -10
- package/dist/environ.js.map +1 -1
- package/dist/exec-cli.js +26 -26
- package/dist/exec-cli.js.map +1 -1
- package/dist/extended/abstract-service.d.ts +533 -0
- package/dist/extended/abstract-service.js +915 -0
- package/dist/extended/abstract-service.js.map +1 -0
- package/dist/extended/index.d.ts +10 -0
- package/dist/extended/index.js +27 -0
- package/dist/extended/index.js.map +1 -0
- package/dist/helpers/helpers.d.ts +273 -0
- package/dist/helpers/helpers.js +613 -0
- package/dist/helpers/helpers.js.map +1 -0
- package/dist/helpers/index.d.ts +10 -0
- package/dist/helpers/index.js +27 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/dynamo/expressions.js +35 -27
- package/dist/lib/dynamo/expressions.js.map +1 -1
- package/dist/lib/dynamo/query.js +24 -20
- package/dist/lib/dynamo/query.js.map +1 -1
- package/dist/lib/dynamo/scan.js +17 -13
- package/dist/lib/dynamo/scan.js.map +1 -1
- package/dist/lib/dynamo/serializer.js +11 -7
- package/dist/lib/dynamo/serializer.js.map +1 -1
- package/dist/lib/dynamo/utils.js +19 -14
- package/dist/lib/dynamo/utils.js.map +1 -1
- package/dist/lib/dynamodb-value.js +3 -3
- package/dist/lib/dynamodb-value.js.map +1 -1
- package/dist/lib/index.js +6 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/tools/express.js +21 -15
- package/dist/tools/express.js.map +1 -1
- package/dist/tools/index.js +6 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/shared.js +12 -6
- package/dist/tools/shared.js.map +1 -1
- package/package.json +16 -15
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.UniqueFieldManager = exports.ModelUtil = exports.TypedStorageService = exports.ProxyStorageService = exports.GeneralModelFilter = exports.GeneralKeyMaker = exports.CORE_FIELDS = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* `proxy-storage-service.js`
|
|
15
|
+
* - common service for `accounts`
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* @author Steve Jung <steve@lemoncloud.io>
|
|
19
|
+
* @date 2019-12-27 initial service skeleton.
|
|
20
|
+
*
|
|
21
|
+
* @copyright (C) 2019 LemonCloud Co Ltd. - All Rights Reserved.
|
|
22
|
+
*/
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
24
|
+
const engine_1 = require("../../engine/");
|
|
25
|
+
const storage_service_1 = require("./storage-service");
|
|
26
|
+
const test_helper_1 = require("../../common/test-helper");
|
|
27
|
+
const general_api_controller_1 = require("../../controllers/general-api-controller");
|
|
28
|
+
const NS = engine_1.$U.NS('PSTR', 'blue'); // NAMESPACE TO BE PRINTED.
|
|
29
|
+
//NOTE! - BE WARE TO USE `ts-transformer-keys` DUE TO MISSING `ttypescript`
|
|
30
|
+
// export const CORE_FIELDS: string[] = keys<CoreModel>().filter(_ => !_.startsWith('_'));
|
|
31
|
+
// _inf(NS, '! CORE_FIELDS =', CORE_FIELDS.join(', ')); // for debugging.
|
|
32
|
+
exports.CORE_FIELDS = 'ns,type,sid,uid,gid,lock,next,meta,createdAt,updatedAt,deletedAt'.split(',');
|
|
33
|
+
/**
|
|
34
|
+
* class: `GeneralKeyMaker`
|
|
35
|
+
* - use ':' as delimiter to join [ns, type, id]
|
|
36
|
+
*/
|
|
37
|
+
class GeneralKeyMaker {
|
|
38
|
+
constructor(ns = '', delimiter = ':') {
|
|
39
|
+
this.NS = ns;
|
|
40
|
+
this.DELIMITER = delimiter;
|
|
41
|
+
}
|
|
42
|
+
asKey$(type, id) {
|
|
43
|
+
if (!id)
|
|
44
|
+
throw new Error('@id (model-id) is required!');
|
|
45
|
+
const ns = `${this.NS || ''}`;
|
|
46
|
+
const _id = [ns, `${type || ''}`, id].map(_ => _.replace(/[:]/gi, '-')).join(this.DELIMITER);
|
|
47
|
+
const res = { ns, id, type, _id };
|
|
48
|
+
return res;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.GeneralKeyMaker = GeneralKeyMaker;
|
|
52
|
+
/**
|
|
53
|
+
* class: `GeneralModelFilter`
|
|
54
|
+
* - general model-filter with differential update.
|
|
55
|
+
* - to customize, override this class.
|
|
56
|
+
*/
|
|
57
|
+
// eslint-disable-next-line prettier/prettier
|
|
58
|
+
class GeneralModelFilter {
|
|
59
|
+
/**
|
|
60
|
+
* default constructor
|
|
61
|
+
*/
|
|
62
|
+
constructor(fields) {
|
|
63
|
+
this.FIELDS = fields;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* parse `.meta` to json
|
|
67
|
+
* @param model the current model
|
|
68
|
+
* @param origin the origin model
|
|
69
|
+
*/
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
71
|
+
afterRead(model, origin) {
|
|
72
|
+
// _log(NS, `filter.afterRead(${model._id || ''})....`);
|
|
73
|
+
if (!model.meta)
|
|
74
|
+
return model;
|
|
75
|
+
const meta = model.meta;
|
|
76
|
+
model.meta = meta && typeof meta == 'string' && meta.startsWith('{') ? JSON.parse(meta) : meta;
|
|
77
|
+
return model;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* filter for before saving.
|
|
81
|
+
* - make sure data conversion
|
|
82
|
+
* - move the unknown fields to `.meta`.
|
|
83
|
+
*
|
|
84
|
+
* @param model the current model
|
|
85
|
+
* @param origin the origin model
|
|
86
|
+
*/
|
|
87
|
+
beforeSave(model, origin) {
|
|
88
|
+
// _log(NS, `filter.beforeSave(${model._id})....`);
|
|
89
|
+
origin = origin || {};
|
|
90
|
+
const FIELDS = this.FIELDS && this.FIELDS.length ? this.FIELDS : null;
|
|
91
|
+
//! call service.onBeforeSave().
|
|
92
|
+
model = this.onBeforeSave(model, origin);
|
|
93
|
+
//TODO - accept only primitive types of field @191228.
|
|
94
|
+
//NOTE! - should not update the core field in save()
|
|
95
|
+
delete model.lock;
|
|
96
|
+
delete model.next;
|
|
97
|
+
//! load the meta data...
|
|
98
|
+
const $meta = (() => {
|
|
99
|
+
if (model.meta !== undefined && !model.meta)
|
|
100
|
+
return {};
|
|
101
|
+
const meta = model.meta || origin.meta || {}; // 만일, 파라미터에 meta 가 있다면, 클라이언트에서 직접 처리함.
|
|
102
|
+
return meta && typeof meta == 'string' ? JSON.parse(meta) : meta;
|
|
103
|
+
})();
|
|
104
|
+
//! move all fields to meta which is not defined in FIELDS.
|
|
105
|
+
model = Object.keys(model).reduce((N, key) => {
|
|
106
|
+
if (key.startsWith('_') || key.startsWith('$'))
|
|
107
|
+
return N;
|
|
108
|
+
if (key == 'createdAt' || key == 'updatedAt' || key == 'deletedAt')
|
|
109
|
+
return N;
|
|
110
|
+
if (/^[A-Z][A-Za-z0-9]*$/.test(key) && !/^[A-Z_]+$/.test(key))
|
|
111
|
+
return N; // ABC_DE 는 상수이며 OK, 다만, AbcDe 는 내부 오브젝트 이므로 무시!!!
|
|
112
|
+
if (key == 'meta')
|
|
113
|
+
return N; // meta itself.
|
|
114
|
+
if (FIELDS && FIELDS.indexOf(key) < 0 && FIELDS.indexOf('*' + key) < 0) {
|
|
115
|
+
$meta[key] = model[key];
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
N[key] = model[key];
|
|
119
|
+
}
|
|
120
|
+
return N;
|
|
121
|
+
}, {});
|
|
122
|
+
model.meta = Object.keys($meta).length ? engine_1.$U.json($meta) : '';
|
|
123
|
+
//! handle for meta.
|
|
124
|
+
if (model.meta === '')
|
|
125
|
+
model.meta = null;
|
|
126
|
+
else if (typeof origin.meta == 'string' && model.meta == origin.meta)
|
|
127
|
+
delete model.meta;
|
|
128
|
+
else if (typeof origin.meta == 'object' && model.meta == engine_1.$U.json(origin.meta))
|
|
129
|
+
delete model.meta;
|
|
130
|
+
else if (!origin.meta && !model.meta)
|
|
131
|
+
model.meta = origin.meta;
|
|
132
|
+
//! filter out only the updated fields.
|
|
133
|
+
const res = Object.keys(model).reduce((N, key) => {
|
|
134
|
+
if (key.startsWith('_') || key.startsWith('$'))
|
|
135
|
+
return N; // ignore.
|
|
136
|
+
const org = origin[key];
|
|
137
|
+
const val = N[key];
|
|
138
|
+
if (!org && val)
|
|
139
|
+
return N;
|
|
140
|
+
else if (org && !val)
|
|
141
|
+
return N;
|
|
142
|
+
else if (org && typeof org === 'object') {
|
|
143
|
+
const org2 = engine_1.$U.json(org);
|
|
144
|
+
const val2 = typeof val === 'object' ? engine_1.$U.json(val) : val;
|
|
145
|
+
if (org2 == val2) {
|
|
146
|
+
delete N[key];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else if ((val === '' || val === null) && (org === null || org === undefined)) {
|
|
150
|
+
//NOTE! - dynamo saves null for '' string.
|
|
151
|
+
delete N[key];
|
|
152
|
+
}
|
|
153
|
+
else if (val === org) {
|
|
154
|
+
delete N[key];
|
|
155
|
+
}
|
|
156
|
+
return N;
|
|
157
|
+
}, model);
|
|
158
|
+
//! if nothing to update, then returns null.
|
|
159
|
+
const keys = Object.keys(model).filter(_ => !_.startsWith('_') && !_.startsWith('$'));
|
|
160
|
+
if (keys.length <= 0)
|
|
161
|
+
return null;
|
|
162
|
+
//! returns the filtered node.
|
|
163
|
+
return res;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* called after saving the model.
|
|
167
|
+
* - parse `.meta` back to json object.
|
|
168
|
+
*
|
|
169
|
+
* @param model the saved model
|
|
170
|
+
* @param origin the origin model.
|
|
171
|
+
*/
|
|
172
|
+
afterSave(model, origin) {
|
|
173
|
+
return this.afterRead(model, origin);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* called before updating the model.
|
|
177
|
+
* @param model the updated model
|
|
178
|
+
* @param incrementals (optional) incremental fields.
|
|
179
|
+
*/
|
|
180
|
+
beforeUpdate(model, incrementals) {
|
|
181
|
+
return model;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* called after updating the model.
|
|
185
|
+
* @param model the updated model
|
|
186
|
+
*/
|
|
187
|
+
afterUpdate(model) {
|
|
188
|
+
return this.afterRead(model, null);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* override this `onBeforeSave()` in sub-class.
|
|
192
|
+
* @param model the current model
|
|
193
|
+
* @param origin (optional) the origin model
|
|
194
|
+
*/
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
196
|
+
onBeforeSave(model, origin) {
|
|
197
|
+
//TODO - override this function.
|
|
198
|
+
//! conversion data-type.
|
|
199
|
+
// if (model.count !== undefined) model.count = $U.N(model.count, 0);
|
|
200
|
+
return model;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.GeneralModelFilter = GeneralModelFilter;
|
|
204
|
+
/**
|
|
205
|
+
* class: `ProxyStorageService`
|
|
206
|
+
* - support `nextSeq()`, `doLock()`, `doRelease()`
|
|
207
|
+
* - proxed storage-service to wrap the parent storage-service w/ more features.
|
|
208
|
+
* - table is supposed to have internal-key as `_id` string.
|
|
209
|
+
*
|
|
210
|
+
* **Usage**
|
|
211
|
+
* ```js
|
|
212
|
+
* type MyType = '' | 'test';
|
|
213
|
+
* interface MyModel extends CoreModel<MyType>{
|
|
214
|
+
* name?: string;
|
|
215
|
+
* }
|
|
216
|
+
* const storage = new ProxyStorageService<MyModel, MyType>(this, 'TestTable', ['id','name']);
|
|
217
|
+
* const $test = storage.makeTypedStorageService('test');
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
// eslint-disable-next-line prettier/prettier
|
|
221
|
+
class ProxyStorageService {
|
|
222
|
+
/**
|
|
223
|
+
* create proxed storage-service.
|
|
224
|
+
*
|
|
225
|
+
* @param service service to support `CoreKeyMakeable`
|
|
226
|
+
* @param storage table-name or the parent storage-service
|
|
227
|
+
* @param fields list of fields.
|
|
228
|
+
* @param filters filters of `CoreModelFilterable`
|
|
229
|
+
* @param idName (optional) internal partition-key (default as '_id')
|
|
230
|
+
*/
|
|
231
|
+
constructor(service, storage, fields, filters, idName) {
|
|
232
|
+
/**
|
|
233
|
+
* say hello()
|
|
234
|
+
*/
|
|
235
|
+
this.hello = () => `proxy-storage-service:${this.storage.hello()}`;
|
|
236
|
+
/**
|
|
237
|
+
* read by _id
|
|
238
|
+
*/
|
|
239
|
+
this.read = (_id) => this.storage.read(_id);
|
|
240
|
+
/**
|
|
241
|
+
* read or create by _id
|
|
242
|
+
*/
|
|
243
|
+
this.readOrCreate = (_id, model) => this.storage.readOrCreate(_id, model);
|
|
244
|
+
/**
|
|
245
|
+
* save by _id
|
|
246
|
+
*/
|
|
247
|
+
this.save = (_id, model) => this.storage.save(_id, model);
|
|
248
|
+
/**
|
|
249
|
+
* update by _id
|
|
250
|
+
*/
|
|
251
|
+
this.update = (_id, model, incrementals) => this.storage.update(_id, model, incrementals);
|
|
252
|
+
/**
|
|
253
|
+
* increment by _id
|
|
254
|
+
*/
|
|
255
|
+
this.increment = (_id, model, $update) => this.storage.increment(_id, model, $update);
|
|
256
|
+
/**
|
|
257
|
+
* delete by _id
|
|
258
|
+
*/
|
|
259
|
+
this.delete = (_id) => this.storage.delete(_id);
|
|
260
|
+
/**
|
|
261
|
+
* get key-id by type+id
|
|
262
|
+
*/
|
|
263
|
+
this.asKey = (type, id) => {
|
|
264
|
+
if (typeof this.service.asKey == 'function') {
|
|
265
|
+
return this.service.asKey(type, `${id}`);
|
|
266
|
+
}
|
|
267
|
+
const $key = this.service.asKey$(type, `${id}`);
|
|
268
|
+
return $key[this.idName];
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* timer to generate the current-time (msec)
|
|
272
|
+
*/
|
|
273
|
+
this.$timer = null;
|
|
274
|
+
this.setTimer = (timer) => {
|
|
275
|
+
const previous = this.$timer;
|
|
276
|
+
this.$timer = timer;
|
|
277
|
+
return previous;
|
|
278
|
+
};
|
|
279
|
+
this.getTime = () => {
|
|
280
|
+
if (this.$timer)
|
|
281
|
+
return this.$timer();
|
|
282
|
+
return new Date().getTime();
|
|
283
|
+
};
|
|
284
|
+
this.idName = idName === undefined ? '_id' : `${idName || ''}`;
|
|
285
|
+
this.service = service;
|
|
286
|
+
this.storage =
|
|
287
|
+
typeof storage == 'string' ? ProxyStorageService.makeStorageService(storage, fields, idName) : storage;
|
|
288
|
+
this.filters = filters || new GeneralModelFilter(fields);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* factory function to create this `proxy-storage-service`
|
|
292
|
+
* @param service key-makeable
|
|
293
|
+
* @param table table-name
|
|
294
|
+
* @param fields list of fields.
|
|
295
|
+
* @param filters model filter.
|
|
296
|
+
* @param idName (optional) internal partition-key (default as '_id')
|
|
297
|
+
*/
|
|
298
|
+
static create(service, table, fields, filters, idName) {
|
|
299
|
+
const storage = ProxyStorageService.makeStorageService(table, fields, idName);
|
|
300
|
+
const res = new ProxyStorageService(service, storage, fields, filters, idName);
|
|
301
|
+
return res;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* get next auto-sequence number.
|
|
305
|
+
*
|
|
306
|
+
* @param type type of seqeunce.
|
|
307
|
+
* @param initNext initial next value if not exist.
|
|
308
|
+
*/
|
|
309
|
+
nextSeq(type, initNext) {
|
|
310
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
+
(0, engine_1._log)(NS, `nextSeq(${type})..`);
|
|
312
|
+
const { createdAt, updatedAt } = this.asTime();
|
|
313
|
+
const _id = this.asKey(ProxyStorageService.TYPE_SEQUENCE, `${type}`);
|
|
314
|
+
let res = yield this.storage.increment(_id, { next: 1 }, { updatedAt }); // it will create new row if not exists. (like upset)
|
|
315
|
+
if (res.next == 1) {
|
|
316
|
+
const $key = this.service.asKey$(ProxyStorageService.TYPE_SEQUENCE, `${type}`);
|
|
317
|
+
initNext = initNext === undefined ? ProxyStorageService.AUTO_SEQUENCE : initNext;
|
|
318
|
+
const $upd = { next: initNext };
|
|
319
|
+
const $inc = Object.assign(Object.assign({}, $key), { createdAt, updatedAt });
|
|
320
|
+
res = yield this.storage.increment(_id, $upd, $inc); //! increment w/ update-set
|
|
321
|
+
}
|
|
322
|
+
return res.next;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* get uuid by type.
|
|
327
|
+
* @param type
|
|
328
|
+
*/
|
|
329
|
+
nextUuid(type) {
|
|
330
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
331
|
+
(0, engine_1._log)(NS, `nextUuid(${type})..`);
|
|
332
|
+
return engine_1.$U.uuid();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* get time-stamp as now.
|
|
337
|
+
*/
|
|
338
|
+
asTime(currentTime) {
|
|
339
|
+
currentTime = currentTime || this.getTime();
|
|
340
|
+
const createdAt = currentTime;
|
|
341
|
+
const updatedAt = currentTime;
|
|
342
|
+
const deletedAt = currentTime;
|
|
343
|
+
return { createdAt, updatedAt, deletedAt };
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* delete sequence-key.
|
|
347
|
+
* @param type type of seqeunce.
|
|
348
|
+
*/
|
|
349
|
+
clearSeq(type) {
|
|
350
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
351
|
+
(0, engine_1._log)(NS, `nextSeq(${type})..`);
|
|
352
|
+
const _id = this.asKey(ProxyStorageService.TYPE_SEQUENCE, `${type}`);
|
|
353
|
+
yield this.storage.delete(_id);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* read model by key + id with optional auto creation.
|
|
358
|
+
*
|
|
359
|
+
* @param type model-type
|
|
360
|
+
* @param id node-id
|
|
361
|
+
* @param $create (optional) initial model if not exist. (or throw 404 error)
|
|
362
|
+
*/
|
|
363
|
+
doRead(type, id, $create) {
|
|
364
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
365
|
+
const $key = this.service.asKey$(type, id);
|
|
366
|
+
const _id = this.asKey(type, id);
|
|
367
|
+
const model = yield this.storage.read(_id).catch((e) => {
|
|
368
|
+
if (`${e.message}`.startsWith('404 NOT FOUND') && $create) {
|
|
369
|
+
const { createdAt, updatedAt } = this.asTime();
|
|
370
|
+
return this.storage.update(_id, Object.assign(Object.assign(Object.assign({}, $create), $key), { createdAt, updatedAt, deletedAt: 0 }));
|
|
371
|
+
}
|
|
372
|
+
throw e;
|
|
373
|
+
});
|
|
374
|
+
//! make sure it has `_id`
|
|
375
|
+
model[this.idName] = _id;
|
|
376
|
+
const res = this.filters.afterRead(model);
|
|
377
|
+
return res;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* delete model by id.
|
|
382
|
+
*
|
|
383
|
+
* @param type model-type
|
|
384
|
+
* @param id node-id
|
|
385
|
+
* @param destroy flag to destroy (real delete)
|
|
386
|
+
*/
|
|
387
|
+
doDelete(type, id, destroy = true) {
|
|
388
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
389
|
+
const _id = this.asKey(type, id);
|
|
390
|
+
if (destroy === undefined || destroy === true)
|
|
391
|
+
return this.storage.delete(_id);
|
|
392
|
+
const { createdAt, updatedAt, deletedAt } = this.asTime();
|
|
393
|
+
const $up = { updatedAt, deletedAt };
|
|
394
|
+
const $org = yield this.read(_id); //! it will make 404 if not found.
|
|
395
|
+
if (!$org.createdAt)
|
|
396
|
+
$up.createdAt = createdAt;
|
|
397
|
+
return this.update(_id, $up);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* update model (or it will create automatically)
|
|
402
|
+
*
|
|
403
|
+
* @param type model-type
|
|
404
|
+
* @param id node-id
|
|
405
|
+
* @param node model
|
|
406
|
+
* @param incrementals (optional) fields to increment
|
|
407
|
+
*/
|
|
408
|
+
doUpdate(type, id, node, incrementals) {
|
|
409
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
410
|
+
const $inc = Object.assign({}, incrementals); //! make copy.
|
|
411
|
+
const _id = this.asKey(type, id);
|
|
412
|
+
// const $key = this.service.asKey$(type, id);
|
|
413
|
+
const node2 = this.filters.beforeUpdate(Object.assign(Object.assign({}, node), { [this.idName]: _id }), $inc);
|
|
414
|
+
delete node2['_id'];
|
|
415
|
+
const { updatedAt } = this.asTime();
|
|
416
|
+
const model = yield this.update(_id, Object.assign(Object.assign({}, node2), { updatedAt }), $inc);
|
|
417
|
+
//! make sure it has `_id`
|
|
418
|
+
model[this.idName] = _id;
|
|
419
|
+
return this.filters.afterUpdate(model);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* update model (or it will create automatically)
|
|
424
|
+
*
|
|
425
|
+
* @param type model-type
|
|
426
|
+
* @param id node-id
|
|
427
|
+
*/
|
|
428
|
+
doIncrement(type, id, $inc, $up) {
|
|
429
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
430
|
+
const _id = this.asKey(type, id);
|
|
431
|
+
const { updatedAt } = this.asTime();
|
|
432
|
+
const model = yield this.increment(_id, Object.assign({}, $inc), Object.assign(Object.assign({}, $up), { updatedAt }));
|
|
433
|
+
//! make sure it has `_id`
|
|
434
|
+
model[this.idName] = _id;
|
|
435
|
+
return this.filters.afterUpdate(model);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* save model by checking origin node.
|
|
440
|
+
* - use `doSave()` rather than `doUpdate()` for both create & update.
|
|
441
|
+
* - if `$create` is null, throw 404 error it if not found.
|
|
442
|
+
*
|
|
443
|
+
* @param type model-type
|
|
444
|
+
* @param id node-id
|
|
445
|
+
* @param node node to save (or update)
|
|
446
|
+
* @param $create (optional) initial creation model if not found.
|
|
447
|
+
*/
|
|
448
|
+
doSave(type, id, node, $create) {
|
|
449
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
450
|
+
//! read origin model w/o error.
|
|
451
|
+
const $org = (yield this.doRead(type, id, null).catch(e => {
|
|
452
|
+
if (`${e.message}`.startsWith('404 NOT FOUND'))
|
|
453
|
+
return null; // mark null to create later.
|
|
454
|
+
throw e;
|
|
455
|
+
}));
|
|
456
|
+
//! if `$create` is undefined, create it with default $key.
|
|
457
|
+
const _id = this.asKey(type, id);
|
|
458
|
+
const model = Object.assign({}, node); // copy from param.
|
|
459
|
+
model[this.idName] = _id; //! make sure the internal id
|
|
460
|
+
//! apply filter.
|
|
461
|
+
const $ups = this.filters.beforeSave(model, $org); //! `$org` should be null if create.
|
|
462
|
+
(0, engine_1._log)(NS, `> ${type}[${id}].update =`, engine_1.$U.json($ups));
|
|
463
|
+
//! if null, then nothing to update.
|
|
464
|
+
if (!$ups) {
|
|
465
|
+
const res = { [this.idName]: _id };
|
|
466
|
+
return res;
|
|
467
|
+
}
|
|
468
|
+
//! determine of create or update.
|
|
469
|
+
const { createdAt, updatedAt } = this.asTime();
|
|
470
|
+
if ($org) {
|
|
471
|
+
const $save = Object.assign(Object.assign({}, $ups), { updatedAt });
|
|
472
|
+
const res = yield this.doUpdate(type, id, $save);
|
|
473
|
+
return this.filters.afterSave(res, $org); //! `$org` should be valid if update.
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
const $key = this.service.asKey$(type, id);
|
|
477
|
+
const $save = Object.assign(Object.assign(Object.assign(Object.assign({}, $ups), $create), $key), { createdAt, updatedAt: createdAt, deletedAt: 0 });
|
|
478
|
+
const res = yield this.storage.save(_id, $save);
|
|
479
|
+
return this.filters.afterSave(res, null); //! `$org` should be null if create.
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* lock data-entry by type+id w/ limited time tick
|
|
485
|
+
* - WARN! must release lock by `doRelease()`
|
|
486
|
+
*
|
|
487
|
+
* `total-waited-time = tick * interval (msec)`
|
|
488
|
+
*
|
|
489
|
+
* @param type model-type
|
|
490
|
+
* @param id model-id
|
|
491
|
+
* @param tick tick count to wait.
|
|
492
|
+
* @param interval timeout interval per each tick (in msec, default 1000 = 1sec)
|
|
493
|
+
*/
|
|
494
|
+
doLock(type, id, tick, interval) {
|
|
495
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
496
|
+
tick = engine_1.$U.N(tick, 30);
|
|
497
|
+
interval = engine_1.$U.N(interval, 1000);
|
|
498
|
+
if (typeof tick != 'number' || tick < 0)
|
|
499
|
+
throw new Error(`@tick (${tick}) is not valid!`);
|
|
500
|
+
if (typeof interval != 'number' || interval < 1)
|
|
501
|
+
throw new Error(`@interval (${interval}) is not valid!`);
|
|
502
|
+
const _id = this.asKey(type, id);
|
|
503
|
+
//! WARN! DO NOT MAKE ANY MODEL CREATION IN HERE.
|
|
504
|
+
// const $org = await this.storage.readOrCreate(_id, { lock: 0, ...$key } as any);
|
|
505
|
+
// _log(NS, `> $org[${type}/${id}].lock =`, $org.lock);
|
|
506
|
+
const thiz = this;
|
|
507
|
+
//! wait some time.
|
|
508
|
+
const wait = (timeout) => __awaiter(this, void 0, void 0, function* () {
|
|
509
|
+
return new Promise(resolve => {
|
|
510
|
+
setTimeout(() => {
|
|
511
|
+
resolve(timeout);
|
|
512
|
+
}, timeout);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
const incLock = (_id, lock) => __awaiter(this, void 0, void 0, function* () {
|
|
516
|
+
const $up = {};
|
|
517
|
+
const $in = { lock };
|
|
518
|
+
return thiz.storage.update(_id, $up, $in).then($t2 => {
|
|
519
|
+
return engine_1.$U.N($t2.lock, 1);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
//! recursive to wait lock()
|
|
523
|
+
const waitLock = (_id, ttl, int) => __awaiter(this, void 0, void 0, function* () {
|
|
524
|
+
//! try to check the current value.....
|
|
525
|
+
const lock = yield incLock(_id, 0).then(n => {
|
|
526
|
+
if (n > 1)
|
|
527
|
+
return n;
|
|
528
|
+
//! then, try to increment the lock
|
|
529
|
+
return incLock(_id, ttl > 0 ? 1 : 0);
|
|
530
|
+
});
|
|
531
|
+
(0, engine_1._log)(NS, `! waitLock(${_id}, ${ttl}). lock =`, lock);
|
|
532
|
+
if (lock == 1 || lock == 0) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
else if (ttl > 0 && lock > 1) {
|
|
536
|
+
return wait(int).then(() => waitLock(_id, ttl - 1, int));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
throw new Error(`400 TIMEOUT - model[${_id}].lock = ${lock}`);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
return waitLock(_id, tick, interval);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* release lock by resetting lock = 0.
|
|
547
|
+
*
|
|
548
|
+
* @param type model-type
|
|
549
|
+
* @param id model-id
|
|
550
|
+
*/
|
|
551
|
+
doRelease(type, id) {
|
|
552
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
553
|
+
(0, engine_1._log)(NS, `doRelease(${type}, ${id})... `);
|
|
554
|
+
const _id = this.asKey(type, id);
|
|
555
|
+
const $up = { lock: 0 };
|
|
556
|
+
const node = yield this.storage.update(_id, $up).catch(() => ({ lock: 0 }));
|
|
557
|
+
const lock = engine_1.$U.N(node.lock, 1);
|
|
558
|
+
return lock === 0 ? true : false;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* create storage-service w/ fields list.
|
|
563
|
+
* - idName should be `_id`
|
|
564
|
+
*
|
|
565
|
+
* @param table table-name or dummy file name (ex: `dummy-data.yml`).
|
|
566
|
+
* @param fields required for dynamo table.
|
|
567
|
+
* @param idName internal partition-key name (default '_id')
|
|
568
|
+
*/
|
|
569
|
+
static makeStorageService(table, fields, idName) {
|
|
570
|
+
if (!table)
|
|
571
|
+
throw new Error(`@table (table-name) is required!`);
|
|
572
|
+
idName = idName === undefined ? '_id' : `${idName || ''}`;
|
|
573
|
+
//! clear the duplicated string
|
|
574
|
+
const clearDuplicated = (arr) => arr.sort().reduce((L, val) => {
|
|
575
|
+
if (val && L.indexOf(val) < 0)
|
|
576
|
+
L.push(val);
|
|
577
|
+
return L;
|
|
578
|
+
}, []);
|
|
579
|
+
//! make internal storage-service by table
|
|
580
|
+
if (table.endsWith('.yml')) {
|
|
581
|
+
return new storage_service_1.DummyStorageService(table, table.split('.')[0], idName);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
if (!fields)
|
|
585
|
+
throw new Error(`@fields (list of field) is required!`);
|
|
586
|
+
fields = clearDuplicated(exports.CORE_FIELDS.concat(fields));
|
|
587
|
+
return new storage_service_1.DynamoStorageService(table, fields, idName);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* create proxy-storage-service by type
|
|
592
|
+
* @param type model-type
|
|
593
|
+
*/
|
|
594
|
+
makeTypedStorageService(type) {
|
|
595
|
+
if (!type)
|
|
596
|
+
throw new Error(`@type (model-type) is required!`);
|
|
597
|
+
// if (!fields) throw new Error(`@fields[${type}] (list of field) is required!`);
|
|
598
|
+
const res = new TypedStorageService(this, type);
|
|
599
|
+
return res;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
exports.ProxyStorageService = ProxyStorageService;
|
|
603
|
+
ProxyStorageService.AUTO_SEQUENCE = 1000000;
|
|
604
|
+
ProxyStorageService.TYPE_SEQUENCE = 'sequence';
|
|
605
|
+
/**
|
|
606
|
+
* class: `TypedStorageService`
|
|
607
|
+
* - wrap id with type + id.
|
|
608
|
+
*/
|
|
609
|
+
// eslint-disable-next-line prettier/prettier
|
|
610
|
+
class TypedStorageService {
|
|
611
|
+
constructor(service, type) {
|
|
612
|
+
/**
|
|
613
|
+
* show self service name
|
|
614
|
+
*/
|
|
615
|
+
this.hello = () => `typed-storage-service:${this.type}/${this.storage.hello()}`;
|
|
616
|
+
/**
|
|
617
|
+
* get next auto-sequence id in number like `1000003`.
|
|
618
|
+
*/
|
|
619
|
+
this.nextId = () => this.storage.nextSeq(this.type);
|
|
620
|
+
/**
|
|
621
|
+
* get uuid like `d01764cd-9ef2-41e2-9e88-68e79555c979`
|
|
622
|
+
*/
|
|
623
|
+
this.nextUuid = () => this.storage.nextUuid(this.type);
|
|
624
|
+
/**
|
|
625
|
+
* read model by key + id with optional auto creation.
|
|
626
|
+
* - throws '404 NOT FOUND' if not found.
|
|
627
|
+
*
|
|
628
|
+
* @param id node-id
|
|
629
|
+
*/
|
|
630
|
+
this.read = (id) => this.storage.doRead(this.type, `${id || ''}`);
|
|
631
|
+
/**
|
|
632
|
+
* read model by key + id with optional auto creation.
|
|
633
|
+
*
|
|
634
|
+
* @param id node-id
|
|
635
|
+
* @param model initial model if not exist. (or throw 404 error)
|
|
636
|
+
*/
|
|
637
|
+
this.readOrCreate = (id, model) => this.storage.doRead(this.type, `${id || ''}`, model);
|
|
638
|
+
/**
|
|
639
|
+
* update model (or it will create automatically)
|
|
640
|
+
*
|
|
641
|
+
* @param id node-id
|
|
642
|
+
* @param model model to update
|
|
643
|
+
* @param incrementals (optional) fields to increment.
|
|
644
|
+
*/
|
|
645
|
+
this.update = (id, model, incrementals) => this.storage.doUpdate(this.type, `${id || ''}`, model, incrementals);
|
|
646
|
+
/**
|
|
647
|
+
* insert model w/ auto generated id
|
|
648
|
+
*
|
|
649
|
+
* @param model model to insert
|
|
650
|
+
*/
|
|
651
|
+
this.insert = (node) => __awaiter(this, void 0, void 0, function* () {
|
|
652
|
+
return this.nextId().then(_ => {
|
|
653
|
+
const id = `${_}`;
|
|
654
|
+
(0, engine_1._log)(NS, `> next-id[${this.type}] =`, id);
|
|
655
|
+
return this.readOrCreate(id, Object.assign(Object.assign({}, node), { id }));
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
/**
|
|
659
|
+
* update model (or it will create automatically)
|
|
660
|
+
*
|
|
661
|
+
* ```ts
|
|
662
|
+
* //before: { count: 1 };
|
|
663
|
+
* const res = await storage.increment(1, { count: 2 }, { total: 2 });
|
|
664
|
+
* //after : { count: 3, total: 2 }
|
|
665
|
+
* ```
|
|
666
|
+
*
|
|
667
|
+
* @param id node-id
|
|
668
|
+
* @param $increments model only with numbers
|
|
669
|
+
*/
|
|
670
|
+
this.increment = (id, $increments, $update) => this.storage.doIncrement(this.type, `${id || ''}`, $increments, $update);
|
|
671
|
+
/**
|
|
672
|
+
* delete model by id.
|
|
673
|
+
*
|
|
674
|
+
* @param id node-id
|
|
675
|
+
* @param destroy flag to destroy (real delete)
|
|
676
|
+
*/
|
|
677
|
+
this.delete = (id, destroy) => this.storage.doDelete(this.type, `${id || ''}`, destroy === undefined ? true : destroy);
|
|
678
|
+
/**
|
|
679
|
+
* save model by checking origin node.
|
|
680
|
+
* - use `doSave()` rather than `doUpdate()` for both create & update.
|
|
681
|
+
* - if `$create` is null, throw 404 error it if not found.
|
|
682
|
+
*
|
|
683
|
+
* @param id node-id
|
|
684
|
+
* @param node node to save (or update)
|
|
685
|
+
* @param $create (optional) initial creation model.
|
|
686
|
+
*/
|
|
687
|
+
this.save = (id, model, $create) => this.storage.doSave(this.type, `${id || ''}`, model, $create);
|
|
688
|
+
/**
|
|
689
|
+
* lock data-entry by type+id w/ limited time tick
|
|
690
|
+
* - WARN! must release lock by `release(id)`
|
|
691
|
+
*
|
|
692
|
+
* `total-waited-time = tick * interval (msec)`
|
|
693
|
+
*
|
|
694
|
+
* **[UPDATES]**
|
|
695
|
+
* 1. read original node (or, throw 404 error)
|
|
696
|
+
* 2. use internal lock.
|
|
697
|
+
*
|
|
698
|
+
* @param id model-id to lock
|
|
699
|
+
* @param tick tick count to wait.
|
|
700
|
+
* @param interval timeout interval per each tick (in msec, default 1000 = 1sec)
|
|
701
|
+
*/
|
|
702
|
+
this.lock = (id, tick, interval) => this.storage
|
|
703
|
+
.doRead(this.type, `${id || ''}`, null)
|
|
704
|
+
.then(node => this.storage.doLock(this.type, node.id, tick, interval));
|
|
705
|
+
/**
|
|
706
|
+
* release lock by resetting lock = 0.
|
|
707
|
+
* @param id model-id
|
|
708
|
+
*/
|
|
709
|
+
this.release = (id) => this.storage.doRelease(this.type, `${id || ''}`);
|
|
710
|
+
/**
|
|
711
|
+
* using `lock()`, guard func with auto lock & release.
|
|
712
|
+
*
|
|
713
|
+
* ```ts
|
|
714
|
+
* const res = await storage.guard(async ()=>{
|
|
715
|
+
* return 'abc';
|
|
716
|
+
* });
|
|
717
|
+
* // res === 'abc'
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
720
|
+
this.guard = (id, handler, tick, interval) => __awaiter(this, void 0, void 0, function* () {
|
|
721
|
+
let locked = false;
|
|
722
|
+
return this.lock(id, tick, interval)
|
|
723
|
+
.then((_) => {
|
|
724
|
+
locked = _;
|
|
725
|
+
try {
|
|
726
|
+
return handler();
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
return Promise.reject(e);
|
|
730
|
+
}
|
|
731
|
+
})
|
|
732
|
+
.then((_) => {
|
|
733
|
+
if (locked)
|
|
734
|
+
return this.release(id).then(() => _);
|
|
735
|
+
return _;
|
|
736
|
+
})
|
|
737
|
+
.catch((e) => {
|
|
738
|
+
if (locked)
|
|
739
|
+
return this.release(id).then(() => Promise.reject(e));
|
|
740
|
+
// throw e;
|
|
741
|
+
return Promise.reject(e);
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
/**
|
|
745
|
+
* make `UniqueFieldManager` for field.
|
|
746
|
+
*/
|
|
747
|
+
this.makeUniqueFieldManager = (field) => new UniqueFieldManager(this, field);
|
|
748
|
+
/**
|
|
749
|
+
* make `GeneralAPIController` for REST API w/ supporting basic CRUD
|
|
750
|
+
*/
|
|
751
|
+
this.makeGeneralAPIController = (search, uniqueField) => new general_api_controller_1.GeneralAPIController(this.type, this, search, uniqueField);
|
|
752
|
+
this.storage = service;
|
|
753
|
+
this.type = type;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
exports.TypedStorageService = TypedStorageService;
|
|
757
|
+
/**
|
|
758
|
+
* class: `ModelUtil`
|
|
759
|
+
* - Helper functions for model.
|
|
760
|
+
*/
|
|
761
|
+
class ModelUtil {
|
|
762
|
+
}
|
|
763
|
+
exports.ModelUtil = ModelUtil;
|
|
764
|
+
ModelUtil.selfRead = (self, key, defValue) => {
|
|
765
|
+
const value = self[key];
|
|
766
|
+
return value === undefined ? defValue : value;
|
|
767
|
+
};
|
|
768
|
+
ModelUtil.selfPop = (self, key, defValue) => {
|
|
769
|
+
const value = ModelUtil.selfRead(self, key, defValue);
|
|
770
|
+
delete self[key];
|
|
771
|
+
return value;
|
|
772
|
+
};
|
|
773
|
+
/**
|
|
774
|
+
* attach `.pop()` method to object.
|
|
775
|
+
*
|
|
776
|
+
* ```js
|
|
777
|
+
* const data = CoreModelUtil.buildPop({'a':1});
|
|
778
|
+
* assert( 1 === data.pop('a) );
|
|
779
|
+
* const final = data.pop();
|
|
780
|
+
* assert( final == data );
|
|
781
|
+
*/
|
|
782
|
+
ModelUtil.buildPop = (thiz, popName = 'pop') => {
|
|
783
|
+
if (!thiz)
|
|
784
|
+
throw new Error('@thiz (object) is required!');
|
|
785
|
+
if (typeof thiz[popName] != 'undefined')
|
|
786
|
+
throw new Error(`.[${popName}] is duplicated!`);
|
|
787
|
+
thiz[popName] = function (key, defValue) {
|
|
788
|
+
if (!key) {
|
|
789
|
+
//! clear pop() if key is null.
|
|
790
|
+
delete this[popName];
|
|
791
|
+
return this;
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
return ModelUtil.selfPop(this, key, defValue);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
return thiz;
|
|
798
|
+
};
|
|
799
|
+
/**
|
|
800
|
+
* class: `UniqueFieldManager`
|
|
801
|
+
* - support `.{field}` is unique in typed-storage-service.
|
|
802
|
+
* - make lookup data entry to save the reverse mapping to origin id.
|
|
803
|
+
* - set `.stereo` as '#' to mark as lookup. (to filter out from Elastic.search())
|
|
804
|
+
* - set `.id` as `#{field}/{name}` or `#{name}`.
|
|
805
|
+
* - set `.meta` as origin id.
|
|
806
|
+
*/
|
|
807
|
+
class UniqueFieldManager {
|
|
808
|
+
constructor(storage, field = 'name') {
|
|
809
|
+
this.hello = () => `unique-field-manager:${this.type}/${this.field}:${this.storage.hello()}`;
|
|
810
|
+
this.type = storage.type;
|
|
811
|
+
this.storage = storage;
|
|
812
|
+
this.field = field;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* validate value format
|
|
816
|
+
* - just check empty string.
|
|
817
|
+
* @param value unique value in same type+field.
|
|
818
|
+
*/
|
|
819
|
+
validate(value) {
|
|
820
|
+
const name2 = `${value || ''}`.trim();
|
|
821
|
+
return name2 && value == name2 ? true : false;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* convert to internal id by value
|
|
825
|
+
* @param value unique value in same type group.
|
|
826
|
+
*/
|
|
827
|
+
asLookupId(value) {
|
|
828
|
+
return `#${this.field || ''}/${value || ''}`;
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* lookup model by value
|
|
832
|
+
* - use `.meta` property to link with the origin.
|
|
833
|
+
* - mark `.stereo` as to '#' to distinguish normal.
|
|
834
|
+
*
|
|
835
|
+
* @param value unique value in same type group.
|
|
836
|
+
* @param $creates (optional) create-set if not found.
|
|
837
|
+
*/
|
|
838
|
+
findOrCreate(value, $creates) {
|
|
839
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
840
|
+
if (!value || typeof value != 'string')
|
|
841
|
+
throw new Error(`@${this.field} (string) is required!`);
|
|
842
|
+
if (!this.validate(value))
|
|
843
|
+
throw new Error(`@${this.field} (${value || ''}) is not valid!`);
|
|
844
|
+
const ID = this.asLookupId(value);
|
|
845
|
+
const field = `${this.field}`;
|
|
846
|
+
if (!$creates) {
|
|
847
|
+
// STEP.1 read the origin name map
|
|
848
|
+
const $map = yield this.storage.read(ID).catch(test_helper_1.NUL404);
|
|
849
|
+
const rid = $map && $map.meta;
|
|
850
|
+
if (!rid)
|
|
851
|
+
throw new Error(`404 NOT FOUND - ${this.type}:${field}/${value}`);
|
|
852
|
+
// STEP.2 read the target node by stereo key.
|
|
853
|
+
const model = yield this.storage.read(rid);
|
|
854
|
+
return model;
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
// STEP.0 validate if value is same
|
|
858
|
+
const $any = $creates || {};
|
|
859
|
+
if ($any[field] !== undefined && $any[field] !== value)
|
|
860
|
+
throw new Error(`@${this.field} (${value}) is not same as (${$any[field]})!`);
|
|
861
|
+
// STEP.1 read the origin value map
|
|
862
|
+
const $new = { stereo: '#', meta: `${$creates.id || ''}`, [field]: value };
|
|
863
|
+
const $map = yield this.storage.readOrCreate(ID, $new);
|
|
864
|
+
const rid = ($map && $map.meta) || $creates.id;
|
|
865
|
+
//! check if already saved, and id is differ.
|
|
866
|
+
if ($any['id'] && $any['id'] != rid)
|
|
867
|
+
throw new Error(`@id (${rid}) is not same as (${$any['id']})`);
|
|
868
|
+
// STEP.2 read the target node or create.
|
|
869
|
+
const $temp = Object.assign(Object.assign({}, $creates), { [field]: value });
|
|
870
|
+
const model = rid ? yield this.storage.readOrCreate(rid, $temp) : yield this.storage.insert($temp);
|
|
871
|
+
model[field] = value;
|
|
872
|
+
// STEP.3 update lookup key.
|
|
873
|
+
const newId = `${rid || model.id || ''}`;
|
|
874
|
+
if ($map.meta != newId) {
|
|
875
|
+
const $upt = { meta: newId };
|
|
876
|
+
yield this.storage.update(ID, $upt);
|
|
877
|
+
$map.meta = newId;
|
|
878
|
+
}
|
|
879
|
+
//! returns.
|
|
880
|
+
return model;
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* update lookup table (or create)
|
|
886
|
+
*
|
|
887
|
+
* @param model target model
|
|
888
|
+
* @param value (optional) new value of model.
|
|
889
|
+
*/
|
|
890
|
+
updateLookup(model, value) {
|
|
891
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
892
|
+
value = value || model[this.field];
|
|
893
|
+
if (!this.validate(value))
|
|
894
|
+
throw new Error(`@${this.field} (${value || ''}) is not valid!`);
|
|
895
|
+
const ID = this.asLookupId(value);
|
|
896
|
+
const field = `${this.field}`;
|
|
897
|
+
// STEP.0 validate if value has changed
|
|
898
|
+
const $any = model;
|
|
899
|
+
if ($any[field] && $any[field] !== value)
|
|
900
|
+
throw new Error(`@${this.field} (${value}) is not same as (${$any[field]})!`);
|
|
901
|
+
// STEP.1 check if value is duplicated.
|
|
902
|
+
const $org = yield this.storage.read(ID).catch(test_helper_1.NUL404);
|
|
903
|
+
const rid = $org && $org.meta;
|
|
904
|
+
if ($org && rid != model.id)
|
|
905
|
+
throw new Error(`400 DUPLICATED NAME - ${field}[${value}] is duplicated to ${this.type}[${rid}]`);
|
|
906
|
+
// STEP.2 save the name mapping.
|
|
907
|
+
const $new = Object.assign(Object.assign({}, model), { [field]: value, id: model.id });
|
|
908
|
+
return yield this.findOrCreate(value, $new);
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
exports.UniqueFieldManager = UniqueFieldManager;
|
|
913
|
+
//# sourceMappingURL=proxy-storage-service.js.map
|