@milaboratories/pl-middle-layer 1.64.41 → 1.65.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.
Files changed (63) hide show
  1. package/dist/index.cjs +17 -0
  2. package/dist/index.d.ts +3 -1
  3. package/dist/index.js +3 -1
  4. package/dist/middle_layer/frontend_path.cjs +1 -0
  5. package/dist/middle_layer/frontend_path.cjs.map +1 -1
  6. package/dist/middle_layer/frontend_path.js +1 -0
  7. package/dist/middle_layer/frontend_path.js.map +1 -1
  8. package/dist/middle_layer/index.d.ts +2 -1
  9. package/dist/middle_layer/middle_layer.cjs +482 -16
  10. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  11. package/dist/middle_layer/middle_layer.d.ts +172 -12
  12. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  13. package/dist/middle_layer/middle_layer.js +485 -19
  14. package/dist/middle_layer/middle_layer.js.map +1 -1
  15. package/dist/middle_layer/ops.cjs +2 -1
  16. package/dist/middle_layer/ops.cjs.map +1 -1
  17. package/dist/middle_layer/ops.d.ts +5 -1
  18. package/dist/middle_layer/ops.d.ts.map +1 -1
  19. package/dist/middle_layer/ops.js +2 -1
  20. package/dist/middle_layer/ops.js.map +1 -1
  21. package/dist/middle_layer/project.d.ts +4 -4
  22. package/dist/middle_layer/project.d.ts.map +1 -1
  23. package/dist/middle_layer/project_list.cjs +19 -0
  24. package/dist/middle_layer/project_list.cjs.map +1 -1
  25. package/dist/middle_layer/project_list.d.ts +1 -1
  26. package/dist/middle_layer/project_list.d.ts.map +1 -1
  27. package/dist/middle_layer/project_list.js +20 -2
  28. package/dist/middle_layer/project_list.js.map +1 -1
  29. package/dist/middle_layer/sharing_list.cjs +203 -0
  30. package/dist/middle_layer/sharing_list.cjs.map +1 -0
  31. package/dist/middle_layer/sharing_list.d.ts +41 -0
  32. package/dist/middle_layer/sharing_list.d.ts.map +1 -0
  33. package/dist/middle_layer/sharing_list.js +198 -0
  34. package/dist/middle_layer/sharing_list.js.map +1 -0
  35. package/dist/model/index.cjs +30 -0
  36. package/dist/model/index.d.ts +2 -1
  37. package/dist/model/index.js +4 -0
  38. package/dist/model/project_model.d.ts +3 -3
  39. package/dist/model/project_model.d.ts.map +1 -1
  40. package/dist/model/sharing_model.cjs +98 -0
  41. package/dist/model/sharing_model.cjs.map +1 -0
  42. package/dist/model/sharing_model.d.ts +122 -0
  43. package/dist/model/sharing_model.d.ts.map +1 -0
  44. package/dist/model/sharing_model.js +84 -0
  45. package/dist/model/sharing_model.js.map +1 -0
  46. package/dist/mutator/block-pack/frontend.cjs +1 -0
  47. package/dist/mutator/block-pack/frontend.cjs.map +1 -1
  48. package/dist/mutator/block-pack/frontend.js +1 -0
  49. package/dist/mutator/block-pack/frontend.js.map +1 -1
  50. package/dist/mutator/sharing.cjs +138 -0
  51. package/dist/mutator/sharing.cjs.map +1 -0
  52. package/dist/mutator/sharing.js +130 -0
  53. package/dist/mutator/sharing.js.map +1 -0
  54. package/package.json +15 -15
  55. package/src/middle_layer/index.ts +1 -0
  56. package/src/middle_layer/middle_layer.ts +715 -23
  57. package/src/middle_layer/ops.ts +7 -0
  58. package/src/middle_layer/project_list.ts +33 -2
  59. package/src/middle_layer/sharing_list.ts +343 -0
  60. package/src/model/index.ts +2 -0
  61. package/src/model/sharing_model.test.ts +22 -0
  62. package/src/model/sharing_model.ts +169 -0
  63. package/src/mutator/sharing.ts +216 -0
