@objectstack/plugin-sharing 9.10.0 → 9.11.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +43 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +122 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/objects/sys-sharing-rule.object.ts +2 -2
- package/src/role-graph.test.ts +60 -0
- package/src/role-graph.ts +108 -0
- package/src/sharing-rule-service.ts +11 -0
- package/src/sharing-service.test.ts +36 -0
- package/src/sharing-service.ts +11 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/plugin-sharing@9.
|
|
2
|
+
> @objectstack/plugin-sharing@9.11.0 build /home/runner/work/framework/framework/packages/plugins/plugin-sharing
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m114.82 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m199.06 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 319ms
|
|
16
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m112.85 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m199.03 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 326ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m403.
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m403.
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 31341ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m403.41 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m403.41 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @objectstack/plugin-sharing
|
|
2
2
|
|
|
3
|
+
## 9.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2365d07: feat(sharing): configurable role-hierarchy widening — `role_and_subordinates` recipient (ADR-0056 D6)
|
|
8
|
+
|
|
9
|
+
Role-hierarchy access widening ("a manager sees records shared with their team") is now
|
|
10
|
+
**implemented and configurable per sharing rule**, not a hardcoded no-op. The
|
|
11
|
+
`role_and_subordinates` recipient (declarable on `sys_sharing_rule.recipient_type`) expands,
|
|
12
|
+
at evaluation time, to the named role **plus every subordinate role** by walking the
|
|
13
|
+
`sys_role.parent` hierarchy via a new `RoleGraphService` (mirroring the department/team
|
|
14
|
+
graphs; cycle-safe). Previously `Role.parent` was declared but never consumed — a silent
|
|
15
|
+
no-op flagged by the ADR-0056 audit. This is the Salesforce "grant access using hierarchies"
|
|
16
|
+
model expressed declaratively: each rule chooses whether to roll up the hierarchy. Unit-proven
|
|
17
|
+
(role-graph traversal, subordinate-user expansion, cycle safety); the recipient is added to
|
|
18
|
+
the authoring select + the `SharingRuleRecipientType` contract.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- e7f6539: feat(spec,sharing): canonical OWD vocabulary on `object.sharingModel` (ADR-0056 D1)
|
|
23
|
+
|
|
24
|
+
Reconciles the Org-Wide-Default naming so authors use ONE vocabulary. `object.sharingModel`
|
|
25
|
+
now accepts the canonical OWD names — `private` | `public_read` | `public_read_write` |
|
|
26
|
+
`controlled_by_parent` — alongside the legacy `read` / `read_write` / `full` aliases (kept,
|
|
27
|
+
non-breaking). The sharing runtime maps them onto the three enforced behaviours
|
|
28
|
+
(`public_read` ≡ legacy `read` = everyone reads / owner writes; `public_read_write` =
|
|
29
|
+
unscoped). Unknown values remain rejected by the enum (authoring-time, fail-closed). The
|
|
30
|
+
showcase announcement now declares the canonical `public_read`, exercised end-to-end by the
|
|
31
|
+
public-read dogfood proof.
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [e7f6539]
|
|
34
|
+
- Updated dependencies [2365d07]
|
|
35
|
+
- Updated dependencies [6595b53]
|
|
36
|
+
- Updated dependencies [fa8964d]
|
|
37
|
+
- Updated dependencies [36138c7]
|
|
38
|
+
- Updated dependencies [a8e4f3b]
|
|
39
|
+
- Updated dependencies [4c213c2]
|
|
40
|
+
- Updated dependencies [2afb612]
|
|
41
|
+
- @objectstack/spec@9.11.0
|
|
42
|
+
- @objectstack/objectql@9.11.0
|
|
43
|
+
- @objectstack/core@9.11.0
|
|
44
|
+
- @objectstack/platform-objects@9.11.0
|
|
45
|
+
|
|
3
46
|
## 9.10.0
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -661,7 +661,7 @@ declare const SysRecordShare: Omit<{
|
|
|
661
661
|
clone: boolean;
|
|
662
662
|
apiMethods?: ("search" | "create" | "import" | "delete" | "list" | "get" | "update" | "upsert" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
663
663
|
} | undefined;
|
|
664
|
-
sharingModel?: "full" | "read" | "private" | "read_write" | undefined;
|
|
664
|
+
sharingModel?: "full" | "read" | "private" | "public_read" | "public_read_write" | "controlled_by_parent" | "read_write" | undefined;
|
|
665
665
|
publicSharing?: {
|
|
666
666
|
enabled: boolean;
|
|
667
667
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -3457,7 +3457,7 @@ declare const SysSharingRule: Omit<{
|
|
|
3457
3457
|
clone: boolean;
|
|
3458
3458
|
apiMethods?: ("search" | "create" | "import" | "delete" | "list" | "get" | "update" | "upsert" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
3459
3459
|
} | undefined;
|
|
3460
|
-
sharingModel?: "full" | "read" | "private" | "read_write" | undefined;
|
|
3460
|
+
sharingModel?: "full" | "read" | "private" | "public_read" | "public_read_write" | "controlled_by_parent" | "read_write" | undefined;
|
|
3461
3461
|
publicSharing?: {
|
|
3462
3462
|
enabled: boolean;
|
|
3463
3463
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -6383,7 +6383,7 @@ declare const SysShareLink: Omit<{
|
|
|
6383
6383
|
clone: boolean;
|
|
6384
6384
|
apiMethods?: ("search" | "create" | "import" | "delete" | "list" | "get" | "update" | "upsert" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
6385
6385
|
} | undefined;
|
|
6386
|
-
sharingModel?: "full" | "read" | "private" | "read_write" | undefined;
|
|
6386
|
+
sharingModel?: "full" | "read" | "private" | "public_read" | "public_read_write" | "controlled_by_parent" | "read_write" | undefined;
|
|
6387
6387
|
publicSharing?: {
|
|
6388
6388
|
enabled: boolean;
|
|
6389
6389
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -661,7 +661,7 @@ declare const SysRecordShare: Omit<{
|
|
|
661
661
|
clone: boolean;
|
|
662
662
|
apiMethods?: ("search" | "create" | "import" | "delete" | "list" | "get" | "update" | "upsert" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
663
663
|
} | undefined;
|
|
664
|
-
sharingModel?: "full" | "read" | "private" | "read_write" | undefined;
|
|
664
|
+
sharingModel?: "full" | "read" | "private" | "public_read" | "public_read_write" | "controlled_by_parent" | "read_write" | undefined;
|
|
665
665
|
publicSharing?: {
|
|
666
666
|
enabled: boolean;
|
|
667
667
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -3457,7 +3457,7 @@ declare const SysSharingRule: Omit<{
|
|
|
3457
3457
|
clone: boolean;
|
|
3458
3458
|
apiMethods?: ("search" | "create" | "import" | "delete" | "list" | "get" | "update" | "upsert" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
3459
3459
|
} | undefined;
|
|
3460
|
-
sharingModel?: "full" | "read" | "private" | "read_write" | undefined;
|
|
3460
|
+
sharingModel?: "full" | "read" | "private" | "public_read" | "public_read_write" | "controlled_by_parent" | "read_write" | undefined;
|
|
3461
3461
|
publicSharing?: {
|
|
3462
3462
|
enabled: boolean;
|
|
3463
3463
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
|
@@ -6383,7 +6383,7 @@ declare const SysShareLink: Omit<{
|
|
|
6383
6383
|
clone: boolean;
|
|
6384
6384
|
apiMethods?: ("search" | "create" | "import" | "delete" | "list" | "get" | "update" | "upsert" | "bulk" | "aggregate" | "history" | "restore" | "purge" | "export")[] | undefined;
|
|
6385
6385
|
} | undefined;
|
|
6386
|
-
sharingModel?: "full" | "read" | "private" | "read_write" | undefined;
|
|
6386
|
+
sharingModel?: "full" | "read" | "private" | "public_read" | "public_read_write" | "controlled_by_parent" | "read_write" | undefined;
|
|
6387
6387
|
publicSharing?: {
|
|
6388
6388
|
enabled: boolean;
|
|
6389
6389
|
allowedAudiences?: ("email" | "public" | "link_only" | "signed_in")[] | undefined;
|
package/dist/index.js
CHANGED
|
@@ -1448,12 +1448,12 @@ var SysSharingRule = import_data2.ObjectSchema.create({
|
|
|
1448
1448
|
group: "Target"
|
|
1449
1449
|
}),
|
|
1450
1450
|
recipient_type: import_data2.Field.select(
|
|
1451
|
-
["user", "team", "department", "role", "queue"],
|
|
1451
|
+
["user", "team", "department", "role", "role_and_subordinates", "queue"],
|
|
1452
1452
|
{
|
|
1453
1453
|
label: "Recipient Type",
|
|
1454
1454
|
required: true,
|
|
1455
1455
|
defaultValue: "department",
|
|
1456
|
-
description: "Kind of principal that receives access \u2014 expanded to user grants at evaluation time. `department` walks the parent_department_id tree; `team` is flat (better-auth).",
|
|
1456
|
+
description: "Kind of principal that receives access \u2014 expanded to user grants at evaluation time. `department` walks the parent_department_id tree; `team` is flat (better-auth); `role` is the role's direct members; `role_and_subordinates` walks the sys_role.parent hierarchy to also include every subordinate role (ADR-0056 D6).",
|
|
1457
1457
|
group: "Recipient"
|
|
1458
1458
|
}
|
|
1459
1459
|
),
|
|
@@ -1711,7 +1711,7 @@ var OWNER_FIELD = "owner_id";
|
|
|
1711
1711
|
function effectiveSharingModel(schema) {
|
|
1712
1712
|
const m = schema?.sharingModel ?? schema?.security?.sharingModel;
|
|
1713
1713
|
if (m === "private") return "private";
|
|
1714
|
-
if (m === "read") return "read";
|
|
1714
|
+
if (m === "read" || m === "public_read") return "read";
|
|
1715
1715
|
return "public";
|
|
1716
1716
|
}
|
|
1717
1717
|
function hasOwnerField(schema) {
|
|
@@ -1978,8 +1978,77 @@ async function expandPrincipal(input, ctx) {
|
|
|
1978
1978
|
return [`${t}:${v}`];
|
|
1979
1979
|
}
|
|
1980
1980
|
|
|
1981
|
-
// src/
|
|
1981
|
+
// src/role-graph.ts
|
|
1982
1982
|
var SYSTEM_CTX3 = { isSystem: true, roles: [], permissions: [] };
|
|
1983
|
+
var RoleGraphService = class {
|
|
1984
|
+
constructor(opts) {
|
|
1985
|
+
var _a, _b;
|
|
1986
|
+
this.engine = opts.engine;
|
|
1987
|
+
this.organizationId = opts.organizationId ?? null;
|
|
1988
|
+
this.cache = opts.cache ?? {};
|
|
1989
|
+
(_a = this.cache).descendants ?? (_a.descendants = /* @__PURE__ */ new Map());
|
|
1990
|
+
(_b = this.cache).expand ?? (_b.expand = /* @__PURE__ */ new Map());
|
|
1991
|
+
this.teamGraph = opts.teamGraph ?? new TeamGraphService({ engine: this.engine, organizationId: this.organizationId });
|
|
1992
|
+
}
|
|
1993
|
+
/** Direct child roles of `roleName` (`sys_role.parent === roleName`). */
|
|
1994
|
+
async childRoles(roleName) {
|
|
1995
|
+
const filter = { parent: roleName };
|
|
1996
|
+
if (this.organizationId) filter.organization_id = this.organizationId;
|
|
1997
|
+
let rows = [];
|
|
1998
|
+
try {
|
|
1999
|
+
rows = await this.engine.find("sys_role", {
|
|
2000
|
+
filter,
|
|
2001
|
+
fields: ["name"],
|
|
2002
|
+
limit: 5e3,
|
|
2003
|
+
context: SYSTEM_CTX3
|
|
2004
|
+
});
|
|
2005
|
+
} catch {
|
|
2006
|
+
rows = [];
|
|
2007
|
+
}
|
|
2008
|
+
return Array.from(new Set((rows ?? []).map((r) => String(r.name ?? "")).filter(Boolean)));
|
|
2009
|
+
}
|
|
2010
|
+
/** `roleName` plus every role beneath it in the hierarchy (BFS, cycle-safe). */
|
|
2011
|
+
async descendantRoles(roleName) {
|
|
2012
|
+
if (!roleName) return [];
|
|
2013
|
+
const cached = this.cache.descendants.get(roleName);
|
|
2014
|
+
if (cached) return cached;
|
|
2015
|
+
const out = [];
|
|
2016
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2017
|
+
const queue = [roleName];
|
|
2018
|
+
while (queue.length) {
|
|
2019
|
+
const r = queue.shift();
|
|
2020
|
+
if (seen.has(r)) continue;
|
|
2021
|
+
seen.add(r);
|
|
2022
|
+
out.push(r);
|
|
2023
|
+
for (const child of await this.childRoles(r)) {
|
|
2024
|
+
if (!seen.has(child)) queue.push(child);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
this.cache.descendants.set(roleName, out);
|
|
2028
|
+
return out;
|
|
2029
|
+
}
|
|
2030
|
+
/** Users holding `roleName` OR any subordinate role (the `role_and_subordinates` set). */
|
|
2031
|
+
async expandRoleAndSubordinates(roleName, organizationId) {
|
|
2032
|
+
if (!roleName) return [];
|
|
2033
|
+
const org = organizationId ?? this.organizationId ?? "*";
|
|
2034
|
+
const key = `${org}::${roleName}`;
|
|
2035
|
+
const cached = this.cache.expand.get(key);
|
|
2036
|
+
if (cached) return cached;
|
|
2037
|
+
const roles = await this.descendantRoles(roleName);
|
|
2038
|
+
const users = /* @__PURE__ */ new Set();
|
|
2039
|
+
for (const role of roles) {
|
|
2040
|
+
for (const uid2 of await this.teamGraph.expandRoleUsers(role, organizationId ?? this.organizationId ?? void 0)) {
|
|
2041
|
+
users.add(uid2);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
const result = Array.from(users);
|
|
2045
|
+
this.cache.expand.set(key, result);
|
|
2046
|
+
return result;
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
|
|
2050
|
+
// src/department-graph.ts
|
|
2051
|
+
var SYSTEM_CTX4 = { isSystem: true, roles: [], permissions: [] };
|
|
1983
2052
|
var DepartmentGraphService = class {
|
|
1984
2053
|
constructor(opts) {
|
|
1985
2054
|
var _a, _b, _c;
|
|
@@ -2001,7 +2070,7 @@ var DepartmentGraphService = class {
|
|
|
2001
2070
|
filter: this.orgScope({ id: departmentId }),
|
|
2002
2071
|
fields: ["id", "active"],
|
|
2003
2072
|
limit: 1,
|
|
2004
|
-
context:
|
|
2073
|
+
context: SYSTEM_CTX4
|
|
2005
2074
|
});
|
|
2006
2075
|
const seedRow = Array.isArray(seedRows) ? seedRows[0] : null;
|
|
2007
2076
|
if (!seedRow) seedActive = false;
|
|
@@ -2023,7 +2092,7 @@ var DepartmentGraphService = class {
|
|
|
2023
2092
|
filter: this.orgScope({ parent_department_id: parent, active: { $ne: false } }),
|
|
2024
2093
|
fields: ["id"],
|
|
2025
2094
|
limit: 1e3,
|
|
2026
|
-
context:
|
|
2095
|
+
context: SYSTEM_CTX4
|
|
2027
2096
|
});
|
|
2028
2097
|
} catch {
|
|
2029
2098
|
children = [];
|
|
@@ -2052,7 +2121,7 @@ var DepartmentGraphService = class {
|
|
|
2052
2121
|
filter: { department_id: { $in: depts } },
|
|
2053
2122
|
fields: ["user_id"],
|
|
2054
2123
|
limit: 1e4,
|
|
2055
|
-
context:
|
|
2124
|
+
context: SYSTEM_CTX4
|
|
2056
2125
|
});
|
|
2057
2126
|
} catch {
|
|
2058
2127
|
rows = [];
|
|
@@ -2072,7 +2141,7 @@ var DepartmentGraphService = class {
|
|
|
2072
2141
|
filter: { id: departmentId },
|
|
2073
2142
|
fields: ["id", "manager_user_id"],
|
|
2074
2143
|
limit: 1,
|
|
2075
|
-
context:
|
|
2144
|
+
context: SYSTEM_CTX4
|
|
2076
2145
|
});
|
|
2077
2146
|
row = Array.isArray(rows) ? rows[0] : null;
|
|
2078
2147
|
} catch {
|
|
@@ -2090,7 +2159,7 @@ var DepartmentGraphService = class {
|
|
|
2090
2159
|
filter: { id: userId },
|
|
2091
2160
|
fields: ["id", "manager_id"],
|
|
2092
2161
|
limit: 1,
|
|
2093
|
-
context:
|
|
2162
|
+
context: SYSTEM_CTX4
|
|
2094
2163
|
});
|
|
2095
2164
|
const row = Array.isArray(rows) ? rows[0] : null;
|
|
2096
2165
|
return row?.manager_id ? String(row.manager_id) : null;
|
|
@@ -2105,7 +2174,7 @@ var DepartmentGraphService = class {
|
|
|
2105
2174
|
};
|
|
2106
2175
|
|
|
2107
2176
|
// src/sharing-rule-service.ts
|
|
2108
|
-
var
|
|
2177
|
+
var SYSTEM_CTX5 = { isSystem: true, roles: [], permissions: [] };
|
|
2109
2178
|
function uid(prefix) {
|
|
2110
2179
|
const g = globalThis;
|
|
2111
2180
|
if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;
|
|
@@ -2161,7 +2230,7 @@ var SharingRuleService = class {
|
|
|
2161
2230
|
const existing = await this.engine.find("sys_sharing_rule", {
|
|
2162
2231
|
filter: orgId ? { name: input.name, organization_id: orgId } : { name: input.name },
|
|
2163
2232
|
limit: 1,
|
|
2164
|
-
context:
|
|
2233
|
+
context: SYSTEM_CTX5
|
|
2165
2234
|
});
|
|
2166
2235
|
if (Array.isArray(existing) && existing[0]) {
|
|
2167
2236
|
const row = existing[0];
|
|
@@ -2177,7 +2246,7 @@ var SharingRuleService = class {
|
|
|
2177
2246
|
active,
|
|
2178
2247
|
updated_at: now
|
|
2179
2248
|
};
|
|
2180
|
-
await this.engine.update("sys_sharing_rule", patch, { context:
|
|
2249
|
+
await this.engine.update("sys_sharing_rule", patch, { context: SYSTEM_CTX5 });
|
|
2181
2250
|
return rowFromRule({ ...row, ...patch });
|
|
2182
2251
|
}
|
|
2183
2252
|
const newRow = {
|
|
@@ -2195,7 +2264,7 @@ var SharingRuleService = class {
|
|
|
2195
2264
|
created_at: now,
|
|
2196
2265
|
updated_at: now
|
|
2197
2266
|
};
|
|
2198
|
-
await this.engine.insert("sys_sharing_rule", newRow, { context:
|
|
2267
|
+
await this.engine.insert("sys_sharing_rule", newRow, { context: SYSTEM_CTX5 });
|
|
2199
2268
|
return rowFromRule(newRow);
|
|
2200
2269
|
}
|
|
2201
2270
|
async listRules(filter, context) {
|
|
@@ -2208,7 +2277,7 @@ var SharingRuleService = class {
|
|
|
2208
2277
|
filter: where,
|
|
2209
2278
|
orderBy: [{ field: "name", order: "asc" }],
|
|
2210
2279
|
limit: 1e3,
|
|
2211
|
-
context:
|
|
2280
|
+
context: SYSTEM_CTX5
|
|
2212
2281
|
});
|
|
2213
2282
|
return Array.isArray(rows) ? rows.map(rowFromRule) : [];
|
|
2214
2283
|
}
|
|
@@ -2218,13 +2287,13 @@ var SharingRuleService = class {
|
|
|
2218
2287
|
const byId = await this.engine.find("sys_sharing_rule", {
|
|
2219
2288
|
filter: { id: idOrName },
|
|
2220
2289
|
limit: 1,
|
|
2221
|
-
context:
|
|
2290
|
+
context: SYSTEM_CTX5
|
|
2222
2291
|
});
|
|
2223
2292
|
if (Array.isArray(byId) && byId[0]) return rowFromRule(byId[0]);
|
|
2224
2293
|
const byName = await this.engine.find("sys_sharing_rule", {
|
|
2225
2294
|
filter: orgId ? { name: idOrName, organization_id: orgId } : { name: idOrName },
|
|
2226
2295
|
limit: 1,
|
|
2227
|
-
context:
|
|
2296
|
+
context: SYSTEM_CTX5
|
|
2228
2297
|
});
|
|
2229
2298
|
if (Array.isArray(byName) && byName[0]) return rowFromRule(byName[0]);
|
|
2230
2299
|
return null;
|
|
@@ -2234,11 +2303,11 @@ var SharingRuleService = class {
|
|
|
2234
2303
|
if (!row) return;
|
|
2235
2304
|
await this.engine.delete("sys_record_share", {
|
|
2236
2305
|
where: { source: "rule", source_id: row.id },
|
|
2237
|
-
context:
|
|
2306
|
+
context: SYSTEM_CTX5
|
|
2238
2307
|
});
|
|
2239
2308
|
await this.engine.delete("sys_sharing_rule", {
|
|
2240
2309
|
where: { id: row.id },
|
|
2241
|
-
context:
|
|
2310
|
+
context: SYSTEM_CTX5
|
|
2242
2311
|
});
|
|
2243
2312
|
}
|
|
2244
2313
|
async evaluateRule(idOrName, context) {
|
|
@@ -2271,7 +2340,7 @@ var SharingRuleService = class {
|
|
|
2271
2340
|
filter,
|
|
2272
2341
|
fields: ["id"],
|
|
2273
2342
|
limit: 5e3,
|
|
2274
|
-
context:
|
|
2343
|
+
context: SYSTEM_CTX5
|
|
2275
2344
|
});
|
|
2276
2345
|
return Array.isArray(rows) ? rows.map((r) => String(r.id)).filter(Boolean) : [];
|
|
2277
2346
|
} catch (err) {
|
|
@@ -2286,7 +2355,7 @@ var SharingRuleService = class {
|
|
|
2286
2355
|
filter,
|
|
2287
2356
|
fields: ["id"],
|
|
2288
2357
|
limit: 1,
|
|
2289
|
-
context:
|
|
2358
|
+
context: SYSTEM_CTX5
|
|
2290
2359
|
});
|
|
2291
2360
|
return Array.isArray(rows) && rows.length > 0;
|
|
2292
2361
|
} catch {
|
|
@@ -2309,6 +2378,14 @@ var SharingRuleService = class {
|
|
|
2309
2378
|
return dept.expandUsers(rule.recipient_id);
|
|
2310
2379
|
}
|
|
2311
2380
|
if (rule.recipient_type === "role") return team.expandRoleUsers(rule.recipient_id, rule.organization_id ?? void 0);
|
|
2381
|
+
if (rule.recipient_type === "role_and_subordinates") {
|
|
2382
|
+
const roleGraph = new RoleGraphService({
|
|
2383
|
+
engine: this.engine,
|
|
2384
|
+
organizationId: rule.organization_id ?? null,
|
|
2385
|
+
teamGraph: team
|
|
2386
|
+
});
|
|
2387
|
+
return roleGraph.expandRoleAndSubordinates(rule.recipient_id, rule.organization_id ?? void 0);
|
|
2388
|
+
}
|
|
2312
2389
|
return [];
|
|
2313
2390
|
}
|
|
2314
2391
|
async reconcile(rule, matchedIds, users) {
|
|
@@ -2316,7 +2393,7 @@ var SharingRuleService = class {
|
|
|
2316
2393
|
filter: { source: "rule", source_id: rule.id },
|
|
2317
2394
|
fields: ["id", "record_id", "recipient_id", "access_level"],
|
|
2318
2395
|
limit: 1e5,
|
|
2319
|
-
context:
|
|
2396
|
+
context: SYSTEM_CTX5
|
|
2320
2397
|
});
|
|
2321
2398
|
const desired = /* @__PURE__ */ new Map();
|
|
2322
2399
|
for (const rid of matchedIds) {
|
|
@@ -2342,7 +2419,7 @@ var SharingRuleService = class {
|
|
|
2342
2419
|
sourceId: rule.id,
|
|
2343
2420
|
reason: `rule:${rule.name}`
|
|
2344
2421
|
},
|
|
2345
|
-
|
|
2422
|
+
SYSTEM_CTX5
|
|
2346
2423
|
);
|
|
2347
2424
|
updated += 1;
|
|
2348
2425
|
}
|
|
@@ -2359,13 +2436,13 @@ var SharingRuleService = class {
|
|
|
2359
2436
|
sourceId: rule.id,
|
|
2360
2437
|
reason: `rule:${rule.name}`
|
|
2361
2438
|
},
|
|
2362
|
-
|
|
2439
|
+
SYSTEM_CTX5
|
|
2363
2440
|
);
|
|
2364
2441
|
created += 1;
|
|
2365
2442
|
}
|
|
2366
2443
|
}
|
|
2367
2444
|
for (const [, stale] of existingMap.entries()) {
|
|
2368
|
-
await this.sharing.revoke(stale.id,
|
|
2445
|
+
await this.sharing.revoke(stale.id, SYSTEM_CTX5);
|
|
2369
2446
|
revoked += 1;
|
|
2370
2447
|
}
|
|
2371
2448
|
return {
|
|
@@ -2382,7 +2459,7 @@ var SharingRuleService = class {
|
|
|
2382
2459
|
filter: { source: "rule", source_id: rule.id, record_id: recordId },
|
|
2383
2460
|
fields: ["id", "record_id", "recipient_id", "access_level"],
|
|
2384
2461
|
limit: 1e3,
|
|
2385
|
-
context:
|
|
2462
|
+
context: SYSTEM_CTX5
|
|
2386
2463
|
});
|
|
2387
2464
|
const existingMap = /* @__PURE__ */ new Map();
|
|
2388
2465
|
for (const row of existing ?? []) existingMap.set(String(row.recipient_id), row);
|
|
@@ -2405,7 +2482,7 @@ var SharingRuleService = class {
|
|
|
2405
2482
|
sourceId: rule.id,
|
|
2406
2483
|
reason: `rule:${rule.name}`
|
|
2407
2484
|
},
|
|
2408
|
-
|
|
2485
|
+
SYSTEM_CTX5
|
|
2409
2486
|
);
|
|
2410
2487
|
updated += 1;
|
|
2411
2488
|
}
|
|
@@ -2422,14 +2499,14 @@ var SharingRuleService = class {
|
|
|
2422
2499
|
sourceId: rule.id,
|
|
2423
2500
|
reason: `rule:${rule.name}`
|
|
2424
2501
|
},
|
|
2425
|
-
|
|
2502
|
+
SYSTEM_CTX5
|
|
2426
2503
|
);
|
|
2427
2504
|
created += 1;
|
|
2428
2505
|
}
|
|
2429
2506
|
}
|
|
2430
2507
|
}
|
|
2431
2508
|
for (const [, stale] of existingMap.entries()) {
|
|
2432
|
-
await this.sharing.revoke(stale.id,
|
|
2509
|
+
await this.sharing.revoke(stale.id, SYSTEM_CTX5);
|
|
2433
2510
|
revoked += 1;
|
|
2434
2511
|
}
|
|
2435
2512
|
return {
|
|
@@ -2446,11 +2523,11 @@ var SharingRuleService = class {
|
|
|
2446
2523
|
filter: { source: "rule", source_id: ruleId },
|
|
2447
2524
|
fields: ["id"],
|
|
2448
2525
|
limit: 1e5,
|
|
2449
|
-
context:
|
|
2526
|
+
context: SYSTEM_CTX5
|
|
2450
2527
|
});
|
|
2451
2528
|
let revoked = 0;
|
|
2452
2529
|
for (const row of existing ?? []) {
|
|
2453
|
-
await this.sharing.revoke(row.id,
|
|
2530
|
+
await this.sharing.revoke(row.id, SYSTEM_CTX5);
|
|
2454
2531
|
revoked += 1;
|
|
2455
2532
|
}
|
|
2456
2533
|
return revoked;
|
|
@@ -2458,7 +2535,7 @@ var SharingRuleService = class {
|
|
|
2458
2535
|
};
|
|
2459
2536
|
|
|
2460
2537
|
// src/share-link-service.ts
|
|
2461
|
-
var
|
|
2538
|
+
var SYSTEM_CTX6 = { isSystem: true, roles: [], permissions: [] };
|
|
2462
2539
|
var TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
2463
2540
|
var TOKEN_LENGTH = 24;
|
|
2464
2541
|
var DEFAULT_MAX_EXPIRY_DAYS = 365;
|
|
@@ -2598,7 +2675,7 @@ var ShareLinkService = class {
|
|
|
2598
2675
|
where: { id: input.recordId },
|
|
2599
2676
|
fields: ["id"],
|
|
2600
2677
|
limit: 1,
|
|
2601
|
-
context:
|
|
2678
|
+
context: SYSTEM_CTX6
|
|
2602
2679
|
});
|
|
2603
2680
|
if (!Array.isArray(exists) || exists.length === 0) {
|
|
2604
2681
|
throw makeError(404, "RECORD_NOT_FOUND", `${input.object}/${input.recordId} does not exist`);
|
|
@@ -2624,7 +2701,7 @@ var ShareLinkService = class {
|
|
|
2624
2701
|
last_used_at: null,
|
|
2625
2702
|
use_count: 0
|
|
2626
2703
|
};
|
|
2627
|
-
await this.engine.insert("sys_share_link", row, { context:
|
|
2704
|
+
await this.engine.insert("sys_share_link", row, { context: SYSTEM_CTX6 });
|
|
2628
2705
|
return row;
|
|
2629
2706
|
}
|
|
2630
2707
|
async revokeLink(idOrToken, _context) {
|
|
@@ -2634,7 +2711,7 @@ var ShareLinkService = class {
|
|
|
2634
2711
|
where: filter,
|
|
2635
2712
|
fields: ["id", "revoked_at"],
|
|
2636
2713
|
limit: 1,
|
|
2637
|
-
context:
|
|
2714
|
+
context: SYSTEM_CTX6
|
|
2638
2715
|
});
|
|
2639
2716
|
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
2640
2717
|
if (!row) return;
|
|
@@ -2642,7 +2719,7 @@ var ShareLinkService = class {
|
|
|
2642
2719
|
await this.engine.update(
|
|
2643
2720
|
"sys_share_link",
|
|
2644
2721
|
{ id: row.id, revoked_at: (/* @__PURE__ */ new Date()).toISOString() },
|
|
2645
|
-
{ context:
|
|
2722
|
+
{ context: SYSTEM_CTX6 }
|
|
2646
2723
|
);
|
|
2647
2724
|
}
|
|
2648
2725
|
async listLinks(filter, context) {
|
|
@@ -2655,7 +2732,7 @@ var ShareLinkService = class {
|
|
|
2655
2732
|
where,
|
|
2656
2733
|
limit: 200,
|
|
2657
2734
|
sort: [{ field: "created_at", order: "desc" }],
|
|
2658
|
-
context: context.isSystem ?
|
|
2735
|
+
context: context.isSystem ? SYSTEM_CTX6 : context
|
|
2659
2736
|
});
|
|
2660
2737
|
return Array.isArray(rows) ? rows : [];
|
|
2661
2738
|
}
|
|
@@ -2664,7 +2741,7 @@ var ShareLinkService = class {
|
|
|
2664
2741
|
const rows = await this.engine.find("sys_share_link", {
|
|
2665
2742
|
where: { token },
|
|
2666
2743
|
limit: 1,
|
|
2667
|
-
context:
|
|
2744
|
+
context: SYSTEM_CTX6
|
|
2668
2745
|
});
|
|
2669
2746
|
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
2670
2747
|
if (!row) return null;
|
|
@@ -2694,7 +2771,7 @@ var ShareLinkService = class {
|
|
|
2694
2771
|
last_used_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2695
2772
|
use_count: (row.use_count ?? 0) + 1
|
|
2696
2773
|
},
|
|
2697
|
-
{ context:
|
|
2774
|
+
{ context: SYSTEM_CTX6 }
|
|
2698
2775
|
);
|
|
2699
2776
|
} catch {
|
|
2700
2777
|
}
|
|
@@ -2703,7 +2780,7 @@ var ShareLinkService = class {
|
|
|
2703
2780
|
};
|
|
2704
2781
|
|
|
2705
2782
|
// src/share-link-routes.ts
|
|
2706
|
-
var
|
|
2783
|
+
var SYSTEM_CTX7 = { isSystem: true, roles: [], permissions: [] };
|
|
2707
2784
|
var defaultContext = (req) => {
|
|
2708
2785
|
const header = (name) => {
|
|
2709
2786
|
const v = req.headers?.[name];
|
|
@@ -2804,7 +2881,7 @@ function registerShareLinkRoutes(http, service, engine, opts = {}) {
|
|
|
2804
2881
|
const probe = await engine.find("sys_share_link", {
|
|
2805
2882
|
where: { token: req.params.token },
|
|
2806
2883
|
limit: 1,
|
|
2807
|
-
context:
|
|
2884
|
+
context: SYSTEM_CTX7
|
|
2808
2885
|
});
|
|
2809
2886
|
const row = Array.isArray(probe) && probe[0] ? probe[0] : null;
|
|
2810
2887
|
if (row && !row.revoked_at && (!row.expires_at || Date.parse(row.expires_at) > Date.now())) {
|
|
@@ -2828,7 +2905,7 @@ function registerShareLinkRoutes(http, service, engine, opts = {}) {
|
|
|
2828
2905
|
const rows = await engine.find(resolved.link.object_name, {
|
|
2829
2906
|
where: { id: resolved.link.record_id },
|
|
2830
2907
|
limit: 1,
|
|
2831
|
-
context:
|
|
2908
|
+
context: SYSTEM_CTX7
|
|
2832
2909
|
});
|
|
2833
2910
|
const record = Array.isArray(rows) && rows[0] ? rows[0] : null;
|
|
2834
2911
|
if (!record) {
|
|
@@ -2865,12 +2942,12 @@ function registerShareLinkRoutes(http, service, engine, opts = {}) {
|
|
|
2865
2942
|
sendError(res, 400, "UNSUPPORTED", "This share link does not expose messages");
|
|
2866
2943
|
return;
|
|
2867
2944
|
}
|
|
2868
|
-
const
|
|
2945
|
+
const SYSTEM_CTX9 = { isSystem: true, roles: [], permissions: [] };
|
|
2869
2946
|
const rows = await engine.find("ai_messages", {
|
|
2870
2947
|
where: { conversation_id: resolved.link.record_id },
|
|
2871
2948
|
sort: [{ field: "created_at", order: "asc" }],
|
|
2872
2949
|
limit: 500,
|
|
2873
|
-
context:
|
|
2950
|
+
context: SYSTEM_CTX9
|
|
2874
2951
|
});
|
|
2875
2952
|
res.status(200).json({ data: rows ?? [] });
|
|
2876
2953
|
} catch (err) {
|
|
@@ -2885,7 +2962,7 @@ function registerShareLinkRoutes(http, service, engine, opts = {}) {
|
|
|
2885
2962
|
}
|
|
2886
2963
|
|
|
2887
2964
|
// src/rule-hooks.ts
|
|
2888
|
-
var
|
|
2965
|
+
var SYSTEM_CTX8 = { isSystem: true, roles: [], permissions: [] };
|
|
2889
2966
|
var SHARING_RULE_HOOK_PACKAGE = "plugin-sharing:rules";
|
|
2890
2967
|
function bindRuleHooks(engine, service, rules, logger) {
|
|
2891
2968
|
const objects = /* @__PURE__ */ new Set();
|
|
@@ -2900,7 +2977,7 @@ function bindRuleHooks(engine, service, rules, logger) {
|
|
|
2900
2977
|
const data = ctx?.result ?? ctx?.input?.data ?? {};
|
|
2901
2978
|
const id = String(data?.id ?? ctx?.input?.id ?? "");
|
|
2902
2979
|
if (!id) return;
|
|
2903
|
-
await service.evaluateAllForRecord(objectName, id,
|
|
2980
|
+
await service.evaluateAllForRecord(objectName, id, SYSTEM_CTX8);
|
|
2904
2981
|
} catch (err) {
|
|
2905
2982
|
logger?.warn?.("[sharing-rule] hook evaluation failed", { object: objectName, error: err?.message });
|
|
2906
2983
|
}
|