@milaboratories/pl-middle-layer 1.71.16 → 1.73.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/dist/index.cjs +10 -5
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -2
- package/dist/middle_layer/build_stamp.cjs +1 -1
- package/dist/middle_layer/build_stamp.js +1 -1
- package/dist/middle_layer/folders.cjs +636 -0
- package/dist/middle_layer/folders.cjs.map +1 -0
- package/dist/middle_layer/folders.d.ts +39 -0
- package/dist/middle_layer/folders.d.ts.map +1 -0
- package/dist/middle_layer/folders.js +617 -0
- package/dist/middle_layer/folders.js.map +1 -0
- package/dist/middle_layer/index.cjs +3 -0
- package/dist/middle_layer/index.d.ts +4 -3
- package/dist/middle_layer/index.js +2 -1
- package/dist/middle_layer/middle_layer.cjs +585 -370
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +215 -122
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +593 -378
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/project_list.cjs +36 -19
- package/dist/middle_layer/project_list.cjs.map +1 -1
- package/dist/middle_layer/project_list.d.ts +1 -1
- package/dist/middle_layer/project_list.d.ts.map +1 -1
- package/dist/middle_layer/project_list.js +37 -21
- package/dist/middle_layer/project_list.js.map +1 -1
- package/dist/middle_layer/sharing_list.cjs +123 -93
- package/dist/middle_layer/sharing_list.cjs.map +1 -1
- package/dist/middle_layer/sharing_list.d.ts +43 -19
- package/dist/middle_layer/sharing_list.d.ts.map +1 -1
- package/dist/middle_layer/sharing_list.js +123 -93
- package/dist/middle_layer/sharing_list.js.map +1 -1
- package/dist/middle_layer/template_list.cjs +45 -20
- package/dist/middle_layer/template_list.cjs.map +1 -1
- package/dist/middle_layer/template_list.d.ts +7 -15
- package/dist/middle_layer/template_list.d.ts.map +1 -1
- package/dist/middle_layer/template_list.js +44 -21
- package/dist/middle_layer/template_list.js.map +1 -1
- package/dist/model/index.cjs +7 -5
- package/dist/model/index.d.ts +2 -2
- package/dist/model/index.js +2 -2
- package/dist/model/sharing_model.cjs +46 -20
- package/dist/model/sharing_model.cjs.map +1 -1
- package/dist/model/sharing_model.d.ts +125 -55
- package/dist/model/sharing_model.d.ts.map +1 -1
- package/dist/model/sharing_model.js +40 -16
- package/dist/model/sharing_model.js.map +1 -1
- package/dist/mutator/list.cjs +30 -0
- package/dist/mutator/list.cjs.map +1 -0
- package/dist/mutator/list.js +29 -0
- package/dist/mutator/list.js.map +1 -0
- package/dist/mutator/project.cjs +14 -1
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +15 -2
- package/dist/mutator/project.js.map +1 -1
- package/dist/mutator/sharing.cjs +130 -76
- package/dist/mutator/sharing.cjs.map +1 -1
- package/dist/mutator/sharing.js +130 -76
- package/dist/mutator/sharing.js.map +1 -1
- package/dist/mutator/template.cjs +24 -17
- package/dist/mutator/template.cjs.map +1 -1
- package/dist/mutator/template.js +26 -20
- package/dist/mutator/template.js.map +1 -1
- package/package.json +17 -17
- package/src/middle_layer/folders.test.ts +1076 -0
- package/src/middle_layer/folders.ts +1069 -0
- package/src/middle_layer/folders_read.test.ts +359 -0
- package/src/middle_layer/index.ts +3 -2
- package/src/middle_layer/middle_layer.ts +813 -547
- package/src/middle_layer/project_list.test.ts +183 -0
- package/src/middle_layer/project_list.ts +58 -21
- package/src/middle_layer/sharing.test.ts +183 -0
- package/src/middle_layer/sharing_list.ts +208 -152
- package/src/middle_layer/template_list.test.ts +148 -0
- package/src/middle_layer/template_list.ts +72 -32
- package/src/middle_layer/templates.test.ts +76 -45
- package/src/model/sharing_model.test.ts +69 -2
- package/src/model/sharing_model.ts +161 -73
- package/src/mutator/list.ts +36 -0
- package/src/mutator/project-v3.test.ts +3 -1
- package/src/mutator/project.ts +13 -2
- package/src/mutator/sharing.ts +193 -120
- package/src/mutator/template.ts +37 -29
- package/src/test/with_ml.ts +73 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project_list.cjs","names":["treeFilter","SynchronizedTreeState","Computable","ProjectMetaKey","ProjectCreatedTimestamp","ProjectLastModifiedTimestamp"],"sources":["../../src/middle_layer/project_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type {\n Filter,\n PlClient,\n PlTransaction,\n ResourceType,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n field,\n isNullSignedResourceId,\n resourceIdToString,\n resourceTypesEqual,\n treeFilter,\n} from \"@milaboratories/pl-client\";\nimport type { TreeAndComputableU } from \"./types\";\nimport type { WatchableValue } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { ProjectId, ProjectListEntry } from \"../model/project_model\";\nimport {\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n ProjectMetaKey,\n} from \"../model/project_model\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport {
|
|
1
|
+
{"version":3,"file":"project_list.cjs","names":["treeFilter","SynchronizedTreeState","Computable","ProjectResourceType","ProjectMetaKey","ProjectCreatedTimestamp","ProjectLastModifiedTimestamp"],"sources":["../../src/middle_layer/project_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type {\n Filter,\n PlClient,\n PlTransaction,\n ResourceType,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n field,\n isNullSignedResourceId,\n resourceIdToString,\n resourceTypesEqual,\n treeFilter,\n} from \"@milaboratories/pl-client\";\nimport type { TreeAndComputableU } from \"./types\";\nimport type { WatchableValue } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { ProjectId, ProjectListEntry } from \"../model/project_model\";\nimport {\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n ProjectMetaKey,\n ProjectResourceType,\n} from \"../model/project_model\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\nimport { asProjectId } from \"@milaboratories/pl-model-common\";\n\nexport const ProjectsField = \"projects\";\nexport const ProjectsResourceType: ResourceType = { name: \"Projects\", version: \"1\" };\n\n/**\n * Resolves the projects-list resource on the transaction's client root, lazily creating (and\n * locking) an empty one when the {@link ProjectsField} is not yet populated. Returns its signed\n * id. Used when writing into a root that may have no projects list yet, e.g. copying a project\n * into another user's root during admin impersonation.\n */\nexport async function ensureProjectListRid(tx: PlTransaction): Promise<SignedResourceId> {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const fData = await tx.getField(projectsField);\n if (isNullSignedResourceId(fData.value)) {\n const ref = tx.createEphemeral(ProjectsResourceType);\n tx.lock(ref);\n tx.setField(projectsField, ref);\n return await ref.globalId;\n }\n return fData.value;\n}\n\nexport const ProjectsListTreePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, ProjectsResourceType)) return [];\n return resource.fields;\n};\n\nexport const projectsListFieldFilter: Filter = treeFilter.resourceTypeEq(\"Projects\");\n\nexport async function createProjectList(\n pl: PlClient,\n rid: SignedResourceId,\n openedProjects: WatchableValue<ProjectId[]>,\n env: MiddleLayerEnvironment,\n): Promise<TreeAndComputableU<ProjectListEntry[]>> {\n const tree = await SynchronizedTreeState.init(\n pl,\n rid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: ProjectsListTreePruningFunction,\n fieldFilter: projectsListFieldFilter,\n },\n env.logger,\n );\n\n const c = Computable.make((ctx) => {\n const node = ctx.accessor(tree.entry()).node();\n const oProjects = openedProjects.getValue(ctx);\n if (node === undefined) return undefined;\n return projectListEntries(node, oProjects);\n }).withStableType();\n\n return { computable: c, tree };\n}\n\n/** The part of a tree node this reader needs from a single entry of the projects list. */\ninterface ProjectListEntryNode {\n readonly id: SignedResourceId;\n readonly resourceType: ResourceType;\n getKeyValueAsJson<T>(key: string): T | undefined;\n}\n\n/** The part of a tree node this reader needs from the projects-list resource itself. */\ninterface ProjectsListNode {\n listDynamicFields(): string[];\n traverse(fieldName: string): ProjectListEntryNode | undefined;\n}\n\n/**\n * Builds the project list out of the projects-list resource, most recently modified first.\n *\n * Skips, rather than throws on, anything that is not a fully synced project. The projects\n * resource carries the user's entire project list, so one unexpected sibling field — or one\n * project whose metadata has not arrived yet — must cost that entry and nothing else. Both the\n * traversal and the metadata reads are watched, so an entry appears as soon as it syncs.\n *\n * The resource type is matched by name only: the point is to exclude foreign resources, not to\n * hide a project stored under an earlier resource-type version.\n */\nexport function projectListEntries(\n node: ProjectsListNode,\n openedProjects: readonly ProjectId[],\n): ProjectListEntry[] {\n const result: ProjectListEntry[] = [];\n\n // Projects list resource keeps projects assigned to fields. Each field name is project's UUID\n for (const field of node.listDynamicFields()) {\n const prj = node.traverse(field);\n if (prj === undefined) continue;\n if (prj.resourceType.name !== ProjectResourceType.name) continue;\n\n const meta = prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey);\n const created = prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp);\n const lastModified = prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp);\n if (meta === undefined || created === undefined || lastModified === undefined) continue;\n\n const projectId = asProjectId(resourceIdToString(prj.id));\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: openedProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n\n result.sort((a, b) => b.lastModified.valueOf() - a.lastModified.valueOf());\n return result;\n}\n"],"mappings":";;;;;;AA8BA,MAAa,gBAAgB;AAC7B,MAAa,uBAAqC;CAAE,MAAM;CAAY,SAAS;AAAI;;;;;;;AAQnF,eAAsB,qBAAqB,IAA8C;CACvF,MAAM,iBAAA,GAAA,0BAAA,MAAA,CAAsB,GAAG,YAAY,aAAa;CACxD,GAAG,YAAY,eAAe,SAAS;CACvC,MAAM,QAAQ,MAAM,GAAG,SAAS,aAAa;CAC7C,KAAA,GAAA,0BAAA,uBAAA,CAA2B,MAAM,KAAK,GAAG;EACvC,MAAM,MAAM,GAAG,gBAAgB,oBAAoB;EACnD,GAAG,KAAK,GAAG;EACX,GAAG,SAAS,eAAe,GAAG;EAC9B,OAAO,MAAM,IAAI;CACnB;CACA,OAAO,MAAM;AACf;AAEA,MAAa,mCAAoD,aAAa;CAC5E,IAAI,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,MAAM,oBAAoB,GAAG,OAAO,CAAC;CACtE,OAAO,SAAS;AAClB;AAEA,MAAa,0BAAkCA,0BAAAA,WAAW,eAAe,UAAU;AAEnF,eAAsB,kBACpB,IACA,KACA,gBACA,KACiD;CACjD,MAAM,OAAO,MAAMC,wBAAAA,sBAAsB,KACvC,IACA,KACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACT,aAAa;CACf,GACA,IAAI,MACN;CASA,OAAO;EAAE,YAPCC,2BAAAA,WAAW,MAAM,QAAQ;GACjC,MAAM,OAAO,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;GAC7C,MAAM,YAAY,eAAe,SAAS,GAAG;GAC7C,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;GAC/B,OAAO,mBAAmB,MAAM,SAAS;EAC3C,CAAC,CAAC,CAAC,eAEkB;EAAG;CAAK;AAC/B;;;;;;;;;;;;AA0BA,SAAgB,mBACd,MACA,gBACoB;CACpB,MAAM,SAA6B,CAAC;CAGpC,KAAK,MAAM,SAAS,KAAK,kBAAkB,GAAG;EAC5C,MAAM,MAAM,KAAK,SAAS,KAAK;EAC/B,IAAI,QAAQ,KAAA,GAAW;EACvB,IAAI,IAAI,aAAa,SAASC,sBAAAA,oBAAoB,MAAM;EAExD,MAAM,OAAO,IAAI,kBAA+BC,sBAAAA,cAAc;EAC9D,MAAM,UAAU,IAAI,kBAA0BC,sBAAAA,uBAAuB;EACrE,MAAM,eAAe,IAAI,kBAA0BC,sBAAAA,4BAA4B;EAC/E,IAAI,SAAS,KAAA,KAAa,YAAY,KAAA,KAAa,iBAAiB,KAAA,GAAW;EAE/E,MAAM,aAAA,GAAA,gCAAA,YAAA,EAAA,GAAA,0BAAA,mBAAA,CAA2C,IAAI,EAAE,CAAC;EACxD,OAAO,KAAK;GACV,IAAI;GACJ,SAAS,IAAI,KAAK,OAAO;GACzB,cAAc,IAAI,KAAK,YAAY;GACnC,QAAQ,eAAe,QAAQ,SAAS,KAAK;GAC7C;EACF,CAAC;CACH;CAEA,OAAO,MAAM,GAAG,MAAM,EAAE,aAAa,QAAQ,IAAI,EAAE,aAAa,QAAQ,CAAC;CACzE,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project_list.d.ts","names":[],"sources":["../../src/middle_layer/project_list.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"project_list.d.ts","names":[],"sources":["../../src/middle_layer/project_list.ts"],"mappings":";;;;;;;qBA8Ba;qBACA,sBAAsB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ProjectCreatedTimestamp, ProjectLastModifiedTimestamp, ProjectMetaKey } from "../model/project_model.js";
|
|
2
|
-
import {
|
|
1
|
+
import { ProjectCreatedTimestamp, ProjectLastModifiedTimestamp, ProjectMetaKey, ProjectResourceType } from "../model/project_model.js";
|
|
2
|
+
import { asProjectId } from "@milaboratories/pl-model-common";
|
|
3
3
|
import { field, isNullSignedResourceId, resourceIdToString, resourceTypesEqual, treeFilter } from "@milaboratories/pl-client";
|
|
4
4
|
import { SynchronizedTreeState } from "@milaboratories/pl-tree";
|
|
5
5
|
import { Computable } from "@milaboratories/computable";
|
|
@@ -43,29 +43,45 @@ async function createProjectList(pl, rid, openedProjects, env) {
|
|
|
43
43
|
const node = ctx.accessor(tree.entry()).node();
|
|
44
44
|
const oProjects = openedProjects.getValue(ctx);
|
|
45
45
|
if (node === void 0) return void 0;
|
|
46
|
-
|
|
47
|
-
for (const field of node.listDynamicFields()) {
|
|
48
|
-
const prj = node.traverse(field);
|
|
49
|
-
if (prj === void 0) continue;
|
|
50
|
-
const meta = notEmpty(prj.getKeyValueAsJson(ProjectMetaKey));
|
|
51
|
-
const created = notEmpty(prj.getKeyValueAsJson(ProjectCreatedTimestamp));
|
|
52
|
-
const lastModified = notEmpty(prj.getKeyValueAsJson(ProjectLastModifiedTimestamp));
|
|
53
|
-
const projectId = resourceIdToString(prj.id);
|
|
54
|
-
result.push({
|
|
55
|
-
id: projectId,
|
|
56
|
-
created: new Date(created),
|
|
57
|
-
lastModified: new Date(lastModified),
|
|
58
|
-
opened: oProjects.indexOf(projectId) >= 0,
|
|
59
|
-
meta
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
result.sort((p) => -p.lastModified.valueOf());
|
|
63
|
-
return result;
|
|
46
|
+
return projectListEntries(node, oProjects);
|
|
64
47
|
}).withStableType(),
|
|
65
48
|
tree
|
|
66
49
|
};
|
|
67
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds the project list out of the projects-list resource, most recently modified first.
|
|
53
|
+
*
|
|
54
|
+
* Skips, rather than throws on, anything that is not a fully synced project. The projects
|
|
55
|
+
* resource carries the user's entire project list, so one unexpected sibling field — or one
|
|
56
|
+
* project whose metadata has not arrived yet — must cost that entry and nothing else. Both the
|
|
57
|
+
* traversal and the metadata reads are watched, so an entry appears as soon as it syncs.
|
|
58
|
+
*
|
|
59
|
+
* The resource type is matched by name only: the point is to exclude foreign resources, not to
|
|
60
|
+
* hide a project stored under an earlier resource-type version.
|
|
61
|
+
*/
|
|
62
|
+
function projectListEntries(node, openedProjects) {
|
|
63
|
+
const result = [];
|
|
64
|
+
for (const field of node.listDynamicFields()) {
|
|
65
|
+
const prj = node.traverse(field);
|
|
66
|
+
if (prj === void 0) continue;
|
|
67
|
+
if (prj.resourceType.name !== ProjectResourceType.name) continue;
|
|
68
|
+
const meta = prj.getKeyValueAsJson(ProjectMetaKey);
|
|
69
|
+
const created = prj.getKeyValueAsJson(ProjectCreatedTimestamp);
|
|
70
|
+
const lastModified = prj.getKeyValueAsJson(ProjectLastModifiedTimestamp);
|
|
71
|
+
if (meta === void 0 || created === void 0 || lastModified === void 0) continue;
|
|
72
|
+
const projectId = asProjectId(resourceIdToString(prj.id));
|
|
73
|
+
result.push({
|
|
74
|
+
id: projectId,
|
|
75
|
+
created: new Date(created),
|
|
76
|
+
lastModified: new Date(lastModified),
|
|
77
|
+
opened: openedProjects.indexOf(projectId) >= 0,
|
|
78
|
+
meta
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
result.sort((a, b) => b.lastModified.valueOf() - a.lastModified.valueOf());
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
68
84
|
//#endregion
|
|
69
|
-
export { ProjectsField, ProjectsListTreePruningFunction, ProjectsResourceType, createProjectList, ensureProjectListRid, projectsListFieldFilter };
|
|
85
|
+
export { ProjectsField, ProjectsListTreePruningFunction, ProjectsResourceType, createProjectList, ensureProjectListRid, projectListEntries, projectsListFieldFilter };
|
|
70
86
|
|
|
71
87
|
//# sourceMappingURL=project_list.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project_list.js","names":[],"sources":["../../src/middle_layer/project_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type {\n Filter,\n PlClient,\n PlTransaction,\n ResourceType,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n field,\n isNullSignedResourceId,\n resourceIdToString,\n resourceTypesEqual,\n treeFilter,\n} from \"@milaboratories/pl-client\";\nimport type { TreeAndComputableU } from \"./types\";\nimport type { WatchableValue } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { ProjectId, ProjectListEntry } from \"../model/project_model\";\nimport {\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n ProjectMetaKey,\n} from \"../model/project_model\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport {
|
|
1
|
+
{"version":3,"file":"project_list.js","names":[],"sources":["../../src/middle_layer/project_list.ts"],"sourcesContent":["import type { PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type {\n Filter,\n PlClient,\n PlTransaction,\n ResourceType,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n field,\n isNullSignedResourceId,\n resourceIdToString,\n resourceTypesEqual,\n treeFilter,\n} from \"@milaboratories/pl-client\";\nimport type { TreeAndComputableU } from \"./types\";\nimport type { WatchableValue } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { ProjectId, ProjectListEntry } from \"../model/project_model\";\nimport {\n ProjectCreatedTimestamp,\n ProjectLastModifiedTimestamp,\n ProjectMetaKey,\n ProjectResourceType,\n} from \"../model/project_model\";\nimport type { MiddleLayerEnvironment } from \"./middle_layer\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\nimport { asProjectId } from \"@milaboratories/pl-model-common\";\n\nexport const ProjectsField = \"projects\";\nexport const ProjectsResourceType: ResourceType = { name: \"Projects\", version: \"1\" };\n\n/**\n * Resolves the projects-list resource on the transaction's client root, lazily creating (and\n * locking) an empty one when the {@link ProjectsField} is not yet populated. Returns its signed\n * id. Used when writing into a root that may have no projects list yet, e.g. copying a project\n * into another user's root during admin impersonation.\n */\nexport async function ensureProjectListRid(tx: PlTransaction): Promise<SignedResourceId> {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const fData = await tx.getField(projectsField);\n if (isNullSignedResourceId(fData.value)) {\n const ref = tx.createEphemeral(ProjectsResourceType);\n tx.lock(ref);\n tx.setField(projectsField, ref);\n return await ref.globalId;\n }\n return fData.value;\n}\n\nexport const ProjectsListTreePruningFunction: PruningFunction = (resource) => {\n if (!resourceTypesEqual(resource.type, ProjectsResourceType)) return [];\n return resource.fields;\n};\n\nexport const projectsListFieldFilter: Filter = treeFilter.resourceTypeEq(\"Projects\");\n\nexport async function createProjectList(\n pl: PlClient,\n rid: SignedResourceId,\n openedProjects: WatchableValue<ProjectId[]>,\n env: MiddleLayerEnvironment,\n): Promise<TreeAndComputableU<ProjectListEntry[]>> {\n const tree = await SynchronizedTreeState.init(\n pl,\n rid,\n {\n ...env.ops.defaultTreeOptions,\n pruning: ProjectsListTreePruningFunction,\n fieldFilter: projectsListFieldFilter,\n },\n env.logger,\n );\n\n const c = Computable.make((ctx) => {\n const node = ctx.accessor(tree.entry()).node();\n const oProjects = openedProjects.getValue(ctx);\n if (node === undefined) return undefined;\n return projectListEntries(node, oProjects);\n }).withStableType();\n\n return { computable: c, tree };\n}\n\n/** The part of a tree node this reader needs from a single entry of the projects list. */\ninterface ProjectListEntryNode {\n readonly id: SignedResourceId;\n readonly resourceType: ResourceType;\n getKeyValueAsJson<T>(key: string): T | undefined;\n}\n\n/** The part of a tree node this reader needs from the projects-list resource itself. */\ninterface ProjectsListNode {\n listDynamicFields(): string[];\n traverse(fieldName: string): ProjectListEntryNode | undefined;\n}\n\n/**\n * Builds the project list out of the projects-list resource, most recently modified first.\n *\n * Skips, rather than throws on, anything that is not a fully synced project. The projects\n * resource carries the user's entire project list, so one unexpected sibling field — or one\n * project whose metadata has not arrived yet — must cost that entry and nothing else. Both the\n * traversal and the metadata reads are watched, so an entry appears as soon as it syncs.\n *\n * The resource type is matched by name only: the point is to exclude foreign resources, not to\n * hide a project stored under an earlier resource-type version.\n */\nexport function projectListEntries(\n node: ProjectsListNode,\n openedProjects: readonly ProjectId[],\n): ProjectListEntry[] {\n const result: ProjectListEntry[] = [];\n\n // Projects list resource keeps projects assigned to fields. Each field name is project's UUID\n for (const field of node.listDynamicFields()) {\n const prj = node.traverse(field);\n if (prj === undefined) continue;\n if (prj.resourceType.name !== ProjectResourceType.name) continue;\n\n const meta = prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey);\n const created = prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp);\n const lastModified = prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp);\n if (meta === undefined || created === undefined || lastModified === undefined) continue;\n\n const projectId = asProjectId(resourceIdToString(prj.id));\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: openedProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n\n result.sort((a, b) => b.lastModified.valueOf() - a.lastModified.valueOf());\n return result;\n}\n"],"mappings":";;;;;;AA8BA,MAAa,gBAAgB;AAC7B,MAAa,uBAAqC;CAAE,MAAM;CAAY,SAAS;AAAI;;;;;;;AAQnF,eAAsB,qBAAqB,IAA8C;CACvF,MAAM,gBAAgB,MAAM,GAAG,YAAY,aAAa;CACxD,GAAG,YAAY,eAAe,SAAS;CACvC,MAAM,QAAQ,MAAM,GAAG,SAAS,aAAa;CAC7C,IAAI,uBAAuB,MAAM,KAAK,GAAG;EACvC,MAAM,MAAM,GAAG,gBAAgB,oBAAoB;EACnD,GAAG,KAAK,GAAG;EACX,GAAG,SAAS,eAAe,GAAG;EAC9B,OAAO,MAAM,IAAI;CACnB;CACA,OAAO,MAAM;AACf;AAEA,MAAa,mCAAoD,aAAa;CAC5E,IAAI,CAAC,mBAAmB,SAAS,MAAM,oBAAoB,GAAG,OAAO,CAAC;CACtE,OAAO,SAAS;AAClB;AAEA,MAAa,0BAAkC,WAAW,eAAe,UAAU;AAEnF,eAAsB,kBACpB,IACA,KACA,gBACA,KACiD;CACjD,MAAM,OAAO,MAAM,sBAAsB,KACvC,IACA,KACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACT,aAAa;CACf,GACA,IAAI,MACN;CASA,OAAO;EAAE,YAPC,WAAW,MAAM,QAAQ;GACjC,MAAM,OAAO,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;GAC7C,MAAM,YAAY,eAAe,SAAS,GAAG;GAC7C,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;GAC/B,OAAO,mBAAmB,MAAM,SAAS;EAC3C,CAAC,CAAC,CAAC,eAEkB;EAAG;CAAK;AAC/B;;;;;;;;;;;;AA0BA,SAAgB,mBACd,MACA,gBACoB;CACpB,MAAM,SAA6B,CAAC;CAGpC,KAAK,MAAM,SAAS,KAAK,kBAAkB,GAAG;EAC5C,MAAM,MAAM,KAAK,SAAS,KAAK;EAC/B,IAAI,QAAQ,KAAA,GAAW;EACvB,IAAI,IAAI,aAAa,SAAS,oBAAoB,MAAM;EAExD,MAAM,OAAO,IAAI,kBAA+B,cAAc;EAC9D,MAAM,UAAU,IAAI,kBAA0B,uBAAuB;EACrE,MAAM,eAAe,IAAI,kBAA0B,4BAA4B;EAC/E,IAAI,SAAS,KAAA,KAAa,YAAY,KAAA,KAAa,iBAAiB,KAAA,GAAW;EAE/E,MAAM,YAAY,YAAY,mBAAmB,IAAI,EAAE,CAAC;EACxD,OAAO,KAAK;GACV,IAAI;GACJ,SAAS,IAAI,KAAK,OAAO;GACzB,cAAc,IAAI,KAAK,YAAY;GACnC,QAAQ,eAAe,QAAQ,SAAS,KAAK;GAC7C;EACF,CAAC;CACH;CAEA,OAAO,MAAM,GAAG,MAAM,EAAE,aAAa,QAAQ,IAAI,EAAE,aAAa,QAAQ,CAAC;CACzE,OAAO;AACT"}
|
|
@@ -3,28 +3,11 @@ let _milaboratories_pl_client = require("@milaboratories/pl-client");
|
|
|
3
3
|
let _milaboratories_pl_tree = require("@milaboratories/pl-tree");
|
|
4
4
|
let _milaboratories_computable = require("@milaboratories/computable");
|
|
5
5
|
//#region src/middle_layer/sharing_list.ts
|
|
6
|
-
/** Per-project view for the donor's change UI, from the envelope's `projects` payload; empty
|
|
7
|
-
* for a payload that carries no project. */
|
|
8
|
-
function envelopeProjects(data) {
|
|
9
|
-
return Object.values(require_sharing_model.envelopeProjectMap(data)).map((p) => ({
|
|
10
|
-
projectId: p.source,
|
|
11
|
-
label: p.label,
|
|
12
|
-
updatedAt: p.updatedAt
|
|
13
|
-
}));
|
|
14
|
-
}
|
|
15
|
-
const SharingOutboxPruningFunction = (resource) => {
|
|
16
|
-
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharingOutboxResourceType) && !(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharedEnvelopeResourceType)) return [];
|
|
17
|
-
return resource.fields;
|
|
18
|
-
};
|
|
19
|
-
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));
|
|
20
6
|
/**
|
|
21
7
|
* Reactive view of the donor's own outbox. Reads each live envelope's immutable
|
|
22
|
-
* {@link EnvelopeData}
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* exactly who reads this view.
|
|
26
|
-
*
|
|
27
|
-
* API-level only in M1 (no UI).
|
|
8
|
+
* {@link EnvelopeData} from the tree, then enriches each share's full recipient list via
|
|
9
|
+
* `ListGrants` on the envelope (`["*"]` for an everyone-share). `ListGrants` is gated backend-side
|
|
10
|
+
* to the envelope owner (the donor), which is exactly who reads this view.
|
|
28
11
|
*/
|
|
29
12
|
function createOutgoingSharesComputable(pl, tree) {
|
|
30
13
|
return _milaboratories_computable.Computable.make((ctx) => {
|
|
@@ -37,17 +20,8 @@ function createOutgoingSharesComputable(pl, tree) {
|
|
|
37
20
|
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
|
|
38
21
|
const data = require_sharing_model.normalizeEnvelopeData(envelope.getDataAsJson());
|
|
39
22
|
if (data === void 0) continue;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
if (!f.startsWith("acceptance/")) continue;
|
|
43
|
-
const login = f.slice(require_sharing_model.AcceptanceFieldPrefix.length);
|
|
44
|
-
const accData = envelope.traverse(f)?.getDataAsJson();
|
|
45
|
-
if (accData === void 0) continue;
|
|
46
|
-
responses[login] = {
|
|
47
|
-
action: accData.action,
|
|
48
|
-
timestamp: accData.timestamp
|
|
49
|
-
};
|
|
50
|
-
}
|
|
23
|
+
const folder = envelopeFolderSummary(data);
|
|
24
|
+
const description = envelopeDescription(data);
|
|
51
25
|
drafts.push({
|
|
52
26
|
shareId: data.shareId,
|
|
53
27
|
sharedAt: data.sharedAt,
|
|
@@ -58,10 +32,11 @@ function createOutgoingSharesComputable(pl, tree) {
|
|
|
58
32
|
projects: envelopeProjects(data),
|
|
59
33
|
...data.payload.kind === "template" ? { template: {
|
|
60
34
|
label: data.payload.label,
|
|
61
|
-
blockCount: data.payload.document.blocks.length
|
|
35
|
+
blockCount: data.payload.document.blocks.length,
|
|
36
|
+
...data.payload.source === void 0 ? {} : { source: data.payload.source }
|
|
62
37
|
} } : {},
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
...folder === void 0 ? {} : { folder },
|
|
39
|
+
...description === void 0 ? {} : { description },
|
|
65
40
|
envelopeRid: envelope.id
|
|
66
41
|
});
|
|
67
42
|
}
|
|
@@ -97,102 +72,73 @@ async function createOutgoingShares(pl, outboxRid, env) {
|
|
|
97
72
|
tree
|
|
98
73
|
};
|
|
99
74
|
}
|
|
100
|
-
const DecisionFieldPrefix = "decision/";
|
|
101
|
-
const SharedEnvelopePruningFunction = (resource) => {
|
|
102
|
-
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharedEnvelopeResourceType)) return [];
|
|
103
|
-
return resource.fields;
|
|
104
|
-
};
|
|
105
|
-
const PendingSharesFieldFilter = _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharedEnvelopeResourceType.name);
|
|
106
75
|
/**
|
|
107
|
-
* Creates the
|
|
108
|
-
* {@link SharedEnvelopeResourceType}
|
|
109
|
-
*
|
|
110
|
-
* An envelope surfaces only when its shareId has NO decision/{shareId} on SharingState —
|
|
111
|
-
* the dedup is applied by the caller, which holds the SharingState tree.
|
|
76
|
+
* Creates the recipient's shared-resource discovery tree: a `{kind:'shared'}` seed over
|
|
77
|
+
* {@link SharedEnvelopeResourceType}. {@link createAvailableSharesComputable} and
|
|
78
|
+
* {@link createLiveEnvelopesComputable} both read it.
|
|
112
79
|
*/
|
|
113
|
-
async function
|
|
80
|
+
async function createAvailableSharesTree(pl, env) {
|
|
114
81
|
return await _milaboratories_pl_tree.SynchronizedTreeState.init(pl, {
|
|
115
82
|
kind: "shared",
|
|
116
83
|
resourceType: (0, _milaboratories_pl_client.resourceType)(require_sharing_model.SharedEnvelopeResourceType.name, require_sharing_model.SharedEnvelopeResourceType.version)
|
|
117
84
|
}, {
|
|
118
85
|
...env.ops.defaultTreeOptions,
|
|
119
86
|
pruning: SharedEnvelopePruningFunction,
|
|
120
|
-
fieldFilter:
|
|
87
|
+
fieldFilter: AvailableSharesFieldFilter
|
|
121
88
|
}, env.logger);
|
|
122
89
|
}
|
|
123
90
|
/**
|
|
124
|
-
* Builds a Computable yielding the
|
|
91
|
+
* Builds a Computable yielding the recipient's currently-live envelopes, read from the
|
|
125
92
|
* shared-resource discovery tree the ML already maintains — the single discovery mechanism.
|
|
126
|
-
*
|
|
127
|
-
* second discovery path. The envelope's signed `rid` (from the tree node) is what the write
|
|
128
|
-
* needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.
|
|
93
|
+
* A copy out of a share `.getValue()`s this instead of re-streaming `ListUserResources`, so there
|
|
94
|
+
* is no second discovery path. The envelope's signed `rid` (from the tree node) is what the write
|
|
95
|
+
* tx needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.
|
|
129
96
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* envelope per shareId is expected (the donor keeps one), and a replace tears the old one down.
|
|
97
|
+
* Yields a flat list; the caller dedups by `shareId` — at most one live envelope per shareId is
|
|
98
|
+
* expected (the donor keeps one), and a replace tears the old one down.
|
|
133
99
|
*/
|
|
134
100
|
function createLiveEnvelopesComputable(sharedTree) {
|
|
135
|
-
return _milaboratories_computable.Computable.make((ctx) =>
|
|
136
|
-
const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();
|
|
137
|
-
const result = [];
|
|
138
|
-
for (const envelope of roots) {
|
|
139
|
-
if (envelope === void 0) continue;
|
|
140
|
-
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
|
|
141
|
-
const data = require_sharing_model.normalizeEnvelopeData(envelope.getDataAsJson());
|
|
142
|
-
if (data === void 0) continue;
|
|
143
|
-
result.push({
|
|
144
|
-
rid: envelope.id,
|
|
145
|
-
data
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
return result;
|
|
149
|
-
});
|
|
101
|
+
return _milaboratories_computable.Computable.make((ctx) => decodedEnvelopes(ctx.accessor(sharedTree.rootsEntry()).nodes()));
|
|
150
102
|
}
|
|
151
103
|
/**
|
|
152
|
-
* Builds the {@link
|
|
153
|
-
*
|
|
154
|
-
*
|
|
104
|
+
* Builds the {@link AvailableShare} computable over the shared-resource discovery tree, marking
|
|
105
|
+
* each entry with whether this user has hidden it (a hiddenField in their own SharingState).
|
|
106
|
+
* Both trees feed one Computable so it recomputes when either changes.
|
|
155
107
|
*
|
|
156
108
|
* `currentUserLogin` (when known) suppresses the user's own shares: a share-with-everybody grants
|
|
157
|
-
* the everyone-user, so the donor discovers their own envelope
|
|
158
|
-
*
|
|
109
|
+
* the everyone-user, so the donor discovers their own envelope here. A donor already owns what
|
|
110
|
+
* they shared, so their own envelopes are dropped from this view.
|
|
159
111
|
*/
|
|
160
|
-
function
|
|
112
|
+
function createAvailableSharesComputable(sharedTree, sharingStateTree, currentUserLogin) {
|
|
161
113
|
return _milaboratories_computable.Computable.make((ctx) => {
|
|
162
|
-
const roots = ctx.accessor(sharedTree.rootsEntry()).nodes();
|
|
163
114
|
const stateNode = ctx.accessor(sharingStateTree.entry()).node();
|
|
164
|
-
const
|
|
115
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
165
116
|
if (stateNode !== void 0) {
|
|
166
|
-
for (const f of stateNode.listDynamicFields()) if (
|
|
117
|
+
for (const f of stateNode.listDynamicFields()) if (require_sharing_model.isHiddenField(f)) hidden.add(require_sharing_model.hiddenFieldShareId(f));
|
|
167
118
|
}
|
|
168
119
|
const result = [];
|
|
169
|
-
for (const
|
|
170
|
-
if (envelope === void 0) continue;
|
|
171
|
-
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
|
|
172
|
-
const data = require_sharing_model.normalizeEnvelopeData(envelope.getDataAsJson());
|
|
173
|
-
if (data === void 0) continue;
|
|
174
|
-
if (handled.has(data.shareId)) continue;
|
|
120
|
+
for (const { data } of decodedEnvelopes(ctx.accessor(sharedTree.rootsEntry()).nodes())) {
|
|
175
121
|
if (currentUserLogin !== null && data.sender === currentUserLogin) continue;
|
|
176
122
|
if (data.expiresAt !== null && data.expiresAt <= Date.now()) continue;
|
|
123
|
+
const folder = envelopeFolderSummary(data);
|
|
124
|
+
const description = envelopeDescription(data);
|
|
177
125
|
result.push({
|
|
178
126
|
shareId: data.shareId,
|
|
179
127
|
sender: data.sender,
|
|
180
128
|
title: data.title,
|
|
181
129
|
mode: data.mode,
|
|
182
130
|
payloadKind: data.payload.kind,
|
|
183
|
-
grantedAt: data.sharedAt
|
|
131
|
+
grantedAt: data.sharedAt,
|
|
132
|
+
...description === void 0 ? {} : { description },
|
|
133
|
+
...folder === void 0 ? {} : { folder },
|
|
134
|
+
hidden: hidden.has(data.shareId)
|
|
184
135
|
});
|
|
185
136
|
}
|
|
186
137
|
result.sort((a, b) => b.grantedAt - a.grantedAt);
|
|
187
138
|
return result;
|
|
188
139
|
});
|
|
189
140
|
}
|
|
190
|
-
|
|
191
|
-
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharingStateResourceType)) return [];
|
|
192
|
-
return resource.fields;
|
|
193
|
-
};
|
|
194
|
-
const SharingStateFieldFilter = _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharingStateResourceType.name);
|
|
195
|
-
/** Creates the acceptor's SharingState synchronized tree (single explicit root). */
|
|
141
|
+
/** Creates the recipient's SharingState synchronized tree (single explicit root). */
|
|
196
142
|
async function createSharingStateTree(pl, stateRid, env) {
|
|
197
143
|
return await _milaboratories_pl_tree.SynchronizedTreeState.init(pl, stateRid, {
|
|
198
144
|
...env.ops.defaultTreeOptions,
|
|
@@ -200,12 +146,96 @@ async function createSharingStateTree(pl, stateRid, env) {
|
|
|
200
146
|
fieldFilter: SharingStateFieldFilter
|
|
201
147
|
}, env.logger);
|
|
202
148
|
}
|
|
149
|
+
const SharingOutboxPruningFunction = (resource) => {
|
|
150
|
+
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharingOutboxResourceType) && !(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharedEnvelopeResourceType)) return [];
|
|
151
|
+
return resource.fields;
|
|
152
|
+
};
|
|
153
|
+
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));
|
|
154
|
+
const SharedEnvelopePruningFunction = (resource) => {
|
|
155
|
+
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharedEnvelopeResourceType)) return [];
|
|
156
|
+
return resource.fields;
|
|
157
|
+
};
|
|
158
|
+
const AvailableSharesFieldFilter = _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharedEnvelopeResourceType.name);
|
|
159
|
+
const SharingStatePruningFunction = (resource) => {
|
|
160
|
+
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, require_sharing_model.SharingStateResourceType)) return [];
|
|
161
|
+
return resource.fields;
|
|
162
|
+
};
|
|
163
|
+
const SharingStateFieldFilter = _milaboratories_pl_client.treeFilter.resourceTypeEq(require_sharing_model.SharingStateResourceType.name);
|
|
164
|
+
/**
|
|
165
|
+
* Every envelope among the discovery tree's roots that this build can read, decoded. A root that
|
|
166
|
+
* is not a {@link SharedEnvelopeResourceType}, or whose schemaVersion or payload kind this build
|
|
167
|
+
* does not know, is left out rather than offered: there is nothing useful to do with a share that
|
|
168
|
+
* cannot be read.
|
|
169
|
+
*/
|
|
170
|
+
function decodedEnvelopes(roots) {
|
|
171
|
+
const result = [];
|
|
172
|
+
for (const envelope of roots) {
|
|
173
|
+
if (envelope === void 0) continue;
|
|
174
|
+
if (!(0, _milaboratories_pl_client.resourceTypesEqual)(envelope.resourceType, require_sharing_model.SharedEnvelopeResourceType)) continue;
|
|
175
|
+
const data = require_sharing_model.normalizeEnvelopeData(envelope.getDataAsJson());
|
|
176
|
+
if (data === void 0) continue;
|
|
177
|
+
result.push({
|
|
178
|
+
rid: envelope.id,
|
|
179
|
+
data
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
/** Per-project view for the donor, from the envelope's `projects` payload; empty for a payload
|
|
185
|
+
* that carries no project. */
|
|
186
|
+
function envelopeProjects(data) {
|
|
187
|
+
return Object.values(require_sharing_model.envelopeProjectMap(data)).map((p) => ({
|
|
188
|
+
projectId: p.source,
|
|
189
|
+
label: p.label,
|
|
190
|
+
updatedAt: p.updatedAt
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* What a folder share is, in the words both lists show it in: the shared folder's name and how
|
|
195
|
+
* much is inside. `undefined` for any other payload.
|
|
196
|
+
*
|
|
197
|
+
* The root folder is excluded from `folderCount` — it is the thing being described, not something
|
|
198
|
+
* inside it. A folder envelope with no single root is one nothing can be rebuilt from, and it is
|
|
199
|
+
* described as nothing rather than guessed at.
|
|
200
|
+
*/
|
|
201
|
+
function envelopeFolderSummary(data) {
|
|
202
|
+
if (data.payload.kind !== "folder") return void 0;
|
|
203
|
+
const { folders, projects, templates } = data.payload;
|
|
204
|
+
const root = require_sharing_model.envelopeFolderRoot(folders);
|
|
205
|
+
if (root === void 0) return void 0;
|
|
206
|
+
return {
|
|
207
|
+
label: folders[root].name,
|
|
208
|
+
source: data.payload.source,
|
|
209
|
+
folderCount: Object.keys(folders).length - 1,
|
|
210
|
+
projectCount: Object.keys(projects).length,
|
|
211
|
+
templateCount: templates.length
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* What the shared thing says about itself: the template's description, the root folder's, or the
|
|
216
|
+
* one project's of a single-project share. `undefined` when it has none, for a pack of several
|
|
217
|
+
* projects, which describes no single thing, and for a folder envelope with no single root.
|
|
218
|
+
*/
|
|
219
|
+
function envelopeDescription(data) {
|
|
220
|
+
const payload = data.payload;
|
|
221
|
+
switch (payload.kind) {
|
|
222
|
+
case "template": return payload.description;
|
|
223
|
+
case "folder": {
|
|
224
|
+
const root = require_sharing_model.envelopeFolderRoot(payload.folders);
|
|
225
|
+
return root === void 0 ? void 0 : payload.folders[root].description;
|
|
226
|
+
}
|
|
227
|
+
case "projects": {
|
|
228
|
+
const projects = Object.values(payload.projects);
|
|
229
|
+
return projects.length === 1 ? projects[0]?.description : void 0;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
203
233
|
//#endregion
|
|
234
|
+
exports.createAvailableSharesComputable = createAvailableSharesComputable;
|
|
235
|
+
exports.createAvailableSharesTree = createAvailableSharesTree;
|
|
204
236
|
exports.createLiveEnvelopesComputable = createLiveEnvelopesComputable;
|
|
205
237
|
exports.createOutgoingShares = createOutgoingShares;
|
|
206
238
|
exports.createOutgoingSharesComputable = createOutgoingSharesComputable;
|
|
207
|
-
exports.createPendingSharesComputable = createPendingSharesComputable;
|
|
208
|
-
exports.createPendingSharesTree = createPendingSharesTree;
|
|
209
239
|
exports.createSharingStateTree = createSharingStateTree;
|
|
210
240
|
|
|
211
241
|
//# sourceMappingURL=sharing_list.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sharing_list.cjs","names":["envelopeProjectMap","SharingOutboxResourceType","SharedEnvelopeResourceType","treeFilter","Computable","normalizeEnvelopeData","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 EnvelopePayloadKind,\n ShareId,\n} from \"../model/sharing_model\";\nimport {\n AcceptanceFieldPrefix,\n asShareId,\n envelopeProjectMap,\n normalizeEnvelopeData,\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 /** What the share carries — a pack of projects, or one template document. */\n payloadKind: EnvelopePayloadKind;\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. Empty for a template share. */\n projects: { projectId: ProjectId; label: string; updatedAt: number }[];\n /** The shared template, for a template share; absent for a project share. */\n template?: { label: string; blockCount: number };\n /** Full recipient logins, from `ListGrants` on the envelope; `[\"*\"]` for everyone-shares. */\n recipients: string[];\n /** Whether {@link responses} can ever be populated for this share. A template share is granted\n * read-only, so no recipient can record a reply on the envelope and the donor never learns who\n * accepted — a view must say so rather than render an empty response list as \"nobody yet\". */\n responsesAvailable: boolean;\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 the envelope's `projects` payload; empty\n * for a payload that carries no project. */\nfunction envelopeProjects(data: EnvelopeData): OutgoingShare[\"projects\"] {\n return Object.values(envelopeProjectMap(data)).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 /** What the offer carries, so the recipient is told what they are being offered — projects to\n * copy, or a template for their own shelf. */\n payloadKind: EnvelopePayloadKind;\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 = normalizeEnvelopeData(envelope.getDataAsJson<unknown>());\n if (data === undefined) continue; // unknown version or payload kind — not ours to show\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 payloadKind: data.payload.kind,\n projects: envelopeProjects(data),\n ...(data.payload.kind === \"template\"\n ? {\n template: {\n label: data.payload.label,\n blockCount: data.payload.document.blocks.length,\n },\n }\n : {}),\n responsesAvailable: data.payload.kind !== \"template\",\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 = normalizeEnvelopeData(envelope.getDataAsJson<unknown>());\n // Same recognise-or-hide rule as the pending view, and for the same envelope: hiding an\n // offer while accept could still resolve it would only move the problem.\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 = normalizeEnvelopeData(envelope.getDataAsJson<unknown>());\n // An envelope whose schemaVersion or payload kind this build does not know is hidden, not\n // offered: there is nothing useful to do with a share we cannot read.\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 payloadKind: data.payload.kind,\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":";;;;;;;AAyDA,SAAS,iBAAiB,MAA+C;CACvE,OAAO,OAAO,OAAOA,sBAAAA,mBAAmB,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;EACzD,WAAW,EAAE;EACb,OAAO,EAAE;EACT,WAAW,EAAE;CACf,EAAE;AACJ;AAcA,MAAM,gCAAiD,aAAa;CAClE,IACE,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,MAAMC,sBAAAA,yBAAyB,KAC5D,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,MAAMC,sBAAAA,0BAA0B,GAE7D,OAAO,CAAC;CACV,OAAO,SAAS;AAClB;AAOA,MAAM,2BAAmCC,0BAAAA,WAAW,GAClDA,0BAAAA,WAAW,eAAeF,sBAAAA,0BAA0B,IAAI,GACxDE,0BAAAA,WAAW,eAAeD,sBAAAA,2BAA2B,IAAI,CAC3D;;;;;;;;;;AAeA,SAAgB,+BACd,IACA,MACyC;CACzC,OAAOE,2BAAAA,WAAW,MACf,QAAQ;EACP,MAAM,OAAO,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK;EAC7C,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;EAE/B,MAAM,SAA+B,CAAC;EAEtC,KAAK,MAAM,aAAa,KAAK,kBAAkB,GAAG;GAChD,MAAM,WAAW,KAAK,SAAS,SAAS;GACxC,IAAI,aAAa,KAAA,GAAW;GAC5B,IAAI,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,cAAcF,sBAAAA,0BAA0B,GAAG;GAE5E,MAAM,OAAOG,sBAAAA,sBAAsB,SAAS,cAAuB,CAAC;GACpE,IAAI,SAAS,KAAA,GAAW;GAExB,MAAM,YAAwC,CAAC;GAC/C,KAAK,MAAM,KAAK,SAAS,kBAAkB,GAAG;IAC5C,IAAI,CAAC,EAAE,WAAA,aAAgC,GAAG;IAC1C,MAAM,QAAQ,EAAE,MAAMC,sBAAAA,sBAAsB,MAAM;IAElD,MAAM,UADM,SAAS,SAAS,CACZ,CAAC,EAAE,cAAkC;IACvD,IAAI,YAAY,KAAA,GAAW;IAC3B,UAAU,SAAS;KAAE,QAAQ,QAAQ;KAAQ,WAAW,QAAQ;IAAU;GAC5E;GAEA,OAAO,KAAK;IACV,SAAS,KAAK;IACd,UAAU,KAAK;IACf,GAAI,KAAK,cAAc,OAAO,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;IAC/D,MAAM,KAAK;IACX,OAAO,KAAK;IACZ,aAAa,KAAK,QAAQ;IAC1B,UAAU,iBAAiB,IAAI;IAC/B,GAAI,KAAK,QAAQ,SAAS,aACtB,EACE,UAAU;KACR,OAAO,KAAK,QAAQ;KACpB,YAAY,KAAK,QAAQ,SAAS,OAAO;IAC3C,EACF,IACA,CAAC;IACL,oBAAoB,KAAK,QAAQ,SAAS;IAC1C;IACA,aAAa,SAAS;GACxB,CAAC;EACH;EACA,OAAO,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;EAC7C,OAAO;CACT,GACA,EAKE,kBAAkB,OAChB,WACyC;EACzC,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;EACjC,MAAM,OAAO,GAAG,cAAc;EAC9B,MAAM,gBAAgB,WACpB,OAAO,MAAM,OAAA,GAAA,0BAAA,oBAAA,CAA0B,EAAE,IAAI,CAAC,IAC1C,CAAC,GAAG,IACJ,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,QAAQ,MAAM,MAAM,IAAI;EAExD,MAAM,WAAW,GAAG,cAAc,iBAAiB;EACnD,MAAM,UAAU,MAAM,QAAQ,WAC5B,OAAO,KAAK,MACV,WACI,GAAG,WAAW,mBAAmB,OAAO,GAAG,WAAW,EAAE,WAAW,CAAC,IACpE,GAAG,cAAc,WAAW,EAAE,WAAW,CAC/C,CACF;EAEA,OAAO,OAAO,KAAK,EAAE,aAAa,cAAc,GAAG,SAAS,MAAqB;GAC/E,MAAM,IAAI,QAAQ;GAClB,OAAO;IAAE,GAAG;IAAO,YAAY,EAAE,WAAW,cAAc,aAAa,EAAE,KAAK,IAAI,CAAC;GAAE;EACvF,CAAC;CACH,EACF,CACF;AACF;;;;;AAMA,eAAsB,qBACpB,IACA,WACA,KAC+F;CAC/F,MAAM,OAAO,MAAMC,wBAAAA,sBAAsB,KACvC,IACA,WACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACT,aAAa;CACf,GACA,IAAI,MACN;CACA,OAAO;EAAE,YAAY,+BAA+B,IAAI,IAAI;EAAG;CAAK;AACtE;AAEA,MAAM,sBAAsB;AAE5B,MAAM,iCAAkD,aAAa;CACnE,IAAI,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,MAAML,sBAAAA,0BAA0B,GAAG,OAAO,CAAC;CAC5E,OAAO,SAAS;AAClB;AAKA,MAAM,2BAAmCC,0BAAAA,WAAW,eAAeD,sBAAAA,2BAA2B,IAAI;;;;;;;;AASlG,eAAsB,wBACpB,IACA,KACgC;CAChC,OAAO,MAAMK,wBAAAA,sBAAsB,KACjC,IACA;EACE,MAAM;EACN,eAAA,GAAA,0BAAA,aAAA,CACEL,sBAAAA,2BAA2B,MAC3BA,sBAAAA,2BAA2B,OAC7B;CACF,GACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACT,aAAa;CACf,GACA,IAAI,MACN;AACF;;;;;;;;;;;;AAiBA,SAAgB,8BACd,YACwC;CACxC,OAAOE,2BAAAA,WAAW,MAAM,QAAQ;EAC9B,MAAM,QAAQ,IAAI,SAAS,WAAW,WAAW,CAAC,CAAC,CAAC,MAAM;EAC1D,MAAM,SAAyB,CAAC;EAChC,KAAK,MAAM,YAAY,OAAO;GAC5B,IAAI,aAAa,KAAA,GAAW;GAC5B,IAAI,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,cAAcF,sBAAAA,0BAA0B,GAAG;GAC5E,MAAM,OAAOG,sBAAAA,sBAAsB,SAAS,cAAuB,CAAC;GAGpE,IAAI,SAAS,KAAA,GAAW;GACxB,OAAO,KAAK;IAAE,KAAK,SAAS;IAAI;GAAK,CAAC;EACxC;EACA,OAAO;CACT,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,8BACd,YACA,kBACA,kBACwC;CACxC,OAAOD,2BAAAA,WAAW,MAAM,QAAQ;EAC9B,MAAM,QAAQ,IAAI,SAAS,WAAW,WAAW,CAAC,CAAC,CAAC,MAAM;EAG1D,MAAM,YAAY,IAAI,SAAS,iBAAiB,MAAM,CAAC,CAAC,CAAC,KAAK;EAC9D,MAAM,0BAAU,IAAI,IAAa;EACjC,IAAI,cAAc,KAAA;QACX,MAAM,KAAK,UAAU,kBAAkB,GAC1C,IAAI,EAAE,WAAW,mBAAmB,GAClC,QAAQ,IAAII,sBAAAA,UAAU,EAAE,MAAM,CAA0B,CAAC,CAAC;EAAA;EAEhE,MAAM,SAAyB,CAAC;EAChC,KAAK,MAAM,YAAY,OAAO;GAC5B,IAAI,aAAa,KAAA,GAAW;GAC5B,IAAI,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,cAAcN,sBAAAA,0BAA0B,GAAG;GAC5E,MAAM,OAAOG,sBAAAA,sBAAsB,SAAS,cAAuB,CAAC;GAGpE,IAAI,SAAS,KAAA,GAAW;GACxB,IAAI,QAAQ,IAAI,KAAK,OAAO,GAAG;GAC/B,IAAI,qBAAqB,QAAQ,KAAK,WAAW,kBAAkB;GACnE,IAAI,KAAK,cAAc,QAAQ,KAAK,aAAa,KAAK,IAAI,GAAG;GAE7D,OAAO,KAAK;IACV,SAAS,KAAK;IACd,QAAQ,KAAK;IACb,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,aAAa,KAAK,QAAQ;IAC1B,WAAW,KAAK;GAClB,CAAC;EACH;EACA,OAAO,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;EAC/C,OAAO;CACT,CAAC;AACH;AAEA,MAAM,+BAAgD,aAAa;CACjE,IAAI,EAAA,GAAA,0BAAA,mBAAA,CAAoB,SAAS,MAAMI,sBAAAA,wBAAwB,GAAG,OAAO,CAAC;CAC1E,OAAO,SAAS;AAClB;AAGA,MAAM,0BAAkCN,0BAAAA,WAAW,eAAeM,sBAAAA,yBAAyB,IAAI;;AAG/F,eAAsB,uBACpB,IACA,UACA,KACgC;CAChC,OAAO,MAAMF,wBAAAA,sBAAsB,KACjC,IACA,UACA;EACE,GAAG,IAAI,IAAI;EACX,SAAS;EACT,aAAa;CACf,GACA,IAAI,MACN;AACF"}
|
|
1
|
+
{"version":3,"file":"sharing_list.cjs","names":["Computable","SharedEnvelopeResourceType","normalizeEnvelopeData","SynchronizedTreeState","isHiddenField","hiddenFieldShareId","SharingOutboxResourceType","treeFilter","SharingStateResourceType","envelopeProjectMap","envelopeFolderRoot"],"sources":["../../src/middle_layer/sharing_list.ts"],"sourcesContent":["import type { PlTreeNodeAccessor, PruningFunction } from \"@milaboratories/pl-tree\";\nimport { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport type { Filter, PlClient, SignedResourceId } from \"@milaboratories/pl-client\";\nimport {\n isEveryoneUserLogin,\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, TemplateId } from \"@milaboratories/pl-model-common\";\nimport type { FolderId } from \"@milaboratories/pl-model-middle-layer\";\nimport type {\n EnvelopeData,\n EnvelopeMode,\n EnvelopePayloadKind,\n ShareId,\n} from \"../model/sharing_model\";\nimport {\n hiddenFieldShareId,\n isHiddenField,\n envelopeFolderRoot,\n envelopeProjectMap,\n normalizeEnvelopeData,\n SharedEnvelopeResourceType,\n SharingOutboxResourceType,\n SharingStateResourceType,\n} from \"../model/sharing_model\";\n\n/** What a folder share carries, as both share lists show it. */\nexport interface EnvelopeFolderSummary {\n label: string;\n /** Donor's own id of the shared folder. */\n source: FolderId;\n folderCount: number;\n projectCount: number;\n templateCount: number;\n}\n\n/** Donor-facing view of one outgoing share. */\nexport interface OutgoingShare {\n shareId: ShareId; // identity of the share; a share that replaces it gets its own\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 /** What the share carries — a pack of projects, one template document, or a folder subtree. */\n payloadKind: EnvelopePayloadKind;\n /** One entry per project in the pack. `projectId` is the donor's source project id, which is\n * what the share dialog matches a project's prior shares on; `updatedAt` is when this\n * project's snapshot was taken. Empty for any share that is not a pack of projects. */\n projects: { projectId: ProjectId; label: string; updatedAt: number }[];\n /** The shared template, for a template share; absent otherwise. `source` is the donor's own\n * template id, which is what the share dialog matches a template's prior shares on; a share\n * made before it was carried has none and matches nothing. */\n template?: { label: string; blockCount: number; source?: TemplateId };\n /** The shared folder, for a folder share; absent otherwise. `label` is the root folder's own\n * name, `source` the donor's own id for it — what a prior share of the same folder is matched\n * on — and the counts are of the whole subtree, the root folder itself excluded. */\n folder?: EnvelopeFolderSummary;\n /** What the shared thing says about itself: a template's, a folder's, or the one project's of\n * a single-project share. Absent for a pack of several, which has no one thing to describe. */\n description?: string;\n /** Full recipient logins, from `ListGrants` on the envelope; `[\"*\"]` for everyone-shares. */\n recipients: string[];\n}\n\n/**\n * Recipient-facing view of one share that is open to this user.\n *\n * A share is a shelf, not an invitation: it stays listed for as long as the envelope lives, and\n * the recipient may copy from it any number of times. {@link hidden} is that recipient's own\n * private choice to stop seeing it, and it can be undone.\n */\nexport interface AvailableShare {\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;\n /** What the share carries — projects to copy, a template to add to the recipient's templates,\n * or a folder subtree to rebuild in the recipient's tree. */\n payloadKind: EnvelopePayloadKind;\n grantedAt: number;\n /** What the shared thing says about itself. See {@link OutgoingShare.description}. */\n description?: string;\n /** The shared folder, for a folder share; absent otherwise. See {@link OutgoingShare.folder}. */\n folder?: EnvelopeFolderSummary;\n /** Whether this recipient has put it out of sight. Hidden shares are listed, not dropped: the\n * view that shows them is what \"show hidden\" turns on. */\n hidden: boolean;\n}\n\n/** A live envelope discovered in the recipient's shared-resource tree: its signed resource id and\n * decoded {@link EnvelopeData}. A copy out of a share finds it by `data.shareId`. */\nexport type LiveEnvelope = { rid: SignedResourceId; data: EnvelopeData };\n\n/**\n * Reactive view of the donor's own outbox. Reads each live envelope's immutable\n * {@link EnvelopeData} from the tree, then enriches each share's full recipient list via\n * `ListGrants` on the envelope (`[\"*\"]` for an everyone-share). `ListGrants` is gated backend-side\n * to the envelope owner (the donor), which is exactly who reads this view.\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 = normalizeEnvelopeData(envelope.getDataAsJson<unknown>());\n if (data === undefined) continue; // unknown version or payload kind — not ours to show\n\n const folder = envelopeFolderSummary(data);\n const description = envelopeDescription(data);\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 payloadKind: data.payload.kind,\n projects: envelopeProjects(data),\n ...(data.payload.kind === \"template\"\n ? {\n template: {\n label: data.payload.label,\n blockCount: data.payload.document.blocks.length,\n ...(data.payload.source === undefined ? {} : { source: data.payload.source }),\n },\n }\n : {}),\n ...(folder === undefined ? {} : { folder }),\n ...(description === undefined ? {} : { description }),\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\n/**\n * Creates the recipient's shared-resource discovery tree: a `{kind:'shared'}` seed over\n * {@link SharedEnvelopeResourceType}. {@link createAvailableSharesComputable} and\n * {@link createLiveEnvelopesComputable} both read it.\n */\nexport async function createAvailableSharesTree(\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: AvailableSharesFieldFilter,\n },\n env.logger,\n );\n}\n\n/**\n * Builds a Computable yielding the recipient's currently-live envelopes, read from the\n * shared-resource discovery tree the ML already maintains — the single discovery mechanism.\n * A copy out of a share `.getValue()`s this instead of re-streaming `ListUserResources`, so there\n * is no second discovery path. The envelope's signed `rid` (from the tree node) is what the write\n * tx needs; `copyEnvelopeProjectsIntoList` reads the project snapshots itself inside the tx.\n *\n * Yields a flat list; the caller dedups by `shareId` — at most one live envelope per shareId is\n * 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) => decodedEnvelopes(ctx.accessor(sharedTree.rootsEntry()).nodes()));\n}\n\n/**\n * Builds the {@link AvailableShare} computable over the shared-resource discovery tree, marking\n * each entry with whether this user has hidden it (a hiddenField in their own SharingState).\n * 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 here. A donor already owns what\n * they shared, so their own envelopes are dropped from this view.\n */\nexport function createAvailableSharesComputable(\n sharedTree: SynchronizedTreeState,\n sharingStateTree: SynchronizedTreeState,\n currentUserLogin: string | null,\n): Computable<AvailableShare[] | undefined> {\n return Computable.make((ctx) => {\n // Shares this user has put out of sight, keyed on the share. A share replaced by a newer one\n // is a new share, so its replacement arrives unhidden.\n const stateNode = ctx.accessor(sharingStateTree.entry()).node();\n const hidden = new Set<ShareId>();\n if (stateNode !== undefined)\n for (const f of stateNode.listDynamicFields())\n if (isHiddenField(f)) hidden.add(hiddenFieldShareId(f));\n\n const result: AvailableShare[] = [];\n for (const { data } of decodedEnvelopes(ctx.accessor(sharedTree.rootsEntry()).nodes())) {\n if (currentUserLogin !== null && data.sender === currentUserLogin) continue; // own share\n if (data.expiresAt !== null && data.expiresAt <= Date.now()) continue;\n\n const folder = envelopeFolderSummary(data);\n const description = envelopeDescription(data);\n result.push({\n shareId: data.shareId,\n sender: data.sender,\n title: data.title,\n mode: data.mode,\n payloadKind: data.payload.kind,\n grantedAt: data.sharedAt,\n ...(description === undefined ? {} : { description }),\n ...(folder === undefined ? {} : { folder }),\n hidden: hidden.has(data.shareId),\n });\n }\n result.sort((a, b) => b.grantedAt - a.grantedAt);\n return result;\n });\n}\n\n/** Creates the recipient'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//\n// Internals\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\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 AvailableSharesFieldFilter: Filter = treeFilter.resourceTypeEq(\n SharedEnvelopeResourceType.name,\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/** 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 * Every envelope among the discovery tree's roots that this build can read, decoded. A root that\n * is not a {@link SharedEnvelopeResourceType}, or whose schemaVersion or payload kind this build\n * does not know, is left out rather than offered: there is nothing useful to do with a share that\n * cannot be read.\n */\nfunction decodedEnvelopes(roots: readonly (PlTreeNodeAccessor | undefined)[]): LiveEnvelope[] {\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 = normalizeEnvelopeData(envelope.getDataAsJson<unknown>());\n if (data === undefined) continue;\n result.push({ rid: envelope.id, data });\n }\n return result;\n}\n\n/** Per-project view for the donor, from the envelope's `projects` payload; empty for a payload\n * that carries no project. */\nfunction envelopeProjects(data: EnvelopeData): OutgoingShare[\"projects\"] {\n return Object.values(envelopeProjectMap(data)).map((p) => ({\n projectId: p.source,\n label: p.label,\n updatedAt: p.updatedAt,\n }));\n}\n\n/**\n * What a folder share is, in the words both lists show it in: the shared folder's name and how\n * much is inside. `undefined` for any other payload.\n *\n * The root folder is excluded from `folderCount` — it is the thing being described, not something\n * inside it. A folder envelope with no single root is one nothing can be rebuilt from, and it is\n * described as nothing rather than guessed at.\n */\nfunction envelopeFolderSummary(data: EnvelopeData): EnvelopeFolderSummary | undefined {\n if (data.payload.kind !== \"folder\") return undefined;\n const { folders, projects, templates } = data.payload;\n const root = envelopeFolderRoot(folders);\n if (root === undefined) return undefined;\n return {\n label: folders[root].name,\n source: data.payload.source,\n folderCount: Object.keys(folders).length - 1,\n projectCount: Object.keys(projects).length,\n templateCount: templates.length,\n };\n}\n\n/**\n * What the shared thing says about itself: the template's description, the root folder's, or the\n * one project's of a single-project share. `undefined` when it has none, for a pack of several\n * projects, which describes no single thing, and for a folder envelope with no single root.\n */\nfunction envelopeDescription(data: EnvelopeData): string | undefined {\n const payload = data.payload;\n switch (payload.kind) {\n case \"template\":\n return payload.description;\n case \"folder\": {\n const root = envelopeFolderRoot(payload.folders);\n return root === undefined ? undefined : payload.folders[root].description;\n }\n case \"projects\": {\n const projects = Object.values(payload.projects);\n return projects.length === 1 ? projects[0]?.description : undefined;\n }\n }\n}\n"],"mappings":"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"}
|