@@ -0,0 +1,203 @@
1
+ const require_sharing_model = require("../model/sharing_model.cjs");
2
+ let _milaboratories_pl_client = require("@milaboratories/pl-client");
3
+ let _milaboratories_pl_tree = require("@milaboratories/pl-tree");
4
+ let _milaboratories_computable = require("@milaboratories/computable");
5
+ //#region src/middle_layer/sharing_list.ts
6
+ /** Per-project view for the donor's change UI, from {@link EnvelopeData.projects}. */
7
+ function envelopeProjects(data) {
8
+ return Object.values(data.projects).map((p) => ({
9
+ projectId: p.source,
10
+ label: p.label,
11
+ updatedAt: p.updatedAt
12
+ }));
13
+ }
14
+ const SharingOutboxPruningFunction = (resource) => {
15
+ if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharingOutboxResourceType) && !(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharedEnvelopeResourceType)) return [];
16
+ return resource.fields;
17
+ };
18
+ const SharingOutboxFieldFilter = _milaboratories_pl_client.treeFilter.or(_milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharingOutboxResourceType.name), _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharedEnvelopeResourceType.name));
19
+ /**
20
+ * Reactive view of the donor's own outbox. Reads each live envelope's immutable
21
+ * {@link EnvelopeData} plus the per-recipient `acceptance/{login}` records from the tree, then
22
+ * enriches each share's full recipient list via `ListGrants` on the envelope (`["*"]` for an
23
+ * everyone-share). `ListGrants` is gated backend-side to the envelope owner (the donor), which is
24
+ * exactly who reads this view.
25
+ *
26
+ * API-level only in M1 (no UI).
27
+ */
28
+ function createOutgoingSharesComputable(pl, tree) {
29
+ return _milaboratories_computable.Computable.make((ctx) => {
30
+ const node = ctx.accessor(tree.entry()).node();
31
+ if (node === void 0) return void 0;
32
+ const drafts = [];
33
+ for (const fieldName of node.listDynamicFields()) {
34
+ const envelope = node.traverse(fieldName);
35
+ if (envelope === void 0) continue;
36
+ if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
37
+ const data = envelope.getDataAsJson();
38
+ if (data === void 0) continue;
39
+ const responses = {};
40
+ for (const f of envelope.listDynamicFields()) {
41
+ if (!f.startsWith("acceptance/")) continue;
42
+ const login = f.slice(require_sharing_model.AcceptanceFieldPrefix.length);
43
+ const accData = envelope.traverse(f)?.getDataAsJson();
44
+ if (accData === void 0) continue;
45
+ responses[login] = {
46
+ action: accData.action,
47
+ timestamp: accData.timestamp
48
+ };
49
+ }
50
+ drafts.push({
51
+ shareId: data.shareId,
52
+ sharedAt: data.sharedAt,
53
+ ...data.expiresAt !== null ? { expiresAt: data.expiresAt } : {},
54
+ mode: data.mode,
55
+ title: data.title,
56
+ projects: envelopeProjects(data),
57
+ responses,
58
+ envelopeRid: envelope.id
59
+ });
60
+ }
61
+ drafts.sort((a, b) => b.sharedAt - a.sharedAt);
62
+ return drafts;
63
+ }, { postprocessValue: async (drafts) => {
64
+ if (drafts === void 0) return void 0;
65
+ const self = pl.userResources.authUser;
66
+ const toRecipients = (grants) => grants.some((g) => (0, _milaboratories_pl_client.isEveryoneUserLogin)(g.user)) ? ["*"] : grants.map((g) => g.user).filter((u) => u !== self);
67
+ const txGrants = pl.hasCapability("txListGrants:v1");
68
+ const settled = await Promise.allSettled(drafts.map((d) => txGrants ? pl.withReadTx("ListShareGrant", (tx) => tx.listGrants(d.envelopeRid)) : pl.userResources.listGrants(d.envelopeRid)));
69
+ return drafts.map(({ envelopeRid: _envelopeRid, ...share }, i) => {
70
+ const r = settled[i];
71
+ return {
72
+ ...share,
73
+ recipients: r.status === "fulfilled" ? toRecipients(r.value) : []
74
+ };
75
+ });
76
+ } });
77
+ }
78
+ /**
79
+ * Creates the donor's outbox synchronized tree (single explicit root) plus the
80
+ * {@link OutgoingShare} computable over it.
81
+ */
82
+ async function createOutgoingShares(pl, outboxRid, env) {
83
+ const tree = await _milaboratories_pl_tree.SynchronizedTreeState.init(pl, outboxRid, {
84
+ ...env.ops.defaultTreeOptions,
85
+ pruning: SharingOutboxPruningFunction,
86
+ fieldFilter: SharingOutboxFieldFilter
87
+ }, env.logger);
88
+ return {
89
+ computable: createOutgoingSharesComputable(pl, tree),
90
+ tree
91
+ };
92
+ }
93
+ const DecisionFieldPrefix = "decision/";
94
+ const SharedEnvelopePruningFunction = (resource) => {
95
+ if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharedEnvelopeResourceType)) return [];
96
+ return resource.fields;
97
+ };
98
+ const PendingSharesFieldFilter = _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharedEnvelopeResourceType.name);
99
+ /**
100
+ * Creates the acceptor's shared-resource discovery tree (a `{kind:'shared'}` seed over
101
+ * {@link SharedEnvelopeResourceType}) plus the {@link PendingShare} computable over it.
102
+ *
103
+ * An envelope surfaces only when its shareId has NO decision/{shareId} on SharingState —
104
+ * the dedup is applied by the caller, which holds the SharingState tree.
105
+ */
106
+ async function createPendingSharesTree(pl, env) {
107
+ return await _milaboratories_pl_tree.SynchronizedTreeState.init(pl, {
108
+ kind: "shared",
109
+ resourceType: (0, _milaboratories_pl_client.resourceType)(require_sharing_model.SharedEnvelopeResourceType.name, require_sharing_model.SharedEnvelopeResourceType.version)
110
+ }, {
111
+ ...env.ops.defaultTreeOptions,
112
+ pruning: SharedEnvelopePruningFunction,
113
+ fieldFilter: PendingSharesFieldFilter
114
+ }, env.logger);
115
+ }
116
+ /**
117
+ * Builds a Computable yielding the acceptor's currently-live envelopes, read from the
118
+ * shared-resource discovery tree the ML already maintains — the single discovery mechanism.
119
+ * Accept/reject `.getValue()` this instead of re-streaming `ListUserResources`, so there is no
120
+ * second discovery path. The envelope's signed `rid` (from the tree node) is what the write tx
121
+ * needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.
122
+ *
123
+ * Returns `undefined` while the tree is still empty/unresolved (mirrors the other tree-backed
124
+ * computables here). Yields a flat list; the caller dedups by `shareId` — at most one live
125
+ * envelope per shareId is expected (the donor keeps one), and a replace tears the old one down.
126
+ */
127
+ function createLiveEnvelopesComputable(sharedTree) {
128
+ return _milaboratories_computable.Computable.make((ctx) => {
129
+ const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();
130
+ const result = [];
131
+ for (const envelope of roots) {
132
+ if (envelope === void 0) continue;
133
+ if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
134
+ const data = envelope.getDataAsJson();
135
+ if (data === void 0) continue;
136
+ result.push({
137
+ rid: envelope.id,
138
+ data
139
+ });
140
+ }
141
+ return result;
142
+ });
143
+ }
144
+ /**
145
+ * Builds the {@link PendingShare} computable over the shared-resource discovery tree, filtered
146
+ * against the set of already-handled shareIds (those with a decision/{shareId} in the acceptor's
147
+ * SharingState). Both trees feed one Computable so it recomputes when either changes.
148
+ *
149
+ * `currentUserLogin` (when known) suppresses the user's own shares: a share-with-everybody grants
150
+ * the everyone-user, so the donor discovers their own envelope as a pending share. There is no
151
+ * scenario where a user accepts their own share, so they are dropped from the pending view.
152
+ */
153
+ function createPendingSharesComputable(sharedTree, sharingStateTree, currentUserLogin) {
154
+ return _milaboratories_computable.Computable.make((ctx) => {
155
+ const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();
156
+ const stateNode = ctx.accessor(sharingStateTree.entry()).node();
157
+ const handled = /* @__PURE__ */ new Set();
158
+ if (stateNode !== void 0) {
159
+ for (const f of stateNode.listDynamicFields()) if (f.startsWith(DecisionFieldPrefix)) handled.add(require_sharing_model.asShareId(f.slice(9)));
160
+ }
161
+ const result = [];
162
+ for (const envelope of roots) {
163
+ if (envelope === void 0) continue;
164
+ if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
165
+ const data = envelope.getDataAsJson();
166
+ if (data === void 0) continue;
167
+ if (handled.has(data.shareId)) continue;
168
+ if (currentUserLogin !== null && data.sender === currentUserLogin) continue;
169
+ if (data.expiresAt !== null && data.expiresAt <= Date.now()) continue;
170
+ result.push({
171
+ shareId: data.shareId,
172
+ sender: data.sender,
173
+ title: data.title,
174
+ mode: data.mode,
175
+ grantedAt: data.sharedAt
176
+ });
177
+ }
178
+ result.sort((a, b) => b.grantedAt - a.grantedAt);
179
+ return result;
180
+ });
181
+ }
182
+ const SharingStatePruningFunction = (resource) => {
183
+ if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharingStateResourceType)) return [];
184
+ return resource.fields;
185
+ };
186
+ const SharingStateFieldFilter = _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharingStateResourceType.name);
187
+ /** Creates the acceptor's SharingState synchronized tree (single explicit root). */
188
+ async function createSharingStateTree(pl, stateRid, env) {
189
+ return await _milaboratories_pl_tree.SynchronizedTreeState.init(pl, stateRid, {
190
+ ...env.ops.defaultTreeOptions,
191
+ pruning: SharingStatePruningFunction,
192
+ fieldFilter: SharingStateFieldFilter
193
+ }, env.logger);
194
+ }
195
+ //#endregion
196
+ exports.createLiveEnvelopesComputable = createLiveEnvelopesComputable;
197
+ exports.createOutgoingShares = createOutgoingShares;
198
+ exports.createOutgoingSharesComputable = createOutgoingSharesComputable;
199
+ exports.createPendingSharesComputable = createPendingSharesComputable;
200
+ exports.createPendingSharesTree = createPendingSharesTree;
201
+ exports.createSharingStateTree = createSharingStateTree;
202
+
203
+ //# sourceMappingURL=sharing_list.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharing_list.cjs","names":["SharingOutboxResourceType","SharedEnvelopeResourceType","treeFilter","Computable","AcceptanceFieldPrefix","SynchronizedTreeState","asShareId","SharingStateResourceType"],"sources":["../../src/middle_layer/sharing_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type { Filter, PlClient, SignedResourceId } from \"@milaboratories/pl-client\";\nimport {\n isEveryoneUserLogin,\n resourceIdToString,\n resourceType,\n resourceTypesEqual,\n treeFilter,\n} from \"@milaboratories/pl-client\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport type { ProjectId } from \"@milaboratories/pl-model-common\";\nimport type {\n EnvelopeAcceptance,\n EnvelopeData,\n EnvelopeMode,\n ShareId,\n} from \"../model/sharing_model\";\nimport {\n AcceptanceFieldPrefix,\n asShareId,\n SharedEnvelopeResourceType,\n SharingOutboxResourceType,\n SharingStateResourceType,\n} from \"../model/sharing_model\";\n\n/** Donor-facing view of one outgoing share. */\nexport interface OutgoingShare {\n shareId: ShareId; // stable logical identity of the share, preserved across changes\n sharedAt: number; // this instance's creation time (ms epoch)\n expiresAt?: number; // EnvelopeData.expiresAt; null maps to undefined = never expires\n mode: EnvelopeMode;\n title: string; // display name shown to recipients; defaults to the first project's name\n /** One entry per project in the pack, so the change UI can offer a per-project decision.\n * `projectId` is the donor's source project id; `updatedAt` is when this project's\n * snapshot was last (re)taken. */\n projects: { projectId: ProjectId; label: string; updatedAt: number }[];\n /** Full recipient logins, from `ListGrants` on the envelope; `[\"*\"]` for everyone-shares. */\n recipients: string[];\n /** Per recipient who has responded: their decision and when, from acceptance/{login}. */\n responses: Record<string, { action: \"accepted\" | \"rejected\"; timestamp: number }>;\n}\n\n/** Per-project view for the donor's change UI, from {@link EnvelopeData.projects}. */\nfunction envelopeProjects(data: EnvelopeData): OutgoingShare[\"projects\"] {\n return Object.values(data.projects).map((p) => ({\n projectId: p.source,\n label: p.label,\n updatedAt: p.updatedAt,\n }));\n}\n\n/** Acceptor-facing view of one pending share. */\nexport interface PendingShare {\n shareId: ShareId;\n sender: string; // EnvelopeData.sender, display only\n title: string; // display name shown to recipients; defaults to the first project's name\n mode: EnvelopeMode; // v1 renders only \"copy\" entries\n grantedAt: number;\n}\n\nconst SharingOutboxPruningFunction: PruningFunction = (resource) => {\n if (\n !resourceTypesEqual(resource.type, SharingOutboxResourceType) &&\n !resourceTypesEqual(resource.type, SharedEnvelopeResourceType)\n )\n return [];\n return resource.fields;\n};\n\n// Server-side traversal scope (modern resourceTree path). Pruning is client-side ONLY and does\n// NOT stop the backend walk — without a fieldFilter the backend descends through the envelope's\n// project/{uuid} snapshots into the whole project graph (StreamManager etc.), whose field-driven\n// finality predicate then throws on the pruned-to-[] fields. Following fields only FROM the outbox\n// and the envelope stops the walk at the project snapshots (UserProject), which we never traverse.\nconst SharingOutboxFieldFilter: Filter = treeFilter.or(\n treeFilter.resourceTypeEq(SharingOutboxResourceType.name),\n treeFilter.resourceTypeEq(SharedEnvelopeResourceType.name),\n);\n\n/** Intermediate of an outgoing share before its full recipient list is fetched: everything the\n * tree carries synchronously, plus the envelope's signed id for the async `ListGrants` enrich. */\ntype OutgoingShareDraft = Omit<OutgoingShare, \"recipients\"> & { envelopeRid: SignedResourceId };\n\n/**\n * Reactive view of the donor's own outbox. Reads each live envelope's immutable\n * {@link EnvelopeData} plus the per-recipient `acceptance/{login}` records from the tree, then\n * enriches each share's full recipient list via `ListGrants` on the envelope (`[\"*\"]` for an\n * everyone-share). `ListGrants` is gated backend-side to the envelope owner (the donor), which is\n * exactly who reads this view.\n *\n * API-level only in M1 (no UI).\n */\nexport function createOutgoingSharesComputable(\n pl: PlClient,\n tree: SynchronizedTreeState,\n): Computable<OutgoingShare[] | undefined> {\n return Computable.make(\n (ctx) => {\n const node = ctx.accessor(tree.entry()).node();\n if (node === undefined) return undefined;\n\n const drafts: OutgoingShareDraft[] = [];\n // Outbox fields are keyed by shareId; each value is a SharedEnvelope.\n for (const fieldName of node.listDynamicFields()) {\n const envelope = node.traverse(fieldName);\n if (envelope === undefined) continue;\n if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;\n\n const data = envelope.getDataAsJson<EnvelopeData>();\n if (data === undefined) continue;\n\n const responses: OutgoingShare[\"responses\"] = {};\n for (const f of envelope.listDynamicFields()) {\n if (!f.startsWith(AcceptanceFieldPrefix)) continue;\n const login = f.slice(AcceptanceFieldPrefix.length);\n const acc = envelope.traverse(f);\n const accData = acc?.getDataAsJson<EnvelopeAcceptance>();\n if (accData === undefined) continue;\n responses[login] = { action: accData.action, timestamp: accData.timestamp };\n }\n\n drafts.push({\n shareId: data.shareId,\n sharedAt: data.sharedAt,\n ...(data.expiresAt !== null ? { expiresAt: data.expiresAt } : {}),\n mode: data.mode,\n title: data.title,\n projects: envelopeProjects(data),\n responses,\n envelopeRid: envelope.id,\n });\n }\n drafts.sort((a, b) => b.sharedAt - a.sharedAt);\n return drafts;\n },\n {\n // Resolve each share's recipients via listGrants. An everyone-grant maps to \"*\"; the donor's\n // own grant is dropped. One tx per envelope so a just-revoked rid faults only its own read,\n // which allSettled degrades to []. Recipients aren't cached — a live share's recipient set can\n // change. Transactional listGrants when available, else the standalone gRPC-only RPC.\n postprocessValue: async (\n drafts: OutgoingShareDraft[] | undefined,\n ): Promise<OutgoingShare[] | undefined> => {\n if (drafts === undefined) return undefined;\n const self = pl.userResources.authUser;\n const toRecipients = (grants: { user: string }[]): string[] =>\n grants.some((g) => isEveryoneUserLogin(g.user))\n ? [\"*\"]\n : grants.map((g) => g.user).filter((u) => u !== self);\n\n const txGrants = pl.hasCapability(\"txListGrants:v1\");\n const settled = await Promise.allSettled(\n drafts.map((d) =>\n txGrants\n ? pl.withReadTx(\"ListShareGrant\", (tx) => tx.listGrants(d.envelopeRid))\n : pl.userResources.listGrants(d.envelopeRid),\n ),\n );\n\n return drafts.map(({ envelopeRid: _envelopeRid, ...share }, i): OutgoingShare => {\n const r = settled[i];\n return { ...share, recipients: r.status === \"fulfilled\" ? toRecipients(r.value) : [] };\n });\n },\n },\n );\n}\n\n/**\n * Creates the donor's outbox synchronized tree (single explicit root) plus the\n * {@link OutgoingShare} computable over it.\n */\nexport async function createOutgoingShares(\n pl: PlClient,\n outboxRid: SignedResourceId,\n env: MiddleLayerEnvironment,\n): Promise<{ tree: SynchronizedTreeState; computable: Computable<OutgoingShare[] | undefined> }> {\n const tree = await SynchronizedTreeState.init(\n pl,\n outboxRid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: SharingOutboxPruningFunction,\n fieldFilter: SharingOutboxFieldFilter,\n },\n env.logger,\n );\n return { computable: createOutgoingSharesComputable(pl, tree), tree };\n}\n\nconst DecisionFieldPrefix = \"decision/\";\n\nconst SharedEnvelopePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, SharedEnvelopeResourceType)) return [];\n return resource.fields;\n};\n\n// Discovery only needs each envelope's immutable EnvelopeData (basic resource data); it must NOT\n// descend into the project snapshots. Following fields only FROM the envelope stops the walk at\n// the UserProject snapshots (their fields are never followed).\nconst PendingSharesFieldFilter: Filter = treeFilter.resourceTypeEq(SharedEnvelopeResourceType.name);\n\n/**\n * Creates the acceptor's shared-resource discovery tree (a `{kind:'shared'}` seed over\n * {@link SharedEnvelopeResourceType}) plus the {@link PendingShare} computable over it.\n *\n * An envelope surfaces only when its shareId has NO decision/{shareId} on SharingState —\n * the dedup is applied by the caller, which holds the SharingState tree.\n */\nexport async function createPendingSharesTree(\n pl: PlClient,\n env: MiddleLayerEnvironment,\n): Promise<SynchronizedTreeState> {\n return await SynchronizedTreeState.init(\n pl,\n {\n kind: \"shared\",\n resourceType: resourceType(\n SharedEnvelopeResourceType.name,\n SharedEnvelopeResourceType.version,\n ),\n },\n {\n ...env.ops.defaultTreeOptions,\n pruning: SharedEnvelopePruningFunction,\n fieldFilter: PendingSharesFieldFilter,\n },\n env.logger,\n );\n}\n\n/** A live envelope discovered in the acceptor's shared-resource tree: its signed resource id and\n * decoded {@link EnvelopeData}. The accept/reject flow keys these by `data.shareId`. */\nexport type LiveEnvelope = { rid: SignedResourceId; data: EnvelopeData };\n\n/**\n * Builds a Computable yielding the acceptor's currently-live envelopes, read from the\n * shared-resource discovery tree the ML already maintains — the single discovery mechanism.\n * Accept/reject `.getValue()` this instead of re-streaming `ListUserResources`, so there is no\n * second discovery path. The envelope's signed `rid` (from the tree node) is what the write tx\n * needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.\n *\n * Returns `undefined` while the tree is still empty/unresolved (mirrors the other tree-backed\n * computables here). Yields a flat list; the caller dedups by `shareId` — at most one live\n * envelope per shareId is expected (the donor keeps one), and a replace tears the old one down.\n */\nexport function createLiveEnvelopesComputable(\n sharedTree: SynchronizedTreeState,\n): Computable<LiveEnvelope[] | undefined> {\n return Computable.make((ctx) => {\n const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();\n const result: LiveEnvelope[] = [];\n for (const envelope of roots) {\n if (envelope === undefined) continue;\n if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;\n const data = envelope.getDataAsJson<EnvelopeData>();\n if (data === undefined) continue;\n result.push({ rid: envelope.id, data });\n }\n return result;\n });\n}\n\n/**\n * Builds the {@link PendingShare} computable over the shared-resource discovery tree, filtered\n * against the set of already-handled shareIds (those with a decision/{shareId} in the acceptor's\n * SharingState). Both trees feed one Computable so it recomputes when either changes.\n *\n * `currentUserLogin` (when known) suppresses the user's own shares: a share-with-everybody grants\n * the everyone-user, so the donor discovers their own envelope as a pending share. There is no\n * scenario where a user accepts their own share, so they are dropped from the pending view.\n */\nexport function createPendingSharesComputable(\n sharedTree: SynchronizedTreeState,\n sharingStateTree: SynchronizedTreeState,\n currentUserLogin: string | null,\n): Computable<PendingShare[] | undefined> {\n return Computable.make((ctx) => {\n const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();\n\n // Set of shareIds already handled (accepted or rejected) — dedup discovery on the logical share.\n const stateNode = ctx.accessor(sharingStateTree.entry()).node();\n const handled = new Set<ShareId>();\n if (stateNode !== undefined)\n for (const f of stateNode.listDynamicFields())\n if (f.startsWith(DecisionFieldPrefix))\n handled.add(asShareId(f.slice(DecisionFieldPrefix.length)));\n\n const result: PendingShare[] = [];\n for (const envelope of roots) {\n if (envelope === undefined) continue;\n if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;\n const data = envelope.getDataAsJson<EnvelopeData>();\n if (data === undefined) continue;\n if (handled.has(data.shareId)) continue; // already accepted or rejected\n if (currentUserLogin !== null && data.sender === currentUserLogin) continue; // own share\n if (data.expiresAt !== null && data.expiresAt <= Date.now()) continue;\n\n result.push({\n shareId: data.shareId,\n sender: data.sender,\n title: data.title,\n mode: data.mode,\n grantedAt: data.sharedAt,\n });\n }\n result.sort((a, b) => b.grantedAt - a.grantedAt);\n return result;\n });\n}\n\nconst SharingStatePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, SharingStateResourceType)) return [];\n return resource.fields;\n};\n\n// decision/{shareId} values are leaf JSON resources; follow fields only from SharingState.\nconst SharingStateFieldFilter: Filter = treeFilter.resourceTypeEq(SharingStateResourceType.name);\n\n/** Creates the acceptor's SharingState synchronized tree (single explicit root). */\nexport async function createSharingStateTree(\n pl: PlClient,\n stateRid: SignedResourceId,\n env: MiddleLayerEnvironment,\n): Promise<SynchronizedTreeState> {\n return await SynchronizedTreeState.init(\n pl,\n stateRid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: SharingStatePruningFunction,\n fieldFilter: SharingStateFieldFilter,\n },\n env.logger,\n );\n}\n\n/** Maps an envelope resource id (signed) to its string form, for failure reporting. */\nexport function envelopeIdString(rid: SignedResourceId): string {\n return resourceIdToString(rid);\n}\n"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { EnvelopeMode, ShareId } from "../model/sharing_model.js";
2
+ import { PlClient, SignedResourceId } from "@milaboratories/pl-client";
3
+ import { SynchronizedTreeState } from "@milaboratories/pl-tree";
4
+ import { Computable } from "@milaboratories/computable";
5
+ import { ProjectId } from "@milaboratories/pl-model-common";
6
+
7
+ //#region src/middle_layer/sharing_list.d.ts
8
+ /** Donor-facing view of one outgoing share. */
9
+ interface OutgoingShare {
10
+ shareId: ShareId;
11
+ sharedAt: number;
12
+ expiresAt?: number;
13
+ mode: EnvelopeMode;
14
+ title: string;
15
+ /** One entry per project in the pack, so the change UI can offer a per-project decision.
16
+ * `projectId` is the donor's source project id; `updatedAt` is when this project's
17
+ * snapshot was last (re)taken. */
18
+ projects: {
19
+ projectId: ProjectId;
20
+ label: string;
21
+ updatedAt: number;
22
+ }[];
23
+ /** Full recipient logins, from `ListGrants` on the envelope; `["*"]` for everyone-shares. */
24
+ recipients: string[];
25
+ /** Per recipient who has responded: their decision and when, from acceptance/{login}. */
26
+ responses: Record<string, {
27
+ action: "accepted" | "rejected";
28
+ timestamp: number;
29
+ }>;
30
+ }
31
+ /** Acceptor-facing view of one pending share. */
32
+ interface PendingShare {
33
+ shareId: ShareId;
34
+ sender: string;
35
+ title: string;
36
+ mode: EnvelopeMode;
37
+ grantedAt: number;
38
+ }
39
+ //#endregion
40
+ export { OutgoingShare, PendingShare };
41
+ //# sourceMappingURL=sharing_list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharing_list.d.ts","names":[],"sources":["../../src/middle_layer/sharing_list.ts"],"mappings":";;;;;;;;UA4BiB,aAAA;EACf,OAAA,EAAS,OAAA;EACT,QAAA;EACA,SAAA;EACA,IAAA,EAAM,YAAA;EACN,KAAA;EAIuB;;;EAAvB,QAAA;IAAY,SAAA,EAAW,SAAA;IAAW,KAAA;IAAe,SAAA;EAAA;EALjD;EAOA,UAAA;EANA;EAQA,SAAA,EAAW,MAAA;IAAiB,MAAA;IAAiC,SAAA;EAAA;AAAA;;UAa9C,YAAA;EACf,OAAA,EAAS,OAAA;EACT,MAAA;EACA,KAAA;EACA,IAAA,EAAM,YAAY;EAClB,SAAA;AAAA"}
@@ -0,0 +1,198 @@
1
+ import { AcceptanceFieldPrefix, SharedEnvelopeResourceType, SharingOutboxResourceType, SharingStateResourceType, asShareId } from "../model/sharing_model.js";
2
+ import { isEveryoneUserLogin, resourceType, resourceTypesEqual, treeFilter } from "@milaboratories/pl-client";
3
+ import { SynchronizedTreeState } from "@milaboratories/pl-tree";
4
+ import { Computable } from "@milaboratories/computable";
5
+ //#region src/middle_layer/sharing_list.ts
6
+ /** Per-project view for the donor's change UI, from {@link EnvelopeData.projects}. */
7
+ function envelopeProjects(data) {
8
+ return Object.values(data.projects).map((p) => ({
9
+ projectId: p.source,
10
+ label: p.label,
11
+ updatedAt: p.updatedAt
12
+ }));
13
+ }
14
+ const SharingOutboxPruningFunction = (resource) => {
15
+ if (!resourceTypesEqual(resource.type, SharingOutboxResourceType) && !resourceTypesEqual(resource.type, SharedEnvelopeResourceType)) return [];
16
+ return resource.fields;
17
+ };
18
+ const SharingOutboxFieldFilter = treeFilter.or(treeFilter.resourceTypeEq(SharingOutboxResourceType.name), treeFilter.resourceTypeEq(SharedEnvelopeResourceType.name));
19
+ /**
20
+ * Reactive view of the donor's own outbox. Reads each live envelope's immutable
21
+ * {@link EnvelopeData} plus the per-recipient `acceptance/{login}` records from the tree, then
22
+ * enriches each share's full recipient list via `ListGrants` on the envelope (`["*"]` for an
23
+ * everyone-share). `ListGrants` is gated backend-side to the envelope owner (the donor), which is
24
+ * exactly who reads this view.
25
+ *
26
+ * API-level only in M1 (no UI).
27
+ */
28
+ function createOutgoingSharesComputable(pl, tree) {
29
+ return Computable.make((ctx) => {
30
+ const node = ctx.accessor(tree.entry()).node();
31
+ if (node === void 0) return void 0;
32
+ const drafts = [];
33
+ for (const fieldName of node.listDynamicFields()) {
34
+ const envelope = node.traverse(fieldName);
35
+ if (envelope === void 0) continue;
36
+ if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;
37
+ const data = envelope.getDataAsJson();
38
+ if (data === void 0) continue;
39
+ const responses = {};
40
+ for (const f of envelope.listDynamicFields()) {
41
+ if (!f.startsWith("acceptance/")) continue;
42
+ const login = f.slice(AcceptanceFieldPrefix.length);
43
+ const accData = envelope.traverse(f)?.getDataAsJson();
44
+ if (accData === void 0) continue;
45
+ responses[login] = {
46
+ action: accData.action,
47
+ timestamp: accData.timestamp
48
+ };
49
+ }
50
+ drafts.push({
51
+ shareId: data.shareId,
52
+ sharedAt: data.sharedAt,
53
+ ...data.expiresAt !== null ? { expiresAt: data.expiresAt } : {},
54
+ mode: data.mode,
55
+ title: data.title,
56
+ projects: envelopeProjects(data),
57
+ responses,
58
+ envelopeRid: envelope.id
59
+ });
60
+ }
61
+ drafts.sort((a, b) => b.sharedAt - a.sharedAt);
62
+ return drafts;
63
+ }, { postprocessValue: async (drafts) => {
64
+ if (drafts === void 0) return void 0;
65
+ const self = pl.userResources.authUser;
66
+ const toRecipients = (grants) => grants.some((g) => isEveryoneUserLogin(g.user)) ? ["*"] : grants.map((g) => g.user).filter((u) => u !== self);
67
+ const txGrants = pl.hasCapability("txListGrants:v1");
68
+ const settled = await Promise.allSettled(drafts.map((d) => txGrants ? pl.withReadTx("ListShareGrant", (tx) => tx.listGrants(d.envelopeRid)) : pl.userResources.listGrants(d.envelopeRid)));
69
+ return drafts.map(({ envelopeRid: _envelopeRid, ...share }, i) => {
70
+ const r = settled[i];
71
+ return {
72
+ ...share,
73
+ recipients: r.status === "fulfilled" ? toRecipients(r.value) : []
74
+ };
75
+ });
76
+ } });
77
+ }
78
+ /**
79
+ * Creates the donor's outbox synchronized tree (single explicit root) plus the
80
+ * {@link OutgoingShare} computable over it.
81
+ */
82
+ async function createOutgoingShares(pl, outboxRid, env) {
83
+ const tree = await SynchronizedTreeState.init(pl, outboxRid, {
84
+ ...env.ops.defaultTreeOptions,
85
+ pruning: SharingOutboxPruningFunction,
86
+ fieldFilter: SharingOutboxFieldFilter
87
+ }, env.logger);
88
+ return {
89
+ computable: createOutgoingSharesComputable(pl, tree),
90
+ tree
91
+ };
92
+ }
93
+ const DecisionFieldPrefix = "decision/";
94
+ const SharedEnvelopePruningFunction = (resource) => {
95
+ if (!resourceTypesEqual(resource.type, SharedEnvelopeResourceType)) return [];
96
+ return resource.fields;
97
+ };
98
+ const PendingSharesFieldFilter = treeFilter.resourceTypeEq(SharedEnvelopeResourceType.name);
99
+ /**
100
+ * Creates the acceptor's shared-resource discovery tree (a `{kind:'shared'}` seed over
101
+ * {@link SharedEnvelopeResourceType}) plus the {@link PendingShare} computable over it.
102
+ *
103
+ * An envelope surfaces only when its shareId has NO decision/{shareId} on SharingState —
104
+ * the dedup is applied by the caller, which holds the SharingState tree.
105
+ */
106
+ async function createPendingSharesTree(pl, env) {
107
+ return await SynchronizedTreeState.init(pl, {
108
+ kind: "shared",
109
+ resourceType: resourceType(SharedEnvelopeResourceType.name, SharedEnvelopeResourceType.version)
110
+ }, {
111
+ ...env.ops.defaultTreeOptions,
112
+ pruning: SharedEnvelopePruningFunction,
113
+ fieldFilter: PendingSharesFieldFilter
114
+ }, env.logger);
115
+ }
116
+ /**
117
+ * Builds a Computable yielding the acceptor's currently-live envelopes, read from the
118
+ * shared-resource discovery tree the ML already maintains — the single discovery mechanism.
119
+ * Accept/reject `.getValue()` this instead of re-streaming `ListUserResources`, so there is no
120
+ * second discovery path. The envelope's signed `rid` (from the tree node) is what the write tx
121
+ * needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.
122
+ *
123
+ * Returns `undefined` while the tree is still empty/unresolved (mirrors the other tree-backed
124
+ * computables here). Yields a flat list; the caller dedups by `shareId` — at most one live
125
+ * envelope per shareId is expected (the donor keeps one), and a replace tears the old one down.
126
+ */
127
+ function createLiveEnvelopesComputable(sharedTree) {
128
+ return Computable.make((ctx) => {
129
+ const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();
130
+ const result = [];
131
+ for (const envelope of roots) {
132
+ if (envelope === void 0) continue;
133
+ if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;
134
+ const data = envelope.getDataAsJson();
135
+ if (data === void 0) continue;
136
+ result.push({
137
+ rid: envelope.id,
138
+ data
139
+ });
140
+ }
141
+ return result;
142
+ });
143
+ }
144
+ /**
145
+ * Builds the {@link PendingShare} computable over the shared-resource discovery tree, filtered
146
+ * against the set of already-handled shareIds (those with a decision/{shareId} in the acceptor's
147
+ * SharingState). Both trees feed one Computable so it recomputes when either changes.
148
+ *
149
+ * `currentUserLogin` (when known) suppresses the user's own shares: a share-with-everybody grants
150
+ * the everyone-user, so the donor discovers their own envelope as a pending share. There is no
151
+ * scenario where a user accepts their own share, so they are dropped from the pending view.
152
+ */
153
+ function createPendingSharesComputable(sharedTree, sharingStateTree, currentUserLogin) {
154
+ return Computable.make((ctx) => {
155
+ const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();
156
+ const stateNode = ctx.accessor(sharingStateTree.entry()).node();
157
+ const handled = /* @__PURE__ */ new Set();
158
+ if (stateNode !== void 0) {
159
+ for (const f of stateNode.listDynamicFields()) if (f.startsWith(DecisionFieldPrefix)) handled.add(asShareId(f.slice(9)));
160
+ }
161
+ const result = [];
162
+ for (const envelope of roots) {
163
+ if (envelope === void 0) continue;
164
+ if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;
165
+ const data = envelope.getDataAsJson();
166
+ if (data === void 0) continue;
167
+ if (handled.has(data.shareId)) continue;
168
+ if (currentUserLogin !== null && data.sender === currentUserLogin) continue;
169
+ if (data.expiresAt !== null && data.expiresAt <= Date.now()) continue;
170
+ result.push({
171
+ shareId: data.shareId,
172
+ sender: data.sender,
173
+ title: data.title,
174
+ mode: data.mode,
175
+ grantedAt: data.sharedAt
176
+ });
177
+ }
178
+ result.sort((a, b) => b.grantedAt - a.grantedAt);
179
+ return result;
180
+ });
181
+ }
182
+ const SharingStatePruningFunction = (resource) => {
183
+ if (!resourceTypesEqual(resource.type, SharingStateResourceType)) return [];
184
+ return resource.fields;
185
+ };
186
+ const SharingStateFieldFilter = treeFilter.resourceTypeEq(SharingStateResourceType.name);
187
+ /** Creates the acceptor's SharingState synchronized tree (single explicit root). */
188
+ async function createSharingStateTree(pl, stateRid, env) {
189
+ return await SynchronizedTreeState.init(pl, stateRid, {
190
+ ...env.ops.defaultTreeOptions,
191
+ pruning: SharingStatePruningFunction,
192
+ fieldFilter: SharingStateFieldFilter
193
+ }, env.logger);
194
+ }
195
+ //#endregion
196
+ export { createLiveEnvelopesComputable, createOutgoingShares, createOutgoingSharesComputable, createPendingSharesComputable, createPendingSharesTree, createSharingStateTree };
197
+
198
+ //# sourceMappingURL=sharing_list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharing_list.js","names":[],"sources":["../../src/middle_layer/sharing_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type { Filter, PlClient, SignedResourceId } from \"@milaboratories/pl-client\";\nimport {\n isEveryoneUserLogin,\n resourceIdToString,\n resourceType,\n resourceTypesEqual,\n treeFilter,\n} from \"@milaboratories/pl-client\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport type { ProjectId } from \"@milaboratories/pl-model-common\";\nimport type {\n EnvelopeAcceptance,\n EnvelopeData,\n EnvelopeMode,\n ShareId,\n} from \"../model/sharing_model\";\nimport {\n AcceptanceFieldPrefix,\n asShareId,\n SharedEnvelopeResourceType,\n SharingOutboxResourceType,\n SharingStateResourceType,\n} from \"../model/sharing_model\";\n\n/** Donor-facing view of one outgoing share. */\nexport interface OutgoingShare {\n shareId: ShareId; // stable logical identity of the share, preserved across changes\n sharedAt: number; // this instance's creation time (ms epoch)\n expiresAt?: number; // EnvelopeData.expiresAt; null maps to undefined = never expires\n mode: EnvelopeMode;\n title: string; // display name shown to recipients; defaults to the first project's name\n /** One entry per project in the pack, so the change UI can offer a per-project decision.\n * `projectId` is the donor's source project id; `updatedAt` is when this project's\n * snapshot was last (re)taken. */\n projects: { projectId: ProjectId; label: string; updatedAt: number }[];\n /** Full recipient logins, from `ListGrants` on the envelope; `[\"*\"]` for everyone-shares. */\n recipients: string[];\n /** Per recipient who has responded: their decision and when, from acceptance/{login}. */\n responses: Record<string, { action: \"accepted\" | \"rejected\"; timestamp: number }>;\n}\n\n/** Per-project view for the donor's change UI, from {@link EnvelopeData.projects}. */\nfunction envelopeProjects(data: EnvelopeData): OutgoingShare[\"projects\"] {\n return Object.values(data.projects).map((p) => ({\n projectId: p.source,\n label: p.label,\n updatedAt: p.updatedAt,\n }));\n}\n\n/** Acceptor-facing view of one pending share. */\nexport interface PendingShare {\n shareId: ShareId;\n sender: string; // EnvelopeData.sender, display only\n title: string; // display name shown to recipients; defaults to the first project's name\n mode: EnvelopeMode; // v1 renders only \"copy\" entries\n grantedAt: number;\n}\n\nconst SharingOutboxPruningFunction: PruningFunction = (resource) => {\n if (\n !resourceTypesEqual(resource.type, SharingOutboxResourceType) &&\n !resourceTypesEqual(resource.type, SharedEnvelopeResourceType)\n )\n return [];\n return resource.fields;\n};\n\n// Server-side traversal scope (modern resourceTree path). Pruning is client-side ONLY and does\n// NOT stop the backend walk — without a fieldFilter the backend descends through the envelope's\n// project/{uuid} snapshots into the whole project graph (StreamManager etc.), whose field-driven\n// finality predicate then throws on the pruned-to-[] fields. Following fields only FROM the outbox\n// and the envelope stops the walk at the project snapshots (UserProject), which we never traverse.\nconst SharingOutboxFieldFilter: Filter = treeFilter.or(\n treeFilter.resourceTypeEq(SharingOutboxResourceType.name),\n treeFilter.resourceTypeEq(SharedEnvelopeResourceType.name),\n);\n\n/** Intermediate of an outgoing share before its full recipient list is fetched: everything the\n * tree carries synchronously, plus the envelope's signed id for the async `ListGrants` enrich. */\ntype OutgoingShareDraft = Omit<OutgoingShare, \"recipients\"> & { envelopeRid: SignedResourceId };\n\n/**\n * Reactive view of the donor's own outbox. Reads each live envelope's immutable\n * {@link EnvelopeData} plus the per-recipient `acceptance/{login}` records from the tree, then\n * enriches each share's full recipient list via `ListGrants` on the envelope (`[\"*\"]` for an\n * everyone-share). `ListGrants` is gated backend-side to the envelope owner (the donor), which is\n * exactly who reads this view.\n *\n * API-level only in M1 (no UI).\n */\nexport function createOutgoingSharesComputable(\n pl: PlClient,\n tree: SynchronizedTreeState,\n): Computable<OutgoingShare[] | undefined> {\n return Computable.make(\n (ctx) => {\n const node = ctx.accessor(tree.entry()).node();\n if (node === undefined) return undefined;\n\n const drafts: OutgoingShareDraft[] = [];\n // Outbox fields are keyed by shareId; each value is a SharedEnvelope.\n for (const fieldName of node.listDynamicFields()) {\n const envelope = node.traverse(fieldName);\n if (envelope === undefined) continue;\n if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;\n\n const data = envelope.getDataAsJson<EnvelopeData>();\n if (data === undefined) continue;\n\n const responses: OutgoingShare[\"responses\"] = {};\n for (const f of envelope.listDynamicFields()) {\n if (!f.startsWith(AcceptanceFieldPrefix)) continue;\n const login = f.slice(AcceptanceFieldPrefix.length);\n const acc = envelope.traverse(f);\n const accData = acc?.getDataAsJson<EnvelopeAcceptance>();\n if (accData === undefined) continue;\n responses[login] = { action: accData.action, timestamp: accData.timestamp };\n }\n\n drafts.push({\n shareId: data.shareId,\n sharedAt: data.sharedAt,\n ...(data.expiresAt !== null ? { expiresAt: data.expiresAt } : {}),\n mode: data.mode,\n title: data.title,\n projects: envelopeProjects(data),\n responses,\n envelopeRid: envelope.id,\n });\n }\n drafts.sort((a, b) => b.sharedAt - a.sharedAt);\n return drafts;\n },\n {\n // Resolve each share's recipients via listGrants. An everyone-grant maps to \"*\"; the donor's\n // own grant is dropped. One tx per envelope so a just-revoked rid faults only its own read,\n // which allSettled degrades to []. Recipients aren't cached — a live share's recipient set can\n // change. Transactional listGrants when available, else the standalone gRPC-only RPC.\n postprocessValue: async (\n drafts: OutgoingShareDraft[] | undefined,\n ): Promise<OutgoingShare[] | undefined> => {\n if (drafts === undefined) return undefined;\n const self = pl.userResources.authUser;\n const toRecipients = (grants: { user: string }[]): string[] =>\n grants.some((g) => isEveryoneUserLogin(g.user))\n ? [\"*\"]\n : grants.map((g) => g.user).filter((u) => u !== self);\n\n const txGrants = pl.hasCapability(\"txListGrants:v1\");\n const settled = await Promise.allSettled(\n drafts.map((d) =>\n txGrants\n ? pl.withReadTx(\"ListShareGrant\", (tx) => tx.listGrants(d.envelopeRid))\n : pl.userResources.listGrants(d.envelopeRid),\n ),\n );\n\n return drafts.map(({ envelopeRid: _envelopeRid, ...share }, i): OutgoingShare => {\n const r = settled[i];\n return { ...share, recipients: r.status === \"fulfilled\" ? toRecipients(r.value) : [] };\n });\n },\n },\n );\n}\n\n/**\n * Creates the donor's outbox synchronized tree (single explicit root) plus the\n * {@link OutgoingShare} computable over it.\n */\nexport async function createOutgoingShares(\n pl: PlClient,\n outboxRid: SignedResourceId,\n env: MiddleLayerEnvironment,\n): Promise<{ tree: SynchronizedTreeState; computable: Computable<OutgoingShare[] | undefined> }> {\n const tree = await SynchronizedTreeState.init(\n pl,\n outboxRid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: SharingOutboxPruningFunction,\n fieldFilter: SharingOutboxFieldFilter,\n },\n env.logger,\n );\n return { computable: createOutgoingSharesComputable(pl, tree), tree };\n}\n\nconst DecisionFieldPrefix = \"decision/\";\n\nconst SharedEnvelopePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, SharedEnvelopeResourceType)) return [];\n return resource.fields;\n};\n\n// Discovery only needs each envelope's immutable EnvelopeData (basic resource data); it must NOT\n// descend into the project snapshots. Following fields only FROM the envelope stops the walk at\n// the UserProject snapshots (their fields are never followed).\nconst PendingSharesFieldFilter: Filter = treeFilter.resourceTypeEq(SharedEnvelopeResourceType.name);\n\n/**\n * Creates the acceptor's shared-resource discovery tree (a `{kind:'shared'}` seed over\n * {@link SharedEnvelopeResourceType}) plus the {@link PendingShare} computable over it.\n *\n * An envelope surfaces only when its shareId has NO decision/{shareId} on SharingState —\n * the dedup is applied by the caller, which holds the SharingState tree.\n */\nexport async function createPendingSharesTree(\n pl: PlClient,\n env: MiddleLayerEnvironment,\n): Promise<SynchronizedTreeState> {\n return await SynchronizedTreeState.init(\n pl,\n {\n kind: \"shared\",\n resourceType: resourceType(\n SharedEnvelopeResourceType.name,\n SharedEnvelopeResourceType.version,\n ),\n },\n {\n ...env.ops.defaultTreeOptions,\n pruning: SharedEnvelopePruningFunction,\n fieldFilter: PendingSharesFieldFilter,\n },\n env.logger,\n );\n}\n\n/** A live envelope discovered in the acceptor's shared-resource tree: its signed resource id and\n * decoded {@link EnvelopeData}. The accept/reject flow keys these by `data.shareId`. */\nexport type LiveEnvelope = { rid: SignedResourceId; data: EnvelopeData };\n\n/**\n * Builds a Computable yielding the acceptor's currently-live envelopes, read from the\n * shared-resource discovery tree the ML already maintains — the single discovery mechanism.\n * Accept/reject `.getValue()` this instead of re-streaming `ListUserResources`, so there is no\n * second discovery path. The envelope's signed `rid` (from the tree node) is what the write tx\n * needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.\n *\n * Returns `undefined` while the tree is still empty/unresolved (mirrors the other tree-backed\n * computables here). Yields a flat list; the caller dedups by `shareId` — at most one live\n * envelope per shareId is expected (the donor keeps one), and a replace tears the old one down.\n */\nexport function createLiveEnvelopesComputable(\n sharedTree: SynchronizedTreeState,\n): Computable<LiveEnvelope[] | undefined> {\n return Computable.make((ctx) => {\n const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();\n const result: LiveEnvelope[] = [];\n for (const envelope of roots) {\n if (envelope === undefined) continue;\n if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;\n const data = envelope.getDataAsJson<EnvelopeData>();\n if (data === undefined) continue;\n result.push({ rid: envelope.id, data });\n }\n return result;\n });\n}\n\n/**\n * Builds the {@link PendingShare} computable over the shared-resource discovery tree, filtered\n * against the set of already-handled shareIds (those with a decision/{shareId} in the acceptor's\n * SharingState). Both trees feed one Computable so it recomputes when either changes.\n *\n * `currentUserLogin` (when known) suppresses the user's own shares: a share-with-everybody grants\n * the everyone-user, so the donor discovers their own envelope as a pending share. There is no\n * scenario where a user accepts their own share, so they are dropped from the pending view.\n */\nexport function createPendingSharesComputable(\n sharedTree: SynchronizedTreeState,\n sharingStateTree: SynchronizedTreeState,\n currentUserLogin: string | null,\n): Computable<PendingShare[] | undefined> {\n return Computable.make((ctx) => {\n const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();\n\n // Set of shareIds already handled (accepted or rejected) — dedup discovery on the logical share.\n const stateNode = ctx.accessor(sharingStateTree.entry()).node();\n const handled = new Set<ShareId>();\n if (stateNode !== undefined)\n for (const f of stateNode.listDynamicFields())\n if (f.startsWith(DecisionFieldPrefix))\n handled.add(asShareId(f.slice(DecisionFieldPrefix.length)));\n\n const result: PendingShare[] = [];\n for (const envelope of roots) {\n if (envelope === undefined) continue;\n if (!resourceTypesEqual(envelope.resourceType, SharedEnvelopeResourceType)) continue;\n const data = envelope.getDataAsJson<EnvelopeData>();\n if (data === undefined) continue;\n if (handled.has(data.shareId)) continue; // already accepted or rejected\n if (currentUserLogin !== null && data.sender === currentUserLogin) continue; // own share\n if (data.expiresAt !== null && data.expiresAt <= Date.now()) continue;\n\n result.push({\n shareId: data.shareId,\n sender: data.sender,\n title: data.title,\n mode: data.mode,\n grantedAt: data.sharedAt,\n });\n }\n result.sort((a, b) => b.grantedAt - a.grantedAt);\n return result;\n });\n}\n\nconst SharingStatePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, SharingStateResourceType)) return [];\n return resource.fields;\n};\n\n// decision/{shareId} values are leaf JSON resources; follow fields only from SharingState.\nconst SharingStateFieldFilter: Filter = treeFilter.resourceTypeEq(SharingStateResourceType.name);\n\n/** Creates the acceptor's SharingState synchronized tree (single explicit root). */\nexport async function createSharingStateTree(\n pl: PlClient,\n stateRid: SignedResourceId,\n env: MiddleLayerEnvironment,\n): Promise<SynchronizedTreeState> {\n return await SynchronizedTreeState.init(\n pl,\n stateRid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: SharingStatePruningFunction,\n fieldFilter: SharingStateFieldFilter,\n },\n env.logger,\n );\n}\n\n/** Maps an envelope resource id (signed) to its string form, for failure reporting. */\nexport function envelopeIdString(rid: SignedResourceId): string {\n return resourceIdToString(rid);\n}\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ const require_project_model = require("./project_model.cjs");
2
+ const require_block_pack_spec = require("./block_pack_spec.cjs");
3
+ const require_sharing_model = require("./sharing_model.cjs");
4
+ exports.AcceptanceFieldPrefix = require_sharing_model.AcceptanceFieldPrefix;
5
+ exports.BlockArgsAuthorKeyPrefix = require_project_model.BlockArgsAuthorKeyPrefix;
6
+ exports.FrontendFromFolderResourceType = require_block_pack_spec.FrontendFromFolderResourceType;
7
+ exports.FrontendFromLocalTgzResourceType = require_block_pack_spec.FrontendFromLocalTgzResourceType;
8
+ exports.FrontendFromUrlResourceType = require_block_pack_spec.FrontendFromUrlResourceType;
9
+ exports.ProjectCreatedTimestamp = require_project_model.ProjectCreatedTimestamp;
10
+ exports.ProjectLastModifiedTimestamp = require_project_model.ProjectLastModifiedTimestamp;
11
+ exports.ProjectMetaKey = require_project_model.ProjectMetaKey;
12
+ exports.ProjectResourceType = require_project_model.ProjectResourceType;
13
+ exports.ProjectStructureAuthorKey = require_project_model.ProjectStructureAuthorKey;
14
+ exports.ProjectStructureKey = require_project_model.ProjectStructureKey;
15
+ exports.SchemaVersionCurrent = require_project_model.SchemaVersionCurrent;
16
+ exports.SchemaVersionKey = require_project_model.SchemaVersionKey;
17
+ exports.SharedEnvelopeResourceType = require_sharing_model.SharedEnvelopeResourceType;
18
+ exports.SharingOutboxField = require_sharing_model.SharingOutboxField;
19
+ exports.SharingOutboxResourceType = require_sharing_model.SharingOutboxResourceType;
20
+ exports.SharingStateField = require_sharing_model.SharingStateField;
21
+ exports.SharingStateResourceType = require_sharing_model.SharingStateResourceType;
22
+ exports.acceptanceField = require_sharing_model.acceptanceField;
23
+ exports.acceptanceFieldLogin = require_sharing_model.acceptanceFieldLogin;
24
+ exports.asShareId = require_sharing_model.asShareId;
25
+ exports.canGrantToEveryone = require_sharing_model.canGrantToEveryone;
26
+ exports.canImpersonate = require_sharing_model.canImpersonate;
27
+ exports.decisionField = require_sharing_model.decisionField;
28
+ exports.decodeEnvelopeData = require_sharing_model.decodeEnvelopeData;
29
+ exports.isAcceptanceField = require_sharing_model.isAcceptanceField;
30
+ exports.newShareId = require_sharing_model.newShareId;
@@ -1,3 +1,4 @@
1
1
  import { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectField, ProjectId, ProjectLastModifiedTimestamp, ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey } from "./project_model.js";
