hola-server 1.0.10 → 2.0.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 +196 -1
- package/core/array.js +79 -142
- package/core/bash.js +208 -259
- package/core/chart.js +26 -16
- package/core/cron.js +14 -3
- package/core/date.js +15 -44
- package/core/encrypt.js +19 -9
- package/core/file.js +42 -29
- package/core/lhs.js +32 -6
- package/core/meta.js +213 -289
- package/core/msg.js +20 -7
- package/core/number.js +105 -103
- package/core/obj.js +15 -12
- package/core/random.js +9 -6
- package/core/role.js +69 -77
- package/core/thread.js +12 -2
- package/core/type.js +300 -261
- package/core/url.js +20 -12
- package/core/validate.js +29 -26
- package/db/db.js +297 -227
- package/db/entity.js +631 -963
- package/db/gridfs.js +120 -166
- package/design/add_default_field_attr.md +56 -0
- package/http/context.js +22 -8
- package/http/cors.js +25 -8
- package/http/error.js +27 -9
- package/http/express.js +70 -41
- package/http/params.js +70 -42
- package/http/router.js +51 -40
- package/http/session.js +59 -36
- package/index.js +85 -9
- package/package.json +2 -2
- package/router/clone.js +28 -36
- package/router/create.js +21 -26
- package/router/delete.js +24 -28
- package/router/read.js +137 -123
- package/router/update.js +38 -56
- package/setting.js +22 -6
- package/skills/array.md +155 -0
- package/skills/bash.md +91 -0
- package/skills/chart.md +54 -0
- package/skills/code.md +422 -0
- package/skills/context.md +177 -0
- package/skills/date.md +58 -0
- package/skills/express.md +255 -0
- package/skills/file.md +60 -0
- package/skills/lhs.md +54 -0
- package/skills/meta.md +1023 -0
- package/skills/msg.md +30 -0
- package/skills/number.md +88 -0
- package/skills/obj.md +36 -0
- package/skills/params.md +206 -0
- package/skills/random.md +22 -0
- package/skills/role.md +59 -0
- package/skills/session.md +281 -0
- package/skills/storage.md +743 -0
- package/skills/thread.md +22 -0
- package/skills/type.md +547 -0
- package/skills/url.md +34 -0
- package/skills/validate.md +48 -0
- package/test/cleanup/close-db.js +5 -0
- package/test/core/array.js +226 -0
- package/test/core/chart.js +51 -0
- package/test/core/file.js +59 -0
- package/test/core/lhs.js +44 -0
- package/test/core/number.js +167 -12
- package/test/core/obj.js +47 -0
- package/test/core/random.js +24 -0
- package/test/core/thread.js +20 -0
- package/test/core/type.js +216 -0
- package/test/core/validate.js +67 -0
- package/test/db/db-ops.js +99 -0
- package/test/db/pipe_test.txt +0 -0
- package/test/db/test_case_design.md +528 -0
- package/test/db/test_db_class.js +613 -0
- package/test/db/test_entity_class.js +414 -0
- package/test/db/test_gridfs_class.js +234 -0
- package/test/entity/create.js +1 -1
- package/test/entity/delete-mixed.js +156 -0
- package/test/entity/ref-filter.js +63 -0
- package/tool/gen_i18n.js +55 -21
- package/test/crud/router.js +0 -99
- package/test/router/user.js +0 -17
package/core/meta.js
CHANGED
|
@@ -1,359 +1,283 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Meta programming core - Entity definition and validation.
|
|
3
|
+
* @module core/meta
|
|
4
|
+
*
|
|
5
|
+
* Field attributes:
|
|
6
|
+
* - name: Field name (required).
|
|
7
|
+
* - type: Data type (string, int, float, etc., default: "string").
|
|
8
|
+
* - required: Whether field is required (default: false).
|
|
9
|
+
* - default: Default value for the field. Value must be valid for the field type.
|
|
10
|
+
* - ref: Reference to another entity (collection name).
|
|
11
|
+
* - link: Link to another field in this entity (must be a field of type 'ref').
|
|
12
|
+
* This field's value will be auto-populated from the referenced entity.
|
|
13
|
+
* Only supports { name, link, list } attributes.
|
|
14
|
+
* - delete: Deletion behavior for ref fields.
|
|
15
|
+
* - "keep": Keep this record when referenced entity is deleted.
|
|
16
|
+
* - "cascade": Delete this record when referenced entity is deleted.
|
|
17
|
+
* - Default: undefined (no action).
|
|
18
|
+
* - create: Show in create form (default: true).
|
|
19
|
+
* - list: Show in table list (default: true).
|
|
20
|
+
* - search: Show in search form (default: true).
|
|
21
|
+
* - update: Allow update (default: true).
|
|
22
|
+
* - clone: Include in clone (default: true).
|
|
23
|
+
* - sys: System field (server-side only, not sent to client unless explicitly needed).
|
|
24
|
+
* - secure: Hidden from client entirely (e.g., password hash).
|
|
25
|
+
* - group: User group sharing control (field name containing group ID).
|
|
26
|
+
* - view: Form view identifier - controls which form view(s) display this field.
|
|
27
|
+
* - "*": All views (default for editable fields).
|
|
28
|
+
* - "view_name": Specific view only.
|
|
29
|
+
* Only applicable to editable fields (create !== false or update !== false).
|
|
30
|
+
* Multiple form views can reference different subsets of fields for different contexts.
|
|
31
|
+
*/
|
|
32
|
+
|
|
2
33
|
const { get_type } = require('./type');
|
|
3
34
|
const { validate_meta_role } = require('./role');
|
|
4
35
|
|
|
5
36
|
const meta_manager = {};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const meta_attrs = ["collection", "roles", "primary_keys", "fields", "creatable", "readable", "updatable", "deleteable", "cloneable", "after_read", "list_query",
|
|
26
|
-
"before_create", "after_create", "before_clone", "after_clone", "before_update", "after_update", "before_delete", "after_delete", "create", "clone", "update", "batch_update", "after_batch_update", "delete",
|
|
27
|
-
"ref_label", "ref_filter", "route", "user_field"];
|
|
28
|
-
|
|
29
|
-
const DELETE_MODE = Object.freeze({
|
|
30
|
-
all: ["keep", "cascade"],
|
|
31
|
-
keep: "keep",
|
|
32
|
-
cascade: "cascade"
|
|
33
|
-
});
|
|
37
|
+
|
|
38
|
+
const FIELD_ATTRS = ["name", "type", "required", "default", "ref", "link", "delete", "create", "list", "search", "update", "clone", "sys", "secure", "group", "view"];
|
|
39
|
+
const LINK_FIELD_ATTRS = ["name", "link", "list"];
|
|
40
|
+
const CALLBACK_NAMES = ["after_read", "list_query", "before_create", "before_clone", "before_update", "before_delete",
|
|
41
|
+
"after_create", "after_clone", "after_update", "after_delete", "create", "clone", "update", "batch_update", "after_batch_update", "delete"];
|
|
42
|
+
const OPERATION_FLAGS = ["creatable", "readable", "updatable", "deleteable", "cloneable", "importable", "exportable"];
|
|
43
|
+
const MODE_MAP = { creatable: "c", readable: "rs", updatable: "u", deleteable: "db", cloneable: "o", importable: "i", exportable: "e" };
|
|
44
|
+
|
|
45
|
+
const META_ATTRS = ["collection", "roles", "primary_keys", "fields", ...OPERATION_FLAGS, ...CALLBACK_NAMES, "ref_label", "ref_filter", "route", "user_field"];
|
|
46
|
+
const DELETE_MODE = Object.freeze({ all: ["keep", "cascade"], keep: "keep", cascade: "cascade" });
|
|
47
|
+
|
|
48
|
+
/** Convert fields array to name-keyed map. */
|
|
49
|
+
const to_fields_map = (fields) => fields.reduce((m, f) => { m[f.name] = f; return m; }, {});
|
|
50
|
+
|
|
51
|
+
/** Format error message with meta context. */
|
|
52
|
+
const meta_error = (collection, msg, field = null) => {
|
|
53
|
+
const prefix = field ? `meta:${collection},field:${field.name}` : `meta:${collection}`;
|
|
54
|
+
return new Error(`${prefix} ${msg}`);
|
|
55
|
+
};
|
|
34
56
|
|
|
35
57
|
/**
|
|
36
|
-
* Validate
|
|
37
|
-
* @param {
|
|
38
|
-
* @param {
|
|
58
|
+
* Validate and normalize field definition.
|
|
59
|
+
* @param {Object} meta - Entity meta.
|
|
60
|
+
* @param {Object} field - Field definition.
|
|
61
|
+
* @throws {Error} If field configuration is invalid.
|
|
39
62
|
*/
|
|
40
63
|
const validate_field = (meta, field) => {
|
|
41
|
-
if (!field.name) {
|
|
42
|
-
throw new Error("name attr is required for field:" + field + ", and meta:" + meta.collection);
|
|
43
|
-
}
|
|
64
|
+
if (!field.name) throw meta_error(meta.collection, `name attr required for field:${JSON.stringify(field)}`);
|
|
44
65
|
|
|
45
|
-
if (field.type)
|
|
46
|
-
|
|
47
|
-
} else {
|
|
48
|
-
if (!field.link) {
|
|
49
|
-
field.type = "string"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
66
|
+
if (field.type) get_type(field.type);
|
|
67
|
+
else if (!field.link) field.type = "string";
|
|
52
68
|
|
|
53
|
-
if (meta.primary_keys.includes(field.name))
|
|
54
|
-
|
|
69
|
+
if (meta.primary_keys.includes(field.name)) field.required = true;
|
|
70
|
+
|
|
71
|
+
// Validate default value against field type
|
|
72
|
+
if (field.default !== undefined && field.type) {
|
|
73
|
+
const type = get_type(field.type);
|
|
74
|
+
const result = type.convert(field.default);
|
|
75
|
+
if (result.err) throw meta_error(meta.collection, `invalid default value [${field.default}] for type [${field.type}]`, field);
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
if (field.ref && !field.link) {
|
|
58
79
|
const ref_meta = meta_manager[field.ref];
|
|
59
|
-
if (!ref_meta) {
|
|
60
|
-
|
|
61
|
-
}
|
|
80
|
+
if (!ref_meta) throw meta_error(meta.collection, `refers invalid meta:${field.ref}`, field);
|
|
81
|
+
if (!ref_meta.ref_label) throw meta_error(meta.collection, `refers meta:${field.ref} without ref_label`, field);
|
|
62
82
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const ref_by_collections = ref_meta.ref_by_metas.map(m => m.collection);
|
|
68
|
-
if (!ref_by_collections.includes(this.collection)) {
|
|
69
|
-
ref_meta.ref_by_metas.push(meta_manager[meta.collection]);
|
|
70
|
-
}
|
|
83
|
+
const ref_by_collections = ref_meta.ref_by_metas.map((m) => m.collection);
|
|
84
|
+
if (!ref_by_collections.includes(this.collection)) ref_meta.ref_by_metas.push(meta_manager[meta.collection]);
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
if (field.delete) {
|
|
74
|
-
if (!field.ref)
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const all_modes = DELETE_MODE.all;
|
|
79
|
-
if (!all_modes.includes(field.delete)) {
|
|
80
|
-
throw new Error("meta:" + meta.collection + ",field:" + field.name + " has invalid delete:" + field.delete + ", valid values:" + JSON.stringify(all_modes));
|
|
81
|
-
}
|
|
88
|
+
if (!field.ref) throw meta_error(meta.collection, `delete not allowed on non-ref field`, field);
|
|
89
|
+
if (!DELETE_MODE.all.includes(field.delete)) throw meta_error(meta.collection, `invalid delete:${field.delete}, valid:${JSON.stringify(DELETE_MODE.all)}`, field);
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
if (field.link) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
keys.forEach(key => {
|
|
88
|
-
if (!support_keys_for_links.includes(key)) {
|
|
89
|
-
throw new Error("Link field just supports name, link,list property. The attribute [" + key + "] isn't supported for LINK field:" + JSON.stringify(field) + " and meta:" + meta.collection);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
93
|
+
const invalid_keys = Object.keys(field).filter((k) => !LINK_FIELD_ATTRS.includes(k));
|
|
94
|
+
if (invalid_keys.length) throw meta_error(meta.collection, `Link field only supports ${LINK_FIELD_ATTRS.join(",")}. Unsupported:${invalid_keys.join(",")}`);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
const editable = (field.create
|
|
95
|
-
|
|
96
|
-
if (field.view)
|
|
97
|
-
if (!editable) {
|
|
98
|
-
throw new Error("view can just defined for editable (create/update) field only. Field:" + JSON.stringify(field) + "] and meta:" + meta.collection);
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
//no view defined for create/update field, then set view to default value "0"
|
|
102
|
-
if (editable) {
|
|
103
|
-
field.view = "0";
|
|
104
|
-
}
|
|
105
|
-
}
|
|
97
|
+
const editable = (field.create !== false) || (field.update !== false);
|
|
98
|
+
if (field.view && !editable) throw meta_error(meta.collection, `view only for editable fields`);
|
|
99
|
+
if (!field.view && editable) field.view = "*";
|
|
106
100
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
throw new Error("The attribute [" + key + "] isn't supported now for field:" + JSON.stringify(field) + " and meta:" + meta.collection);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}
|
|
101
|
+
const invalid_attrs = Object.keys(field).filter((k) => !FIELD_ATTRS.includes(k));
|
|
102
|
+
if (invalid_attrs.length) throw meta_error(meta.collection, `Unsupported attribute [${invalid_attrs.join(",")}] for field:${JSON.stringify(field)}`);
|
|
103
|
+
};
|
|
114
104
|
|
|
115
105
|
/**
|
|
116
|
-
* Validate all
|
|
117
|
-
* @param {
|
|
118
|
-
* @param {
|
|
106
|
+
* Validate all fields in meta definition.
|
|
107
|
+
* @param {Object} meta - Entity meta.
|
|
108
|
+
* @param {Object[]} fields - Field definitions.
|
|
109
|
+
* @returns {boolean} True if valid.
|
|
110
|
+
* @throws {Error} If validation fails.
|
|
119
111
|
*/
|
|
120
112
|
const validate_fields = (meta, fields) => {
|
|
121
|
-
const fields_map = fields
|
|
122
|
-
const
|
|
113
|
+
const fields_map = to_fields_map(fields);
|
|
114
|
+
const seen_names = new Set();
|
|
123
115
|
|
|
124
|
-
|
|
116
|
+
for (const field of fields) {
|
|
125
117
|
validate_field(meta, field);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
118
|
+
|
|
119
|
+
if (seen_names.has(field.name)) throw meta_error(meta.collection, `Duplicate field [${JSON.stringify(field)}]`);
|
|
120
|
+
seen_names.add(field.name);
|
|
121
|
+
|
|
131
122
|
if (field.link) {
|
|
132
123
|
const link_field = fields_map[field.link];
|
|
133
|
-
if (!link_field) {
|
|
134
|
-
|
|
135
|
-
} else {
|
|
136
|
-
if (!link_field.ref) {
|
|
137
|
-
throw new Error("link field [" + JSON.stringify(field) + "] link to field [" + JSON.stringify(link_field) + "] should ref to one entity in meta:" + meta.collection);
|
|
138
|
-
}
|
|
139
|
-
const entity = get_entity_meta(link_field.ref);
|
|
140
|
-
const link_entity_field = entity.fields_map[field.name];
|
|
141
|
-
if (!link_entity_field) {
|
|
142
|
-
throw new Error("link field [" + JSON.stringify(field) + "] should link to one field defined in meta:" + entity.collection);
|
|
143
|
-
}
|
|
124
|
+
if (!link_field) throw meta_error(meta.collection, `link field [${JSON.stringify(field)}] should link to field`);
|
|
125
|
+
if (!link_field.ref) throw meta_error(meta.collection, `link field [${JSON.stringify(field)}] target [${JSON.stringify(link_field)}] must ref entity`);
|
|
144
126
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
field.create = false;
|
|
149
|
-
field.search = false;
|
|
150
|
-
field.update = false;
|
|
151
|
-
field.clone = false;
|
|
152
|
-
field.delete = "cascade";
|
|
153
|
-
|
|
154
|
-
if (link_entity_field.ref) {
|
|
155
|
-
field.ref = link_entity_field.ref;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
});
|
|
127
|
+
const entity = get_entity_meta(link_field.ref);
|
|
128
|
+
const link_entity_field = entity.fields_map[field.name];
|
|
129
|
+
if (!link_entity_field) throw meta_error(entity.collection, `link field [${JSON.stringify(field)}] should link to field`);
|
|
160
130
|
|
|
131
|
+
Object.assign(field, { type: link_entity_field.type, required: false, create: false, search: false, update: false, clone: false, delete: "cascade" });
|
|
132
|
+
if (link_entity_field.ref) field.ref = link_entity_field.ref;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
161
135
|
return true;
|
|
162
|
-
}
|
|
136
|
+
};
|
|
163
137
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
166
|
-
*/
|
|
167
|
-
const validate_all_metas = () => {
|
|
168
|
-
const metas = Object.keys(meta_manager);
|
|
169
|
-
metas.forEach(meta_name => {
|
|
170
|
-
const meta = meta_manager[meta_name];
|
|
171
|
-
meta.validate_meta_info();
|
|
172
|
-
});
|
|
173
|
-
}
|
|
138
|
+
/** Validate all registered metas after loading. */
|
|
139
|
+
const validate_all_metas = () => Object.values(meta_manager).forEach((m) => m.validate_meta_info());
|
|
174
140
|
|
|
175
141
|
/**
|
|
176
|
-
* Set function
|
|
177
|
-
* @param {
|
|
178
|
-
* @param {
|
|
179
|
-
* @param {
|
|
142
|
+
* Set callback function on entity meta.
|
|
143
|
+
* @param {Object} entity_meta - Entity meta object.
|
|
144
|
+
* @param {string} cb_name - Callback name.
|
|
145
|
+
* @param {Function} cb - Callback function.
|
|
146
|
+
* @throws {Error} If callback is not a function.
|
|
180
147
|
*/
|
|
181
148
|
const set_callback = (entity_meta, cb_name, cb) => {
|
|
182
|
-
if (cb)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
throw new Error("callback [" + cb_name + "] configured for meta:" + meta.collection + " isn't function object");
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
149
|
+
if (!cb) return;
|
|
150
|
+
if (!(cb instanceof Function)) throw new Error(`callback [${cb_name}] for meta:${entity_meta.collection} isn't function`);
|
|
151
|
+
entity_meta[cb_name] = cb;
|
|
152
|
+
};
|
|
190
153
|
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
* @param {meta collection name} collection
|
|
194
|
-
* @returns
|
|
195
|
-
*/
|
|
196
|
-
const get_entity_meta = (collection) => {
|
|
197
|
-
return meta_manager[collection];
|
|
198
|
-
}
|
|
154
|
+
/** Get entity meta by collection name. */
|
|
155
|
+
const get_entity_meta = (collection) => meta_manager[collection];
|
|
199
156
|
|
|
200
|
-
/**
|
|
201
|
-
|
|
202
|
-
* @returns
|
|
203
|
-
*/
|
|
204
|
-
const get_all_metas = () => {
|
|
205
|
-
return Object.keys(meta_manager);
|
|
206
|
-
}
|
|
157
|
+
/** Get all registered meta collection names. */
|
|
158
|
+
const get_all_metas = () => Object.keys(meta_manager);
|
|
207
159
|
|
|
208
160
|
|
|
209
161
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* 2) set the default values of the meta
|
|
162
|
+
* Entity Meta wrapper class.
|
|
163
|
+
* Validates meta structure and sets default values.
|
|
213
164
|
*/
|
|
214
165
|
class EntityMeta {
|
|
215
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Create EntityMeta instance.
|
|
168
|
+
* @param {Object} meta - Meta definition object.
|
|
169
|
+
* @throws {Error} If duplicate meta registered.
|
|
170
|
+
*/
|
|
216
171
|
constructor(meta) {
|
|
217
172
|
this.meta = meta;
|
|
218
|
-
this.collection =
|
|
219
|
-
this.roles =
|
|
220
|
-
|
|
221
|
-
this.
|
|
222
|
-
this.
|
|
223
|
-
this.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
this.exportable = is_undefined(meta.exportable) ? false : meta.exportable;
|
|
173
|
+
this.collection = meta.collection;
|
|
174
|
+
this.roles = meta.roles;
|
|
175
|
+
this.primary_keys = meta.primary_keys;
|
|
176
|
+
this.user_field = meta.user_field;
|
|
177
|
+
this.ref_label = meta.ref_label;
|
|
178
|
+
this.ref_filter = meta.ref_filter;
|
|
179
|
+
|
|
180
|
+
// Set operation flags with defaults
|
|
181
|
+
OPERATION_FLAGS.forEach((flag) => { this[flag] = meta[flag] ?? false; });
|
|
228
182
|
this.editable = this.creatable || this.updatable;
|
|
229
183
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
this.creatable && (modes.push("c"));
|
|
233
|
-
//prefer the infinite scrolling mode, so doesn't include p mode
|
|
234
|
-
this.readable && (modes.push("rs"));
|
|
235
|
-
this.updatable && (modes.push("u"));
|
|
236
|
-
this.deleteable && (modes.push("db"));
|
|
237
|
-
this.cloneable && (modes.push("o"));
|
|
238
|
-
this.importable && (modes.push("i"));
|
|
239
|
-
this.exportable && (modes.push("e"));
|
|
240
|
-
this.mode = modes.join("");
|
|
241
|
-
|
|
242
|
-
this.ref_label = this.meta.ref_label;
|
|
243
|
-
this.ref_filter = this.meta.ref_filter;
|
|
244
|
-
this.ref_fields = this.meta.fields.filter(field => field.ref);
|
|
245
|
-
this.ref_by_metas = [];
|
|
184
|
+
// Build mode string
|
|
185
|
+
this.mode = OPERATION_FLAGS.filter((f) => this[f]).map((f) => MODE_MAP[f]).join("");
|
|
246
186
|
|
|
247
|
-
|
|
248
|
-
this.fields_map = meta.fields.reduce((map, field) => { map[field.name] = field; return map; }, {});
|
|
187
|
+
// Field organization
|
|
249
188
|
this.fields = meta.fields;
|
|
250
|
-
this.
|
|
251
|
-
this.field_names = this.fields.map(
|
|
252
|
-
this.
|
|
189
|
+
this.fields_map = to_fields_map(meta.fields);
|
|
190
|
+
this.field_names = this.fields.map((f) => f.name);
|
|
191
|
+
this._init_field_subsets(meta);
|
|
253
192
|
|
|
254
|
-
|
|
255
|
-
this.
|
|
256
|
-
this.
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.file_fields = meta.fields.filter(f => f.type === 'file');
|
|
265
|
-
this.upload_fields = this.file_fields && this.file_fields.length > 0 ? this.file_fields.map(f => ({ name: f.name })) : [];
|
|
266
|
-
|
|
267
|
-
set_callback(this, "after_read", meta.after_read);
|
|
268
|
-
set_callback(this, "list_query", meta.list_query);
|
|
269
|
-
set_callback(this, "before_create", meta.before_create);
|
|
270
|
-
set_callback(this, "before_clone", meta.before_clone);
|
|
271
|
-
set_callback(this, "before_update", meta.before_update);
|
|
272
|
-
set_callback(this, "before_delete", meta.before_delete);
|
|
273
|
-
set_callback(this, "after_create", meta.after_create);
|
|
274
|
-
set_callback(this, "after_clone", meta.after_clone);
|
|
275
|
-
set_callback(this, "after_update", meta.after_update);
|
|
276
|
-
set_callback(this, "after_delete", meta.after_delete);
|
|
277
|
-
set_callback(this, "create", meta.create);
|
|
278
|
-
set_callback(this, "clone", meta.clone);
|
|
279
|
-
set_callback(this, "update", meta.update);
|
|
280
|
-
set_callback(this, "batch_update", meta.batch_update);
|
|
281
|
-
set_callback(this, "after_batch_update", meta.after_batch_update);
|
|
282
|
-
set_callback(this, "delete", meta.delete);
|
|
283
|
-
|
|
284
|
-
if (meta_manager[meta.collection]) {
|
|
285
|
-
throw new Error("Duplicate meta info:" + this.collection);
|
|
286
|
-
} else {
|
|
287
|
-
meta_manager[meta.collection] = this;
|
|
288
|
-
}
|
|
193
|
+
// Reference handling
|
|
194
|
+
this.ref_fields = this.fields.filter((f) => f.ref);
|
|
195
|
+
this.link_fields = this.fields.filter((f) => f.link);
|
|
196
|
+
this.ref_by_metas = [];
|
|
197
|
+
|
|
198
|
+
// Set callbacks and register meta
|
|
199
|
+
CALLBACK_NAMES.forEach((cb) => set_callback(this, cb, meta[cb]));
|
|
200
|
+
if (meta_manager[meta.collection]) throw new Error(`Duplicate meta info:${this.collection}`);
|
|
201
|
+
meta_manager[meta.collection] = this;
|
|
289
202
|
}
|
|
290
203
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
204
|
+
/** Initialize field subsets for different operations. @private */
|
|
205
|
+
_init_field_subsets(meta) {
|
|
206
|
+
const not_sys = (f) => f.sys !== true;
|
|
207
|
+
const not_secure = (f) => f.secure !== true;
|
|
208
|
+
|
|
209
|
+
this.client_fields = this.fields.filter(not_sys);
|
|
210
|
+
this.property_fields = this.fields.filter((f) => not_sys(f) && not_secure(f));
|
|
211
|
+
this.create_fields = this.fields.filter((f) => f.create !== false && not_sys(f));
|
|
212
|
+
this.update_fields = this.fields.filter((f) => f.create !== false && f.update !== false && not_sys(f));
|
|
213
|
+
this.search_fields = this.fields.filter((f) => f.search !== false && not_sys(f));
|
|
214
|
+
this.clone_fields = this.fields.filter((f) => f.clone !== false && not_sys(f));
|
|
215
|
+
this.list_fields = this.fields.filter((f) => f.list !== false && not_sys(f) && not_secure(f));
|
|
216
|
+
this.primary_key_fields = this.fields.filter((f) => meta.primary_keys.includes(f.name));
|
|
217
|
+
this.required_field_names = this.fields.filter((f) => f.required === true || this.primary_keys.includes(f.name)).map((f) => f.name);
|
|
218
|
+
this.file_fields = this.fields.filter((f) => f.type === 'file');
|
|
219
|
+
this.upload_fields = this.file_fields.map((f) => ({ name: f.name }));
|
|
220
|
+
}
|
|
295
221
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (!this.primary_keys) {
|
|
304
|
-
throw new Error("no primary_keys configured for meta:" + this.collection);
|
|
305
|
-
} else if (!Array.isArray(this.primary_keys)) {
|
|
306
|
-
throw new Error("primary_keys of meta [" + this.collection + "] should be array");
|
|
307
|
-
} else {
|
|
308
|
-
this.primary_keys.forEach(key => {
|
|
309
|
-
if (!this.field_names.includes(key)) {
|
|
310
|
-
throw new Error("wrong primary_key " + key + " configured in meta:" + this.collection);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
}
|
|
222
|
+
/**
|
|
223
|
+
* Validate meta information.
|
|
224
|
+
* @returns {boolean} True if valid.
|
|
225
|
+
* @throws {Error} If validation fails.
|
|
226
|
+
*/
|
|
227
|
+
validate_meta_info() {
|
|
228
|
+
if (!this.collection) throw new Error(`no collection defined for meta:${JSON.stringify(this.meta)}`);
|
|
314
229
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
throw new Error("roles of meta [" + this.collection + "] should be array");
|
|
318
|
-
}
|
|
230
|
+
const invalid_attrs = Object.keys(this.meta).filter((k) => !META_ATTRS.includes(k));
|
|
231
|
+
if (invalid_attrs.length) throw meta_error(this.collection, `Unsupported attribute [${invalid_attrs.join(",")}]`);
|
|
319
232
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
233
|
+
this._validate_primary_keys();
|
|
234
|
+
this._validate_roles();
|
|
235
|
+
this._validate_field_exists("ref_label", this.ref_label);
|
|
236
|
+
this._validate_field_exists("user_field", this.user_field);
|
|
237
|
+
if (this.ref_filter && this.ref_filter.constructor !== Object) {
|
|
238
|
+
throw meta_error(this.collection, `ref_filter should be object`);
|
|
239
|
+
}
|
|
325
240
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
throw new Error("role [" + role_name + "] in meta [" + this.collection + "] not defined in setting's role config.");
|
|
329
|
-
}
|
|
241
|
+
return validate_fields(this.meta, this.fields);
|
|
242
|
+
}
|
|
330
243
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
});
|
|
244
|
+
/** Validate primary keys configuration. @private */
|
|
245
|
+
_validate_primary_keys() {
|
|
246
|
+
if (!this.primary_keys) throw meta_error(this.collection, `no primary_keys defined`);
|
|
247
|
+
if (!Array.isArray(this.primary_keys)) throw meta_error(this.collection, `primary_keys should be array`);
|
|
248
|
+
for (const key of this.primary_keys) {
|
|
249
|
+
if (!this.field_names.includes(key)) throw meta_error(this.collection, `wrong primary_key ${key}`);
|
|
341
250
|
}
|
|
251
|
+
}
|
|
342
252
|
|
|
343
|
-
|
|
344
|
-
|
|
253
|
+
/** Validate a field name exists in fields. @private */
|
|
254
|
+
_validate_field_exists(attr_name, value) {
|
|
255
|
+
if (value && !this.field_names.includes(value)) {
|
|
256
|
+
throw meta_error(this.collection, `${attr_name} [${value}] not found in fields`);
|
|
345
257
|
}
|
|
258
|
+
}
|
|
346
259
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
260
|
+
/** Validate roles configuration. @private */
|
|
261
|
+
_validate_roles() {
|
|
262
|
+
if (!this.roles) return;
|
|
263
|
+
if (!Array.isArray(this.roles)) throw meta_error(this.collection, `roles should be array`);
|
|
350
264
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
265
|
+
for (const role of this.roles) {
|
|
266
|
+
const parts = role.split(":");
|
|
267
|
+
if (parts.length < 2 || parts.length > 3) {
|
|
268
|
+
throw meta_error(this.collection, `wrong role config [${role}]. Use : to separate role name with mode.`);
|
|
269
|
+
}
|
|
354
270
|
|
|
355
|
-
|
|
271
|
+
const [role_name, role_mode] = parts;
|
|
272
|
+
if (!validate_meta_role(role_name)) throw meta_error(this.collection, `role [${role_name}] not defined in settings`);
|
|
273
|
+
|
|
274
|
+
if (role_mode !== "*") {
|
|
275
|
+
for (const mode of role_mode) {
|
|
276
|
+
if (!this.mode.includes(mode)) throw meta_error(this.collection, `role [${role_name}] mode [${mode}] doesn't match entity mode [${this.mode}]`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
356
280
|
}
|
|
357
281
|
}
|
|
358
282
|
|
|
359
|
-
module.exports = { EntityMeta, validate_all_metas, get_entity_meta, get_all_metas, DELETE_MODE }
|
|
283
|
+
module.exports = { EntityMeta, validate_all_metas, get_entity_meta, get_all_metas, DELETE_MODE };
|
package/core/msg.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message sending utility functions using wxmnode.
|
|
3
|
+
* @module core/msg
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const wxm = require('wxmnode');
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
8
|
+
/**
|
|
9
|
+
* Initialize wxm with credentials.
|
|
10
|
+
* @param {string} name - Account name.
|
|
11
|
+
* @param {string} password - Account password.
|
|
12
|
+
*/
|
|
13
|
+
const init_wxm = (name, password) => wxm.init(name, password);
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* Send message via wxm.
|
|
17
|
+
* @param {string} content - Message content.
|
|
18
|
+
* @param {string} type - Message type.
|
|
19
|
+
* @param {Object} detail - Additional message details.
|
|
20
|
+
* @returns {Promise<Object>} Message send result.
|
|
21
|
+
*/
|
|
22
|
+
const send_msg = async (content, type, detail) => await wxm.sendMsg(content, type, detail);
|
|
10
23
|
|
|
11
|
-
module.exports = { init_wxm, send_msg }
|
|
24
|
+
module.exports = { init_wxm, send_msg };
|