@yongdall/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.mts +389 -0
- package/index.mjs +1709 -0
- package/index.mjs.map +1 -0
- package/package.json +21 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,1709 @@
|
|
|
1
|
+
import { Hook, Search, isFunction } from "@yongdall/common";
|
|
2
|
+
import { Query, Where, afterCreate, afterCreateMany, afterDelete, afterUpdate, beforeCreate, beforeCreateMany, beforeDelete, beforeUpdate, coefficient, decrement, expandModel, getDefinePermission, increment, multiply } from "@yongdall/model";
|
|
3
|
+
import { createKeyStore, createStoreSerializable } from "@yongdall/context";
|
|
4
|
+
|
|
5
|
+
//#region packages/core/polyfill.mjs
|
|
6
|
+
if (!Symbol.metadata) Symbol.metadata = Symbol.for("Symbol.metadata");
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region packages/core/config.mjs
|
|
10
|
+
/** @import { Tenant } from '@yongdall/types' */
|
|
11
|
+
/** @import { BootConfiguration } from '@yongdall/common' */
|
|
12
|
+
/** @type {Tenant} */
|
|
13
|
+
const mainTenant = {
|
|
14
|
+
id: "",
|
|
15
|
+
label: "拥道YongDall",
|
|
16
|
+
single: false,
|
|
17
|
+
providers: {},
|
|
18
|
+
salt: "YongDall"
|
|
19
|
+
};
|
|
20
|
+
/** @type {BootConfiguration} */
|
|
21
|
+
const bootConfiguration = {
|
|
22
|
+
pages: {},
|
|
23
|
+
theme: "@yongdall/theme#allSider",
|
|
24
|
+
authenticator: "",
|
|
25
|
+
path: "/workbench/"
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param {Partial<Omit<Tenant, 'id'>>} tenant
|
|
30
|
+
* @param {object} boot
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
function initConfig({ label, single, providers, salt }, boot) {
|
|
34
|
+
Object.assign(bootConfiguration, boot);
|
|
35
|
+
mainTenant.label = typeof label === "string" && label || "拥道YongDall";
|
|
36
|
+
mainTenant.single = Boolean(single);
|
|
37
|
+
if (salt) mainTenant.salt = salt;
|
|
38
|
+
mainTenant.providers = Object.fromEntries(Object.entries(typeof providers === "object" && providers || {}).filter(([k, v]) => typeof v === "string" && v));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region packages/core/hooks.mjs
|
|
43
|
+
/** @import { Hooks } from './types.mjs' */
|
|
44
|
+
/** @type {Hook<Hooks>} */
|
|
45
|
+
const hooks = new Hook();
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region packages/core/menu.mjs
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {string} type
|
|
52
|
+
* @param {string} [group]
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
function getMenus(type, group = "") {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region packages/core/models.mjs
|
|
61
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
62
|
+
/** @import { FieldDefine, Fields, ModelTable, TableDefine } from '@yongdall/model' */
|
|
63
|
+
/** @import { Hooks } from './types.mjs' */
|
|
64
|
+
/** @type {Hooks['models']} */
|
|
65
|
+
let models = Object.create(null);
|
|
66
|
+
/** @type {Hooks['modelLoaders']} */
|
|
67
|
+
let modelLoaders = Object.create(null);
|
|
68
|
+
/** @type {((modelId: string, groupId?: string | null) => PromiseLike<Permission[] | null> | Permission[] | null)[]} */
|
|
69
|
+
let getModelPermissionsHooks = [];
|
|
70
|
+
/** @type {WeakMap<ModelTable, ModelTable>} */
|
|
71
|
+
let modelMap = /* @__PURE__ */ new WeakMap();
|
|
72
|
+
/** @type {Record<string, Fields<TableDefine>>} */
|
|
73
|
+
let modelExpands = {};
|
|
74
|
+
hooks.listen(() => {
|
|
75
|
+
modelMap = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
models = hooks.get("models").named();
|
|
77
|
+
modelLoaders = hooks.get("modelLoaders").named();
|
|
78
|
+
getModelPermissionsHooks = [...hooks.get("getModelPermissions").values()].filter((v) => typeof v === "function");
|
|
79
|
+
const modelFields = [...hooks.get("modelExpands").entriesObject()].flatMap(([p, modelId, fields]) => [fields].flatMap((v) => {
|
|
80
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return [];
|
|
81
|
+
return Object.entries(v).map(([fieldName, field]) => [
|
|
82
|
+
modelId,
|
|
83
|
+
fieldName,
|
|
84
|
+
field
|
|
85
|
+
]);
|
|
86
|
+
}));
|
|
87
|
+
const groups = Object.groupBy(modelFields, (v) => v[0]);
|
|
88
|
+
/** @type {Record<string, Fields<TableDefine>>} */
|
|
89
|
+
const expands = {};
|
|
90
|
+
for (const [modelId, fields] of Object.entries(groups)) {
|
|
91
|
+
if (!fields?.length) continue;
|
|
92
|
+
expands[modelId] = Object.fromEntries(fields.map(([, fieldName, field]) => [fieldName, field]));
|
|
93
|
+
}
|
|
94
|
+
modelExpands = expands;
|
|
95
|
+
}, -1e6);
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param {string | string[]} ident
|
|
99
|
+
* @returns {Promise<ModelTable?>}
|
|
100
|
+
*/
|
|
101
|
+
async function findModel(ident) {
|
|
102
|
+
const [model, ...collection] = (Array.isArray(ident) ? ident : ident.split(":")).filter(Boolean);
|
|
103
|
+
if (!collection.length) {
|
|
104
|
+
const define = Object.hasOwn(models, model) && models[model];
|
|
105
|
+
return typeof define === "object" && define || null;
|
|
106
|
+
}
|
|
107
|
+
const loader = Object.hasOwn(modelLoaders, model) && modelLoaders[model];
|
|
108
|
+
if (typeof loader !== "function") return null;
|
|
109
|
+
const define = await loader(collection);
|
|
110
|
+
return typeof define === "object" && define || null;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @param {string | string[]} ident
|
|
115
|
+
* @returns {Promise<ModelTable?>}
|
|
116
|
+
*/
|
|
117
|
+
async function getModel(ident) {
|
|
118
|
+
const model = await findModel(ident);
|
|
119
|
+
if (!model) return model;
|
|
120
|
+
const define = modelMap.get(model);
|
|
121
|
+
if (define) return define;
|
|
122
|
+
const modelIdent = Array.isArray(ident) ? ident.join(":") : ident;
|
|
123
|
+
const newDefine = expandModel(model, Object.hasOwn(modelExpands, modelIdent) ? modelExpands[modelIdent] : {});
|
|
124
|
+
modelMap.set(model, newDefine);
|
|
125
|
+
return newDefine;
|
|
126
|
+
}
|
|
127
|
+
/** @type {(modelId: string, groupId?: string?) => Promise<Permission[]?>} */
|
|
128
|
+
const getModelPermissions = createKeyStore(async (modelId, groupId) => {
|
|
129
|
+
for (const fn of getModelPermissionsHooks) {
|
|
130
|
+
const permissions = await fn(modelId, groupId);
|
|
131
|
+
if (permissions) return permissions;
|
|
132
|
+
}
|
|
133
|
+
if (groupId) return null;
|
|
134
|
+
return getDefinePermission(await getModel(modelId));
|
|
135
|
+
}, (modelId, groupId) => [modelId, groupId || ""].join("\n"));
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
* @param {string} modelId
|
|
139
|
+
* @param {string} authorization
|
|
140
|
+
* @param {string?} [groupId]
|
|
141
|
+
* @returns {Promise<Permission[]?>}
|
|
142
|
+
*/
|
|
143
|
+
async function getModelAuthorizationPermissions(modelId, authorization, groupId) {
|
|
144
|
+
const permissions = await getModelPermissions(modelId, groupId);
|
|
145
|
+
if (!permissions?.length) return permissions;
|
|
146
|
+
return permissions.filter(({ authorizations }) => {
|
|
147
|
+
if (!authorizations) return false;
|
|
148
|
+
if (Array.isArray(authorizations)) return authorizations.includes(authorization);
|
|
149
|
+
if (authorizations instanceof Set) return authorizations.has(authorization);
|
|
150
|
+
return false;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region packages/core/tenant.mjs
|
|
156
|
+
/** @import { Tenant } from '@yongdall/types' */
|
|
157
|
+
const [tenantGetter, tenantSetter] = createStoreSerializable("tenantStatus", async (state) => {
|
|
158
|
+
if (!state) return null;
|
|
159
|
+
return state;
|
|
160
|
+
}, (tenant) => {
|
|
161
|
+
return tenant ? Promise.resolve(tenant) : null;
|
|
162
|
+
}, null);
|
|
163
|
+
function getTenantManager() {
|
|
164
|
+
return [...hooks.get("tenantManager").values()].filter(Boolean);
|
|
165
|
+
}
|
|
166
|
+
let tenantManagers = getTenantManager();
|
|
167
|
+
hooks.listen(() => {
|
|
168
|
+
tenantManagers = getTenantManager();
|
|
169
|
+
}, -1e6);
|
|
170
|
+
/**
|
|
171
|
+
*
|
|
172
|
+
* @returns {Promise<Tenant>}
|
|
173
|
+
*/
|
|
174
|
+
async function getCurrentTenant() {
|
|
175
|
+
if (!tenantManagers.length) return mainTenant;
|
|
176
|
+
for (const get of tenantManagers) {
|
|
177
|
+
const u = await get();
|
|
178
|
+
if (!u) continue;
|
|
179
|
+
return u;
|
|
180
|
+
}
|
|
181
|
+
throw new Error("[noTenant] 找不到租户");
|
|
182
|
+
}
|
|
183
|
+
async function get$1() {
|
|
184
|
+
let state = tenantGetter();
|
|
185
|
+
if (!state) {
|
|
186
|
+
state = getCurrentTenant();
|
|
187
|
+
tenantSetter(state);
|
|
188
|
+
}
|
|
189
|
+
return state;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* @returns {Promise<Tenant>}
|
|
194
|
+
*/
|
|
195
|
+
function useTenant() {
|
|
196
|
+
return get$1();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region packages/core/user.mjs
|
|
201
|
+
/** @import { Tenant } from '@yongdall/types' */
|
|
202
|
+
/** @import { UserManager, MaybePromise } from './types.mjs' */
|
|
203
|
+
/**
|
|
204
|
+
* @typedef {[result: string | null, UserManager['set'] | null, UserManager['exit'] | null, user: string | null, sUser: string | null]} State
|
|
205
|
+
*/
|
|
206
|
+
const [userGetter, userSetter] = createStoreSerializable("userStatus", async (state) => {
|
|
207
|
+
if (!state) return null;
|
|
208
|
+
const s = await state;
|
|
209
|
+
return {
|
|
210
|
+
user: s[3],
|
|
211
|
+
sUser: s[4]
|
|
212
|
+
};
|
|
213
|
+
}, (val) => {
|
|
214
|
+
if (!val) return null;
|
|
215
|
+
const { user: userId, sUser: sUserId } = val;
|
|
216
|
+
return Promise.resolve([
|
|
217
|
+
null,
|
|
218
|
+
null,
|
|
219
|
+
null,
|
|
220
|
+
userId,
|
|
221
|
+
sUserId
|
|
222
|
+
]);
|
|
223
|
+
}, null);
|
|
224
|
+
function getUserLoaders() {
|
|
225
|
+
return [...hooks.get("userManager").entries()].map(([k, v]) => [k, v?.loadPlugin]).filter(([, v]) => typeof v === "function");
|
|
226
|
+
}
|
|
227
|
+
function getUserManagers() {
|
|
228
|
+
/** @type {Partial<UserManager>[]} */
|
|
229
|
+
const userMangers = [...hooks.get("userManager").values()].filter(Boolean);
|
|
230
|
+
const defaultLogin = userMangers.find((v) => typeof v.login === "function")?.login || null;
|
|
231
|
+
const switchableList = userMangers.map((v) => v.switchable).filter(isFunction);
|
|
232
|
+
/** @type {(() => MaybePromise<string?>)[]} */
|
|
233
|
+
const suGets = userMangers.map((v) => v.suGet).filter(isFunction);
|
|
234
|
+
/** @type {[UserManager['get'], UserManager['set'] | null, UserManager['exit'] | null][]} */
|
|
235
|
+
const getLoggedUserFn = [];
|
|
236
|
+
/** @type {Record<string, ((userId: string, password: string) => MaybePromise<number>)[]>} */
|
|
237
|
+
const verifiers = Object.create(null);
|
|
238
|
+
/** @type {Record<string, ((userId: string, password: string?, deadline?: Date?) => MaybePromise<boolean>)[]>} */
|
|
239
|
+
const setVerifiers = Object.create(null);
|
|
240
|
+
const finders = userMangers.map((v) => v.find).filter((v) => typeof v === "function");
|
|
241
|
+
const loaders = userMangers.map((v) => v.load).filter((v) => typeof v === "function");
|
|
242
|
+
const existTesters = userMangers.map((v) => v.exist).filter((v) => typeof v === "function");
|
|
243
|
+
const getPermissions = userMangers.map((v) => v.getPermissions).filter((v) => typeof v === "function");
|
|
244
|
+
const getOrganizationPermissions = userMangers.map((v) => v.getOrganizationPermissions).filter((v) => typeof v === "function");
|
|
245
|
+
const getOrganizationAllPermissions = userMangers.map((v) => v.getOrganizationAllPermissions).filter((v) => typeof v === "function");
|
|
246
|
+
for (const { get, set, exit, find, verify, setVerify } of userMangers) {
|
|
247
|
+
if (typeof get === "function") getLoggedUserFn.push([
|
|
248
|
+
get,
|
|
249
|
+
set || null,
|
|
250
|
+
exit || null
|
|
251
|
+
]);
|
|
252
|
+
if (verify) for (const [k, v] of Object.entries(verify)) {
|
|
253
|
+
const list = verifiers[k];
|
|
254
|
+
if (list) list.push(v);
|
|
255
|
+
else verifiers[k] = [v];
|
|
256
|
+
}
|
|
257
|
+
if (setVerify) for (const [k, v] of Object.entries(setVerify)) {
|
|
258
|
+
const list = setVerifiers[k];
|
|
259
|
+
if (list) list.push(v);
|
|
260
|
+
else setVerifiers[k] = [v];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* @param {Tenant} tenant
|
|
265
|
+
* @param {string} user
|
|
266
|
+
* @param {string} sUser
|
|
267
|
+
* @returns {Promise<boolean>}
|
|
268
|
+
*/
|
|
269
|
+
async function switchable(tenant, user, sUser) {
|
|
270
|
+
if (!switchableList.length) return false;
|
|
271
|
+
for (const s of switchableList) {
|
|
272
|
+
if (await s(user, sUser)) continue;
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
defaultLogin,
|
|
279
|
+
switchable,
|
|
280
|
+
suGets,
|
|
281
|
+
getLoggedUserFn,
|
|
282
|
+
finders,
|
|
283
|
+
verifiers,
|
|
284
|
+
setVerifiers,
|
|
285
|
+
loaders,
|
|
286
|
+
existTesters,
|
|
287
|
+
getPermissions,
|
|
288
|
+
getOrganizationPermissions,
|
|
289
|
+
getOrganizationAllPermissions
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
let userPluginLoaders = getUserLoaders();
|
|
293
|
+
let { defaultLogin, switchable, suGets, getLoggedUserFn, finders, verifiers, setVerifiers, loaders, existTesters, getPermissions, getOrganizationPermissions, getOrganizationAllPermissions } = getUserManagers();
|
|
294
|
+
hooks.listen(() => {
|
|
295
|
+
userPluginLoaders = getUserLoaders();
|
|
296
|
+
({defaultLogin, switchable, suGets, getLoggedUserFn, finders, verifiers, setVerifiers, loaders, existTesters, getPermissions, getOrganizationPermissions, getOrganizationAllPermissions} = getUserManagers());
|
|
297
|
+
}, -1e6);
|
|
298
|
+
/**
|
|
299
|
+
*
|
|
300
|
+
* @param {string} userId
|
|
301
|
+
* @param {boolean} [login]
|
|
302
|
+
* @returns {Promise<boolean>}
|
|
303
|
+
*/
|
|
304
|
+
async function existUser(userId, login) {
|
|
305
|
+
for (const fn of existTesters) if (await fn(userId, login)) return true;
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
*
|
|
310
|
+
* @returns {Promise<State>}
|
|
311
|
+
*/
|
|
312
|
+
async function getLoggedUser() {
|
|
313
|
+
const tenant = await useTenant();
|
|
314
|
+
if (tenant.single) {
|
|
315
|
+
for (const suGet of suGets) {
|
|
316
|
+
const suUser = await suGet();
|
|
317
|
+
if (!suUser) continue;
|
|
318
|
+
if (!await existUser(suUser)) continue;
|
|
319
|
+
return [
|
|
320
|
+
null,
|
|
321
|
+
null,
|
|
322
|
+
null,
|
|
323
|
+
null,
|
|
324
|
+
suUser
|
|
325
|
+
];
|
|
326
|
+
}
|
|
327
|
+
return [
|
|
328
|
+
null,
|
|
329
|
+
null,
|
|
330
|
+
null,
|
|
331
|
+
null,
|
|
332
|
+
null
|
|
333
|
+
];
|
|
334
|
+
}
|
|
335
|
+
/** @type {UserManager['set']?} */
|
|
336
|
+
let setUser = null;
|
|
337
|
+
/** @type {UserManager['exit']?} */
|
|
338
|
+
let exitUser = null;
|
|
339
|
+
/** @type {string?} */
|
|
340
|
+
let user = null;
|
|
341
|
+
for (const [get, set, exit] of getLoggedUserFn) {
|
|
342
|
+
const u = await get();
|
|
343
|
+
if (!u || !await existUser(u)) continue;
|
|
344
|
+
user = u;
|
|
345
|
+
setUser = set;
|
|
346
|
+
exitUser = exit;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
if (!user) return [
|
|
350
|
+
null,
|
|
351
|
+
defaultLogin,
|
|
352
|
+
null,
|
|
353
|
+
null,
|
|
354
|
+
null
|
|
355
|
+
];
|
|
356
|
+
for (const suGet of suGets) {
|
|
357
|
+
const suUser = await suGet();
|
|
358
|
+
if (!suUser) continue;
|
|
359
|
+
if (!await existUser(suUser)) continue;
|
|
360
|
+
if (await switchable(tenant, user, suUser)) continue;
|
|
361
|
+
return [
|
|
362
|
+
null,
|
|
363
|
+
setUser,
|
|
364
|
+
exitUser,
|
|
365
|
+
user,
|
|
366
|
+
suUser
|
|
367
|
+
];
|
|
368
|
+
}
|
|
369
|
+
return [
|
|
370
|
+
null,
|
|
371
|
+
setUser,
|
|
372
|
+
exitUser,
|
|
373
|
+
user,
|
|
374
|
+
null
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
async function get() {
|
|
378
|
+
let state = userGetter();
|
|
379
|
+
if (!state) {
|
|
380
|
+
state = getLoggedUser();
|
|
381
|
+
userSetter(state);
|
|
382
|
+
}
|
|
383
|
+
return state;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* @param {boolean} [logged]
|
|
387
|
+
* @returns {Promise<boolean>}
|
|
388
|
+
*/
|
|
389
|
+
function isSingleUser(logged) {
|
|
390
|
+
if (logged) return useTenant().then((g) => g.single);
|
|
391
|
+
return get().then(([, , , su]) => su ? false : useTenant().then((g) => g.single));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
*
|
|
395
|
+
* @param {boolean?} [logged] 是否为登陆用户
|
|
396
|
+
* @returns {Promise<string?>}
|
|
397
|
+
*/
|
|
398
|
+
function getUser(logged) {
|
|
399
|
+
return get().then(([, , , current, su]) => {
|
|
400
|
+
if (logged === false) return su;
|
|
401
|
+
if (logged) return current;
|
|
402
|
+
return su || current;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
*
|
|
407
|
+
* @param {string?} userId
|
|
408
|
+
* @param {boolean?} [sudo]
|
|
409
|
+
* @returns {Promise<string?>}
|
|
410
|
+
*/
|
|
411
|
+
function setUser(userId, sudo) {
|
|
412
|
+
let state = Promise.resolve(userGetter() || getLoggedUser());
|
|
413
|
+
if (sudo) state = state.then(async ([, set, exit, current]) => {
|
|
414
|
+
if (!userId || !await existUser(userId)) return [
|
|
415
|
+
null,
|
|
416
|
+
set,
|
|
417
|
+
exit,
|
|
418
|
+
current,
|
|
419
|
+
null
|
|
420
|
+
];
|
|
421
|
+
if (current || await useTenant().then((t) => t.single)) return [
|
|
422
|
+
userId,
|
|
423
|
+
set,
|
|
424
|
+
exit,
|
|
425
|
+
current,
|
|
426
|
+
userId
|
|
427
|
+
];
|
|
428
|
+
return [
|
|
429
|
+
null,
|
|
430
|
+
set,
|
|
431
|
+
exit,
|
|
432
|
+
null,
|
|
433
|
+
null
|
|
434
|
+
];
|
|
435
|
+
});
|
|
436
|
+
else state = state.then(async ([, set, exit, current, su]) => {
|
|
437
|
+
if (!userId) {
|
|
438
|
+
if (current && exit) await exit(current);
|
|
439
|
+
return [
|
|
440
|
+
null,
|
|
441
|
+
set,
|
|
442
|
+
exit,
|
|
443
|
+
null,
|
|
444
|
+
null
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
if (!set) return [
|
|
448
|
+
current,
|
|
449
|
+
set,
|
|
450
|
+
exit,
|
|
451
|
+
current,
|
|
452
|
+
su
|
|
453
|
+
];
|
|
454
|
+
if ((await useTenant()).single) return [
|
|
455
|
+
null,
|
|
456
|
+
set,
|
|
457
|
+
exit,
|
|
458
|
+
null,
|
|
459
|
+
su
|
|
460
|
+
];
|
|
461
|
+
if (!await set(userId)) return [
|
|
462
|
+
null,
|
|
463
|
+
set,
|
|
464
|
+
exit,
|
|
465
|
+
current,
|
|
466
|
+
su
|
|
467
|
+
];
|
|
468
|
+
return [
|
|
469
|
+
userId,
|
|
470
|
+
set,
|
|
471
|
+
exit,
|
|
472
|
+
userId,
|
|
473
|
+
null
|
|
474
|
+
];
|
|
475
|
+
});
|
|
476
|
+
userSetter(state);
|
|
477
|
+
return state.then((v) => v[0]);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
*
|
|
481
|
+
* @param {string | Record<string, unknown>} loginName
|
|
482
|
+
* @param {string | string[]} [loginType]
|
|
483
|
+
* @returns {Promise<string>}
|
|
484
|
+
*/
|
|
485
|
+
async function findUser(loginName, loginType) {
|
|
486
|
+
const types = new Set([loginType || ""].flat().filter(Boolean));
|
|
487
|
+
for (const fn of finders) {
|
|
488
|
+
const userId = await fn(loginName, types);
|
|
489
|
+
if (userId) return userId;
|
|
490
|
+
}
|
|
491
|
+
return "";
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
*
|
|
495
|
+
* @param {string} userId
|
|
496
|
+
* @param {string} password
|
|
497
|
+
* @param {string} type
|
|
498
|
+
* @param {boolean} [returnError]
|
|
499
|
+
* @returns {Promise<number>}
|
|
500
|
+
*/
|
|
501
|
+
async function verifyUser(userId, password, type, returnError) {
|
|
502
|
+
for (const fn of verifiers[type] || []) {
|
|
503
|
+
const res = await fn(userId, password);
|
|
504
|
+
if (res < 0) return returnError ? res : 0;
|
|
505
|
+
if (res > 0) return res;
|
|
506
|
+
}
|
|
507
|
+
return 0;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
*
|
|
511
|
+
* @param {string} userId
|
|
512
|
+
* @param {string} type
|
|
513
|
+
* @param {string?} password
|
|
514
|
+
* @param {Date?} [deadline]
|
|
515
|
+
* @returns {Promise<boolean>}
|
|
516
|
+
*/
|
|
517
|
+
async function setUserVerify(userId, type, password, deadline) {
|
|
518
|
+
for (const fn of setVerifiers[type] || []) if (await fn(userId, password, deadline)) return true;
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
/** @type {(user?: string?) => Promise<Set<string>>} */
|
|
522
|
+
const loadUserPermissions = createKeyStore(async (user) => {
|
|
523
|
+
if (!user) return /* @__PURE__ */ new Set();
|
|
524
|
+
for (const f of getPermissions) {
|
|
525
|
+
const r = await f(user);
|
|
526
|
+
if (r) return new Set(r);
|
|
527
|
+
}
|
|
528
|
+
return /* @__PURE__ */ new Set();
|
|
529
|
+
}, (user) => user || "");
|
|
530
|
+
/**
|
|
531
|
+
*
|
|
532
|
+
* @param {boolean?} [logged] 是否为登陆用户
|
|
533
|
+
* @returns {Promise<Set<string>>}
|
|
534
|
+
*/
|
|
535
|
+
async function getUserPermissions(logged) {
|
|
536
|
+
return loadUserPermissions(await getUser(logged));
|
|
537
|
+
}
|
|
538
|
+
/** @type {(user?: string?) => Promise<Set<string>>} */
|
|
539
|
+
const loadUserOrganizationAllPermissions = createKeyStore(async (user) => {
|
|
540
|
+
if (!user) return /* @__PURE__ */ new Set();
|
|
541
|
+
for (const f of getOrganizationAllPermissions) {
|
|
542
|
+
const r = await f(user);
|
|
543
|
+
if (r) return new Set(r);
|
|
544
|
+
}
|
|
545
|
+
return /* @__PURE__ */ new Set();
|
|
546
|
+
}, (user) => user || "");
|
|
547
|
+
/**
|
|
548
|
+
*
|
|
549
|
+
* @param {boolean?} [logged] 是否为登陆用户
|
|
550
|
+
* @returns {Promise<Set<string>>}
|
|
551
|
+
*/
|
|
552
|
+
async function getUserOrganizationAllPermissions(logged) {
|
|
553
|
+
return loadUserOrganizationAllPermissions(await getUser(logged));
|
|
554
|
+
}
|
|
555
|
+
/** @type {(user?: string?) => Promise<Record<string, Set<string>>>} */
|
|
556
|
+
const loadUserOrganizationPermissions = createKeyStore(async (user) => {
|
|
557
|
+
if (!user) return Object.create(null);
|
|
558
|
+
for (const f of getOrganizationPermissions) {
|
|
559
|
+
const r = await f(user);
|
|
560
|
+
if (r) return Object.fromEntries(Object.entries(r).map(([k, v]) => [k, new Set(v)]));
|
|
561
|
+
}
|
|
562
|
+
return Object.create(null);
|
|
563
|
+
}, (user) => user || "");
|
|
564
|
+
/**
|
|
565
|
+
*
|
|
566
|
+
* @param {boolean?} [logged] 是否为登陆用户
|
|
567
|
+
* @returns {Promise<Record<string, Set<string>>>}
|
|
568
|
+
*/
|
|
569
|
+
async function getUserOrganizationPermissions(logged) {
|
|
570
|
+
return loadUserOrganizationPermissions(await getUser(logged));
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
*
|
|
574
|
+
* @param {string} userId
|
|
575
|
+
* @returns {Promise<Record<string, any>>}
|
|
576
|
+
*/
|
|
577
|
+
async function loadPluginUser(userId) {
|
|
578
|
+
const list2 = await Promise.all(userPluginLoaders.map(([p, f]) => Promise.all([p, f(userId)])));
|
|
579
|
+
return Object.fromEntries(Object.entries(Object.groupBy(list2, ([k]) => k)).map(([k, l]) => {
|
|
580
|
+
return [k, Object.assign({}, ...l?.map((v) => v[1]) || [])];
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
*
|
|
585
|
+
* @param {string} userId
|
|
586
|
+
* @returns {Promise<Record<string, any>>}
|
|
587
|
+
*/
|
|
588
|
+
async function loadUser(userId, plugin = false) {
|
|
589
|
+
const [list, plugins, permissions] = await Promise.all([
|
|
590
|
+
Promise.all(loaders.map((f) => f(userId))),
|
|
591
|
+
plugin ? loadPluginUser(userId) : {},
|
|
592
|
+
loadUserPermissions(userId).then((p) => [...p])
|
|
593
|
+
]);
|
|
594
|
+
return Object.assign({}, ...list, {
|
|
595
|
+
id: userId,
|
|
596
|
+
plugins,
|
|
597
|
+
permissions
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region packages/core/permission/createPermissionMatches.mjs
|
|
603
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
604
|
+
/**
|
|
605
|
+
*
|
|
606
|
+
* @param {Exclude<Permission['constraints'], void>?} [constraints]
|
|
607
|
+
* @param {string?} [userId]
|
|
608
|
+
* @returns {Iterable<[string, string, any]>}
|
|
609
|
+
*/
|
|
610
|
+
function* yieldConstraintWheres(constraints, userId) {
|
|
611
|
+
if (!constraints) return;
|
|
612
|
+
for (const [field, constraint] of Object.entries(constraints)) {
|
|
613
|
+
if (!constraint) continue;
|
|
614
|
+
const { value, values, user } = constraint;
|
|
615
|
+
if (value !== void 0) yield [
|
|
616
|
+
field,
|
|
617
|
+
"=",
|
|
618
|
+
value
|
|
619
|
+
];
|
|
620
|
+
else if (Array.isArray(values) && values.length) yield [
|
|
621
|
+
field,
|
|
622
|
+
"in",
|
|
623
|
+
values
|
|
624
|
+
];
|
|
625
|
+
else if (values instanceof Set && values.size) yield [
|
|
626
|
+
field,
|
|
627
|
+
"in",
|
|
628
|
+
[...values]
|
|
629
|
+
];
|
|
630
|
+
else if (user && userId) yield [
|
|
631
|
+
field,
|
|
632
|
+
"=",
|
|
633
|
+
userId
|
|
634
|
+
];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* @param {Permission[]} permissions
|
|
639
|
+
*/
|
|
640
|
+
async function createPermissionMatches(permissions) {
|
|
641
|
+
let permissionInGlobal = permissions.filter((v) => !v.organizationField);
|
|
642
|
+
const orWhere = new Where();
|
|
643
|
+
if (permissionInGlobal.length) {
|
|
644
|
+
const permissions = await getUserPermissions();
|
|
645
|
+
permissionInGlobal = permissionInGlobal.filter((v) => !v.permission || permissions.has(v.permission));
|
|
646
|
+
}
|
|
647
|
+
const userId = await getUser();
|
|
648
|
+
for (const { constraints } of permissionInGlobal) {
|
|
649
|
+
const wheres = [...yieldConstraintWheres(constraints, userId)];
|
|
650
|
+
if (!wheres.length) return null;
|
|
651
|
+
const where = new Where();
|
|
652
|
+
for (const v of wheres) where.and(...v);
|
|
653
|
+
orWhere.or(where);
|
|
654
|
+
}
|
|
655
|
+
let permissionInOrganization = permissions.filter((v) => v.organizationField);
|
|
656
|
+
if (permissionInOrganization.length) {
|
|
657
|
+
const organizationPermissions = await getUserOrganizationAllPermissions();
|
|
658
|
+
permissionInOrganization = permissionInOrganization.filter((v) => !v.permission || organizationPermissions.has(v.permission));
|
|
659
|
+
}
|
|
660
|
+
if (!permissionInOrganization.length) return orWhere;
|
|
661
|
+
if (permissionInOrganization.length) {
|
|
662
|
+
const organizationPermissions = Object.entries(await getUserOrganizationPermissions());
|
|
663
|
+
for (const { constraints, permission, organizationField } of permissionInOrganization) {
|
|
664
|
+
const organizations = permission ? organizationPermissions.filter((v) => v[1].has(permission)).map((v) => v[0]) : organizationPermissions.map((v) => v[0]);
|
|
665
|
+
if (!organizations.length) continue;
|
|
666
|
+
const where = Where.and(organizationField || "", "in", organizations);
|
|
667
|
+
for (const v of yieldConstraintWheres(constraints, userId)) where.and(...v);
|
|
668
|
+
orWhere.or(where);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return orWhere;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region packages/core/permission/testPermissionConstraints.mjs
|
|
676
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
677
|
+
/**
|
|
678
|
+
*
|
|
679
|
+
* @param {Exclude<Permission['constraints'], void>} constraints
|
|
680
|
+
* @param {Record<string, any>} document
|
|
681
|
+
* @param {string?} userId
|
|
682
|
+
*/
|
|
683
|
+
function testPermissionConstraints(constraints, document, userId) {
|
|
684
|
+
for (const [field, constraint] of Object.entries(constraints)) {
|
|
685
|
+
if (!constraint) continue;
|
|
686
|
+
const { value, values, user } = constraint;
|
|
687
|
+
const val = document[field];
|
|
688
|
+
if (value !== void 0) {
|
|
689
|
+
if (val !== value) return false;
|
|
690
|
+
} else if (Array.isArray(values) && values.length) {
|
|
691
|
+
if (!values.includes(val)) return false;
|
|
692
|
+
} else if (values instanceof Set && values.size) {
|
|
693
|
+
if (!values.has(val)) return false;
|
|
694
|
+
} else if (user) {
|
|
695
|
+
if (val !== userId) return false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return true;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region packages/core/permission/filterPermissions.mjs
|
|
703
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
704
|
+
/**
|
|
705
|
+
*
|
|
706
|
+
* @param {Permission[]} allPermissions
|
|
707
|
+
* @param {Record<string, any>} document
|
|
708
|
+
* @returns
|
|
709
|
+
*/
|
|
710
|
+
async function filterPermissions(allPermissions, document) {
|
|
711
|
+
const permissionInGlobal = allPermissions.filter((v) => !v.organizationField);
|
|
712
|
+
const permissionInOrganization = allPermissions.filter((v) => v.organizationField);
|
|
713
|
+
/** @type {Permission[]} */
|
|
714
|
+
const filteredPermissions = [];
|
|
715
|
+
const userId = await getUser();
|
|
716
|
+
if (permissionInGlobal.length) {
|
|
717
|
+
const permissions = await getUserPermissions();
|
|
718
|
+
for (const p of permissionInGlobal) {
|
|
719
|
+
const { permission, constraints } = p;
|
|
720
|
+
if (permission && !permissions.has(permission)) continue;
|
|
721
|
+
if (constraints && !testPermissionConstraints(constraints, document, userId)) continue;
|
|
722
|
+
filteredPermissions.push(p);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (permissionInOrganization.length) {
|
|
726
|
+
const permissions = await getUserOrganizationPermissions();
|
|
727
|
+
for (const p of permissionInOrganization) {
|
|
728
|
+
const { permission, organizationField, constraints } = p;
|
|
729
|
+
if (constraints && !testPermissionConstraints(constraints, document, userId)) continue;
|
|
730
|
+
const organizationId = document[organizationField || ""];
|
|
731
|
+
if (!organizationId) continue;
|
|
732
|
+
const organizationPermissions = permissions[organizationId];
|
|
733
|
+
if (!organizationPermissions) continue;
|
|
734
|
+
if (permission && !organizationPermissions.has(permission)) continue;
|
|
735
|
+
filteredPermissions.push(p);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return filteredPermissions;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region packages/core/permission/filterPermissionDocument.mjs
|
|
743
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
744
|
+
/** @import { ModelTable, FieldDefine } from '@yongdall/model' */
|
|
745
|
+
/**
|
|
746
|
+
* @param {ModelTable} model
|
|
747
|
+
* @param {Permission[]} permissions
|
|
748
|
+
* @param {Record<string, any>} document
|
|
749
|
+
*/
|
|
750
|
+
async function filterPermissionDocument({ fields }, permissions, document) {
|
|
751
|
+
const userPermissions = await filterPermissions(permissions, document);
|
|
752
|
+
if (userPermissions && !userPermissions.length) return null;
|
|
753
|
+
let groups = new Set(userPermissions.flatMap((v) => v.group || ""));
|
|
754
|
+
let fieldsAuth = new Set(userPermissions.flatMap((v) => v.fields || ""));
|
|
755
|
+
groups.delete("");
|
|
756
|
+
fieldsAuth.delete("");
|
|
757
|
+
if (!(groups.size + fieldsAuth.size)) return null;
|
|
758
|
+
/**
|
|
759
|
+
*
|
|
760
|
+
* @param {[string, FieldDefine & {group?: string}][]} fields
|
|
761
|
+
* @param {Record<string, any>} document
|
|
762
|
+
* @param {string[]} parent
|
|
763
|
+
* @returns
|
|
764
|
+
*/
|
|
765
|
+
function filter(fields, document, parent) {
|
|
766
|
+
/** @type {Record<string, any>} */
|
|
767
|
+
const value = {};
|
|
768
|
+
for (const [name, field] of fields) {
|
|
769
|
+
if (!field) continue;
|
|
770
|
+
const fieldNames = [...parent, name];
|
|
771
|
+
const key = fieldNames.join(".");
|
|
772
|
+
if (!field.primary && !groups.has(field.group || "") && !fieldsAuth.has(key) && !fieldsAuth.has("*")) continue;
|
|
773
|
+
const { type } = field;
|
|
774
|
+
if (!type || typeof type !== "object" || !type.table) {
|
|
775
|
+
value[name] = document[name];
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const subDocument = document[name];
|
|
779
|
+
const list = Object.entries(type.fields);
|
|
780
|
+
if (field.array) value[name] = (Array.isArray(subDocument) ? subDocument : subDocument ? [subDocument] : []).map((doc) => filter(list, doc, key));
|
|
781
|
+
else if (subDocument && !Array.isArray(subDocument)) {
|
|
782
|
+
const val = filter(list, subDocument, fieldNames);
|
|
783
|
+
if (!Object.keys(val)) continue;
|
|
784
|
+
value[name] = val;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return value;
|
|
788
|
+
}
|
|
789
|
+
return filter(Object.entries(fields), document, []);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region packages/core/permission/filterPermissionUpdate.mjs
|
|
794
|
+
/** @import { FieldDefine, ModelTable } from '@yongdall/model' */
|
|
795
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
796
|
+
/**
|
|
797
|
+
* @param {ModelTable} model
|
|
798
|
+
* @param {Permission[]} permissions
|
|
799
|
+
* @param {Record<string, any>} document
|
|
800
|
+
* @param {Record<string, any>} newDocument
|
|
801
|
+
*/
|
|
802
|
+
async function filterPermissionUpdate({ fields }, permissions, document, newDocument) {
|
|
803
|
+
let newData = newDocument;
|
|
804
|
+
const userPermissions = await filterPermissions(permissions, {
|
|
805
|
+
...document,
|
|
806
|
+
...newData
|
|
807
|
+
});
|
|
808
|
+
if (userPermissions && !userPermissions.length) return null;
|
|
809
|
+
let groups = new Set(userPermissions.flatMap((v) => v.group || ""));
|
|
810
|
+
let fieldsAuth = new Set(userPermissions.flatMap((v) => v.fields || ""));
|
|
811
|
+
groups.delete("");
|
|
812
|
+
fieldsAuth.delete("");
|
|
813
|
+
while (groups.size + fieldsAuth.size) {
|
|
814
|
+
const oldData = newData;
|
|
815
|
+
newData = {};
|
|
816
|
+
for (const [key, value] of Object.entries(oldData)) {
|
|
817
|
+
/** @type {FieldDefine & {group?: string}?} */
|
|
818
|
+
const field = fields[key];
|
|
819
|
+
if (!field) continue;
|
|
820
|
+
if (!groups.has(field.group || "") && !fieldsAuth.has(key)) continue;
|
|
821
|
+
newData[key] = value;
|
|
822
|
+
}
|
|
823
|
+
const newPermissionGroups = await filterPermissions(permissions, {
|
|
824
|
+
...document,
|
|
825
|
+
...newData
|
|
826
|
+
});
|
|
827
|
+
if (newPermissionGroups && !newPermissionGroups.length) return null;
|
|
828
|
+
const newGroups = new Set(newPermissionGroups.flatMap((v) => v.group || ""));
|
|
829
|
+
const newFieldsAuth = new Set(newPermissionGroups.flatMap((v) => v.fields || ""));
|
|
830
|
+
newGroups.delete("");
|
|
831
|
+
newFieldsAuth.delete("");
|
|
832
|
+
if (newGroups.size === groups.size && newFieldsAuth.size === fieldsAuth.size) break;
|
|
833
|
+
groups = newGroups;
|
|
834
|
+
fieldsAuth = newFieldsAuth;
|
|
835
|
+
}
|
|
836
|
+
if (!(groups.size + fieldsAuth.size)) return null;
|
|
837
|
+
/**
|
|
838
|
+
*
|
|
839
|
+
* @param {[string, FieldDefine & {group?: string}][]} fields
|
|
840
|
+
* @param {Record<string, any>} document
|
|
841
|
+
* @param {string[]} parent
|
|
842
|
+
* @returns
|
|
843
|
+
*/
|
|
844
|
+
function filter(fields, document, parent) {
|
|
845
|
+
/** @type {Record<string, any>} */
|
|
846
|
+
const value = {};
|
|
847
|
+
for (const [name, field] of fields) {
|
|
848
|
+
if (!field) continue;
|
|
849
|
+
const fieldNames = [...parent, name];
|
|
850
|
+
const key = fieldNames.join(".");
|
|
851
|
+
if ((parent ? !field.primary : true) && !groups.has(field.group || "") && !fieldsAuth.has(key) && !fieldsAuth.has("*")) continue;
|
|
852
|
+
if (!Object.hasOwn(document, name)) continue;
|
|
853
|
+
const { type } = field;
|
|
854
|
+
if (!type || typeof type !== "object" || !type.table) {
|
|
855
|
+
value[name] = document[name];
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
const subDocument = document[name];
|
|
859
|
+
const list = Object.entries(type.fields);
|
|
860
|
+
if (field.array) value[name] = (Array.isArray(subDocument) ? subDocument : [subDocument]).map((doc) => filter(list, doc, fieldNames));
|
|
861
|
+
else if (subDocument && !Array.isArray(subDocument)) {
|
|
862
|
+
const val = filter(list, subDocument, fieldNames);
|
|
863
|
+
if (!Object.keys(val)) continue;
|
|
864
|
+
value[name] = val;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return value;
|
|
868
|
+
}
|
|
869
|
+
return filter(Object.entries(fields), newDocument, []);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region packages/core/permission/testPermissions.mjs
|
|
874
|
+
/** @import { Permission } from '@yongdall/types' */
|
|
875
|
+
/**
|
|
876
|
+
*
|
|
877
|
+
* @param {Permission[]?} [allPermissions]
|
|
878
|
+
* @param {Record<string, any>} [document]
|
|
879
|
+
* @returns
|
|
880
|
+
*/
|
|
881
|
+
async function testPermissions(allPermissions, document) {
|
|
882
|
+
if (await isSingleUser()) return true;
|
|
883
|
+
if (!allPermissions?.length) return false;
|
|
884
|
+
const permissionInGlobal = allPermissions.filter((v) => !v.organizationField);
|
|
885
|
+
const permissionInOrganization = allPermissions.filter((v) => v.organizationField);
|
|
886
|
+
if (!document) {
|
|
887
|
+
if (permissionInGlobal.length) {
|
|
888
|
+
const permissions = await getUserPermissions();
|
|
889
|
+
for (const { permission } of permissionInGlobal) {
|
|
890
|
+
if (permission && !permissions.has(permission)) continue;
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (permissionInOrganization.length) {
|
|
895
|
+
const permissions = await getUserOrganizationAllPermissions();
|
|
896
|
+
for (const { permission } of permissionInOrganization) {
|
|
897
|
+
if (permission && !permissions.has(permission)) continue;
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
const userId = await getUser();
|
|
904
|
+
if (permissionInGlobal.length) {
|
|
905
|
+
const permissions = await getUserPermissions();
|
|
906
|
+
for (const { permission, constraints } of permissionInGlobal) {
|
|
907
|
+
if (permission && !permissions.has(permission)) continue;
|
|
908
|
+
if (constraints && !testPermissionConstraints(constraints, document, userId)) continue;
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (permissionInOrganization.length) {
|
|
913
|
+
const permissions = await getUserOrganizationPermissions();
|
|
914
|
+
for (const { permission, organizationField, constraints } of permissionInOrganization) {
|
|
915
|
+
if (constraints && !testPermissionConstraints(constraints, document, userId)) continue;
|
|
916
|
+
const organizationId = document[organizationField || ""];
|
|
917
|
+
if (!organizationId) continue;
|
|
918
|
+
const organizationPermissions = permissions[organizationId];
|
|
919
|
+
if (!organizationPermissions) continue;
|
|
920
|
+
if (permission && !organizationPermissions.has(permission)) continue;
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region packages/core/decorators/modelHook.mjs
|
|
929
|
+
/** @import { Hooks, ClassDecorator } from '@yongdall/model' */
|
|
930
|
+
/** @returns {{[K in keyof Hooks]: Record<string, Hooks[K][]>}} */
|
|
931
|
+
function loadHooks() {
|
|
932
|
+
/** @returns {{[K in keyof Hooks]: Record<string, Hooks[K][]>}} */
|
|
933
|
+
const modelHooks = Object.create(null);
|
|
934
|
+
for (const [, model, v] of hooks.get("modelHook").entriesObject()) for (const [name, fn] of Object.entries(v)) {
|
|
935
|
+
if (typeof fn !== "function") continue;
|
|
936
|
+
const list = modelHooks[name] ||= Object.create(null);
|
|
937
|
+
(list[model] ||= []).push(fn);
|
|
938
|
+
}
|
|
939
|
+
return modelHooks;
|
|
940
|
+
}
|
|
941
|
+
const modelHooks = loadHooks();
|
|
942
|
+
/**
|
|
943
|
+
*
|
|
944
|
+
* @param {Partial<Hooks>} hooks
|
|
945
|
+
* @param {...string | string[]} hookNames
|
|
946
|
+
* @returns {Hooks}
|
|
947
|
+
*/
|
|
948
|
+
function bindModelHooks({ where, beforeCreateMany, afterCreateMany, beforeCreate, afterCreate, beforeUpdate, afterUpdate, beforeDelete, afterDelete }, ...hookNames) {
|
|
949
|
+
/** @type {string[]} */
|
|
950
|
+
const names = hookNames.flat(21);
|
|
951
|
+
return {
|
|
952
|
+
where: where || (() => {}),
|
|
953
|
+
async beforeCreateMany(conn, model, record) {
|
|
954
|
+
let data = record;
|
|
955
|
+
for (const name of names) for (const hook of modelHooks.beforeCreateMany[name] || []) {
|
|
956
|
+
if (typeof hook !== "function") continue;
|
|
957
|
+
data = await hook(conn, model, data) || data;
|
|
958
|
+
}
|
|
959
|
+
if (typeof beforeCreateMany === "function") data = await beforeCreateMany(conn, model, data) || data;
|
|
960
|
+
return data;
|
|
961
|
+
},
|
|
962
|
+
async afterCreateMany(conn, model, record) {
|
|
963
|
+
if (typeof afterCreateMany === "function") await afterCreateMany(conn, model, record);
|
|
964
|
+
for (const name of names) for (const hook of modelHooks.afterCreateMany[name] || []) {
|
|
965
|
+
if (typeof hook !== "function") continue;
|
|
966
|
+
await hook(conn, model, record);
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
async beforeCreate(conn, model, record) {
|
|
970
|
+
let data = record;
|
|
971
|
+
for (const name of names) for (const hook of modelHooks.beforeCreate[name] || []) {
|
|
972
|
+
if (typeof hook !== "function") continue;
|
|
973
|
+
data = await hook(conn, model, data) || data;
|
|
974
|
+
}
|
|
975
|
+
if (typeof beforeCreate === "function") data = await beforeCreate(conn, model, data) || data;
|
|
976
|
+
return data;
|
|
977
|
+
},
|
|
978
|
+
async afterCreate(conn, model, record) {
|
|
979
|
+
if (typeof afterCreate === "function") await afterCreate(conn, model, record);
|
|
980
|
+
for (const name of names) for (const hook of modelHooks.afterCreate[name] || []) {
|
|
981
|
+
if (typeof hook !== "function") continue;
|
|
982
|
+
await hook(conn, model, record);
|
|
983
|
+
}
|
|
984
|
+
},
|
|
985
|
+
async beforeUpdate(conn, model, record, set) {
|
|
986
|
+
let data = set;
|
|
987
|
+
for (const name of names) for (const hook of modelHooks.beforeUpdate[name] || []) {
|
|
988
|
+
if (typeof hook !== "function") continue;
|
|
989
|
+
data = await hook(conn, model, record, data) || data;
|
|
990
|
+
}
|
|
991
|
+
if (typeof beforeUpdate === "function") data = await beforeUpdate(conn, model, record, data) || data;
|
|
992
|
+
return data;
|
|
993
|
+
},
|
|
994
|
+
async afterUpdate(conn, model, record, oldRecord) {
|
|
995
|
+
if (typeof afterUpdate === "function") await afterUpdate(conn, model, record, oldRecord);
|
|
996
|
+
for (const name of names) for (const hook of modelHooks.afterUpdate[name] || []) {
|
|
997
|
+
if (typeof hook !== "function") continue;
|
|
998
|
+
await hook(conn, model, record, oldRecord);
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
async beforeDelete(conn, model, record, update) {
|
|
1002
|
+
for (const name of names) for (const hook of modelHooks.beforeDelete[name] || []) {
|
|
1003
|
+
if (typeof hook !== "function") continue;
|
|
1004
|
+
await hook(conn, model, record, update);
|
|
1005
|
+
}
|
|
1006
|
+
if (typeof beforeDelete === "function") await beforeDelete(conn, model, record, update);
|
|
1007
|
+
},
|
|
1008
|
+
async afterDelete(conn, model, record, update) {
|
|
1009
|
+
if (typeof afterDelete === "function") await afterDelete(conn, model, record, update);
|
|
1010
|
+
for (const name of names) for (const hook of modelHooks.afterDelete[name] || []) {
|
|
1011
|
+
if (typeof hook !== "function") continue;
|
|
1012
|
+
await hook(conn, model, record, update);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
*
|
|
1019
|
+
* @param {...string | string[]} hookNames
|
|
1020
|
+
* @returns {ClassDecorator}
|
|
1021
|
+
*/
|
|
1022
|
+
function modelHook(...hookNames) {
|
|
1023
|
+
/** @type {string[]} */
|
|
1024
|
+
const names = hookNames.flat(21);
|
|
1025
|
+
return (Model, ctx) => {
|
|
1026
|
+
if (!names.length) return;
|
|
1027
|
+
beforeCreateMany(Model, async (conn, model, record) => {
|
|
1028
|
+
let data = record;
|
|
1029
|
+
for (const name of names) for (const hook of modelHooks.beforeCreateMany[name] || []) {
|
|
1030
|
+
if (typeof hook !== "function") continue;
|
|
1031
|
+
data = await hook(conn, model, data) || data;
|
|
1032
|
+
}
|
|
1033
|
+
return data;
|
|
1034
|
+
});
|
|
1035
|
+
afterCreateMany(Model, async (conn, model, record) => {
|
|
1036
|
+
for (const name of names) for (const hook of modelHooks.afterCreateMany[name] || []) {
|
|
1037
|
+
if (typeof hook !== "function") continue;
|
|
1038
|
+
await hook(conn, model, record);
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
beforeCreate(Model, async (conn, model, record) => {
|
|
1042
|
+
let data = record;
|
|
1043
|
+
for (const name of names) for (const hook of modelHooks.beforeCreate[name] || []) {
|
|
1044
|
+
if (typeof hook !== "function") continue;
|
|
1045
|
+
data = await hook(conn, model, data) || data;
|
|
1046
|
+
}
|
|
1047
|
+
return data;
|
|
1048
|
+
});
|
|
1049
|
+
afterCreate(Model, async (conn, model, record) => {
|
|
1050
|
+
for (const name of names) for (const hook of modelHooks.afterCreate[name] || []) {
|
|
1051
|
+
if (typeof hook !== "function") continue;
|
|
1052
|
+
await hook(conn, model, record);
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
beforeUpdate(Model, async (conn, model, record, set) => {
|
|
1056
|
+
let data = set;
|
|
1057
|
+
for (const name of names) for (const hook of modelHooks.beforeUpdate[name] || []) {
|
|
1058
|
+
if (typeof hook !== "function") continue;
|
|
1059
|
+
data = await hook(conn, model, record, data) || data;
|
|
1060
|
+
}
|
|
1061
|
+
return data;
|
|
1062
|
+
});
|
|
1063
|
+
afterUpdate(Model, async (conn, model, record, oldRecord) => {
|
|
1064
|
+
for (const name of names) for (const hook of modelHooks.afterUpdate[name] || []) {
|
|
1065
|
+
if (typeof hook !== "function") continue;
|
|
1066
|
+
await hook(conn, model, record, oldRecord);
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
beforeDelete(Model, async (conn, model, record, update) => {
|
|
1070
|
+
for (const name of names) for (const hook of modelHooks.beforeDelete[name] || []) {
|
|
1071
|
+
if (typeof hook !== "function") continue;
|
|
1072
|
+
await hook(conn, model, record, update);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
afterDelete(Model, async (conn, model, record, update) => {
|
|
1076
|
+
for (const name of names) for (const hook of modelHooks.afterDelete[name] || []) {
|
|
1077
|
+
if (typeof hook !== "function") continue;
|
|
1078
|
+
await hook(conn, model, record, update);
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region packages/core/ServerError.mjs
|
|
1086
|
+
var ServerError = class extends Error {
|
|
1087
|
+
status = 500;
|
|
1088
|
+
/**
|
|
1089
|
+
*
|
|
1090
|
+
* @param {string} message
|
|
1091
|
+
* @param {number} [status]
|
|
1092
|
+
*/
|
|
1093
|
+
constructor(message, status = 500) {
|
|
1094
|
+
super(message);
|
|
1095
|
+
this.status = status;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
//#endregion
|
|
1100
|
+
//#region packages/core/decorators/nestedSetTree.mjs
|
|
1101
|
+
/**
|
|
1102
|
+
*
|
|
1103
|
+
* @param {Connection} conn
|
|
1104
|
+
* @param {any} Model
|
|
1105
|
+
* @param {number} id
|
|
1106
|
+
* @param {number} lftValue
|
|
1107
|
+
* @param {number} rgtValue
|
|
1108
|
+
* @param {string} idField
|
|
1109
|
+
* @param {string} lftField
|
|
1110
|
+
* @param {string} rgtField
|
|
1111
|
+
*/
|
|
1112
|
+
async function treeValidateLoop(conn, Model, id, lftValue, rgtValue, idField, lftField, rgtField) {
|
|
1113
|
+
if ((await conn.select(new Query(Model).select(idField).where(lftField, "<=", lftValue).where(rgtField, ">=", rgtValue).where({ [idField]: id }).limit(1))).length) throw new ServerError("项目不能被添加到自己的后代");
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
*
|
|
1117
|
+
* @param {string} parentField
|
|
1118
|
+
* @param {string} lftField
|
|
1119
|
+
* @param {string} rgtField
|
|
1120
|
+
* @param {string} idField
|
|
1121
|
+
* @param {...string} groupFields
|
|
1122
|
+
*/
|
|
1123
|
+
function nestedSetTree(parentField, lftField = "lft", rgtField = "rgt", idField = "id", ...groupFields) {
|
|
1124
|
+
/**
|
|
1125
|
+
*
|
|
1126
|
+
* @param {any} Model
|
|
1127
|
+
* @param {ClassDecoratorContext} ctx
|
|
1128
|
+
*/
|
|
1129
|
+
return function(Model, ctx) {
|
|
1130
|
+
beforeCreate(Model, async (conn, record) => {
|
|
1131
|
+
let begin = 1;
|
|
1132
|
+
const parent = record[parentField];
|
|
1133
|
+
const where = new Where();
|
|
1134
|
+
for (const g of groupFields) where.and(g, record[g]);
|
|
1135
|
+
if (parent) {
|
|
1136
|
+
const [{ [rgtField]: rgtValue }] = await conn.select(new Query(Model, true).select(rgtField).where(where).where({ [idField]: parent }).limit(1));
|
|
1137
|
+
begin = rgtValue;
|
|
1138
|
+
await conn.update(new Query(Model).where(where).where(rgtField, ">=", begin), { [rgtField]: increment(2) });
|
|
1139
|
+
await conn.update(new Query(Model).where(where).where(lftField, ">=", begin), { [lftField]: increment(2) });
|
|
1140
|
+
} else {
|
|
1141
|
+
/** @type {{r: number}[]} */
|
|
1142
|
+
const [{ r } = { r: 0 }] = await conn.select(new Query(Model, true).select({ r: rgtField }).where(where).where({ [idField]: parent }).limit(1).sort(rgtField, true));
|
|
1143
|
+
begin = r + 1;
|
|
1144
|
+
}
|
|
1145
|
+
record[lftField] = begin;
|
|
1146
|
+
record[rgtField] = begin + 1;
|
|
1147
|
+
}, true);
|
|
1148
|
+
beforeUpdate(Model, async (conn, oldDocument, updatedData) => {
|
|
1149
|
+
const parentId = parentField in updatedData ? updatedData[parentField] : oldDocument[parentField];
|
|
1150
|
+
if (parentId === oldDocument[parentField]) {
|
|
1151
|
+
updatedData[lftField] = oldDocument[lftField];
|
|
1152
|
+
updatedData[rgtField] = oldDocument[rgtField];
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const where = new Where();
|
|
1156
|
+
for (const g of groupFields) where.and(g, updatedData[g] ?? oldDocument[g]);
|
|
1157
|
+
if (parentId) {
|
|
1158
|
+
const [parent] = await conn.select(new Query(Model).select({
|
|
1159
|
+
lft: lftField,
|
|
1160
|
+
rgt: rgtField
|
|
1161
|
+
}).where(where).where({ [idField]: parentId }));
|
|
1162
|
+
const { lft, rgt } = parent;
|
|
1163
|
+
await treeValidateLoop(conn, Model, oldDocument[idField], lft, rgt, idField, lftField, rgtField);
|
|
1164
|
+
}
|
|
1165
|
+
await conn.update(new Query(Model).where(where).where(lftField, ">=", oldDocument[rgtField]).where(rgtField, "<=", oldDocument[rgtField]), {
|
|
1166
|
+
[rgtField]: multiply(-1),
|
|
1167
|
+
[rgtField]: multiply(-1)
|
|
1168
|
+
});
|
|
1169
|
+
const diff = oldDocument[rgtField] - oldDocument[lftField] + 1;
|
|
1170
|
+
await conn.update(new Query(Model).where(where).where(lftField, ">", oldDocument[rgtField]), { [lftField]: decrement(diff) });
|
|
1171
|
+
await conn.update(new Query(Model).where(where).where(rgtField, ">", oldDocument[rgtField]), { [rgtField]: decrement(diff) });
|
|
1172
|
+
let newLft = 0;
|
|
1173
|
+
if (parentId) {
|
|
1174
|
+
const [{ [rgtField]: rgtValue }] = await conn.select(new Query(Model).select(rgtField).where(where).where({ [idField]: parentId }));
|
|
1175
|
+
await conn.update(new Query(Model).where(where).where(rgtField, ">=", rgtValue), { [rgtField]: increment(diff) });
|
|
1176
|
+
await conn.update(new Query(Model).where(where).where(lftField, ">=", rgtValue), { [lftField]: increment(diff) });
|
|
1177
|
+
newLft = rgtValue;
|
|
1178
|
+
} else {
|
|
1179
|
+
/** @type {{r: number}[]} */
|
|
1180
|
+
const [{ r } = { r: 0 }] = await conn.select(new Query(Model, true).select({ r: rgtField }).where(where).where({ [idField]: parent }).limit(1).sort(rgtField, true));
|
|
1181
|
+
newLft = r + 1;
|
|
1182
|
+
}
|
|
1183
|
+
const val = newLft - oldDocument[lftField];
|
|
1184
|
+
await conn.update(new Query(Model).where(where).where(lftField, "<", 0), {
|
|
1185
|
+
[lftField]: coefficient(val, -1),
|
|
1186
|
+
[rgtField]: coefficient(val, -1)
|
|
1187
|
+
});
|
|
1188
|
+
updatedData[lftField] = oldDocument[lftField] + val;
|
|
1189
|
+
updatedData[rgtField] = oldDocument[rgtField] + val;
|
|
1190
|
+
}, true);
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region packages/core/toDocumentFields.mjs
|
|
1196
|
+
/** @import { Field } from '@yongdall/types' */
|
|
1197
|
+
/** @import { FieldDefine } from '@yongdall/model' */
|
|
1198
|
+
/**
|
|
1199
|
+
*
|
|
1200
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1201
|
+
* @param {Set<string>} ignore
|
|
1202
|
+
* @returns {Record<string, Field>?}
|
|
1203
|
+
*/
|
|
1204
|
+
function toSubDocumentFields(fields, ignore) {
|
|
1205
|
+
/** @type {Record<string, Field>} */
|
|
1206
|
+
const res = {};
|
|
1207
|
+
for (const [name, { type, ...field }] of Object.entries(fields)) {
|
|
1208
|
+
if (ignore.has(name)) continue;
|
|
1209
|
+
if (!type) continue;
|
|
1210
|
+
if (typeof type !== "object" && typeof type !== "function") {
|
|
1211
|
+
res[name] = {
|
|
1212
|
+
type,
|
|
1213
|
+
array: field.array,
|
|
1214
|
+
nullable: field.nullable,
|
|
1215
|
+
default: field.default,
|
|
1216
|
+
scale: field.scale,
|
|
1217
|
+
primary: field.primary,
|
|
1218
|
+
uncreatable: Boolean(field.uncreatable || field.updating || field.creating || field.fixedValue),
|
|
1219
|
+
immutable: Boolean(field.primary || field.immutable || field.updating || field.creating || field.fixedValue),
|
|
1220
|
+
layoutOrder: field.layoutOrder,
|
|
1221
|
+
label: field.label,
|
|
1222
|
+
description: field.description,
|
|
1223
|
+
placeholder: field.placeholder,
|
|
1224
|
+
step: field.step,
|
|
1225
|
+
max: field.max,
|
|
1226
|
+
min: field.min,
|
|
1227
|
+
readonly: field.readonly,
|
|
1228
|
+
required: field.required,
|
|
1229
|
+
clearable: field.clearable,
|
|
1230
|
+
disabled: field.disabled,
|
|
1231
|
+
group: field.group,
|
|
1232
|
+
renderer: field.renderer,
|
|
1233
|
+
scripts: field.scripts,
|
|
1234
|
+
layout: field.layout,
|
|
1235
|
+
model: field.model,
|
|
1236
|
+
sort: field.sort
|
|
1237
|
+
};
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
const fields = type.fields;
|
|
1241
|
+
if (!fields) continue;
|
|
1242
|
+
const subFields = toSubDocumentFields(fields, new Set(Object.keys(field.constraints || {})));
|
|
1243
|
+
if (!subFields) continue;
|
|
1244
|
+
res[name] = {
|
|
1245
|
+
type: subFields,
|
|
1246
|
+
array: field.array,
|
|
1247
|
+
default: field.default,
|
|
1248
|
+
uncreatable: Boolean(field.uncreatable),
|
|
1249
|
+
immutable: Boolean(field.immutable),
|
|
1250
|
+
layoutOrder: field.layoutOrder,
|
|
1251
|
+
label: field.label,
|
|
1252
|
+
description: field.description,
|
|
1253
|
+
placeholder: field.placeholder,
|
|
1254
|
+
readonly: field.readonly,
|
|
1255
|
+
required: field.required,
|
|
1256
|
+
clearable: field.clearable,
|
|
1257
|
+
disabled: field.disabled,
|
|
1258
|
+
group: field.group,
|
|
1259
|
+
renderer: field.renderer,
|
|
1260
|
+
scripts: field.scripts,
|
|
1261
|
+
layout: field.layout
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
if (!Object.keys(res).length) return null;
|
|
1265
|
+
return res;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
*
|
|
1269
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1270
|
+
* @returns {Record<string, Field>}
|
|
1271
|
+
*/
|
|
1272
|
+
function toDocumentFields(fields) {
|
|
1273
|
+
return toSubDocumentFields(fields, /* @__PURE__ */ new Set()) || {};
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
//#endregion
|
|
1277
|
+
//#region packages/core/enumerations.mjs
|
|
1278
|
+
/** @import { MaybePromise } from './types.mjs' */
|
|
1279
|
+
/** @type {Record<string, (Record<string, any> | (() => MaybePromise<Record<string, any>[]>))[]>} */
|
|
1280
|
+
let enumerations = {};
|
|
1281
|
+
hooks.listen(() => {
|
|
1282
|
+
const groups = Object.groupBy([...hooks.get("enumerations").values()].flatMap((v) => Object.entries(v)), ([k]) => k);
|
|
1283
|
+
enumerations = Object.fromEntries(Object.entries(groups).map(([k, v]) => [k, v?.map((v) => v[1]).flat(2) || []]));
|
|
1284
|
+
}, -1e6);
|
|
1285
|
+
/**
|
|
1286
|
+
*
|
|
1287
|
+
* @param {string} name
|
|
1288
|
+
* @returns {Promise<Record<string, any>[]>}
|
|
1289
|
+
*/
|
|
1290
|
+
async function getEnumerations(name) {
|
|
1291
|
+
if (!Object.hasOwn(enumerations, name)) return [];
|
|
1292
|
+
return (await Promise.all(enumerations[name].map((v) => typeof v === "function" ? v() : v))).flat();
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
//#endregion
|
|
1296
|
+
//#region packages/core/search2where/hook.mjs
|
|
1297
|
+
/** @import { MaybePromise, WhereHook } from '../types.mjs' */
|
|
1298
|
+
/**
|
|
1299
|
+
*
|
|
1300
|
+
* @param {MaybePromise<Where?>} [where]
|
|
1301
|
+
*/
|
|
1302
|
+
async function toNot(where) {
|
|
1303
|
+
const w = await where;
|
|
1304
|
+
if (!w) return null;
|
|
1305
|
+
return Where.not(w);
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* @template T
|
|
1309
|
+
* @param {Record<string, T>} map
|
|
1310
|
+
* @param {string} key
|
|
1311
|
+
* @param {() => T} create
|
|
1312
|
+
*/
|
|
1313
|
+
function select(map, key, create) {
|
|
1314
|
+
if (Object.hasOwn(map, key)) return map[key];
|
|
1315
|
+
return map[key] = create();
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* @typedef WhereDefine
|
|
1319
|
+
* @property {string} label
|
|
1320
|
+
* @property {string} notLabel
|
|
1321
|
+
* @property {WhereHook.Value[]} value
|
|
1322
|
+
* @property {WhereHook.Value[]} notValue
|
|
1323
|
+
* @property {WhereHook.Named[]} named
|
|
1324
|
+
* @property {WhereHook.Named[]} notNamed
|
|
1325
|
+
* @property {WhereHook.Field[]} field
|
|
1326
|
+
* @property {WhereHook.Field[]} notField
|
|
1327
|
+
*/
|
|
1328
|
+
/**
|
|
1329
|
+
*
|
|
1330
|
+
* @returns {WhereDefine}
|
|
1331
|
+
*/
|
|
1332
|
+
function createWhereDefine() {
|
|
1333
|
+
return {
|
|
1334
|
+
label: "",
|
|
1335
|
+
notLabel: "",
|
|
1336
|
+
value: [],
|
|
1337
|
+
notValue: [],
|
|
1338
|
+
named: [],
|
|
1339
|
+
notNamed: [],
|
|
1340
|
+
field: [],
|
|
1341
|
+
notField: []
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function getWhere() {
|
|
1345
|
+
/** @type {Record<string, Record<string, WhereDefine>>} */
|
|
1346
|
+
const filterMap = {};
|
|
1347
|
+
/** @type {Record<string, Record<string, WhereDefine>>} */
|
|
1348
|
+
const notFilterMap = {};
|
|
1349
|
+
for (const [, type, fieldWhere] of hooks.get("where").entriesObject()) {
|
|
1350
|
+
if (!fieldWhere || typeof fieldWhere !== "object") continue;
|
|
1351
|
+
const typeFilters = select(filterMap, type, () => ({}));
|
|
1352
|
+
const notTypeFilters = select(notFilterMap, type, () => ({}));
|
|
1353
|
+
for (const [op, filters] of Object.entries(fieldWhere)) for (const filter of [filters].flat()) {
|
|
1354
|
+
if (!filter || typeof filter !== "object") continue;
|
|
1355
|
+
let { label, notLabel, not, value, notValue, named, notNamed, field, notField } = filter;
|
|
1356
|
+
if (not) {
|
|
1357
|
+
if (typeof value === "function" && true) notValue = (...p) => toNot(value(...p));
|
|
1358
|
+
if (typeof field === "function" && true) notField = (...p) => toNot(field(...p));
|
|
1359
|
+
if (typeof named === "function" && true) notNamed = (...p) => toNot(named(...p));
|
|
1360
|
+
}
|
|
1361
|
+
const typeFilter = select(typeFilters, op, createWhereDefine);
|
|
1362
|
+
if (label && typeof label === "string") typeFilter.label = label;
|
|
1363
|
+
if (notLabel && typeof notLabel === "string") typeFilter.notLabel = notLabel;
|
|
1364
|
+
if (typeof value === "function") typeFilter.value.push(value);
|
|
1365
|
+
if (typeof notValue === "function") typeFilter.notValue.push(notValue);
|
|
1366
|
+
if (typeof named === "function") typeFilter.named.push(named);
|
|
1367
|
+
if (typeof notNamed === "function") typeFilter.notNamed.push(notNamed);
|
|
1368
|
+
if (typeof field === "function") typeFilter.field.push(field);
|
|
1369
|
+
if (typeof notField === "function") typeFilter.notField.push(notField);
|
|
1370
|
+
const notOp = typeof not === "string" && not || "";
|
|
1371
|
+
if (!notOp) continue;
|
|
1372
|
+
const notTypeFilter = select(notTypeFilters, notOp, createWhereDefine);
|
|
1373
|
+
if (label && typeof label === "string") notTypeFilter.notLabel = label;
|
|
1374
|
+
if (notLabel && typeof notLabel === "string") notTypeFilter.label = notLabel;
|
|
1375
|
+
if (typeof value === "function") notTypeFilter.notValue.push(value);
|
|
1376
|
+
if (typeof notValue === "function") notTypeFilter.value.push(notValue);
|
|
1377
|
+
if (typeof named === "function") notTypeFilter.notNamed.push(named);
|
|
1378
|
+
if (typeof notNamed === "function") notTypeFilter.named.push(notNamed);
|
|
1379
|
+
if (typeof field === "function") notTypeFilter.notField.push(field);
|
|
1380
|
+
if (typeof notField === "function") notTypeFilter.field.push(notField);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
for (const type of Object.keys({
|
|
1384
|
+
...filterMap,
|
|
1385
|
+
...notFilterMap
|
|
1386
|
+
})) {
|
|
1387
|
+
let typeFilters = Object.hasOwn(filterMap, type) && filterMap[type] || null;
|
|
1388
|
+
let notTypeFilters = Object.hasOwn(notFilterMap, type) && notFilterMap[type] || null;
|
|
1389
|
+
if (!typeFilters && notTypeFilters) {
|
|
1390
|
+
typeFilters = notTypeFilters;
|
|
1391
|
+
filterMap[type] = typeFilters;
|
|
1392
|
+
}
|
|
1393
|
+
if (!typeFilters) continue;
|
|
1394
|
+
for (const op of Object.keys({
|
|
1395
|
+
...typeFilters,
|
|
1396
|
+
...notTypeFilters
|
|
1397
|
+
})) {
|
|
1398
|
+
let opFilters = Object.hasOwn(typeFilters, op) && typeFilters[op] || null;
|
|
1399
|
+
let notOpFilters = notTypeFilters && Object.hasOwn(notTypeFilters, op) && notTypeFilters[op] || null;
|
|
1400
|
+
if (!opFilters && notOpFilters) {
|
|
1401
|
+
opFilters = notOpFilters;
|
|
1402
|
+
typeFilters[type] = opFilters;
|
|
1403
|
+
}
|
|
1404
|
+
if (!opFilters) continue;
|
|
1405
|
+
if (!opFilters.label) opFilters.label = notOpFilters?.label || op;
|
|
1406
|
+
if (!opFilters.notLabel) opFilters.notLabel = notOpFilters?.notLabel || `!${op}`;
|
|
1407
|
+
if (!notOpFilters) continue;
|
|
1408
|
+
for (const fn of notOpFilters.value) opFilters.value.push(fn);
|
|
1409
|
+
for (const fn of notOpFilters.notValue) opFilters.notValue.push(fn);
|
|
1410
|
+
for (const fn of notOpFilters.named) opFilters.named.push(fn);
|
|
1411
|
+
for (const fn of notOpFilters.notNamed) opFilters.notNamed.push(fn);
|
|
1412
|
+
for (const fn of notOpFilters.field) opFilters.field.push(fn);
|
|
1413
|
+
for (const fn of notOpFilters.notField) opFilters.notField.push(fn);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return filterMap;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
*
|
|
1420
|
+
* @param {ReturnType<typeof getWhere>} filterMap
|
|
1421
|
+
*/
|
|
1422
|
+
function toWhereList(filterMap) {
|
|
1423
|
+
/** @type {{type: string; operator: string; label: string; notLabel: string; value: boolean; notValue: boolean; named: boolean; notNamed: boolean; field: boolean; notField: boolean}[]} */
|
|
1424
|
+
const list = [];
|
|
1425
|
+
for (const [type, filters] of Object.entries(filterMap)) for (const [operator, filter] of Object.entries(filters)) list.push({
|
|
1426
|
+
type,
|
|
1427
|
+
operator,
|
|
1428
|
+
label: filter.label,
|
|
1429
|
+
notLabel: filter.notLabel,
|
|
1430
|
+
value: Boolean(filter.value.length),
|
|
1431
|
+
notValue: Boolean(filter.notValue.length),
|
|
1432
|
+
named: Boolean(filter.named.length),
|
|
1433
|
+
notNamed: Boolean(filter.notNamed.length),
|
|
1434
|
+
field: Boolean(filter.field.length),
|
|
1435
|
+
notField: Boolean(filter.notField.length)
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
let allWhere = getWhere();
|
|
1439
|
+
let whereList = toWhereList(allWhere);
|
|
1440
|
+
hooks.listen(() => {
|
|
1441
|
+
allWhere = getWhere();
|
|
1442
|
+
whereList = toWhereList(allWhere);
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
//#endregion
|
|
1446
|
+
//#region packages/core/search2where/toWhere.mjs
|
|
1447
|
+
/** @import { FieldDefine } from '@yongdall/model' */
|
|
1448
|
+
/** @import { MaybePromise } from '../types.mjs' */
|
|
1449
|
+
/**
|
|
1450
|
+
* @typedef {object} WhereParam.Value
|
|
1451
|
+
* @property {'value'} type
|
|
1452
|
+
* @property {FieldDefine} define
|
|
1453
|
+
* @property {string} name
|
|
1454
|
+
* @property {string?} operator
|
|
1455
|
+
* @property {boolean} not
|
|
1456
|
+
* @property {string | RecursiveArray<string> | null} value
|
|
1457
|
+
*/
|
|
1458
|
+
/**
|
|
1459
|
+
* @typedef {object} WhereParam.Field
|
|
1460
|
+
* @property {'field'} type
|
|
1461
|
+
* @property {FieldDefine} define
|
|
1462
|
+
* @property {string} name
|
|
1463
|
+
* @property {FieldDefine} fieldDefine
|
|
1464
|
+
* @property {string} field
|
|
1465
|
+
* @property {string?} operator
|
|
1466
|
+
* @property {boolean} not
|
|
1467
|
+
*/
|
|
1468
|
+
/**
|
|
1469
|
+
* @typedef {WhereParam.Value | WhereParam.Field} WhereParam
|
|
1470
|
+
*/
|
|
1471
|
+
/**
|
|
1472
|
+
*
|
|
1473
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1474
|
+
* @param {string[]} path
|
|
1475
|
+
* @param {Search.Match | Search.Children} and
|
|
1476
|
+
* @returns {Generator<WhereParam, Where?, Where?>}
|
|
1477
|
+
*/
|
|
1478
|
+
function* runItemWhere(fields, path, and) {
|
|
1479
|
+
const name = path.shift();
|
|
1480
|
+
if (!name) return null;
|
|
1481
|
+
const define = Object.hasOwn(fields, name) && fields[name];
|
|
1482
|
+
if (!define) return null;
|
|
1483
|
+
const type = define.type;
|
|
1484
|
+
if (!type) return null;
|
|
1485
|
+
if (path.length) {
|
|
1486
|
+
if (typeof type !== "object") return null;
|
|
1487
|
+
if (!type.table) return null;
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
if (and.length === 2) {
|
|
1491
|
+
if (typeof type !== "object") return null;
|
|
1492
|
+
if (!type.table) return null;
|
|
1493
|
+
return null;
|
|
1494
|
+
}
|
|
1495
|
+
if (typeof type !== "string" && (typeof type !== "object" || type.table)) return null;
|
|
1496
|
+
const [, value, operator, sign] = and;
|
|
1497
|
+
if (operator === null) return yield {
|
|
1498
|
+
type: "value",
|
|
1499
|
+
define,
|
|
1500
|
+
name: define.column || name,
|
|
1501
|
+
value: null,
|
|
1502
|
+
operator,
|
|
1503
|
+
not: Boolean(sign)
|
|
1504
|
+
};
|
|
1505
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return yield {
|
|
1506
|
+
type: "value",
|
|
1507
|
+
define,
|
|
1508
|
+
name: define.column || name,
|
|
1509
|
+
value,
|
|
1510
|
+
operator,
|
|
1511
|
+
not: Boolean(sign)
|
|
1512
|
+
};
|
|
1513
|
+
const { field, table, value: rValue } = value;
|
|
1514
|
+
if (rValue) return yield {
|
|
1515
|
+
type: "value",
|
|
1516
|
+
define,
|
|
1517
|
+
name: define.column || name,
|
|
1518
|
+
value: rValue,
|
|
1519
|
+
operator,
|
|
1520
|
+
not: Boolean(sign)
|
|
1521
|
+
};
|
|
1522
|
+
if (field) {
|
|
1523
|
+
if (table) return null;
|
|
1524
|
+
const fieldDefine = Object.hasOwn(fields, name) && fields[field];
|
|
1525
|
+
if (!fieldDefine) return null;
|
|
1526
|
+
const type = fieldDefine.type;
|
|
1527
|
+
if (!type) return null;
|
|
1528
|
+
if (typeof type !== "string" && (typeof type !== "object" || type.table)) return null;
|
|
1529
|
+
return yield {
|
|
1530
|
+
type: "field",
|
|
1531
|
+
define,
|
|
1532
|
+
name: define.column || name,
|
|
1533
|
+
field: fieldDefine.column || field,
|
|
1534
|
+
fieldDefine,
|
|
1535
|
+
operator,
|
|
1536
|
+
not: Boolean(sign)
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
*
|
|
1543
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1544
|
+
* @param {Search.Match | Search.Children} and
|
|
1545
|
+
* @returns {Generator<WhereParam, Where?, Where?>}
|
|
1546
|
+
*/
|
|
1547
|
+
function* toItemWhere(fields, and) {
|
|
1548
|
+
const path = and[0];
|
|
1549
|
+
if (path.find((v) => Array.isArray(v) && v.length)) return null;
|
|
1550
|
+
return yield* runItemWhere(fields, path.map((v) => typeof v === "string" ? v : v[0]), and);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
*
|
|
1554
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1555
|
+
* @param {Search.OrList} or
|
|
1556
|
+
* @returns {Generator<WhereParam, Where?, Where?>}
|
|
1557
|
+
*/
|
|
1558
|
+
function* toOrWhere(fields, or) {
|
|
1559
|
+
/** @type {Where?} */
|
|
1560
|
+
let where = null;
|
|
1561
|
+
for (const item of or) if (Array.isArray(item)) {
|
|
1562
|
+
const w = yield* toItemWhere(fields, item);
|
|
1563
|
+
if (w) where = (where || new Where()).or(w);
|
|
1564
|
+
} else {
|
|
1565
|
+
const w = yield* toWhere(fields, item.and, item.or);
|
|
1566
|
+
if (w) where = (where || new Where()).or(w);
|
|
1567
|
+
}
|
|
1568
|
+
return where;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
*
|
|
1572
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1573
|
+
* @param {Search.AndList} [and]
|
|
1574
|
+
* @param {...Search.OrList | null | undefined} orList
|
|
1575
|
+
* @returns {Generator<WhereParam, Where?, Where?>}
|
|
1576
|
+
*/
|
|
1577
|
+
function* toWhere(fields, and, ...orList) {
|
|
1578
|
+
/** @type {Where?} */
|
|
1579
|
+
let where = null;
|
|
1580
|
+
if (and) for (const item of and) {
|
|
1581
|
+
const w = yield* toItemWhere(fields, item);
|
|
1582
|
+
if (w) where = (where || new Where()).and(w);
|
|
1583
|
+
}
|
|
1584
|
+
for (const or of orList) {
|
|
1585
|
+
if (!or) continue;
|
|
1586
|
+
const w = yield* toOrWhere(fields, or);
|
|
1587
|
+
if (w) where = (where || new Where()).and(w);
|
|
1588
|
+
}
|
|
1589
|
+
return where;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
//#endregion
|
|
1593
|
+
//#region packages/core/search2where/index.mjs
|
|
1594
|
+
/** @import { RecursiveArray } from '@yongdall/common' */
|
|
1595
|
+
/** @import { FieldDefine } from '@yongdall/model' */
|
|
1596
|
+
/** @import { MaybePromise } from '../types.mjs' */
|
|
1597
|
+
/** @import { WhereParam } from './toWhere.mjs' */
|
|
1598
|
+
/**
|
|
1599
|
+
*
|
|
1600
|
+
* @param {string | {component: string; [k: string]: any} | (string | {component: string; [k: string]: any})[]} [def]
|
|
1601
|
+
* @returns {Iterable<string>}
|
|
1602
|
+
*/
|
|
1603
|
+
function* getRenderer(def) {
|
|
1604
|
+
if (!def) return;
|
|
1605
|
+
for (const s of Array.isArray(def) ? def : [def]) {
|
|
1606
|
+
if (!s) continue;
|
|
1607
|
+
if (typeof s === "string") {
|
|
1608
|
+
yield s;
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
if (typeof s !== "object") continue;
|
|
1612
|
+
const component = s.component;
|
|
1613
|
+
if (!component || typeof component !== "string") continue;
|
|
1614
|
+
yield component;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
*
|
|
1619
|
+
* @param {FieldDefine} field
|
|
1620
|
+
* @returns {Iterable<string>}
|
|
1621
|
+
*/
|
|
1622
|
+
function* getKey(field) {
|
|
1623
|
+
const type = field.type;
|
|
1624
|
+
const renderers = field.renderer;
|
|
1625
|
+
const array = field.array ? "[]" : "";
|
|
1626
|
+
if (typeof type === "object") return;
|
|
1627
|
+
if (!type) {
|
|
1628
|
+
for (const renderer of getRenderer(renderers)) {
|
|
1629
|
+
yield `:${renderer}${array}`;
|
|
1630
|
+
yield `${renderer}${array}`;
|
|
1631
|
+
}
|
|
1632
|
+
yield `${array}`;
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
for (const renderer of getRenderer(renderers)) {
|
|
1636
|
+
yield `${type}:${renderer}${array}`;
|
|
1637
|
+
yield `${renderer}${array}`;
|
|
1638
|
+
}
|
|
1639
|
+
yield `${type}${array}`;
|
|
1640
|
+
yield `${array}`;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
*
|
|
1644
|
+
* @param {WhereParam} param
|
|
1645
|
+
*/
|
|
1646
|
+
async function execWhere(param) {
|
|
1647
|
+
const operator = param.operator;
|
|
1648
|
+
if (operator === null) return Where.and(param.name, "=", null, param.not);
|
|
1649
|
+
for (const k of getKey(param.define)) {
|
|
1650
|
+
const typeWhere = Object.hasOwn(allWhere, k) && allWhere[k];
|
|
1651
|
+
if (!typeWhere) continue;
|
|
1652
|
+
const op = Object.hasOwn(typeWhere, operator) && typeWhere[operator];
|
|
1653
|
+
if (!op) continue;
|
|
1654
|
+
if (param.type === "value") {
|
|
1655
|
+
for (const fn of param.not ? op.notValue : op.value) {
|
|
1656
|
+
const w = await fn(param.name, param.value, param.define);
|
|
1657
|
+
if (w) return w;
|
|
1658
|
+
}
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1661
|
+
if (param.type === "field") {
|
|
1662
|
+
for (const fn of param.not ? op.notField : op.field) {
|
|
1663
|
+
const w = await fn(param.name, param.field, param.define, param.fieldDefine);
|
|
1664
|
+
if (w) return w;
|
|
1665
|
+
}
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (param.type === "field") for (const k of getKey(param.fieldDefine)) {
|
|
1670
|
+
const typeWhere = Object.hasOwn(allWhere, k) && allWhere[k];
|
|
1671
|
+
if (!typeWhere) continue;
|
|
1672
|
+
const op = Object.hasOwn(typeWhere, operator) && typeWhere[operator];
|
|
1673
|
+
if (!op) continue;
|
|
1674
|
+
for (const fn of param.not ? op.notField : op.field) {
|
|
1675
|
+
const w = await fn(param.field, param.name, param.fieldDefine, param.define);
|
|
1676
|
+
if (w) return w;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
*
|
|
1683
|
+
* @param {Record<string, FieldDefine>} fields
|
|
1684
|
+
* @param {Search.AndList} [and]
|
|
1685
|
+
* @param {Search.OrList} [or]
|
|
1686
|
+
* @param {Search.OrList} [or2]
|
|
1687
|
+
* @returns {Promise<Where?>}
|
|
1688
|
+
*/
|
|
1689
|
+
async function search2where(fields, and, or, or2) {
|
|
1690
|
+
return Search.asyncRunGenerator(toWhere(fields, and, or, or2), execWhere, null);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
//#endregion
|
|
1694
|
+
//#region packages/core/index.mjs
|
|
1695
|
+
/**
|
|
1696
|
+
*
|
|
1697
|
+
* @param {Partial<Omit<Tenant, 'id'>>} tenant
|
|
1698
|
+
* @param {object} boot
|
|
1699
|
+
* @param {Record<string, Partial<Hooks>>} pluginHooks
|
|
1700
|
+
* @returns
|
|
1701
|
+
*/
|
|
1702
|
+
function initSystem(tenant, boot, pluginHooks) {
|
|
1703
|
+
initConfig(tenant, boot);
|
|
1704
|
+
Hook.update(hooks, pluginHooks);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
//#endregion
|
|
1708
|
+
export { ServerError, bindModelHooks, bootConfiguration, createPermissionMatches, initSystem as default, existUser, filterPermissionDocument, filterPermissionUpdate, filterPermissions, findModel, findUser, getEnumerations, getMenus, getModel, getModelAuthorizationPermissions, getModelPermissions, getUser, getUserOrganizationAllPermissions, getUserOrganizationPermissions, getUserPermissions, hooks, initConfig, isSingleUser, loadPluginUser, loadUser, loadUserOrganizationAllPermissions, loadUserOrganizationPermissions, loadUserPermissions, mainTenant, modelHook, modelLoaders, models, nestedSetTree, search2where, setUser, setUserVerify, testPermissionConstraints, testPermissions, toDocumentFields, useTenant, verifyUser };
|
|
1709
|
+
//# sourceMappingURL=index.mjs.map
|