2
+ import { AcceptanceFieldPrefix, EnvelopeAcceptance, EnvelopeData, EnvelopeMode, EnvelopeProject, ProjectChangeAction, ProjectFieldUuid, ShareId, ShareProjectsOptions, SharedEnvelopeResourceType, SharingDecision, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, canImpersonate, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId } from "./sharing_model.js";
2
3
  import { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromLocalTgz, FrontendFromLocalTgzData, FrontendFromLocalTgzResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec } from "./block_pack_spec.js";
3
- export { BlockArgsAuthorKeyPrefix, BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromLocalTgz, FrontendFromLocalTgzData, FrontendFromLocalTgzResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec, ProjectCreatedTimestamp, type ProjectField, ProjectLastModifiedTimestamp, type ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey };
4
+ export { AcceptanceFieldPrefix, BlockArgsAuthorKeyPrefix, BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared, EnvelopeAcceptance, EnvelopeData, EnvelopeMode, EnvelopeProject, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromLocalTgz, FrontendFromLocalTgzData, FrontendFromLocalTgzResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec, ProjectChangeAction, ProjectCreatedTimestamp, type ProjectField, ProjectFieldUuid, ProjectLastModifiedTimestamp, type ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey, ShareId, ShareProjectsOptions, SharedEnvelopeResourceType, SharingDecision, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, canImpersonate, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId };
@@ -0,0 +1,4 @@
1
+ import { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectLastModifiedTimestamp, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey } from "./project_model.js";
2
+ import { FrontendFromFolderResourceType, FrontendFromLocalTgzResourceType, FrontendFromUrlResourceType } from "./block_pack_spec.js";
3
+ import { AcceptanceFieldPrefix, SharedEnvelopeResourceType, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, canImpersonate, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId } from "./sharing_model.js";
4
+ export { AcceptanceFieldPrefix, BlockArgsAuthorKeyPrefix, FrontendFromFolderResourceType, FrontendFromLocalTgzResourceType, FrontendFromUrlResourceType, ProjectCreatedTimestamp, ProjectLastModifiedTimestamp, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey, SharedEnvelopeResourceType, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, canImpersonate, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId };
@@ -1,11 +1,11 @@
1
1
  import { ProjectListEntry, ProjectMeta } from "@milaboratories/pl-model-middle-layer";
