@aooth/arbac-moost 0.1.77 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atscript/index.d.mts +61 -3
- package/dist/atscript/index.mjs +82 -4
- package/dist/index.d.mts +17 -0
- package/dist/index.mjs +283 -18
- package/package.json +20 -20
|
@@ -32,6 +32,61 @@ interface ArbacUserTable<T extends object> {
|
|
|
32
32
|
};
|
|
33
33
|
}): Promise<T | null>;
|
|
34
34
|
}
|
|
35
|
+
/** Options of {@link AtscriptArbacUserProvider}. @since 0.1.78 */
|
|
36
|
+
interface AtscriptArbacUserProviderOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Extra user fields to select in the provider's single per-event user
|
|
39
|
+
* fetch, next to the id, `@arbac.role` and `@arbac.attribute` fields — read
|
|
40
|
+
* them in an `extractAttrs` / `extractRoles` override instead of fetching
|
|
41
|
+
* the user a second time (e.g. a `username` that scope functions need but
|
|
42
|
+
* that cannot carry `@arbac.attribute`).
|
|
43
|
+
*/
|
|
44
|
+
extraSelect?: readonly string[];
|
|
45
|
+
/**
|
|
46
|
+
* OPT-IN cross-request cache of the fetched user record (roles + attrs),
|
|
47
|
+
* see {@link ArbacUserRecordCache}. Off by default: without it every
|
|
48
|
+
* request reads the user once.
|
|
49
|
+
*/
|
|
50
|
+
cache?: ArbacUserRecordCache;
|
|
51
|
+
}
|
|
52
|
+
/** Options of {@link ArbacUserRecordCache}. @since 0.1.78 */
|
|
53
|
+
interface ArbacUserRecordCacheOptions {
|
|
54
|
+
/** How long a fetched record is reused, in ms (keep it short — seconds). */
|
|
55
|
+
ttlMs: number;
|
|
56
|
+
/** Most cached users; the oldest entry is dropped past it. Default 1000. */
|
|
57
|
+
max?: number;
|
|
58
|
+
/** Clock (ms) — for tests. Default `Date.now`. */
|
|
59
|
+
now?: () => number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* OPT-IN, process-local cache of the user records an
|
|
63
|
+
* {@link AtscriptArbacUserProvider} fetches for ARBAC (`userId` → roles +
|
|
64
|
+
* attributes), shared across requests for `ttlMs`. Pass it as the provider's
|
|
65
|
+
* `cache` option; off by default.
|
|
66
|
+
*
|
|
67
|
+
* Staleness: a role or attribute change reaches ARBAC within `ttlMs` — or
|
|
68
|
+
* immediately on THIS process when you call {@link invalidate} (wire it to
|
|
69
|
+
* your user writes, e.g. `userService.onUserWrite((id) => cache.invalidate(id))`).
|
|
70
|
+
* Other processes keep their entry until it expires, so the cross-node lag is
|
|
71
|
+
* at most `ttlMs`. Only the stored record is cached: credential attenuation
|
|
72
|
+
* (read from the token per request) and every policy evaluation still run
|
|
73
|
+
* per request. A missing user is never cached.
|
|
74
|
+
*
|
|
75
|
+
* @since 0.1.78
|
|
76
|
+
*/
|
|
77
|
+
declare class ArbacUserRecordCache {
|
|
78
|
+
private readonly ttlMs;
|
|
79
|
+
private readonly max;
|
|
80
|
+
private readonly now;
|
|
81
|
+
private readonly entries;
|
|
82
|
+
constructor(opts: ArbacUserRecordCacheOptions);
|
|
83
|
+
/** Drop the cached record of `userId` (all users when omitted). */
|
|
84
|
+
invalidate(userId?: string): void;
|
|
85
|
+
/** @internal The live record cached for `owner` (a provider), if any. */
|
|
86
|
+
get(owner: object, userId: string): Promise<unknown> | undefined;
|
|
87
|
+
/** @internal Cache `record` (dropped again if it rejects or resolves to `null`). */
|
|
88
|
+
set(owner: object, userId: string, record: Promise<unknown>): void;
|
|
89
|
+
}
|
|
35
90
|
/**
|
|
36
91
|
* `@Injectable()` (SINGLETON) — moost@0.6.x's `Injectable` metadata is NOT
|
|
37
92
|
* inherited from the base `ArbacUserProvider` by infact, so the decorator
|
|
@@ -44,7 +99,9 @@ interface ArbacUserTable<T extends object> {
|
|
|
44
99
|
* table, and register the subclass via `setReplaceRegistry`.
|
|
45
100
|
*
|
|
46
101
|
* `extractRoles` and `extractAttrs` are protected seams — override to
|
|
47
|
-
* reshape roles/attrs without re-implementing the fetch path
|
|
102
|
+
* reshape roles/attrs without re-implementing the fetch path; pass
|
|
103
|
+
* `extraSelect` (third constructor argument) to have the fetch include the
|
|
104
|
+
* extra fields they read.
|
|
48
105
|
*/
|
|
49
106
|
declare abstract class AtscriptArbacUserProvider<T extends object = object> extends ArbacUserProvider {
|
|
50
107
|
protected readonly userType: TAtscriptAnnotatedType;
|
|
@@ -52,7 +109,8 @@ declare abstract class AtscriptArbacUserProvider<T extends object = object> exte
|
|
|
52
109
|
private readonly spec;
|
|
53
110
|
private readonly projection;
|
|
54
111
|
private readonly withClause;
|
|
55
|
-
|
|
112
|
+
private readonly recordCache;
|
|
113
|
+
constructor(userType: TAtscriptAnnotatedType, table: ArbacUserTable<T>, options?: AtscriptArbacUserProviderOptions);
|
|
56
114
|
abstract getUserId(): string | Promise<string>;
|
|
57
115
|
getRoles(id: string): Promise<string[]>;
|
|
58
116
|
getAttrs(id: string): Promise<object>;
|
|
@@ -233,4 +291,4 @@ declare class AoothArbacUserCredentials extends AoothUserCredentials {
|
|
|
233
291
|
}
|
|
234
292
|
// prettier-ignore-end
|
|
235
293
|
//#endregion
|
|
236
|
-
export { AoothArbacUserCredentials, type AoothCredentialMetadataSpec, type AoothUserHandleSpec, type ArbacAttenuationSpec, type ArbacUserTable, AtscriptArbacUserProvider, extractAttenuation, getAoothCredentialMetadataSpec, getAoothUserHandleSpec, getArbacAttenuationSpec, validateAttenuationTargets };
|
|
294
|
+
export { AoothArbacUserCredentials, type AoothCredentialMetadataSpec, type AoothUserHandleSpec, type ArbacAttenuationSpec, ArbacUserRecordCache, type ArbacUserRecordCacheOptions, type ArbacUserTable, AtscriptArbacUserProvider, type AtscriptArbacUserProviderOptions, extractAttenuation, getAoothCredentialMetadataSpec, getAoothUserHandleSpec, getArbacAttenuationSpec, validateAttenuationTargets };
|
package/dist/atscript/index.mjs
CHANGED
|
@@ -141,20 +141,88 @@ const useFetchCache = defineWook((ctx) => {
|
|
|
141
141
|
}
|
|
142
142
|
return cache;
|
|
143
143
|
});
|
|
144
|
+
/**
|
|
145
|
+
* OPT-IN, process-local cache of the user records an
|
|
146
|
+
* {@link AtscriptArbacUserProvider} fetches for ARBAC (`userId` → roles +
|
|
147
|
+
* attributes), shared across requests for `ttlMs`. Pass it as the provider's
|
|
148
|
+
* `cache` option; off by default.
|
|
149
|
+
*
|
|
150
|
+
* Staleness: a role or attribute change reaches ARBAC within `ttlMs` — or
|
|
151
|
+
* immediately on THIS process when you call {@link invalidate} (wire it to
|
|
152
|
+
* your user writes, e.g. `userService.onUserWrite((id) => cache.invalidate(id))`).
|
|
153
|
+
* Other processes keep their entry until it expires, so the cross-node lag is
|
|
154
|
+
* at most `ttlMs`. Only the stored record is cached: credential attenuation
|
|
155
|
+
* (read from the token per request) and every policy evaluation still run
|
|
156
|
+
* per request. A missing user is never cached.
|
|
157
|
+
*
|
|
158
|
+
* @since 0.1.78
|
|
159
|
+
*/
|
|
160
|
+
var ArbacUserRecordCache = class {
|
|
161
|
+
ttlMs;
|
|
162
|
+
max;
|
|
163
|
+
now;
|
|
164
|
+
entries = /* @__PURE__ */ new Map();
|
|
165
|
+
constructor(opts) {
|
|
166
|
+
if (!(opts.ttlMs > 0)) throw new Error("ArbacUserRecordCache: ttlMs must be > 0");
|
|
167
|
+
this.ttlMs = opts.ttlMs;
|
|
168
|
+
this.max = opts.max ?? 1e3;
|
|
169
|
+
this.now = opts.now ?? Date.now;
|
|
170
|
+
}
|
|
171
|
+
/** Drop the cached record of `userId` (all users when omitted). */
|
|
172
|
+
invalidate(userId) {
|
|
173
|
+
if (userId === void 0) this.entries.clear();
|
|
174
|
+
else this.entries.delete(userId);
|
|
175
|
+
}
|
|
176
|
+
/** @internal The live record cached for `owner` (a provider), if any. */
|
|
177
|
+
get(owner, userId) {
|
|
178
|
+
const entry = this.entries.get(userId);
|
|
179
|
+
if (!entry) return void 0;
|
|
180
|
+
if (entry.owner !== owner || entry.expiresAt <= this.now()) {
|
|
181
|
+
this.entries.delete(userId);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
return entry.record;
|
|
185
|
+
}
|
|
186
|
+
/** @internal Cache `record` (dropped again if it rejects or resolves to `null`). */
|
|
187
|
+
set(owner, userId, record) {
|
|
188
|
+
this.entries.delete(userId);
|
|
189
|
+
if (this.entries.size >= this.max) {
|
|
190
|
+
const oldest = this.entries.keys().next();
|
|
191
|
+
if (!oldest.done) this.entries.delete(oldest.value);
|
|
192
|
+
}
|
|
193
|
+
const entry = {
|
|
194
|
+
owner,
|
|
195
|
+
expiresAt: this.now() + this.ttlMs,
|
|
196
|
+
record
|
|
197
|
+
};
|
|
198
|
+
this.entries.set(userId, entry);
|
|
199
|
+
const drop = () => {
|
|
200
|
+
if (this.entries.get(userId) === entry) this.entries.delete(userId);
|
|
201
|
+
};
|
|
202
|
+
record.then((r) => r === null && drop(), drop);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
144
205
|
let AtscriptArbacUserProvider = class AtscriptArbacUserProvider extends ArbacUserProvider {
|
|
145
206
|
userType;
|
|
146
207
|
table;
|
|
147
208
|
spec;
|
|
148
209
|
projection;
|
|
149
210
|
withClause;
|
|
150
|
-
|
|
211
|
+
recordCache;
|
|
212
|
+
constructor(userType, table, options) {
|
|
151
213
|
super();
|
|
152
214
|
this.userType = userType;
|
|
153
215
|
this.table = table;
|
|
154
216
|
this.spec = getArbacExtractSpec(userType);
|
|
155
217
|
if (!this.spec.userIdField) throw new Error("AtscriptArbacUserProvider: userType has no @arbac.userId, @db.table.preferredId.uniqueIndex field, or @meta.id — annotate one to identify users");
|
|
156
|
-
|
|
218
|
+
const projection = getArbacProjection(userType);
|
|
219
|
+
const extra = options?.extraSelect?.filter((f) => !Object.hasOwn(projection, f)) ?? [];
|
|
220
|
+
this.projection = extra.length > 0 ? {
|
|
221
|
+
...projection,
|
|
222
|
+
...Object.fromEntries(extra.map((f) => [f, 1]))
|
|
223
|
+
} : projection;
|
|
157
224
|
this.withClause = this.spec.roleField?.shape === "rel.from" ? [{ name: this.spec.roleField.name }] : void 0;
|
|
225
|
+
this.recordCache = options?.cache;
|
|
158
226
|
}
|
|
159
227
|
async getRoles(id) {
|
|
160
228
|
const record = await this.fetchRecord(id);
|
|
@@ -217,6 +285,11 @@ let AtscriptArbacUserProvider = class AtscriptArbacUserProvider extends ArbacUse
|
|
|
217
285
|
}
|
|
218
286
|
const cached = perProvider.get(userId);
|
|
219
287
|
if (cached !== void 0) return cached;
|
|
288
|
+
const shared = this.recordCache?.get(this, userId);
|
|
289
|
+
if (shared !== void 0) {
|
|
290
|
+
perProvider.set(userId, shared);
|
|
291
|
+
return shared;
|
|
292
|
+
}
|
|
220
293
|
const userIdField = this.spec.userIdField;
|
|
221
294
|
const controls = { $select: this.projection };
|
|
222
295
|
if (this.withClause) controls.$with = this.withClause;
|
|
@@ -226,10 +299,15 @@ let AtscriptArbacUserProvider = class AtscriptArbacUserProvider extends ArbacUse
|
|
|
226
299
|
});
|
|
227
300
|
promise.catch(() => perProvider?.delete(userId));
|
|
228
301
|
perProvider.set(userId, promise);
|
|
302
|
+
this.recordCache?.set(this, userId, promise);
|
|
229
303
|
return promise;
|
|
230
304
|
}
|
|
231
305
|
};
|
|
232
|
-
AtscriptArbacUserProvider = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
|
|
306
|
+
AtscriptArbacUserProvider = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
|
|
307
|
+
Object,
|
|
308
|
+
Object,
|
|
309
|
+
Object
|
|
310
|
+
])], AtscriptArbacUserProvider);
|
|
233
311
|
//#endregion
|
|
234
312
|
//#region src/atscript/attenuation-extract.ts
|
|
235
313
|
const specCache$2 = /* @__PURE__ */ new WeakMap();
|
|
@@ -439,4 +517,4 @@ var AoothArbacUserCredentials = class {
|
|
|
439
517
|
};
|
|
440
518
|
defineAnnotatedType("object", AoothArbacUserCredentials).prop("id", defineAnnotatedType().designType("string").tags("string").annotate("meta.id", true).annotate("db.default.uuid", true).$type).prop("username", defineAnnotatedType().designType("string").tags("string").annotate("db.index.unique", "username_idx", true).$type).prop("version", defineAnnotatedType().designType("number").tags("int", "number").annotate("db.column.version", true).annotate("expect.int", true).$type).prop("password", defineAnnotatedType("object").prop("hash", defineAnnotatedType().designType("string").tags("string").$type).prop("history", defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).$type).prop("lastChanged", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("isInitial", defineAnnotatedType().designType("boolean").tags("boolean").$type).annotate("db.patch.strategy", "merge").$type).prop("account", defineAnnotatedType("object").prop("active", defineAnnotatedType().designType("boolean").tags("boolean").$type).prop("locked", defineAnnotatedType().designType("boolean").tags("boolean").$type).prop("lockReason", defineAnnotatedType().designType("string").tags("string").$type).prop("lockEnds", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("failedLoginAttempts", defineAnnotatedType().designType("number").tags("number").$type).prop("lastLogin", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("pendingInvitation", defineAnnotatedType().designType("boolean").tags("boolean").optional().$type).prop("verifiedEmail", defineAnnotatedType().designType("string").tags("string").optional().$type).annotate("db.patch.strategy", "merge").$type).prop("mfa", defineAnnotatedType("object").prop("methods", defineAnnotatedType("array").of(defineAnnotatedType("object").prop("name", defineAnnotatedType().designType("string").tags("string").$type).prop("confirmed", defineAnnotatedType().designType("boolean").tags("boolean").$type).prop("value", defineAnnotatedType().designType("string").tags("string").$type).prop("lastUsedWindow", defineAnnotatedType().designType("number").tags("int", "number").annotate("expect.int", true).optional().$type).$type).$type).prop("defaultMethod", defineAnnotatedType().designType("string").tags("string").$type).prop("autoSend", defineAnnotatedType().designType("boolean").tags("boolean").$type).annotate("db.patch.strategy", "merge").$type).prop("trustedDevices", defineAnnotatedType("array").of(defineAnnotatedType("object").prop("token", defineAnnotatedType().designType("string").tags("string").$type).prop("ip", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("issuedAt", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("expiresAt", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("name", defineAnnotatedType().designType("string").tags("string").optional().$type).$type).annotate("db.patch.strategy", "merge").optional().$type).prop("seenDevices", defineAnnotatedType("array").of(defineAnnotatedType("object").prop("token", defineAnnotatedType().designType("string").tags("string").$type).prop("ip", defineAnnotatedType().designType("string").tags("string").optional().$type).prop("issuedAt", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("expiresAt", defineAnnotatedType().designType("number").tags("timestamp", "number").annotate("expect.int", true).$type).prop("name", defineAnnotatedType().designType("string").tags("string").optional().$type).$type).annotate("db.patch.strategy", "merge").optional().$type).prop("roles", defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).annotate("arbac.role", true).$type);
|
|
441
519
|
//#endregion
|
|
442
|
-
export { AoothArbacUserCredentials, AtscriptArbacUserProvider, extractAttenuation, getAoothCredentialMetadataSpec, getAoothUserHandleSpec, getArbacAttenuationSpec, validateAttenuationTargets };
|
|
520
|
+
export { AoothArbacUserCredentials, ArbacUserRecordCache, AtscriptArbacUserProvider, extractAttenuation, getAoothCredentialMetadataSpec, getAoothUserHandleSpec, getArbacAttenuationSpec, validateAttenuationTargets };
|
package/dist/index.d.mts
CHANGED
|
@@ -884,6 +884,23 @@ declare class MoostArbac<TUserAttrs extends object, TScope extends object> exten
|
|
|
884
884
|
}): Promise<TArbacEvalResult$1<TScope>>;
|
|
885
885
|
private readonly warnedFields;
|
|
886
886
|
private readonly warnUnknownField;
|
|
887
|
+
private metaMemoMax;
|
|
888
|
+
/**
|
|
889
|
+
* Configure the `/meta` projection memo of the ARBAC DB / value-help
|
|
890
|
+
* controllers. The overlay still evaluates every grant per request; only
|
|
891
|
+
* the pure pruning of the cached envelope by those decisions is reused —
|
|
892
|
+
* keyed by the decisions themselves (never by user, scopes' JSON or row
|
|
893
|
+
* filters), up to `max` projections per envelope (default 64, LRU).
|
|
894
|
+
* Memoized projections are frozen and pre-serialized with a weak ETag.
|
|
895
|
+
* `false` turns it off (every `/meta` is pruned afresh).
|
|
896
|
+
*
|
|
897
|
+
* @since 0.1.78
|
|
898
|
+
*/
|
|
899
|
+
setMetaMemo(options: false | {
|
|
900
|
+
max?: number;
|
|
901
|
+
}): this;
|
|
902
|
+
/** Most memoized `/meta` projections per envelope (`0` = memo off). @since 0.1.78 */
|
|
903
|
+
getMetaMemoMax(): number;
|
|
887
904
|
/** The registered custom scope field rules. @since 0.1.72 */
|
|
888
905
|
getScopeFields(): TScopeFieldRules<TScope>;
|
|
889
906
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { Inherit, Injectable, TInterceptorPriority, defineBeforeInterceptor, get
|
|
|
7
7
|
import { computeInsights, containsRelationFilter, findAncestorInSet, getPath, hasRelationOp, isPlainObject, isRelationOp, translateQueryTree } from "@atscript/db";
|
|
8
8
|
import { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsValueHelpController, DB_CRUD_HANDLERS, VALUE_HELP_CRUD_HANDLERS, badRequest, discoverRowLevelActions, getControllerFormType, getDbEndpoint, hasActionDelegations, resolveTerminalRef } from "@atscript/moost-db";
|
|
9
9
|
import { groupByFields, walkFilter } from "@uniqu/core";
|
|
10
|
+
import { prerenderJson } from "@wooksjs/event-http";
|
|
10
11
|
//#region src/db/helpers.ts
|
|
11
12
|
/** `map.get(key)`, created with `create()` and stored on a miss. */
|
|
12
13
|
function getOrCreate(map, key, create) {
|
|
@@ -248,7 +249,7 @@ function buildScopeVisibility(scopes, table, opts = {}) {
|
|
|
248
249
|
return source === void 0 || alwaysVisible.has(source) || base(source);
|
|
249
250
|
};
|
|
250
251
|
const declared = /* @__PURE__ */ new Map();
|
|
251
|
-
return {
|
|
252
|
+
return tagBuilt({
|
|
252
253
|
allowed,
|
|
253
254
|
alwaysVisible,
|
|
254
255
|
withGrants,
|
|
@@ -261,7 +262,16 @@ function buildScopeVisibility(scopes, table, opts = {}) {
|
|
|
261
262
|
if (resolved || !withGrants.has(name)) return resolved ?? void 0;
|
|
262
263
|
return getOrCreate(declared, name, () => buildScopeVisibility(eff.withScopes(name), table?.relatedTable?.(name)));
|
|
263
264
|
}
|
|
264
|
-
};
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const BUILT = Symbol("arbac.builtVisibility");
|
|
268
|
+
function tagBuilt(vis) {
|
|
269
|
+
vis[BUILT] = vis;
|
|
270
|
+
return vis;
|
|
271
|
+
}
|
|
272
|
+
/** `vis` came from {@link buildScopeVisibility} as is (not hand-built, not a copy). */
|
|
273
|
+
function isBuiltVisibility(vis) {
|
|
274
|
+
return vis[BUILT] === vis;
|
|
265
275
|
}
|
|
266
276
|
const scopeVisibilityCache = /* @__PURE__ */ new WeakMap();
|
|
267
277
|
const NO_TABLE = {
|
|
@@ -725,6 +735,8 @@ function fieldChildrenOf(table) {
|
|
|
725
735
|
}
|
|
726
736
|
//#endregion
|
|
727
737
|
//#region src/moost-arbac.ts
|
|
738
|
+
/** Default number of memoized `/meta` projections per envelope. */
|
|
739
|
+
const DEFAULT_META_MEMO_MAX = 64;
|
|
728
740
|
let MoostArbac = class MoostArbac extends Arbac$1 {
|
|
729
741
|
scopeFieldRules = {};
|
|
730
742
|
/**
|
|
@@ -790,6 +802,26 @@ let MoostArbac = class MoostArbac extends Arbac$1 {
|
|
|
790
802
|
this.warnedFields.add(field);
|
|
791
803
|
console.warn(`[arbac] Scope field "${field}" has no rule registered with registerScopeFields: it reaches only your own code, and an attenuated request carrying it fails with 500.`);
|
|
792
804
|
};
|
|
805
|
+
metaMemoMax = DEFAULT_META_MEMO_MAX;
|
|
806
|
+
/**
|
|
807
|
+
* Configure the `/meta` projection memo of the ARBAC DB / value-help
|
|
808
|
+
* controllers. The overlay still evaluates every grant per request; only
|
|
809
|
+
* the pure pruning of the cached envelope by those decisions is reused —
|
|
810
|
+
* keyed by the decisions themselves (never by user, scopes' JSON or row
|
|
811
|
+
* filters), up to `max` projections per envelope (default 64, LRU).
|
|
812
|
+
* Memoized projections are frozen and pre-serialized with a weak ETag.
|
|
813
|
+
* `false` turns it off (every `/meta` is pruned afresh).
|
|
814
|
+
*
|
|
815
|
+
* @since 0.1.78
|
|
816
|
+
*/
|
|
817
|
+
setMetaMemo(options) {
|
|
818
|
+
this.metaMemoMax = options === false ? 0 : Math.max(0, options.max ?? DEFAULT_META_MEMO_MAX);
|
|
819
|
+
return this;
|
|
820
|
+
}
|
|
821
|
+
/** Most memoized `/meta` projections per envelope (`0` = memo off). @since 0.1.78 */
|
|
822
|
+
getMetaMemoMax() {
|
|
823
|
+
return this.metaMemoMax;
|
|
824
|
+
}
|
|
793
825
|
/** The registered custom scope field rules. @since 0.1.72 */
|
|
794
826
|
getScopeFields() {
|
|
795
827
|
return this.scopeFieldRules;
|
|
@@ -843,6 +875,7 @@ async function loadPrincipal(user) {
|
|
|
843
875
|
attrs: (id) => attrs ??= Promise.resolve(user.getAttrs(id))
|
|
844
876
|
};
|
|
845
877
|
}
|
|
878
|
+
const arbacDeps = cachedBy((_controller, ctx) => Promise.all([useControllerContext(ctx).instantiate(ArbacUserProviderToken), useControllerContext(ctx).instantiate(MoostArbac)]));
|
|
846
879
|
const principalKey = key("arbac.principal");
|
|
847
880
|
function isHttpEvent(ctx) {
|
|
848
881
|
return ctx.has(eventTypeKey) && ctx.get(eventTypeKey) === "http";
|
|
@@ -858,7 +891,7 @@ function evaluateArbac(ctx, target, memoPrincipal = false) {
|
|
|
858
891
|
const cc = useControllerContext(ctx);
|
|
859
892
|
const table = target.table ?? cc.getController()?.readable;
|
|
860
893
|
const pending = (async () => {
|
|
861
|
-
const [user, arbac] = await
|
|
894
|
+
const [user, arbac] = await arbacDeps(cc.getController(), ctx);
|
|
862
895
|
let principal;
|
|
863
896
|
if (!memoPrincipal || !isHttpEvent(ctx)) principal = loadPrincipal(user);
|
|
864
897
|
else if (ctx.has(principalKey)) principal = ctx.get(principalKey);
|
|
@@ -919,7 +952,12 @@ function conjoinForTable(a, b, table, fields) {
|
|
|
919
952
|
fields
|
|
920
953
|
})[0]);
|
|
921
954
|
}
|
|
922
|
-
const scopeFieldsSlot = cached(async (ctx) => (await useControllerContext(ctx).
|
|
955
|
+
const scopeFieldsSlot = cached(async (ctx) => (await arbacDeps(useControllerContext(ctx).getController(), ctx))[1].getScopeFields());
|
|
956
|
+
/** The current controller's `MoostArbac` — resolved once per (event, controller). */
|
|
957
|
+
async function currentMoostArbac() {
|
|
958
|
+
const ctx = current();
|
|
959
|
+
return (await arbacDeps(useControllerContext(ctx).getController(), ctx))[1];
|
|
960
|
+
}
|
|
923
961
|
/** The current app's custom scope field rules — resolved once per event. */
|
|
924
962
|
function currentScopeFields() {
|
|
925
963
|
return current().get(scopeFieldsSlot);
|
|
@@ -1090,6 +1128,189 @@ const ArbacResource = (name) => getArbacMate().decorate("arbacResourceId", name)
|
|
|
1090
1128
|
*/
|
|
1091
1129
|
const ArbacAction = (name) => getArbacMate().decorate("arbacActionId", name);
|
|
1092
1130
|
//#endregion
|
|
1131
|
+
//#region src/db/meta-memo.ts
|
|
1132
|
+
/** Deepest relation nesting the decision record follows; deeper → not memoized. */
|
|
1133
|
+
const MAX_RECORD_DEPTH = 16;
|
|
1134
|
+
var Unkeyable = class extends Error {};
|
|
1135
|
+
const UNKEYABLE = new Unkeyable("unkeyable /meta decision");
|
|
1136
|
+
function refuse() {
|
|
1137
|
+
throw UNKEYABLE;
|
|
1138
|
+
}
|
|
1139
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
1140
|
+
let nextId = 1;
|
|
1141
|
+
function idOf(obj) {
|
|
1142
|
+
if (!obj) return 0;
|
|
1143
|
+
let id = ids.get(obj);
|
|
1144
|
+
if (id === void 0) ids.set(obj, id = nextId++);
|
|
1145
|
+
return id;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Validate a scope list without copying it: no symbol-keyed scope (the
|
|
1149
|
+
* `INHERITED_CONJUNCTION` marker of a one-sided `with` sub-scope) and no
|
|
1150
|
+
* function-valued scope piece, `with` sub-scopes recursively. Each scope
|
|
1151
|
+
* object is visited once per record (`seen`). The record itself keys on the
|
|
1152
|
+
* visibility DERIVED from the scopes (row data never reaches `/meta`).
|
|
1153
|
+
*/
|
|
1154
|
+
function checkScopes(scopes, seen, depth = 0) {
|
|
1155
|
+
if (!scopes) return;
|
|
1156
|
+
if (depth > MAX_RECORD_DEPTH) refuse();
|
|
1157
|
+
for (const scope of scopes) {
|
|
1158
|
+
if (seen.has(scope)) continue;
|
|
1159
|
+
if (scope === null || typeof scope !== "object") refuse();
|
|
1160
|
+
seen.add(scope);
|
|
1161
|
+
if (Object.getOwnPropertySymbols(scope).length > 0) refuse();
|
|
1162
|
+
const record = scope;
|
|
1163
|
+
for (const key of Object.keys(record)) {
|
|
1164
|
+
const value = record[key];
|
|
1165
|
+
if (typeof value === "function" || typeof value === "symbol") refuse();
|
|
1166
|
+
if (key === "with" && value !== void 0 && value !== null) {
|
|
1167
|
+
if (typeof value !== "object" || Object.getOwnPropertySymbols(value).length > 0) refuse();
|
|
1168
|
+
checkScopes(Object.values(value), seen, depth + 1);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/** A projection (`path` → `0 | 1`) as sorted `[path, flag, …]`; anything else refuses. */
|
|
1174
|
+
function projectionKey(projection) {
|
|
1175
|
+
if (Object.getOwnPropertySymbols(projection).length > 0) refuse();
|
|
1176
|
+
const keys = Object.keys(projection).toSorted();
|
|
1177
|
+
const out = [];
|
|
1178
|
+
for (const key of keys) {
|
|
1179
|
+
const flag = projection[key];
|
|
1180
|
+
if (flag !== 0 && flag !== 1) refuse();
|
|
1181
|
+
out.push(key, flag);
|
|
1182
|
+
}
|
|
1183
|
+
return out;
|
|
1184
|
+
}
|
|
1185
|
+
/** Sorted strings of a set / list; a non-string refuses. */
|
|
1186
|
+
function sortedNames(names) {
|
|
1187
|
+
const out = [];
|
|
1188
|
+
for (const name of names) {
|
|
1189
|
+
if (typeof name !== "string") refuse();
|
|
1190
|
+
out.push(name);
|
|
1191
|
+
}
|
|
1192
|
+
return out.toSorted();
|
|
1193
|
+
}
|
|
1194
|
+
const NO_TREE = /* @__PURE__ */ new Map();
|
|
1195
|
+
/**
|
|
1196
|
+
* One visibility level: its table, normalized projection, parent-authority
|
|
1197
|
+
* relations, and every relation the pruning can resolve at this level — the
|
|
1198
|
+
* `/meta` relation names (`tree`, what the overlay resolved) and the declared
|
|
1199
|
+
* `with` names — as the child level or `null`. Only levels built by
|
|
1200
|
+
* `buildScopeVisibility` (whose `isAllowed` / `relation` are pure functions
|
|
1201
|
+
* of exactly these) are accepted.
|
|
1202
|
+
*/
|
|
1203
|
+
function encodeLevel(vis, tree, ctx, seen, depth) {
|
|
1204
|
+
if (depth > MAX_RECORD_DEPTH || !isBuiltVisibility(vis)) refuse();
|
|
1205
|
+
if (vis.alwaysVisible !== ctx.identifiers(vis.table)) refuse();
|
|
1206
|
+
checkScopes(vis.scopes, seen);
|
|
1207
|
+
const withGrants = sortedNames(vis.withGrants);
|
|
1208
|
+
const names = tree.size === 0 ? withGrants : sortedNames(/* @__PURE__ */ new Set([...tree.keys(), ...withGrants]));
|
|
1209
|
+
const relations = [];
|
|
1210
|
+
for (const name of names) {
|
|
1211
|
+
const sub = vis.relation?.(name);
|
|
1212
|
+
relations.push(name, sub ? encodeLevel(sub, tree.get(name) ?? NO_TREE, ctx, seen, depth + 1) : 0);
|
|
1213
|
+
}
|
|
1214
|
+
return [
|
|
1215
|
+
idOf(vis.table),
|
|
1216
|
+
projectionKey(vis.allowed),
|
|
1217
|
+
withGrants,
|
|
1218
|
+
relations
|
|
1219
|
+
];
|
|
1220
|
+
}
|
|
1221
|
+
/** Bit string of `flags`. */
|
|
1222
|
+
function bits(flags) {
|
|
1223
|
+
let out = "";
|
|
1224
|
+
for (const flag of flags) out += flag ? "1" : "0";
|
|
1225
|
+
return out;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* The memo key of a `/meta` projection: its decision record, serialized —
|
|
1229
|
+
* or `undefined` when an input cannot be keyed exactly (then the request is
|
|
1230
|
+
* not memoized).
|
|
1231
|
+
*/
|
|
1232
|
+
function metaDecisionKey(input, ctx) {
|
|
1233
|
+
try {
|
|
1234
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1235
|
+
checkScopes(input.writeScopes, seen);
|
|
1236
|
+
checkScopes(input.readScopes, seen);
|
|
1237
|
+
const p = input.projection;
|
|
1238
|
+
if (!p) return JSON.stringify([bits(input.actions), bits(input.crud)]);
|
|
1239
|
+
const refTargets = [];
|
|
1240
|
+
const types = [...p.refTargets.keys()].toSorted((a, b) => idOf(a) - idOf(b));
|
|
1241
|
+
for (const type of types) {
|
|
1242
|
+
const vis = p.refTargets.get(type);
|
|
1243
|
+
refTargets.push(idOf(type), ctx.isServed(type), vis ? encodeLevel(vis, NO_TREE, ctx, seen, 1) : 0);
|
|
1244
|
+
}
|
|
1245
|
+
const { writable, nestedWrites, groupBy } = p;
|
|
1246
|
+
if (groupBy !== void 0 && typeof groupBy !== "boolean" && !Array.isArray(groupBy)) refuse();
|
|
1247
|
+
return JSON.stringify([
|
|
1248
|
+
bits(input.actions),
|
|
1249
|
+
bits(input.crud),
|
|
1250
|
+
encodeLevel(p.vis, p.tree, ctx, seen, 0),
|
|
1251
|
+
writable === void 0 ? 0 : writable === "all" ? 1 : sortedNames(writable),
|
|
1252
|
+
nestedWrites ? sortedNames(nestedWrites) : 0,
|
|
1253
|
+
Array.isArray(groupBy) ? sortedNames(groupBy) : groupBy ?? 0,
|
|
1254
|
+
refTargets,
|
|
1255
|
+
p.vis.table?.type ? ctx.isServed(p.vis.table.type) : 0
|
|
1256
|
+
]);
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
if (error instanceof Unkeyable) return void 0;
|
|
1259
|
+
throw error;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const variants = /* @__PURE__ */ new WeakMap();
|
|
1263
|
+
const seenEnvelopes = /* @__PURE__ */ new WeakSet();
|
|
1264
|
+
/**
|
|
1265
|
+
* The projection of `envelope` for `key`, computed once per (envelope, key)
|
|
1266
|
+
* and reused while it stays among the `max` most recently used. No `key`
|
|
1267
|
+
* (unkeyable decisions) or `max` ≤ 0 → computed per call, uncached.
|
|
1268
|
+
*/
|
|
1269
|
+
function memoMetaProjection(envelope, key, max, compute) {
|
|
1270
|
+
if (key === void 0 || max <= 0) return compute();
|
|
1271
|
+
if (!seenEnvelopes.has(envelope)) {
|
|
1272
|
+
seenEnvelopes.add(envelope);
|
|
1273
|
+
return compute();
|
|
1274
|
+
}
|
|
1275
|
+
let byKey = variants.get(envelope);
|
|
1276
|
+
const hit = byKey?.get(key);
|
|
1277
|
+
if (hit) {
|
|
1278
|
+
byKey.delete(key);
|
|
1279
|
+
byKey.set(key, hit);
|
|
1280
|
+
return hit;
|
|
1281
|
+
}
|
|
1282
|
+
const out = shareMeta(compute());
|
|
1283
|
+
if (!byKey) variants.set(envelope, byKey = /* @__PURE__ */ new Map());
|
|
1284
|
+
while (byKey.size >= max) byKey.delete(byKey.keys().next().value);
|
|
1285
|
+
byKey.set(key, out);
|
|
1286
|
+
return out;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Make a memoized `/meta` object shareable across requests: frozen (deeply in
|
|
1290
|
+
* development / test, so an in-place mutation throws instead of serving old
|
|
1291
|
+
* bytes) and registered for prerendering with a weak ETag.
|
|
1292
|
+
*/
|
|
1293
|
+
function shareMeta(obj) {
|
|
1294
|
+
if (shouldDeepFreeze()) deepFreeze(obj);
|
|
1295
|
+
else Object.freeze(obj);
|
|
1296
|
+
return prerenderJson(obj, { etag: true });
|
|
1297
|
+
}
|
|
1298
|
+
let deepFreezeOn;
|
|
1299
|
+
function shouldDeepFreeze() {
|
|
1300
|
+
if (deepFreezeOn === void 0) {
|
|
1301
|
+
const env = typeof process === "undefined" ? void 0 : process.env.NODE_ENV;
|
|
1302
|
+
deepFreezeOn = env === "test" || env === "development";
|
|
1303
|
+
}
|
|
1304
|
+
return deepFreezeOn;
|
|
1305
|
+
}
|
|
1306
|
+
const deepFrozen = /* @__PURE__ */ new WeakSet();
|
|
1307
|
+
function deepFreeze(value) {
|
|
1308
|
+
if (value === null || typeof value !== "object" || deepFrozen.has(value)) return;
|
|
1309
|
+
deepFrozen.add(value);
|
|
1310
|
+
Object.freeze(value);
|
|
1311
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
1312
|
+
}
|
|
1313
|
+
//#endregion
|
|
1093
1314
|
//#region src/db/meta-projection.ts
|
|
1094
1315
|
/**
|
|
1095
1316
|
* Exact-or-ancestor membership in the write whitelist: `credit.credentials.user`
|
|
@@ -1648,30 +1869,45 @@ async function applyArbacMetaOverlay(meta, source, _indexes) {
|
|
|
1648
1869
|
const instance = useControllerContext().getController();
|
|
1649
1870
|
const { methods } = controllerMethodIndex(instance);
|
|
1650
1871
|
const crudKeys = Object.keys(meta.crud);
|
|
1651
|
-
const [allowedActions, crudScopes] = await Promise.all([
|
|
1872
|
+
const [allowedActions, crudScopes, arbac] = await Promise.all([
|
|
1873
|
+
grantedActions(instance, meta.actions.map((entry) => entry.name)),
|
|
1874
|
+
Promise.all(crudKeys.map((op) => evaluateHandlers(crudHandlerIds(instance, op, methods)))),
|
|
1875
|
+
currentMoostArbac()
|
|
1876
|
+
]);
|
|
1877
|
+
const memoMax = arbac.getMetaMemoMax();
|
|
1652
1878
|
const allowed = new Set(allowedActions);
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1879
|
+
const actionBits = meta.actions.map((entry) => allowed.has(entry.name));
|
|
1880
|
+
const crudBits = crudScopes.map((scopes) => scopes !== void 0);
|
|
1655
1881
|
const readScopes = [];
|
|
1656
1882
|
const writeScopes = [];
|
|
1657
1883
|
for (let i = 0; i < crudKeys.length; i++) {
|
|
1658
1884
|
const scopes = crudScopes[i];
|
|
1659
1885
|
if (!scopes) continue;
|
|
1660
|
-
crud[crudKeys[i]] = meta.crud[crudKeys[i]];
|
|
1661
1886
|
(WRITE_CRUD_OPS.has(crudKeys[i]) ? writeScopes : readScopes).push(...scopes);
|
|
1662
1887
|
}
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1888
|
+
const overlay = () => {
|
|
1889
|
+
const crud = {};
|
|
1890
|
+
for (let i = 0; i < crudKeys.length; i++) if (crudBits[i]) crud[crudKeys[i]] = meta.crud[crudKeys[i]];
|
|
1891
|
+
return {
|
|
1892
|
+
...meta,
|
|
1893
|
+
actions: meta.actions.filter((_, i) => actionBits[i]),
|
|
1894
|
+
crud
|
|
1895
|
+
};
|
|
1667
1896
|
};
|
|
1668
|
-
if (readScopes.length === 0) return
|
|
1897
|
+
if (readScopes.length === 0) return memoMetaProjection(meta, memoMax > 0 ? metaDecisionKey({
|
|
1898
|
+
actions: actionBits,
|
|
1899
|
+
crud: crudBits,
|
|
1900
|
+
readScopes,
|
|
1901
|
+
writeScopes
|
|
1902
|
+
}, RECORD_CTX) : void 0, memoMax, overlay);
|
|
1669
1903
|
const groupBy = effectiveScope(readScopes).controls.$groupBy;
|
|
1670
1904
|
const writable = collectWritableFields(writeScopes, false);
|
|
1671
1905
|
const nestedWrites = effectiveScope(writeScopes).nestedWrites;
|
|
1672
1906
|
if (isIdentifierSet(source)) {
|
|
1673
1907
|
const vis = buildScopeVisibility(readScopes, void 0, source);
|
|
1674
|
-
|
|
1908
|
+
const visible = Object.keys(vis.allowed).length > 0 || vis.withGrants.size > 0;
|
|
1909
|
+
const overlaid = overlay();
|
|
1910
|
+
return gateGroupable(visible ? pruneMetaByVisibility(overlaid, {
|
|
1675
1911
|
...vis,
|
|
1676
1912
|
writable,
|
|
1677
1913
|
nestedWrites
|
|
@@ -1679,12 +1915,30 @@ async function applyArbacMetaOverlay(meta, source, _indexes) {
|
|
|
1679
1915
|
}
|
|
1680
1916
|
const vis = buildScopeVisibility(readScopes, source, { relations: await resolveRelationTree(readScopes, source, metaRelationNames(meta), { listSiblings: false }) });
|
|
1681
1917
|
const refTargets = await resolveRefTargets(vis, meta);
|
|
1682
|
-
return
|
|
1918
|
+
return memoMetaProjection(meta, memoMax > 0 ? metaDecisionKey({
|
|
1919
|
+
actions: actionBits,
|
|
1920
|
+
crud: crudBits,
|
|
1921
|
+
readScopes,
|
|
1922
|
+
writeScopes,
|
|
1923
|
+
projection: {
|
|
1924
|
+
vis,
|
|
1925
|
+
tree: metaRelationNames(meta),
|
|
1926
|
+
writable,
|
|
1927
|
+
nestedWrites,
|
|
1928
|
+
groupBy,
|
|
1929
|
+
refTargets
|
|
1930
|
+
}
|
|
1931
|
+
}, RECORD_CTX) : void 0, memoMax, () => gateGroupable(pruneMetaByVisibility(overlay(), {
|
|
1683
1932
|
...vis,
|
|
1684
1933
|
writable,
|
|
1685
1934
|
nestedWrites
|
|
1686
|
-
}, refTargets), groupBy);
|
|
1935
|
+
}, refTargets), groupBy));
|
|
1687
1936
|
}
|
|
1937
|
+
/** What the `/meta` memo's decision record reads from the pruning rules. */
|
|
1938
|
+
const RECORD_CTX = {
|
|
1939
|
+
isServed: isDbServed,
|
|
1940
|
+
identifiers: identifierSet
|
|
1941
|
+
};
|
|
1688
1942
|
/**
|
|
1689
1943
|
* The `/meta/form/:name` overlay: a form is a plain annotated type (no table),
|
|
1690
1944
|
* so only its cross-table pointers are projected. Every prop's `ref` and
|
|
@@ -2595,8 +2849,9 @@ function insertRowsVerdict(rows, check) {
|
|
|
2595
2849
|
if (error instanceof UnsupportedCheckError) return void 0;
|
|
2596
2850
|
throw error;
|
|
2597
2851
|
}
|
|
2852
|
+
const fieldList = [...fields];
|
|
2598
2853
|
let verdict = true;
|
|
2599
|
-
for (const row of rows) if (
|
|
2854
|
+
for (const row of rows) if (fieldList.some((f) => getPath(row, f) === void 0)) verdict = void 0;
|
|
2600
2855
|
else if (!predicate(row)) return false;
|
|
2601
2856
|
return verdict;
|
|
2602
2857
|
}
|
|
@@ -3190,11 +3445,21 @@ function requestsField(requested, field) {
|
|
|
3190
3445
|
async function applyValueHelpMetaOverlay(host, meta) {
|
|
3191
3446
|
const overlaid = await applyArbacMetaOverlay(meta, host.source);
|
|
3192
3447
|
if (!overlaid.searchable || overlaid.fields === meta.fields) return overlaid;
|
|
3193
|
-
|
|
3448
|
+
if (host.searchableFields.some((f) => f in overlaid.fields)) return overlaid;
|
|
3449
|
+
if (!Object.isFrozen(overlaid)) return {
|
|
3194
3450
|
...overlaid,
|
|
3195
3451
|
searchable: false
|
|
3196
3452
|
};
|
|
3453
|
+
let perHost = unsearchable.get(overlaid);
|
|
3454
|
+
if (!perHost) unsearchable.set(overlaid, perHost = /* @__PURE__ */ new WeakMap());
|
|
3455
|
+
let out = perHost.get(host);
|
|
3456
|
+
if (!out) perHost.set(host, out = shareMeta({
|
|
3457
|
+
...overlaid,
|
|
3458
|
+
searchable: false
|
|
3459
|
+
}));
|
|
3460
|
+
return out;
|
|
3197
3461
|
}
|
|
3462
|
+
const unsearchable = /* @__PURE__ */ new WeakMap();
|
|
3198
3463
|
let AsArbacValueHelpController = class AsArbacValueHelpController extends AsValueHelpController {
|
|
3199
3464
|
_arbacHost;
|
|
3200
3465
|
/** Makes this controller the binding / `ref` policy source for its bound type's ARBAC resource. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/arbac-moost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.78",
|
|
4
4
|
"description": "Moost RBAC integration for aoothjs (migrated from @moostjs/arbac)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abac",
|
|
@@ -60,31 +60,31 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@uniqu/core": "^0.1.
|
|
64
|
-
"@aooth/arbac": "0.1.
|
|
65
|
-
"@aooth/
|
|
66
|
-
"@aooth/
|
|
63
|
+
"@uniqu/core": "^0.1.14",
|
|
64
|
+
"@aooth/arbac": "0.1.78",
|
|
65
|
+
"@aooth/user": "0.1.78",
|
|
66
|
+
"@aooth/arbac-core": "0.1.78"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@atscript/core": "^0.1.
|
|
70
|
-
"@atscript/db": "^0.1.
|
|
71
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
72
|
-
"@atscript/db-sqlite": "^0.1.
|
|
73
|
-
"@atscript/moost-db": "^0.1.
|
|
74
|
-
"@atscript/typescript": "^0.1.
|
|
75
|
-
"@moostjs/event-http": "^0.6.
|
|
69
|
+
"@atscript/core": "^0.1.102",
|
|
70
|
+
"@atscript/db": "^0.1.152",
|
|
71
|
+
"@atscript/db-sql-tools": "^0.1.152",
|
|
72
|
+
"@atscript/db-sqlite": "^0.1.152",
|
|
73
|
+
"@atscript/moost-db": "^0.1.152",
|
|
74
|
+
"@atscript/typescript": "^0.1.102",
|
|
75
|
+
"@moostjs/event-http": "^0.6.48",
|
|
76
76
|
"@types/better-sqlite3": "^7.6.13",
|
|
77
77
|
"better-sqlite3": "^12.6.2",
|
|
78
|
-
"unplugin-atscript": "^0.1.
|
|
78
|
+
"unplugin-atscript": "^0.1.102"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@atscript/db": "^0.1.
|
|
82
|
-
"@atscript/moost-db": "^0.1.
|
|
83
|
-
"@atscript/typescript": "^0.1.
|
|
84
|
-
"@moostjs/event-http": "^0.6.
|
|
85
|
-
"@wooksjs/event-core": "^0.7.
|
|
86
|
-
"@wooksjs/event-http": "^0.7.
|
|
87
|
-
"moost": "^0.6.
|
|
81
|
+
"@atscript/db": "^0.1.152",
|
|
82
|
+
"@atscript/moost-db": "^0.1.152",
|
|
83
|
+
"@atscript/typescript": "^0.1.102",
|
|
84
|
+
"@moostjs/event-http": "^0.6.48",
|
|
85
|
+
"@wooksjs/event-core": "^0.7.27",
|
|
86
|
+
"@wooksjs/event-http": "^0.7.27",
|
|
87
|
+
"moost": "^0.6.48"
|
|
88
88
|
},
|
|
89
89
|
"peerDependenciesMeta": {
|
|
90
90
|
"@atscript/db": {
|