@opensaas/stack-core 0.38.0 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +125 -0
- package/CLAUDE.md +21 -0
- package/dist/access/access-filter.d.ts +46 -1
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +113 -4
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/declared-dependencies.d.ts.map +1 -1
- package/dist/access/declared-dependencies.js +4 -9
- package/dist/access/declared-dependencies.js.map +1 -1
- package/dist/access/engine.d.ts +0 -20
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +0 -25
- package/dist/access/engine.js.map +1 -1
- package/dist/access/errors.d.ts +49 -0
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +75 -0
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +39 -1
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +108 -42
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +229 -2
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-transforms.d.ts +0 -27
- package/dist/access/field-transforms.d.ts.map +1 -1
- package/dist/access/field-visibility.d.ts +0 -4
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +13 -39
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +6 -2
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +12 -2
- package/dist/access/index.js.map +1 -1
- package/dist/access/query-validation.d.ts +121 -0
- package/dist/access/query-validation.d.ts.map +1 -0
- package/dist/access/query-validation.js +292 -0
- package/dist/access/query-validation.js.map +1 -0
- package/dist/access/relationship-count.d.ts.map +1 -1
- package/dist/access/relationship-count.js +2 -10
- package/dist/access/relationship-count.js.map +1 -1
- package/dist/access/relationship-label-filter.d.ts +23 -23
- package/dist/access/relationship-label-filter.d.ts.map +1 -1
- package/dist/access/relationship-label-filter.js +5 -76
- package/dist/access/relationship-label-filter.js.map +1 -1
- package/dist/access/relationship-label-filter.test.js +19 -56
- package/dist/access/relationship-label-filter.test.js.map +1 -1
- package/dist/access/transaction-registry.d.ts +45 -0
- package/dist/access/transaction-registry.d.ts.map +1 -0
- package/dist/access/transaction-registry.js +37 -0
- package/dist/access/transaction-registry.js.map +1 -0
- package/dist/access/types.d.ts +55 -59
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +9 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -18
- package/dist/config/index.js.map +1 -1
- package/dist/config/label.d.ts +6 -13
- package/dist/config/label.d.ts.map +1 -1
- package/dist/config/label.js +6 -13
- package/dist/config/label.js.map +1 -1
- package/dist/config/nav-count.d.ts +11 -19
- package/dist/config/nav-count.d.ts.map +1 -1
- package/dist/config/nav-count.js +11 -19
- package/dist/config/nav-count.js.map +1 -1
- package/dist/config/plugin-engine.d.ts +0 -10
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +1 -37
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +94 -109
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/apply-defaults.d.ts +8 -31
- package/dist/context/apply-defaults.d.ts.map +1 -1
- package/dist/context/apply-defaults.js +12 -41
- package/dist/context/apply-defaults.js.map +1 -1
- package/dist/context/hook-pipeline.d.ts +4 -29
- package/dist/context/hook-pipeline.d.ts.map +1 -1
- package/dist/context/hook-pipeline.js +8 -41
- package/dist/context/hook-pipeline.js.map +1 -1
- package/dist/context/index.d.ts +4 -15
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +190 -193
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts +7 -26
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +73 -171
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/transaction-boundary.d.ts +36 -47
- package/dist/context/transaction-boundary.d.ts.map +1 -1
- package/dist/context/transaction-boundary.js +92 -79
- package/dist/context/transaction-boundary.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +25 -64
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +81 -111
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/format-prisma-default.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.js +4 -7
- package/dist/fields/format-prisma-default.js.map +1 -1
- package/dist/fields/index.d.ts +17 -2
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +136 -147
- package/dist/fields/index.js.map +1 -1
- package/dist/filter/collect.d.ts +26 -3
- package/dist/filter/collect.d.ts.map +1 -1
- package/dist/filter/collect.js +30 -12
- package/dist/filter/collect.js.map +1 -1
- package/dist/filter/filter.test.js +98 -15
- package/dist/filter/filter.test.js.map +1 -1
- package/dist/filter/index.d.ts +1 -0
- package/dist/filter/index.d.ts.map +1 -1
- package/dist/filter/index.js.map +1 -1
- package/dist/filter/map.d.ts.map +1 -1
- package/dist/filter/map.js +0 -5
- package/dist/filter/map.js.map +1 -1
- package/dist/filter/parse.d.ts.map +1 -1
- package/dist/filter/parse.js +2 -9
- package/dist/filter/parse.js.map +1 -1
- package/dist/filter/serialize.d.ts.map +1 -1
- package/dist/filter/serialize.js +2 -4
- package/dist/filter/serialize.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -42
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -59
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/case-utils.d.ts +0 -32
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +0 -32
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/mcp/handler.d.ts +0 -4
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +12 -69
- package/dist/mcp/handler.js.map +1 -1
- package/dist/query/index.d.ts +26 -227
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +14 -117
- package/dist/query/index.js.map +1 -1
- package/dist/utils/password.d.ts +0 -68
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +7 -76
- package/dist/utils/password.js.map +1 -1
- package/dist/validation/field-config.d.ts.map +1 -1
- package/dist/validation/field-config.js +0 -6
- package/dist/validation/field-config.js.map +1 -1
- package/dist/validation/schema.d.ts +0 -7
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +0 -10
- package/dist/validation/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.ts +152 -5
- package/src/access/declared-dependencies.ts +4 -9
- package/src/access/engine.ts +0 -25
- package/src/access/errors.ts +81 -0
- package/src/access/field-access.test.ts +312 -2
- package/src/access/field-access.ts +118 -42
- package/src/access/field-transforms.ts +8 -39
- package/src/access/field-visibility.ts +16 -47
- package/src/access/index.ts +20 -2
- package/src/access/query-validation.ts +359 -0
- package/src/access/relationship-count.ts +2 -10
- package/src/access/relationship-label-filter.test.ts +19 -68
- package/src/access/relationship-label-filter.ts +27 -104
- package/src/access/transaction-registry.ts +58 -0
- package/src/access/types.ts +83 -86
- package/src/config/index.ts +10 -22
- package/src/config/label.ts +6 -13
- package/src/config/nav-count.ts +11 -19
- package/src/config/plugin-engine.ts +1 -37
- package/src/config/types.ts +95 -110
- package/src/context/apply-defaults.ts +12 -44
- package/src/context/hook-pipeline.ts +12 -67
- package/src/context/index.ts +263 -247
- package/src/context/nested-operations.ts +94 -226
- package/src/context/transaction-boundary.ts +113 -109
- package/src/context/write-pipeline.ts +99 -155
- package/src/fields/format-prisma-default.ts +4 -7
- package/src/fields/index.ts +155 -147
- package/src/filter/collect.ts +48 -12
- package/src/filter/filter.test.ts +153 -15
- package/src/filter/index.ts +1 -0
- package/src/filter/map.ts +0 -5
- package/src/filter/parse.ts +2 -9
- package/src/filter/serialize.ts +2 -4
- package/src/hooks/index.ts +5 -59
- package/src/index.ts +23 -4
- package/src/internal.ts +5 -0
- package/src/lib/case-utils.ts +0 -32
- package/src/mcp/handler.ts +13 -71
- package/src/query/index.ts +29 -235
- package/src/utils/password.ts +7 -76
- package/src/validation/field-config.ts +0 -6
- package/src/validation/schema.ts +0 -10
- package/tests/context.test.ts +700 -1
- package/tests/field-types.test.ts +222 -0
- package/tests/mcp-handler.test.ts +34 -0
- package/tests/transaction-boundary-outermost.test.ts +530 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { checkAccess, getRelatedListConfig } from './engine.js';
|
|
2
|
-
import { mergeResolvedMember } from './relationship-count.js';
|
|
3
1
|
/**
|
|
4
2
|
* Whether a field is a to-one relationship — the only field kind whose Filter
|
|
5
3
|
* spec emits a nested `{ is: {...} } }` condition against the related list's
|
|
@@ -12,82 +10,13 @@ export function isToOneRelationshipField(field) {
|
|
|
12
10
|
typeof field.ref === 'string' &&
|
|
13
11
|
field.ref.length > 0);
|
|
14
12
|
}
|
|
15
|
-
/** Narrow a filter member's field value to a `{ is: {...} } }` clause, if it is one. */
|
|
16
|
-
function readIsClause(value) {
|
|
17
|
-
if (!value || typeof value !== 'object')
|
|
18
|
-
return null;
|
|
19
|
-
const isValue = value.is;
|
|
20
|
-
if (!isValue || typeof isValue !== 'object')
|
|
21
|
-
return null;
|
|
22
|
-
return isValue;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Resolve one to-one relationship label-filter member into an access-scoped
|
|
26
|
-
* condition by ANDing the related list's `query` access filter into the nested
|
|
27
|
-
* `is` clause. Returns `{ id: { in: [] } }` (never matches) when the related
|
|
28
|
-
* list is fully denied, the original member unchanged when it is fully
|
|
29
|
-
* readable, and the `is` clause ANDed with the access filter otherwise.
|
|
30
|
-
*/
|
|
31
|
-
async function resolveOneLabelFilter(field, fieldName, isClause, args, config) {
|
|
32
|
-
if (!('ref' in field) || typeof field.ref !== 'string') {
|
|
33
|
-
return { [fieldName]: { is: isClause } };
|
|
34
|
-
}
|
|
35
|
-
const related = getRelatedListConfig(field.ref, config);
|
|
36
|
-
if (!related)
|
|
37
|
-
return { [fieldName]: { is: isClause } };
|
|
38
|
-
const queryAccess = related.listConfig.access?.operation?.query;
|
|
39
|
-
const result = await checkAccess(queryAccess, { session: args.session, context: args.context });
|
|
40
|
-
if (result === false)
|
|
41
|
-
return { id: { in: [] } };
|
|
42
|
-
if (result === true)
|
|
43
|
-
return { [fieldName]: { is: isClause } };
|
|
44
|
-
return { [fieldName]: { is: { AND: [result, isClause] } } };
|
|
45
|
-
}
|
|
46
13
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* top-level AND members (the relationship Filter spec does not declare
|
|
50
|
-
* `freeText`, so it never participates in the free-text OR), so this walks
|
|
51
|
-
* only the top level. Returns the `where` unchanged when it contains no such
|
|
52
|
-
* members, so lists without to-one relationship filters pay nothing.
|
|
14
|
+
* No longer folds access into label filters — see the module doc above.
|
|
15
|
+
* Returns `where` unchanged.
|
|
53
16
|
*/
|
|
54
17
|
export async function resolveRelationshipLabelFilters(where,
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return where;
|
|
59
|
-
const andValue = where.AND;
|
|
60
|
-
const members = Array.isArray(andValue)
|
|
61
|
-
? andValue
|
|
62
|
-
: [where];
|
|
63
|
-
const findLabelFilter = (member) => {
|
|
64
|
-
for (const key of Object.keys(member)) {
|
|
65
|
-
const field = listConfig.fields[key];
|
|
66
|
-
if (!isToOneRelationshipField(field))
|
|
67
|
-
continue;
|
|
68
|
-
const isClause = readIsClause(member[key]);
|
|
69
|
-
if (!isClause)
|
|
70
|
-
continue;
|
|
71
|
-
return { field: key, isClause };
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
};
|
|
75
|
-
if (!members.some((member) => findLabelFilter(member) !== null)) {
|
|
76
|
-
return where;
|
|
77
|
-
}
|
|
78
|
-
const resolvedMembers = [];
|
|
79
|
-
for (const member of members) {
|
|
80
|
-
const found = findLabelFilter(member);
|
|
81
|
-
if (!found) {
|
|
82
|
-
resolvedMembers.push(member);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
const field = listConfig.fields[found.field];
|
|
86
|
-
const resolved = await resolveOneLabelFilter(field, found.field, found.isClause, args, config);
|
|
87
|
-
const siblings = { ...member };
|
|
88
|
-
delete siblings[found.field];
|
|
89
|
-
resolvedMembers.push(mergeResolvedMember(siblings, resolved));
|
|
90
|
-
}
|
|
91
|
-
return resolvedMembers.length === 1 ? resolvedMembers[0] : { AND: resolvedMembers };
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo, kept for API compatibility (see module doc)
|
|
19
|
+
_listConfig, _args, _config) {
|
|
20
|
+
return where;
|
|
92
21
|
}
|
|
93
22
|
//# sourceMappingURL=relationship-label-filter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-label-filter.js","sourceRoot":"","sources":["../../src/access/relationship-label-filter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"relationship-label-filter.js","sourceRoot":"","sources":["../../src/access/relationship-label-filter.ts"],"names":[],"mappings":"AAmCA;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAA8B;IACrE,OAAO,CACL,KAAK,EAAE,IAAI,KAAK,cAAc;QAC9B,CAAC,CAAC,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QACzC,KAAK,IAAI,KAAK;QACd,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;QAC7B,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CACrB,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,KAA0C;AAC1C,kJAAkJ;AAClJ,WAA4B,EAC5B,KAAsB,EACtB,OAAuB;IAEvB,OAAO,KAAK,CAAA;AACd,CAAC"}
|
|
@@ -3,14 +3,15 @@ import { list } from '../config/index.js';
|
|
|
3
3
|
import { text, relationship, integer } from '../fields/index.js';
|
|
4
4
|
import { resolveRelationshipLabelFilters, isToOneRelationshipField, } from './relationship-label-filter.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* `resolveRelationshipLabelFilters` used to fold the related list's `query`
|
|
7
|
+
* access into a to-one relationship label filter's nested `is` clause
|
|
8
|
+
* (issue #749). Since #916, the engine itself scopes every relation filter in
|
|
9
|
+
* `where` (`buildAccessScopedWhere`), including this exact `{ is: {...} } }`
|
|
10
|
+
* shape, so this resolver's own fold is redundant and has been removed — it
|
|
11
|
+
* is now a pass-through, kept exported only for API compatibility. These
|
|
12
|
+
* tests pin that pass-through behavior; `access-filter.test.ts` and
|
|
13
|
+
* `context.test.ts` cover the actual access-scoping this resolver used to do.
|
|
11
14
|
*/
|
|
12
|
-
// Post is fully open; User (the `author` relation target) is scoped so only
|
|
13
|
-
// active users are queryable; Widget ships fully closed (no access block).
|
|
14
15
|
function makeConfig() {
|
|
15
16
|
return {
|
|
16
17
|
db: { provider: 'sqlite', prismaClientConstructor: () => null },
|
|
@@ -52,75 +53,37 @@ describe('isToOneRelationshipField', () => {
|
|
|
52
53
|
expect(isToOneRelationshipField(postFields.title)).toBe(false);
|
|
53
54
|
});
|
|
54
55
|
});
|
|
55
|
-
describe('resolveRelationshipLabelFilters', () => {
|
|
56
|
-
it('returns
|
|
56
|
+
describe('resolveRelationshipLabelFilters (pass-through since #916)', () => {
|
|
57
|
+
it('returns a plain where unchanged', async () => {
|
|
57
58
|
const config = makeConfig();
|
|
58
59
|
const where = { title: { contains: 'hello' } };
|
|
59
60
|
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
60
61
|
expect(resolved).toBe(where);
|
|
61
62
|
});
|
|
62
|
-
it(
|
|
63
|
+
it('returns a to-one label-filter `is` clause unchanged — the engine scopes it now', async () => {
|
|
63
64
|
const config = makeConfig();
|
|
64
65
|
const where = { author: { is: { name: { contains: 'Ada' } } } };
|
|
65
66
|
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
66
|
-
expect(resolved).
|
|
67
|
-
author: { is: { AND: [{ active: { equals: 1 } }, { name: { contains: 'Ada' } }] } },
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
it('leaves the member unchanged when the related list is fully readable', async () => {
|
|
71
|
-
const config = {
|
|
72
|
-
db: { provider: 'sqlite', prismaClientConstructor: () => null },
|
|
73
|
-
lists: {
|
|
74
|
-
Tag: list({ fields: { name: text() }, access: { operation: { query: () => true } } }),
|
|
75
|
-
Post: list({ fields: { title: text(), tag: relationship({ ref: 'Tag' }) } }),
|
|
76
|
-
},
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- minimal config for unit test
|
|
78
|
-
};
|
|
79
|
-
const where = { tag: { is: { name: { contains: 'news' } } } };
|
|
80
|
-
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
81
|
-
expect(resolved).toEqual(where);
|
|
67
|
+
expect(resolved).toBe(where);
|
|
82
68
|
});
|
|
83
|
-
it('
|
|
69
|
+
it('returns a label filter against a fully denied related list unchanged too', async () => {
|
|
84
70
|
const config = makeConfig();
|
|
85
71
|
const where = { widget: { is: { name: { contains: 'Gadget' } } } };
|
|
86
72
|
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
87
|
-
expect(resolved).
|
|
88
|
-
});
|
|
89
|
-
it('preserves sibling conditions, ANDing the resolved id constraint for a denied member', async () => {
|
|
90
|
-
const config = makeConfig();
|
|
91
|
-
const where = {
|
|
92
|
-
AND: [{ title: { contains: 'hello' } }, { widget: { is: { name: { contains: 'Gadget' } } } }],
|
|
93
|
-
};
|
|
94
|
-
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
95
|
-
expect(resolved).toEqual({
|
|
96
|
-
AND: [{ title: { contains: 'hello' } }, { id: { in: [] } }],
|
|
97
|
-
});
|
|
73
|
+
expect(resolved).toBe(where);
|
|
98
74
|
});
|
|
99
|
-
it('
|
|
75
|
+
it('returns a where with nested AND/label-filter members unchanged', async () => {
|
|
100
76
|
const config = makeConfig();
|
|
101
77
|
const where = {
|
|
102
78
|
AND: [{ title: { contains: 'hello' } }, { author: { is: { name: { contains: 'Ada' } } } }],
|
|
103
79
|
};
|
|
104
80
|
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
105
|
-
expect(resolved).
|
|
106
|
-
AND: [
|
|
107
|
-
{ title: { contains: 'hello' } },
|
|
108
|
-
{ author: { is: { AND: [{ active: { equals: 1 } }, { name: { contains: 'Ada' } }] } } },
|
|
109
|
-
],
|
|
110
|
-
});
|
|
81
|
+
expect(resolved).toBe(where);
|
|
111
82
|
});
|
|
112
|
-
it('
|
|
83
|
+
it('returns undefined unchanged when there is no where at all', async () => {
|
|
113
84
|
const config = makeConfig();
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
};
|
|
117
|
-
const resolved = await resolveRelationshipLabelFilters(where, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
118
|
-
expect(resolved).toEqual({
|
|
119
|
-
AND: [
|
|
120
|
-
{ author: { is: { AND: [{ active: { equals: 1 } }, { name: { contains: 'Ada' } }] } } },
|
|
121
|
-
{ views: { gt: 10 } },
|
|
122
|
-
],
|
|
123
|
-
});
|
|
85
|
+
const resolved = await resolveRelationshipLabelFilters(undefined, config.lists.Post, { session: null, context: makeContext() }, config);
|
|
86
|
+
expect(resolved).toBeUndefined();
|
|
124
87
|
});
|
|
125
88
|
});
|
|
126
89
|
//# sourceMappingURL=relationship-label-filter.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-label-filter.test.js","sourceRoot":"","sources":["../../src/access/relationship-label-filter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAGhE,OAAO,EACL,+BAA+B,EAC/B,wBAAwB,GACzB,MAAM,gCAAgC,CAAA;AAEvC
|
|
1
|
+
{"version":3,"file":"relationship-label-filter.test.js","sourceRoot":"","sources":["../../src/access/relationship-label-filter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferral registry for transaction-boundary hooks on a JOINED write (ADR-0028,
|
|
3
|
+
* issue #899).
|
|
4
|
+
*
|
|
5
|
+
* A write that joins a transaction it did not open — because the client it was
|
|
6
|
+
* handed exposes no way to open one (a Prisma transaction client, or a plain
|
|
7
|
+
* write issued from a hook whose context is rebound to one) — cannot itself
|
|
8
|
+
* observe when the enclosing transaction settles. Instead of firing
|
|
9
|
+
* `afterTransaction` optimistically at write time, it enqueues a flush here.
|
|
10
|
+
* The transaction owner (`context.transaction()`, or the Write Pipeline when it
|
|
11
|
+
* actually opens the transaction) drains the queue, in enqueue (write) order,
|
|
12
|
+
* once it observes its own transaction settle — supplying the real
|
|
13
|
+
* committed/rolled-back outcome.
|
|
14
|
+
*
|
|
15
|
+
* This is internal plumbing with no public surface: it is threaded through the
|
|
16
|
+
* same context-rebind path ADR-0010/0012 already use for `plugins` and
|
|
17
|
+
* `_resolveOutputChain` (see `AccessContext['_transactionOwner']`), never
|
|
18
|
+
* exposed on the public `StackContext` type.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The outcome the transaction OWNER observed for the transaction as a whole —
|
|
22
|
+
* distinct from a single write's own {@link import('../hooks/index.js').TransactionOutcome},
|
|
23
|
+
* which additionally carries that write's own persisted `item` or `error`.
|
|
24
|
+
*/
|
|
25
|
+
export interface TransactionSettleOutcome {
|
|
26
|
+
status: 'committed' | 'rolled-back';
|
|
27
|
+
error?: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One deferred write's flush: given the owner's settle outcome, runs that
|
|
31
|
+
* write's `afterTransaction` bracket and appends any hook errors to `errors`.
|
|
32
|
+
*/
|
|
33
|
+
export type QueuedTransactionFlush = (settle: TransactionSettleOutcome, errors: unknown[]) => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* A FIFO queue of deferred transaction-boundary flushes, owned by whichever
|
|
36
|
+
* component opened the enclosing transaction. One registry per owned
|
|
37
|
+
* transaction; a nested `context.transaction()` joins its enclosing owner's
|
|
38
|
+
* registry rather than creating a second one.
|
|
39
|
+
*/
|
|
40
|
+
export declare class TransactionRegistry {
|
|
41
|
+
private readonly queue;
|
|
42
|
+
enqueue(flush: QueuedTransactionFlush): void;
|
|
43
|
+
drain(settle: TransactionSettleOutcome, errors: unknown[]): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=transaction-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-registry.d.ts","sourceRoot":"","sources":["../../src/access/transaction-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,WAAW,GAAG,aAAa,CAAA;IACnC,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,CACnC,MAAM,EAAE,wBAAwB,EAChC,MAAM,EAAE,OAAO,EAAE,KACd,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB;;;;;GAKG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IAErD,OAAO,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAE3C;IAEK,KAAK,CAAC,MAAM,EAAE,wBAAwB,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9E;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferral registry for transaction-boundary hooks on a JOINED write (ADR-0028,
|
|
3
|
+
* issue #899).
|
|
4
|
+
*
|
|
5
|
+
* A write that joins a transaction it did not open — because the client it was
|
|
6
|
+
* handed exposes no way to open one (a Prisma transaction client, or a plain
|
|
7
|
+
* write issued from a hook whose context is rebound to one) — cannot itself
|
|
8
|
+
* observe when the enclosing transaction settles. Instead of firing
|
|
9
|
+
* `afterTransaction` optimistically at write time, it enqueues a flush here.
|
|
10
|
+
* The transaction owner (`context.transaction()`, or the Write Pipeline when it
|
|
11
|
+
* actually opens the transaction) drains the queue, in enqueue (write) order,
|
|
12
|
+
* once it observes its own transaction settle — supplying the real
|
|
13
|
+
* committed/rolled-back outcome.
|
|
14
|
+
*
|
|
15
|
+
* This is internal plumbing with no public surface: it is threaded through the
|
|
16
|
+
* same context-rebind path ADR-0010/0012 already use for `plugins` and
|
|
17
|
+
* `_resolveOutputChain` (see `AccessContext['_transactionOwner']`), never
|
|
18
|
+
* exposed on the public `StackContext` type.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* A FIFO queue of deferred transaction-boundary flushes, owned by whichever
|
|
22
|
+
* component opened the enclosing transaction. One registry per owned
|
|
23
|
+
* transaction; a nested `context.transaction()` joins its enclosing owner's
|
|
24
|
+
* registry rather than creating a second one.
|
|
25
|
+
*/
|
|
26
|
+
export class TransactionRegistry {
|
|
27
|
+
queue = [];
|
|
28
|
+
enqueue(flush) {
|
|
29
|
+
this.queue.push(flush);
|
|
30
|
+
}
|
|
31
|
+
async drain(settle, errors) {
|
|
32
|
+
for (const flush of this.queue) {
|
|
33
|
+
await flush(settle, errors);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=transaction-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-registry.js","sourceRoot":"","sources":["../../src/access/transaction-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAqBH;;;;;GAKG;AACH,MAAM,OAAO,mBAAmB;IACb,KAAK,GAA6B,EAAE,CAAA;IAErD,OAAO,CAAC,KAA6B;QACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,MAAgC,EAAE,MAAiB;QAC7D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;CACF"}
|
package/dist/access/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Fragment, FieldSelection, ResultOf } from '../query/index.js';
|
|
2
|
+
import type { TransactionRegistry } from './transaction-registry.js';
|
|
2
3
|
/**
|
|
3
4
|
* Session interface - can be augmented by developers to add custom fields
|
|
4
5
|
*
|
|
@@ -35,9 +36,6 @@ import type { Fragment, FieldSelection, ResultOf } from '../query/index.js';
|
|
|
35
36
|
export interface Session {
|
|
36
37
|
[key: string]: unknown;
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Generic Prisma model delegate type
|
|
40
|
-
*/
|
|
41
39
|
export type PrismaModelDelegate = {
|
|
42
40
|
findUnique: (args: unknown) => Promise<unknown>;
|
|
43
41
|
findFirst: (args: unknown) => Promise<unknown>;
|
|
@@ -47,12 +45,6 @@ export type PrismaModelDelegate = {
|
|
|
47
45
|
delete: (args: unknown) => Promise<unknown>;
|
|
48
46
|
count: (args?: unknown) => Promise<number>;
|
|
49
47
|
};
|
|
50
|
-
/**
|
|
51
|
-
* Generic Prisma client type
|
|
52
|
-
* This is intentionally permissive to allow actual PrismaClient types
|
|
53
|
-
* Uses `any` because Prisma generates highly complex client types that are difficult to constrain
|
|
54
|
-
* This type is used as a generic constraint and the actual type safety comes from TPrisma parameter
|
|
55
|
-
*/
|
|
56
48
|
export type PrismaClientLike = any;
|
|
57
49
|
/**
|
|
58
50
|
* Extra query arguments accepted when a `query` Fragment is provided alongside
|
|
@@ -157,10 +149,6 @@ export interface AugmentedFindUnique<TOriginal extends (...args: any[]) => any>
|
|
|
157
149
|
}): Promise<ResultOf<Fragment<TItem, TFields>> | null>;
|
|
158
150
|
(...args: Parameters<TOriginal>): ReturnType<TOriginal>;
|
|
159
151
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Map Prisma client to access-controlled database context
|
|
162
|
-
* Preserves Prisma's type information for each model
|
|
163
|
-
*/
|
|
164
152
|
export type AccessControlledDB<TPrisma extends PrismaClientLike> = {
|
|
165
153
|
[K in keyof TPrisma]: TPrisma[K] extends {
|
|
166
154
|
findUnique: any;
|
|
@@ -195,42 +183,12 @@ export type AccessControlledDB<TPrisma extends PrismaClientLike> = {
|
|
|
195
183
|
} & {
|
|
196
184
|
[key: string]: any;
|
|
197
185
|
};
|
|
198
|
-
/**
|
|
199
|
-
* Storage utilities for file/image uploads
|
|
200
|
-
*/
|
|
201
186
|
export type StorageUtils = {
|
|
202
|
-
/**
|
|
203
|
-
* Upload a file to storage
|
|
204
|
-
* @param providerName - Name of storage provider from config
|
|
205
|
-
* @param file - File object
|
|
206
|
-
* @param buffer - File contents as Buffer
|
|
207
|
-
* @param options - Upload options (validation, metadata)
|
|
208
|
-
*/
|
|
209
187
|
uploadFile: (providerName: string, file: File, buffer: Buffer, options?: unknown) => Promise<unknown>;
|
|
210
|
-
/**
|
|
211
|
-
* Upload an image with transformations
|
|
212
|
-
* @param providerName - Name of storage provider from config
|
|
213
|
-
* @param file - File object
|
|
214
|
-
* @param buffer - File contents as Buffer
|
|
215
|
-
* @param options - Upload options (validation, transformations, metadata)
|
|
216
|
-
*/
|
|
217
188
|
uploadImage: (providerName: string, file: File, buffer: Buffer, options?: unknown) => Promise<unknown>;
|
|
218
|
-
/**
|
|
219
|
-
* Delete a file from storage
|
|
220
|
-
* @param providerName - Name of storage provider from config
|
|
221
|
-
* @param filename - Name of file to delete
|
|
222
|
-
*/
|
|
223
189
|
deleteFile: (providerName: string, filename: string) => Promise<void>;
|
|
224
|
-
/**
|
|
225
|
-
* Delete an image and all its transformations
|
|
226
|
-
* @param metadata - Image metadata containing storage provider and filename
|
|
227
|
-
*/
|
|
228
190
|
deleteImage: (metadata: unknown) => Promise<void>;
|
|
229
191
|
};
|
|
230
|
-
/**
|
|
231
|
-
* Context type (simplified for access control)
|
|
232
|
-
* Using interface instead of type to allow module augmentation
|
|
233
|
-
*/
|
|
234
192
|
export interface AccessContext<TPrisma extends PrismaClientLike = PrismaClientLike> {
|
|
235
193
|
session: Session | null;
|
|
236
194
|
prisma: TPrisma;
|
|
@@ -255,14 +213,20 @@ export interface AccessContext<TPrisma extends PrismaClientLike = PrismaClientLi
|
|
|
255
213
|
listKey: string;
|
|
256
214
|
fieldKey: string;
|
|
257
215
|
}[];
|
|
216
|
+
/**
|
|
217
|
+
* Present when this context is JOINED into an enclosing transaction it did
|
|
218
|
+
* not open (ADR-0028, #899) — set by `context.transaction()`, or by the
|
|
219
|
+
* Write Pipeline when it opens the transaction the current write's hooks
|
|
220
|
+
* are rebound into. A write reached through a context carrying this defers
|
|
221
|
+
* its `afterTransaction` bracket to the registry instead of firing it at
|
|
222
|
+
* write time; `undefined` for a top-level context with no owner. Threaded
|
|
223
|
+
* through the same context-rebind path as `plugins`/`_resolveOutputChain`.
|
|
224
|
+
* @internal
|
|
225
|
+
*/
|
|
226
|
+
_transactionOwner?: TransactionRegistry;
|
|
258
227
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Prisma filter type - represents a where clause
|
|
261
|
-
* Uses Partial to allow filtering by any subset of fields
|
|
262
|
-
*/
|
|
263
228
|
export type PrismaFilter<T = Record<string, unknown>> = Partial<Record<keyof T, unknown>>;
|
|
264
229
|
/**
|
|
265
|
-
* Access control function type
|
|
266
230
|
* Can return:
|
|
267
231
|
* - boolean: true = allow, false = deny
|
|
268
232
|
* - PrismaFilter: Prisma where clause to filter results
|
|
@@ -273,16 +237,16 @@ export type AccessControl<T = Record<string, unknown>> = (args: {
|
|
|
273
237
|
context: AccessContext;
|
|
274
238
|
}) => boolean | PrismaFilter<T> | Promise<boolean | PrismaFilter<T>>;
|
|
275
239
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
240
|
+
* The per-operation argument shapes a `FieldAccessControl` function is called
|
|
241
|
+
* with — the discriminated union `FieldAccessControl` wraps, and the source
|
|
242
|
+
* of truth `FieldAccess`'s individual `read`/`create`/`update` members are
|
|
243
|
+
* picked from (via `Extract`) below. Keeping this as its own named type is
|
|
244
|
+
* what lets both sides reference the exact same three call shapes instead of
|
|
245
|
+
* two independently-maintained descriptions drifting apart.
|
|
282
246
|
*/
|
|
283
|
-
|
|
247
|
+
type FieldAccessControlArgs<TItem, TCreateInput, TUpdateInput> = {
|
|
284
248
|
session: Session | null;
|
|
285
|
-
item
|
|
249
|
+
item: TItem;
|
|
286
250
|
context: AccessContext;
|
|
287
251
|
inputData?: undefined;
|
|
288
252
|
operation: 'read';
|
|
@@ -298,13 +262,45 @@ export type FieldAccessControl<TItem = Record<string, unknown>, TCreateInput = R
|
|
|
298
262
|
context: AccessContext;
|
|
299
263
|
inputData: TUpdateInput;
|
|
300
264
|
operation: 'update';
|
|
301
|
-
}
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* For create/update operations, receives inputData to validate incoming values.
|
|
268
|
+
*
|
|
269
|
+
* Unlike operation-level `AccessControl`, this returns `boolean` only. Field
|
|
270
|
+
* access is a per-field visibility decision, not a row filter — a denied
|
|
271
|
+
* field is removed, never used to scope which rows are returned (see the
|
|
272
|
+
* "Field-level access" glossary entry in `CONTEXT.md`, ADR-0001, and
|
|
273
|
+
* ADR-0030). A rule that needs to depend on the row or the write payload
|
|
274
|
+
* should evaluate the condition itself and return a boolean, e.g.
|
|
275
|
+
* `({ item, session }) => item?.ownerId === session?.userId`. The runtime
|
|
276
|
+
* evaluator (`checkFieldAccess`) enforces this: a rule that somehow returns
|
|
277
|
+
* anything other than `true`/`false` (bypassing this type) throws rather than
|
|
278
|
+
* defaulting to allow.
|
|
279
|
+
*
|
|
280
|
+
* This is the general, all-operations union — useful for a single function
|
|
281
|
+
* reused across more than one of `FieldAccess`'s `read`/`create`/`update`
|
|
282
|
+
* slots, narrowing on `operation` to tell the call shapes apart. A function
|
|
283
|
+
* written for exactly one slot doesn't need to: `FieldAccess` picks each
|
|
284
|
+
* slot's own single-operation shape out of this union (see below), so e.g. a
|
|
285
|
+
* `read`-only rule sees `item` as always present with no narrowing required.
|
|
286
|
+
*/
|
|
287
|
+
export type FieldAccessControl<TItem = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = (args: FieldAccessControlArgs<TItem, TCreateInput, TUpdateInput>) => boolean | Promise<boolean>;
|
|
302
288
|
/**
|
|
303
|
-
*
|
|
289
|
+
* `read` is typed from the single `operation: 'read'` member of
|
|
290
|
+
* `FieldAccessControlArgs`, not the full `FieldAccessControl` union — so a
|
|
291
|
+
* rule written directly for this slot sees `item` as always present (never
|
|
292
|
+
* `TItem | undefined`) with no narrowing, cast, or `any` needed. A function
|
|
293
|
+
* typed as the broader `FieldAccessControl` union remains assignable here
|
|
294
|
+
* (and to `create`/`update`) — accepting more call shapes than the slot
|
|
295
|
+
* requires is a valid substitute, same as anywhere else function parameters
|
|
296
|
+
* are contravariant.
|
|
304
297
|
*/
|
|
305
298
|
export type FieldAccess<TItem = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = {
|
|
306
|
-
read?:
|
|
299
|
+
read?: (args: Extract<FieldAccessControlArgs<TItem, TCreateInput, TUpdateInput>, {
|
|
300
|
+
operation: 'read';
|
|
301
|
+
}>) => boolean | Promise<boolean>;
|
|
307
302
|
create?: FieldAccessControl<TItem, TCreateInput, TUpdateInput>;
|
|
308
303
|
update?: FieldAccessControl<TItem, TCreateInput, TUpdateInput>;
|
|
309
304
|
};
|
|
305
|
+
export {};
|
|
310
306
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/access/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/access/types.ts"],"names":[],"mappings":"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"}
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { OpenSaasConfig, ListConfig, ListConfigInput } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Executes plugins if present in config.plugins array
|
|
3
|
+
* Define an OpenSaas config, executing `plugins` if present.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Returns synchronously when there are no plugins (backward compatible);
|
|
6
|
+
* returns a `Promise` when plugins are present, since plugin execution is
|
|
7
|
+
* async.
|
|
8
8
|
*/
|
|
9
9
|
export declare function config(userConfig: OpenSaasConfig): OpenSaasConfig | Promise<OpenSaasConfig>;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Define a list with type safety.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* `ListConfigInput<TTypeInfo>` accepts raw field configs; the return type
|
|
14
|
+
* `ListConfig<TTypeInfo>` injects `TTypeInfo` so hook/field callbacks (e.g.
|
|
15
|
+
* `resolveInput`'s `item`) are properly typed once `TTypeInfo` is supplied
|
|
16
|
+
* from generated types.
|
|
15
17
|
*
|
|
16
18
|
* @example
|
|
17
19
|
* ```typescript
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,eAAe,EAGhB,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,eAAe,EAGhB,MAAM,YAAY,CAAA;AAwBnB;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,UAAU,EAAE,cAAc,GAAG,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAM3F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,IAAI,CAAC,SAAS,SAAS,OAAO,YAAY,EAAE,QAAQ,EAClE,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,UAAU,CAAC,SAAS,CAAC,CAQvB;AAED,YAAY,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,eAAe,EACf,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,0BAA0B,EAC1B,WAAW,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,yBAAyB,EACzB,MAAM,EACN,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,YAAY,CAAA"}
|