2
2
  import { ResourceType } from "@milaboratories/pl-client";
3
- import { ProjectId } from "@milaboratories/pl-model-common";
3
+ import { ProjectId as ProjectId$1 } from "@milaboratories/pl-model-common";
4
4
 
5
5
  //#region src/model/project_model.d.ts
6
6
  interface ProjectListEntry$1 extends Omit<ProjectListEntry, "id"> {
7
7
  /** Unique project identifier in middle layer. Use to operate with given project. */
8
- id: ProjectId;
8
+ id: ProjectId$1;
9
9
  }
10
10
  declare const ProjectResourceType: ResourceType;
11
11
  declare const SchemaVersionKey = "SchemaVersion";
@@ -21,5 +21,5 @@ interface ProjectField {
21
21
  fieldName: "blockPack" | "blockSettings" | "blockStorage" | "prodArgs" | "currentArgs" | "currentPrerunArgs" | "prodChainCtx" | "prodCtx" | "prodUiCtx" | "prodOutput" | "prodCtxPrevious" | "prodUiCtxPrevious" | "prodOutputPrevious" | "stagingCtx" | "stagingUiCtx" | "stagingOutput" | "stagingCtxPrevious" | "stagingUiCtxPrevious" | "stagingOutputPrevious";
22
22
  }
23
23
  //#endregion
24
- export { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectField, type ProjectId, ProjectLastModifiedTimestamp, ProjectListEntry$1 as ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey };
24
+ export { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectField, type ProjectId$1 as ProjectId, ProjectLastModifiedTimestamp, ProjectListEntry$1 as ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey };
25
25
  //# sourceMappingURL=project_model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project_model.d.ts","names":[],"sources":["../../src/model/project_model.ts"],"mappings":";;;;;UAUiB,kBAAA,SAAyB,IAAA,CAAK,gBAAA;EAA9B;EAEf,EAAA,EAAI,SAAA;AAAA;AAAA,cA+DO,mBAAA,EAAqB,YAAoD;AAAA,cAEzE,gBAAA;AAAA,cAGA,oBAAA;AAAA,cAEA,uBAAA;AAAA,cACA,4BAAA;AAAA,cACA,cAAA;AAAA,cACA,mBAAA;AAAA,cAGA,wBAAA;AAAA,cAOA,yBAAA;AAAA,UAQI,YAAA;EACf,OAAA;EACA,SAAS;AAAA"}
1
+ {"version":3,"file":"project_model.d.ts","names":[],"sources":["../../src/model/project_model.ts"],"mappings":";;;;;UAUiB,kBAAA,SAAyB,IAAA,CAAK,gBAAA;EAA9B;EAEf,EAAA,EAAI,WAAA;AAAA;AAAA,cA+DO,mBAAA,EAAqB,YAAoD;AAAA,cAEzE,gBAAA;AAAA,cAGA,oBAAA;AAAA,cAEA,uBAAA;AAAA,cACA,4BAAA;AAAA,cACA,cAAA;AAAA,cACA,mBAAA;AAAA,cAGA,wBAAA;AAAA,cAOA,yBAAA;AAAA,UAQI,YAAA;EACf,OAAA;EACA,SAAS;AAAA"}