@milaboratories/pl-middle-layer 1.64.42 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"middle_layer.js","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type {\n PlClient,\n PlTransaction,\n SignedResourceId,\n ResourceRef,\n Role,\n} from \"@milaboratories/pl-client\";\nimport {\n isEveryoneUserLogin,\n field,\n GrantType,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport {\n acceptanceFieldLogin,\n canGrantToEveryone,\n decodeEnvelopeData,\n isAcceptanceField,\n SharingOutboxField,\n SharingOutboxResourceType,\n SharingStateField,\n SharingStateResourceType,\n type EnvelopeAcceptance,\n type EnvelopeData,\n type ProjectChangeAction,\n type ProjectFieldUuid,\n type ShareId,\n type ShareProjectsOptions,\n} from \"../model/sharing_model\";\nimport {\n buildShareEnvelope,\n copyEnvelopeProjectsIntoList,\n envelopeProjectFieldUuid,\n isEnvelopeProjectField,\n resourceIdsToStrings,\n writeEnvelopeAcceptance,\n writeSharingDecision,\n type EnvelopeProjectSource,\n} from \"../mutator/sharing\";\nimport type { LiveEnvelope, OutgoingShare, PendingShare } from \"./sharing_list\";\nimport {\n createLiveEnvelopesComputable,\n createOutgoingShares,\n createPendingSharesComputable,\n createPendingSharesTree,\n createSharingStateTree,\n} from \"./sharing_list\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher, cachedDeserialize } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { Computable, ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly sharingOutboxResourceId: SignedResourceId,\n private readonly sharingStateResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n private readonly sharingOutboxTree: SynchronizedTreeState,\n private readonly sharingStateTree: SynchronizedTreeState,\n private readonly pendingSharesTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>,\n /** Reactive view of the donor's outbox — the shares this user has created.\n * v1: API only, no UI. */\n public outgoingShares: Computable<OutgoingShare[] | undefined>,\n /** Envelopes granted to this user, not yet accepted or rejected. Fed by the\n * shared-resource discovery tree. */\n public pendingShares: Computable<PendingShare[] | undefined>,\n /** Internal: the acceptor's currently-live envelopes, read from the same shared-resource\n * discovery tree as {@link pendingShares}. The single source the accept/reject flow resolves\n * live envelopes from — no second discovery path. */\n private readonly liveEnvelopes: Computable<LiveEnvelope[] | undefined>,\n ) {\n this.pl = this.env.pl;\n this.startEnvelopeCleanup();\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /**\n * Runtime capabilities advertised by the connected backend (tokens of\n * the form `<feature>:<version>`, e.g. \"wasm:v1\"). Empty list if the\n * backend predates the capability mechanism — that's the desired\n * fail-closed behaviour for blocks declaring any `requiredCapabilities`.\n */\n public get serverCapabilities(): string[] {\n return this.pl.serverInfo.capabilities ?? [];\n }\n\n /**\n * Login of the authenticated user, for the \"Signed in as\" UI. `null` when the\n * backend has no auth (local/dev mode) — the UI hides the element.\n */\n public get currentUserLogin(): string | null {\n return this.pl.userResources.authUser;\n }\n\n /**\n * Whether the connected backend supports project sharing. Synthetic — computed\n * in the middle layer from the backend capabilities the share flow needs (the\n * cross-color field-reference relaxation the accept flow rests on). It can absorb\n * additional required capabilities later without a UI change.\n */\n public get sharingSupported(): boolean {\n return this.serverCapabilities.includes(\"crossTreeRefs:v1\");\n }\n\n /**\n * Role of the authenticated user, from the `GetSessionInfo` RPC surfaced through the\n * pl-client. `null` in no-auth mode (when {@link currentUserLogin} is null).\n */\n public get currentUserRole(): Role | null {\n return this.pl.currentUserRole;\n }\n\n /**\n * Whether the UI offers share-with-everybody — no role policy lives in the UI:\n * serverCapabilities.has(\"publicGrants:v1\") && canGrantToEveryone(currentUserRole)\n * Not a security boundary: a crafted call still hits the backend's role +\n * permission-ceiling gate.\n */\n public get canShareWithEveryone(): boolean {\n return (\n this.serverCapabilities.includes(\"publicGrants:v1\") &&\n canGrantToEveryone(this.currentUserRole)\n );\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n //\n // Project Sharing (Copy & Share)\n //\n\n /**\n * Shares the given projects (Copy & Share). Snapshots the projects, creates one envelope, and\n * grants it — all in one atomic write transaction, so a failed grant rolls the whole thing back\n * and the outbox is left as it was.\n *\n * Two variants (see {@link ShareProjectsOptions}):\n * - `{ recipients }` — one writable grant per named recipient; the envelope expires after the\n * default TTL (`sharedAt + envelopeTtlMs`).\n * - `{ everyone: true }` — one make-public grant (backend rewrites the target to the\n * everyone-user); the envelope's `expiresAt` is `null`, so it never expires.\n *\n * v1 always passes `mode: \"copy\"`.\n */\n public async shareProjects(\n projectIds: ProjectId[],\n options: ShareProjectsOptions,\n ): Promise<void> {\n if (projectIds.length === 0) throw new Error(\"shareProjects: no projects given\");\n\n // Everyone + replace: refresh the existing everyone-share of this project under its stable\n // shareId (so recipients who already decided aren't re-prompted), if one exists. Found\n // automatically by project overlap; falls through to a fresh share when none exists.\n if (\"everyone\" in options && options.replace) {\n const priorEveryone = (await this.findSupersedableEnvelopes(projectIds)).find(\n (p) => p.everyone,\n );\n if (priorEveryone !== undefined) {\n await this.changeShare(priorEveryone.shareId, { title: options.title });\n return;\n }\n }\n\n await this.createNewShare(projectIds, options);\n }\n\n /**\n * Mints a fresh share: snapshots the projects into one new envelope (a fresh shareId),\n * supersedes prior shares of the same project, and grants it — all in one atomic write\n * transaction, so a failed grant rolls the whole thing back and the outbox is left as it was.\n * The everyone-refresh path is the {@link changeShare} branch of {@link shareProjects}; this is\n * the mint-a-new-envelope branch.\n */\n private async createNewShare(\n projectIds: ProjectId[],\n options: ShareProjectsOptions,\n ): Promise<void> {\n const everyone = \"everyone\" in options;\n const sources: EnvelopeProjectSource[] = await Promise.all(\n projectIds.map(\n async (id): Promise<EnvelopeProjectSource> => ({\n kind: \"fresh\",\n projectId: id,\n sourceRid: await this.resolveProjectId(id),\n }),\n ),\n );\n const sender = this.currentUserLogin ?? \"\";\n // Targeted share: sharedAt + ttl. Share-with-everybody: never expires (null).\n const expiresAt = everyone ? null : Date.now() + this.env.ops.envelopeTtlMs;\n\n // Supersede prior shares of the same project(s) so they never pile up. Resolved before\n // the write tx (ListGrants is a separate RPC). Everyone-share supersedes a prior\n // everyone-share of the same project; a targeted share pulls each named recipient out of\n // any prior share of that project, deleting that share if it ends up with no recipients.\n const priors = await this.findSupersedableEnvelopes(projectIds);\n\n await this.pl.withWriteTx(\"MLShareProjects\", async (tx) => {\n if (everyone) {\n for (const prior of priors) {\n if (prior.everyone) tx.removeField(field(this.sharingOutboxResourceId, prior.fieldName));\n }\n } else {\n const newRecipients = new Set(options.recipients);\n for (const prior of priors) {\n if (prior.everyone) continue; // a single user can't be pulled from an everyone-grant\n const toRemove = prior.recipients.filter((u) => newRecipients.has(u));\n if (toRemove.length === 0) continue;\n const remaining = prior.recipients.filter((u) => !newRecipients.has(u));\n if (remaining.length === 0) {\n // Nobody left on the old share — drop the whole envelope.\n tx.removeField(field(this.sharingOutboxResourceId, prior.fieldName));\n } else {\n for (const u of toRemove) tx.revokeAccess(prior.rid, u);\n }\n }\n }\n\n const { envelope } = await buildShareEnvelope(tx, this.sharingOutboxResourceId, sources, {\n mode: options.mode,\n sender,\n title: options.title,\n expiresAt,\n });\n\n // Grant in the same transaction (writable: the cross-color accept rule demands a writable\n // grant on the envelope). Atomic with the create.\n const envelopeGid = await envelope.globalId;\n if (everyone) {\n // One everyone-grant: empty/ignored target, ANY_AUTHORISED. The backend rewrites\n // the target to the everyone-user; gated by role + permission ceiling.\n tx.grantAccess(envelopeGid, \"\", { writable: true }, GrantType.ANY_AUTHORISED);\n } else {\n for (const recipient of options.recipients) {\n tx.grantAccess(envelopeGid, recipient, { writable: true });\n }\n }\n\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n }\n\n /**\n * Changes a share in place (same {@link ShareId}), in one write transaction: re-snapshots live\n * source projects and carries deleted ones' snapshots forward; applies edited recipients/title;\n * transfers already-decided recipients' accept/reject records (they keep their copy and aren't\n * re-prompted); re-grants; drops the old envelope.\n *\n * `opts.recipients` is the full targeted set (decided users are always kept). `opts.title`\n * replaces the title — omit keeps the current one. `opts.everyone` upgrades targeted ->\n * everyone; the reverse is impossible and ignored.\n *\n * `opts.projectActions` is a per-source-project decision, keyed by projectId: `update`\n * re-snapshots the live source (falls back to carry if the source is gone), `keep` carries the\n * existing snapshot (and its timestamp), `remove` drops the project from the pack. A project not\n * in the map defaults to `keep`. Omit the whole map for the legacy auto behavior (live sources\n * updated, gone ones kept) — the everyone-refresh path relies on that.\n */\n public async changeShare(\n shareId: ShareId,\n opts: {\n recipients?: string[];\n everyone?: boolean;\n title?: string;\n projectActions?: Record<ProjectId, ProjectChangeAction>;\n } = {},\n ): Promise<void> {\n await this.pl.withWriteTx(\"MLChangeShare\", async (tx) => {\n const old = await this.resolveOutboxEnvelope(tx, shareId);\n if (old === undefined)\n throw new Error(`changeShare: no live share with id ${shareId} in the outbox.`);\n\n const self = this.currentUserLogin ?? \"\";\n const grants = await tx.listGrants(old.rid);\n // A targeted share may be upgraded to everyone; an everyone-share can't be narrowed back.\n const everyone = grants.some((g) => isEveryoneUserLogin(g.user)) || opts.everyone === true;\n\n // Read the old envelope's project snapshots (uuid -> rid) and accept/reject records.\n const oldRd = await tx.getResourceData(old.rid, true);\n const snapshotByUuid = new Map<string, SignedResourceId>();\n const acceptances: { login: string; acc: EnvelopeAcceptance }[] = [];\n for (const f of oldRd.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (isEnvelopeProjectField(f.name)) {\n snapshotByUuid.set(envelopeProjectFieldUuid(f.name), f.value);\n } else if (isAcceptanceField(f.name)) {\n const raw = (await tx.getResourceData(f.value, false)).data;\n if (raw === undefined) continue;\n acceptances.push({\n login: acceptanceFieldLogin(f.name),\n acc: cachedDeserialize(raw) as EnvelopeAcceptance,\n });\n }\n }\n const decidedLogins = acceptances.map((a) => a.login);\n\n // Everyone-shares ignore recipients; targeted shares keep decided users plus the edited set.\n const priorRecipients = grants\n .filter((g) => !isEveryoneUserLogin(g.user) && g.user !== self)\n .map((g) => g.user);\n const recipients = everyone\n ? []\n : Array.from(new Set([...(opts.recipients ?? priorRecipients), ...decidedLogins]));\n\n // Live source projects by persistable id — these get a fresh snapshot.\n const liveProjects = new Map<string, SignedResourceId>();\n const projList = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of projList.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n liveProjects.set(resourceIdToString(f.value), f.value);\n }\n\n // Per project (keyed by field uuid), apply the caller's decision (default `keep`); with no\n // projectActions map, fall back to the legacy auto behavior: update a live source, keep a gone one.\n const actions = opts.projectActions;\n const sources: EnvelopeProjectSource[] = [];\n for (const uuid of Object.keys(old.data.projects) as ProjectFieldUuid[]) {\n const { label, source, updatedAt } = old.data.projects[uuid];\n const liveRid = liveProjects.get(source);\n\n const action = actions\n ? (actions[source] ?? \"keep\")\n : liveRid !== undefined\n ? \"update\"\n : \"keep\";\n if (action === \"remove\") continue;\n\n if (action === \"update\" && liveRid !== undefined) {\n sources.push({ kind: \"fresh\", projectId: source, sourceRid: liveRid });\n } else {\n // keep, or an \"update\" whose source vanished before commit (deleted meanwhile, e.g. from\n // another client): carry the prior snapshot. Liveness is read inside this write tx — race-safe.\n const snapshotRid = snapshotByUuid.get(uuid);\n if (snapshotRid !== undefined)\n sources.push({ kind: \"carry\", projectId: source, label, snapshotRid, updatedAt });\n }\n }\n\n // Omit (undefined) keeps the current title; a provided value replaces it.\n const title = opts.title === undefined ? old.data.title : opts.title.trim();\n const expiresAt = everyone ? null : Date.now() + this.env.ops.envelopeTtlMs;\n\n // Same shareId, same outbox field name — detach the old field before rebuilding, or they collide.\n tx.removeField(field(this.sharingOutboxResourceId, old.fieldName));\n\n const { envelope } = await buildShareEnvelope(tx, this.sharingOutboxResourceId, sources, {\n mode: old.data.mode,\n sender: self,\n title,\n expiresAt,\n shareId, // SAME shareId — the essence of change\n });\n\n // Transfer the decided users' records onto the new envelope (donor-written copies).\n for (const { login, acc } of acceptances) {\n if (!everyone && !recipients.includes(login)) continue;\n writeEnvelopeAcceptance(tx, envelope, login, acc.action, acc.timestamp);\n }\n\n const gid = await envelope.globalId;\n if (everyone) tx.grantAccess(gid, \"\", { writable: true }, GrantType.ANY_AUTHORISED);\n else for (const r of recipients) tx.grantAccess(gid, r, { writable: true });\n\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n }\n\n /**\n * Finds the donor's own outgoing envelopes built from any of the given source projects —\n * the supersede candidates for a fresh share of the same project(s). Reads each envelope's\n * recipient set via `ListGrants` so the caller can pull individual recipients or detect an\n * everyone-share.\n */\n private async findSupersedableEnvelopes(projectIds: ProjectId[]): Promise<\n {\n fieldName: string;\n rid: SignedResourceId;\n shareId: ShareId;\n everyone: boolean;\n recipients: string[];\n }[]\n > {\n const wanted = new Set(projectIds);\n\n const matched = await this.pl.withReadTx(\"MLFindSupersede\", async (tx) => {\n const outbox = await tx.getResourceData(this.sharingOutboxResourceId, true);\n const out: { fieldName: string; rid: SignedResourceId; shareId: ShareId }[] = [];\n for (const f of outbox.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const rd = await tx.getResourceData(f.value, false);\n if (rd.data === undefined) continue;\n const data = decodeEnvelopeData(rd.data);\n if (Object.values(data.projects).some((p) => wanted.has(p.source)))\n out.push({ fieldName: f.name, rid: f.value, shareId: data.shareId });\n }\n return out;\n });\n\n return await Promise.all(\n matched.map(async ({ fieldName, rid, shareId }) => {\n const grants = await this.pl.userResources.listGrants(rid);\n return {\n fieldName,\n rid,\n shareId,\n everyone: grants.some((g) => isEveryoneUserLogin(g.user)),\n recipients: grants.filter((g) => !isEveryoneUserLogin(g.user)).map((g) => g.user),\n };\n }),\n );\n }\n\n /**\n * Revokes and deletes an outgoing share for all recipients: detaches and deletes the envelope, and\n * its grants are revoked along with it. Already-accepted copies are unaffected (ref-counting keeps\n * the adopted resources alive). Idempotent — revoking a share that is already gone is a no-op.\n */\n public async revokeShare(shareId: ShareId): Promise<void> {\n await this.pl.withWriteTx(\"MLRevokeShare\", async (tx) => {\n const target = await this.resolveOutboxEnvelope(tx, shareId);\n if (target === undefined) return;\n tx.removeField(field(this.sharingOutboxResourceId, target.fieldName));\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n }\n\n /**\n * Resolves a live envelope from the donor's own outbox by its logical `shareId`, returning the\n * outbox field name (for detach), the signed envelope id, and its decoded {@link EnvelopeData}.\n * The outbox is keyed by `{shareId}` directly, but a replaced/legacy share may have drifted, so\n * we match on the decoded `shareId` rather than the field name alone.\n */\n private async resolveOutboxEnvelope(\n tx: PlTransaction,\n shareId: ShareId,\n ): Promise<{ fieldName: string; rid: SignedResourceId; data: EnvelopeData } | undefined> {\n const outboxData = await tx.getResourceData(this.sharingOutboxResourceId, true);\n for (const f of outboxData.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const rd = await tx.getResourceData(f.value, false);\n if (rd.data === undefined) continue;\n const data = decodeEnvelopeData(rd.data);\n if (data.shareId === shareId) return { fieldName: f.name, rid: f.value, data };\n }\n return undefined;\n }\n\n /**\n * Resolves currently-shared envelopes (granted to this user) to their resource ids, keyed by\n * the envelope's logical `shareId`.\n *\n * Reads the {@link liveEnvelopes} Computable — the same shared-resource discovery tree that\n * feeds {@link pendingShares}. This is the single discovery mechanism: there is no separate\n * `ListUserResources` re-stream on every accept/reject. `refreshState()` is awaited first so a\n * just-granted envelope is observed (the tree's discovery poll may otherwise lag a freshly\n * landed grant). The tree is gRPC-only, so this is empty on a REST-connected client.\n */\n private async resolveLiveEnvelopes(): Promise<Map<ShareId, LiveEnvelope>> {\n await this.pendingSharesTree.refreshState();\n const live = (await this.liveEnvelopes.getValue()) ?? [];\n // Dedup by logical shareId (last writer wins — at most one live envelope per shareId).\n const map = new Map<ShareId, LiveEnvelope>();\n for (const e of live) map.set(e.data.shareId, e);\n return map;\n }\n\n /**\n * Accepts one or more pending shares: duplicates each share's projects into this user's\n * project list, records the decision per share, and (read-write share) writes the donor-visible\n * acceptance onto the envelope. Per-share failures (e.g. an expiry race) are collected, not\n * short-circuited — the rest still get accepted. Accept-all = pass every current pending shareId.\n *\n * `rename` resolves label collisions (same callback contract as {@link duplicateProject}), but\n * the source lives in the envelope tree, so accept calls the low-level mutator directly.\n */\n public async acceptShare(\n shareIds: ShareId[],\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<{ accepted: ProjectId[]; failed: { shareId: ShareId; error: string }[] }> {\n const live = await this.resolveLiveEnvelopes();\n const login = this.currentUserLogin;\n\n const accepted: ProjectId[] = [];\n const failed: { shareId: ShareId; error: string }[] = [];\n\n for (const shareId of shareIds) {\n const envelope = live.get(shareId);\n if (envelope === undefined) {\n failed.push({ shareId, error: \"Share is no longer available.\" });\n continue;\n }\n try {\n const now = Date.now();\n const createdRids = await this.pl.withWriteTx(\"MLAcceptShare\", async (tx) => {\n const created = await copyEnvelopeProjectsIntoList(\n tx,\n envelope.rid,\n this.projectListResourceId,\n rename,\n );\n\n // Record the decision on the acceptor's own SharingState, keyed on shareId.\n writeSharingDecision(tx, this.sharingStateResourceId, shareId, {\n decision: \"accepted\",\n timestamp: now,\n envelopeSharedAt: envelope.data.sharedAt,\n acceptedProjects: resourceIdsToStrings(created),\n });\n\n // Read-write share: write the donor-visible acceptance onto the envelope.\n if (login !== null && envelope.data.mode !== \"read-only\")\n writeEnvelopeAcceptance(tx, envelope.rid, login, \"accepted\", now);\n\n await tx.commit();\n return created;\n });\n for (const rid of createdRids) {\n const projectId = resourceIdToString(rid) as ProjectId;\n this.projectIdCache.set(projectId, rid);\n accepted.push(projectId);\n }\n } catch (e) {\n failed.push({ shareId, error: e instanceof Error ? e.message : String(e) });\n }\n }\n\n await Promise.all([this.projectListTree.refreshState(), this.sharingStateTree.refreshState()]);\n return { accepted, failed };\n }\n\n /** Records rejection of a pending share; it never surfaces again. */\n public async rejectShare(shareId: ShareId): Promise<void> {\n const live = await this.resolveLiveEnvelopes();\n const envelope = live.get(shareId);\n const login = this.currentUserLogin;\n const now = Date.now();\n\n await this.pl.withWriteTx(\"MLRejectShare\", async (tx) => {\n writeSharingDecision(tx, this.sharingStateResourceId, shareId, {\n decision: \"rejected\",\n timestamp: now,\n envelopeSharedAt: envelope?.data.sharedAt ?? now,\n acceptedProjects: [],\n });\n\n // Read-write share: write the donor-visible rejection onto the envelope (if still live).\n if (envelope !== undefined && login !== null && envelope.data.mode !== \"read-only\")\n writeEnvelopeAcceptance(tx, envelope.rid, login, \"rejected\", now);\n\n await tx.commit();\n });\n\n await this.sharingStateTree.refreshState();\n }\n\n //\n // Outbox cleanup (donor side)\n //\n\n private static readonly EnvelopeCleanupIntervalMs = 6 * 3600 * 1000; // every 6h\n private envelopeCleanupTimer: ReturnType<typeof setInterval> | undefined;\n\n /** On ML start and every 6h, delete envelopes whose immutable `expiresAt` has passed. */\n private startEnvelopeCleanup(): void {\n void this.runEnvelopeCleanup();\n this.envelopeCleanupTimer = setInterval(() => {\n void this.runEnvelopeCleanup();\n }, MiddleLayer.EnvelopeCleanupIntervalMs);\n // Don't keep the process alive solely for cleanup.\n this.envelopeCleanupTimer.unref?.();\n }\n\n /** Scans the donor's outbox and deletes expired envelopes (backend auto-revokes their grants).\n * Envelopes with `expiresAt: null` (share-with-everybody) are skipped. */\n private async runEnvelopeCleanup(): Promise<void> {\n try {\n const now = Date.now();\n const expired = await this.pl.withReadTx(\"MLEnvelopeCleanupScan\", async (tx) => {\n const data = await tx.getResourceData(this.sharingOutboxResourceId, true);\n const toDelete: { fieldName: string }[] = [];\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const rd = await tx.getResourceData(f.value, false);\n if (rd.data === undefined) continue;\n const envData = decodeEnvelopeData(rd.data);\n if (envData.expiresAt === null) continue; // never expires\n if (envData.expiresAt <= now) toDelete.push({ fieldName: f.name });\n }\n return toDelete;\n });\n\n if (expired.length === 0) return;\n\n await this.pl.withWriteTx(\"MLEnvelopeCleanup\", async (tx) => {\n for (const { fieldName } of expired)\n tx.removeField(field(this.sharingOutboxResourceId, fieldName));\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n } catch (e) {\n this.env.logger.warn(\n `envelope cleanup failed: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n if (this.envelopeCleanupTimer !== undefined) clearInterval(this.envelopeCleanupTimer);\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await Promise.all([\n this.projectListTree.terminate(),\n this.sharingOutboxTree.terminate(),\n this.sharingStateTree.terminate(),\n this.pendingSharesTree.terminate(),\n ]);\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode\n if (\n ops.defaultTreeOptions.traversalMode === undefined &&\n getDebugFlags().treeTraversalMode !== undefined\n )\n ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;\n\n const { projects, sharingOutbox, sharingState } = await pl.withWriteTx(\n \"MLInitialization\",\n async (tx) => {\n // Lazily create each clientRoot-attached singleton resource. Returns the existing\n // resource id if the field is already populated, otherwise creates + locks + sets it.\n const lazyInit = async (\n fieldName: string,\n type: { name: string; version: string },\n ): Promise<{ ref?: ResourceRef; existing?: SignedResourceId }> => {\n const f = field(tx.clientRoot, fieldName);\n tx.createField(f, \"Dynamic\");\n const fData = await tx.getField(f);\n if (isNullSignedResourceId(fData.value)) {\n const ref = tx.createEphemeral(type);\n tx.lock(ref);\n tx.setField(f, ref);\n return { ref };\n }\n return { existing: fData.value };\n };\n\n const projectsR = await lazyInit(ProjectsField, ProjectsResourceType);\n const outboxR = await lazyInit(SharingOutboxField, SharingOutboxResourceType);\n const stateR = await lazyInit(SharingStateField, SharingStateResourceType);\n\n await tx.commit();\n\n return {\n projects: projectsR.existing ?? (await projectsR.ref!.globalId),\n sharingState: stateR.existing ?? (await stateR.ref!.globalId),\n sharingOutbox: outboxR.existing ?? (await outboxR.ref!.globalId),\n };\n },\n );\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n // Project sharing trees and reactive views.\n const outgoingTC = await createOutgoingShares(pl, sharingOutbox, env);\n const sharingStateTree = await createSharingStateTree(pl, sharingState, env);\n const pendingSharesTree = await createPendingSharesTree(pl, env);\n const pendingShares = createPendingSharesComputable(\n pendingSharesTree,\n sharingStateTree,\n pl.userResources.authUser,\n );\n const liveEnvelopes = createLiveEnvelopesComputable(pendingSharesTree);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n sharingOutbox,\n sharingState,\n openedProjects,\n projectListTC.tree,\n outgoingTC.tree,\n sharingStateTree,\n pendingSharesTree,\n v2RegistryProvider,\n projectListTC.computable,\n outgoingTC.computable,\n pendingShares,\n liveEnvelopes,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middle_layer.js","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type {\n PlClient,\n PlTransaction,\n SignedResourceId,\n ResourceRef,\n Role,\n} from \"@milaboratories/pl-client\";\nimport {\n isEveryoneUserLogin,\n field,\n GrantType,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport {\n createProjectList,\n ensureProjectListRid,\n ProjectsField,\n ProjectsResourceType,\n} from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport {\n acceptanceFieldLogin,\n canGrantToEveryone,\n canImpersonate,\n decodeEnvelopeData,\n isAcceptanceField,\n SharingOutboxField,\n SharingOutboxResourceType,\n SharingStateField,\n SharingStateResourceType,\n type EnvelopeAcceptance,\n type EnvelopeData,\n type ProjectChangeAction,\n type ProjectFieldUuid,\n type ShareId,\n type ShareProjectsOptions,\n} from \"../model/sharing_model\";\nimport {\n buildShareEnvelope,\n copyEnvelopeProjectsIntoList,\n envelopeProjectFieldUuid,\n isEnvelopeProjectField,\n resourceIdsToStrings,\n writeEnvelopeAcceptance,\n writeSharingDecision,\n type EnvelopeProjectSource,\n} from \"../mutator/sharing\";\nimport type { LiveEnvelope, OutgoingShare, PendingShare } from \"./sharing_list\";\nimport {\n createLiveEnvelopesComputable,\n createOutgoingShares,\n createPendingSharesComputable,\n createPendingSharesTree,\n createSharingStateTree,\n} from \"./sharing_list\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher, cachedDeserialize } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { Computable, ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly sharingOutboxResourceId: SignedResourceId,\n private readonly sharingStateResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n private readonly sharingOutboxTree: SynchronizedTreeState,\n private readonly sharingStateTree: SynchronizedTreeState,\n private readonly pendingSharesTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>,\n /** Reactive view of the donor's outbox — the shares this user has created.\n * v1: API only, no UI. */\n public outgoingShares: Computable<OutgoingShare[] | undefined>,\n /** Envelopes granted to this user, not yet accepted or rejected. Fed by the\n * shared-resource discovery tree. */\n public pendingShares: Computable<PendingShare[] | undefined>,\n /** Internal: the acceptor's currently-live envelopes, read from the same shared-resource\n * discovery tree as {@link pendingShares}. The single source the accept/reject flow resolves\n * live envelopes from — no second discovery path. */\n private readonly liveEnvelopes: Computable<LiveEnvelope[] | undefined>,\n ) {\n this.pl = this.env.pl;\n this.startEnvelopeCleanup();\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /**\n * Runtime capabilities advertised by the connected backend (tokens of\n * the form `<feature>:<version>`, e.g. \"wasm:v1\"). Empty list if the\n * backend predates the capability mechanism — that's the desired\n * fail-closed behaviour for blocks declaring any `requiredCapabilities`.\n */\n public get serverCapabilities(): string[] {\n return this.pl.serverInfo.capabilities ?? [];\n }\n\n /**\n * Login of the authenticated user, for the \"Signed in as\" UI. `null` when the\n * backend has no auth (local/dev mode) — the UI hides the element.\n */\n public get currentUserLogin(): string | null {\n return this.pl.userResources.authUser;\n }\n\n /**\n * Whether the connected backend supports project sharing. Synthetic — computed\n * in the middle layer from the backend capabilities the share flow needs (the\n * cross-color field-reference relaxation the accept flow rests on). It can absorb\n * additional required capabilities later without a UI change.\n */\n public get sharingSupported(): boolean {\n // Sharing does not compose with impersonation (discovery is scoped to the admin's session,\n // decisions attribute to the admin), so it is disabled while impersonating. Admins move\n // projects across roots with duplicateProjectToUser instead.\n return this.serverCapabilities.includes(\"crossTreeRefs:v1\") && !this.impersonating;\n }\n\n /** True when this session is impersonating another user (an admin opened another user's root). */\n public get impersonating(): boolean {\n return this.pl.conf.asUser !== undefined;\n }\n\n /**\n * Role of the authenticated user, from the `GetSessionInfo` RPC surfaced through the\n * pl-client. `null` in no-auth mode (when {@link currentUserLogin} is null).\n */\n public get currentUserRole(): Role | null {\n return this.pl.currentUserRole;\n }\n\n /**\n * Whether the UI offers share-with-everybody — no role policy lives in the UI:\n * serverCapabilities.has(\"publicGrants:v1\") && canGrantToEveryone(currentUserRole)\n * Not a security boundary: a crafted call still hits the backend's role +\n * permission-ceiling gate.\n */\n public get canShareWithEveryone(): boolean {\n return (\n !this.impersonating &&\n this.serverCapabilities.includes(\"publicGrants:v1\") &&\n canGrantToEveryone(this.currentUserRole)\n );\n }\n\n /**\n * Whether the authenticated user may impersonate others (open another user's root). Mirrors\n * the backend's `CanImpersonate` role gate — admin/controller only, never a regular user.\n * Derived from the session role, which stays the authenticated admin's even while\n * impersonating, so this stays true across a switch: the \"return to my root\" affordance must\n * not vanish mid-impersonation. Not a security boundary; the backend re-checks on every call.\n */\n public get currentUserCanImpersonate(): boolean {\n return canImpersonate(this.currentUserRole);\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n /**\n * Duplicates a project into another user's root, minted in the TARGET user's color so the target\n * owns it. Sibling of {@link duplicateProject}, but writes into a different root. The source\n * project (on the current client root) is referenced cross-color for its block data, kept alive\n * by refcounting, exactly like accepting a shared project. Works both ways: pull (while\n * impersonating a user, copy their project to yourself) and push (from your own root, copy a\n * project to a user). Admin cross-root op; requires the crossTreeRefs:v1 backend capability.\n */\n public async duplicateProjectToUser(\n srcProjectId: ProjectId,\n targetLogin: string,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<void> {\n if (!this.serverCapabilities.includes(\"crossTreeRefs:v1\"))\n throw new Error(\"duplicateProjectToUser requires the crossTreeRefs:v1 backend capability.\");\n\n const sourceRid = await this.resolveProjectId(srcProjectId);\n const targetRoot = await this.pl.getUserRoot({ login: targetLogin, createIfNotExists: true });\n\n // Run on the target root: the tx default color is the target's, so the new project (and the\n // target's project list, if created here) are minted in the target's color.\n await this.pl.withWriteTxOnRoot(targetRoot, \"MLDuplicateProjectToUser\", async (tx) => {\n // Resolve or lazily create the target root's project list (tx.clientRoot === targetRoot).\n const targetProjectListRid = await ensureProjectListRid(tx);\n\n // Source label + the target's existing labels, for collision-aware renaming.\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n const targetListData = await tx.getResourceData(targetProjectListRid, true);\n const existingLabels = (\n await Promise.all(\n targetListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId)\n .map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n tx.createField(field(targetProjectListRid, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n });\n }\n\n //\n // Project Sharing (Copy & Share)\n //\n\n /**\n * Shares the given projects (Copy & Share). Snapshots the projects, creates one envelope, and\n * grants it — all in one atomic write transaction, so a failed grant rolls the whole thing back\n * and the outbox is left as it was.\n *\n * Two variants (see {@link ShareProjectsOptions}):\n * - `{ recipients }` — one writable grant per named recipient; the envelope expires after the\n * default TTL (`sharedAt + envelopeTtlMs`).\n * - `{ everyone: true }` — one make-public grant (backend rewrites the target to the\n * everyone-user); the envelope's `expiresAt` is `null`, so it never expires.\n *\n * v1 always passes `mode: \"copy\"`.\n */\n public async shareProjects(\n projectIds: ProjectId[],\n options: ShareProjectsOptions,\n ): Promise<void> {\n if (projectIds.length === 0) throw new Error(\"shareProjects: no projects given\");\n\n // Everyone + replace: refresh the existing everyone-share of this project under its stable\n // shareId (so recipients who already decided aren't re-prompted), if one exists. Found\n // automatically by project overlap; falls through to a fresh share when none exists.\n if (\"everyone\" in options && options.replace) {\n const priorEveryone = (await this.findSupersedableEnvelopes(projectIds)).find(\n (p) => p.everyone,\n );\n if (priorEveryone !== undefined) {\n await this.changeShare(priorEveryone.shareId, { title: options.title });\n return;\n }\n }\n\n await this.createNewShare(projectIds, options);\n }\n\n /**\n * Mints a fresh share: snapshots the projects into one new envelope (a fresh shareId),\n * supersedes prior shares of the same project, and grants it — all in one atomic write\n * transaction, so a failed grant rolls the whole thing back and the outbox is left as it was.\n * The everyone-refresh path is the {@link changeShare} branch of {@link shareProjects}; this is\n * the mint-a-new-envelope branch.\n */\n private async createNewShare(\n projectIds: ProjectId[],\n options: ShareProjectsOptions,\n ): Promise<void> {\n const everyone = \"everyone\" in options;\n const sources: EnvelopeProjectSource[] = await Promise.all(\n projectIds.map(\n async (id): Promise<EnvelopeProjectSource> => ({\n kind: \"fresh\",\n projectId: id,\n sourceRid: await this.resolveProjectId(id),\n }),\n ),\n );\n const sender = this.currentUserLogin ?? \"\";\n // Targeted share: sharedAt + ttl. Share-with-everybody: never expires (null).\n const expiresAt = everyone ? null : Date.now() + this.env.ops.envelopeTtlMs;\n\n // Supersede prior shares of the same project(s) so they never pile up. Resolved before\n // the write tx (ListGrants is a separate RPC). Everyone-share supersedes a prior\n // everyone-share of the same project; a targeted share pulls each named recipient out of\n // any prior share of that project, deleting that share if it ends up with no recipients.\n const priors = await this.findSupersedableEnvelopes(projectIds);\n\n await this.pl.withWriteTx(\"MLShareProjects\", async (tx) => {\n if (everyone) {\n for (const prior of priors) {\n if (prior.everyone) tx.removeField(field(this.sharingOutboxResourceId, prior.fieldName));\n }\n } else {\n const newRecipients = new Set(options.recipients);\n for (const prior of priors) {\n if (prior.everyone) continue; // a single user can't be pulled from an everyone-grant\n const toRemove = prior.recipients.filter((u) => newRecipients.has(u));\n if (toRemove.length === 0) continue;\n const remaining = prior.recipients.filter((u) => !newRecipients.has(u));\n if (remaining.length === 0) {\n // Nobody left on the old share — drop the whole envelope.\n tx.removeField(field(this.sharingOutboxResourceId, prior.fieldName));\n } else {\n for (const u of toRemove) tx.revokeAccess(prior.rid, u);\n }\n }\n }\n\n const { envelope } = await buildShareEnvelope(tx, this.sharingOutboxResourceId, sources, {\n mode: options.mode,\n sender,\n title: options.title,\n expiresAt,\n });\n\n // Grant in the same transaction (writable: the cross-color accept rule demands a writable\n // grant on the envelope). Atomic with the create.\n const envelopeGid = await envelope.globalId;\n if (everyone) {\n // One everyone-grant: empty/ignored target, ANY_AUTHORISED. The backend rewrites\n // the target to the everyone-user; gated by role + permission ceiling.\n tx.grantAccess(envelopeGid, \"\", { writable: true }, GrantType.ANY_AUTHORISED);\n } else {\n for (const recipient of options.recipients) {\n tx.grantAccess(envelopeGid, recipient, { writable: true });\n }\n }\n\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n }\n\n /**\n * Changes a share in place (same {@link ShareId}), in one write transaction: re-snapshots live\n * source projects and carries deleted ones' snapshots forward; applies edited recipients/title;\n * transfers already-decided recipients' accept/reject records (they keep their copy and aren't\n * re-prompted); re-grants; drops the old envelope.\n *\n * `opts.recipients` is the full targeted set (decided users are always kept). `opts.title`\n * replaces the title — omit keeps the current one. `opts.everyone` upgrades targeted ->\n * everyone; the reverse is impossible and ignored.\n *\n * `opts.projectActions` is a per-source-project decision, keyed by projectId: `update`\n * re-snapshots the live source (falls back to carry if the source is gone), `keep` carries the\n * existing snapshot (and its timestamp), `remove` drops the project from the pack. A project not\n * in the map defaults to `keep`. Omit the whole map for the legacy auto behavior (live sources\n * updated, gone ones kept) — the everyone-refresh path relies on that.\n */\n public async changeShare(\n shareId: ShareId,\n opts: {\n recipients?: string[];\n everyone?: boolean;\n title?: string;\n projectActions?: Record<ProjectId, ProjectChangeAction>;\n } = {},\n ): Promise<void> {\n await this.pl.withWriteTx(\"MLChangeShare\", async (tx) => {\n const old = await this.resolveOutboxEnvelope(tx, shareId);\n if (old === undefined)\n throw new Error(`changeShare: no live share with id ${shareId} in the outbox.`);\n\n const self = this.currentUserLogin ?? \"\";\n const grants = await tx.listGrants(old.rid);\n // A targeted share may be upgraded to everyone; an everyone-share can't be narrowed back.\n const everyone = grants.some((g) => isEveryoneUserLogin(g.user)) || opts.everyone === true;\n\n // Read the old envelope's project snapshots (uuid -> rid) and accept/reject records.\n const oldRd = await tx.getResourceData(old.rid, true);\n const snapshotByUuid = new Map<string, SignedResourceId>();\n const acceptances: { login: string; acc: EnvelopeAcceptance }[] = [];\n for (const f of oldRd.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (isEnvelopeProjectField(f.name)) {\n snapshotByUuid.set(envelopeProjectFieldUuid(f.name), f.value);\n } else if (isAcceptanceField(f.name)) {\n const raw = (await tx.getResourceData(f.value, false)).data;\n if (raw === undefined) continue;\n acceptances.push({\n login: acceptanceFieldLogin(f.name),\n acc: cachedDeserialize(raw) as EnvelopeAcceptance,\n });\n }\n }\n const decidedLogins = acceptances.map((a) => a.login);\n\n // Everyone-shares ignore recipients; targeted shares keep decided users plus the edited set.\n const priorRecipients = grants\n .filter((g) => !isEveryoneUserLogin(g.user) && g.user !== self)\n .map((g) => g.user);\n const recipients = everyone\n ? []\n : Array.from(new Set([...(opts.recipients ?? priorRecipients), ...decidedLogins]));\n\n // Live source projects by persistable id — these get a fresh snapshot.\n const liveProjects = new Map<string, SignedResourceId>();\n const projList = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of projList.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n liveProjects.set(resourceIdToString(f.value), f.value);\n }\n\n // Per project (keyed by field uuid), apply the caller's decision (default `keep`); with no\n // projectActions map, fall back to the legacy auto behavior: update a live source, keep a gone one.\n const actions = opts.projectActions;\n const sources: EnvelopeProjectSource[] = [];\n for (const uuid of Object.keys(old.data.projects) as ProjectFieldUuid[]) {\n const { label, source, updatedAt } = old.data.projects[uuid];\n const liveRid = liveProjects.get(source);\n\n const action = actions\n ? (actions[source] ?? \"keep\")\n : liveRid !== undefined\n ? \"update\"\n : \"keep\";\n if (action === \"remove\") continue;\n\n if (action === \"update\" && liveRid !== undefined) {\n sources.push({ kind: \"fresh\", projectId: source, sourceRid: liveRid });\n } else {\n // keep, or an \"update\" whose source vanished before commit (deleted meanwhile, e.g. from\n // another client): carry the prior snapshot. Liveness is read inside this write tx — race-safe.\n const snapshotRid = snapshotByUuid.get(uuid);\n if (snapshotRid !== undefined)\n sources.push({ kind: \"carry\", projectId: source, label, snapshotRid, updatedAt });\n }\n }\n\n // Omit (undefined) keeps the current title; a provided value replaces it.\n const title = opts.title === undefined ? old.data.title : opts.title.trim();\n const expiresAt = everyone ? null : Date.now() + this.env.ops.envelopeTtlMs;\n\n // Same shareId, same outbox field name — detach the old field before rebuilding, or they collide.\n tx.removeField(field(this.sharingOutboxResourceId, old.fieldName));\n\n const { envelope } = await buildShareEnvelope(tx, this.sharingOutboxResourceId, sources, {\n mode: old.data.mode,\n sender: self,\n title,\n expiresAt,\n shareId, // SAME shareId — the essence of change\n });\n\n // Transfer the decided users' records onto the new envelope (donor-written copies).\n for (const { login, acc } of acceptances) {\n if (!everyone && !recipients.includes(login)) continue;\n writeEnvelopeAcceptance(tx, envelope, login, acc.action, acc.timestamp);\n }\n\n const gid = await envelope.globalId;\n if (everyone) tx.grantAccess(gid, \"\", { writable: true }, GrantType.ANY_AUTHORISED);\n else for (const r of recipients) tx.grantAccess(gid, r, { writable: true });\n\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n }\n\n /**\n * Finds the donor's own outgoing envelopes built from any of the given source projects —\n * the supersede candidates for a fresh share of the same project(s). Reads each envelope's\n * recipient set via `ListGrants` so the caller can pull individual recipients or detect an\n * everyone-share.\n */\n private async findSupersedableEnvelopes(projectIds: ProjectId[]): Promise<\n {\n fieldName: string;\n rid: SignedResourceId;\n shareId: ShareId;\n everyone: boolean;\n recipients: string[];\n }[]\n > {\n const wanted = new Set(projectIds);\n\n const matched = await this.pl.withReadTx(\"MLFindSupersede\", async (tx) => {\n const outbox = await tx.getResourceData(this.sharingOutboxResourceId, true);\n const out: { fieldName: string; rid: SignedResourceId; shareId: ShareId }[] = [];\n for (const f of outbox.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const rd = await tx.getResourceData(f.value, false);\n if (rd.data === undefined) continue;\n const data = decodeEnvelopeData(rd.data);\n if (Object.values(data.projects).some((p) => wanted.has(p.source)))\n out.push({ fieldName: f.name, rid: f.value, shareId: data.shareId });\n }\n return out;\n });\n\n return await Promise.all(\n matched.map(async ({ fieldName, rid, shareId }) => {\n const grants = await this.pl.userResources.listGrants(rid);\n return {\n fieldName,\n rid,\n shareId,\n everyone: grants.some((g) => isEveryoneUserLogin(g.user)),\n recipients: grants.filter((g) => !isEveryoneUserLogin(g.user)).map((g) => g.user),\n };\n }),\n );\n }\n\n /**\n * Revokes and deletes an outgoing share for all recipients: detaches and deletes the envelope, and\n * its grants are revoked along with it. Already-accepted copies are unaffected (ref-counting keeps\n * the adopted resources alive). Idempotent — revoking a share that is already gone is a no-op.\n */\n public async revokeShare(shareId: ShareId): Promise<void> {\n await this.pl.withWriteTx(\"MLRevokeShare\", async (tx) => {\n const target = await this.resolveOutboxEnvelope(tx, shareId);\n if (target === undefined) return;\n tx.removeField(field(this.sharingOutboxResourceId, target.fieldName));\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n }\n\n /**\n * Resolves a live envelope from the donor's own outbox by its logical `shareId`, returning the\n * outbox field name (for detach), the signed envelope id, and its decoded {@link EnvelopeData}.\n * The outbox is keyed by `{shareId}` directly, but a replaced/legacy share may have drifted, so\n * we match on the decoded `shareId` rather than the field name alone.\n */\n private async resolveOutboxEnvelope(\n tx: PlTransaction,\n shareId: ShareId,\n ): Promise<{ fieldName: string; rid: SignedResourceId; data: EnvelopeData } | undefined> {\n const outboxData = await tx.getResourceData(this.sharingOutboxResourceId, true);\n for (const f of outboxData.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const rd = await tx.getResourceData(f.value, false);\n if (rd.data === undefined) continue;\n const data = decodeEnvelopeData(rd.data);\n if (data.shareId === shareId) return { fieldName: f.name, rid: f.value, data };\n }\n return undefined;\n }\n\n /**\n * Resolves currently-shared envelopes (granted to this user) to their resource ids, keyed by\n * the envelope's logical `shareId`.\n *\n * Reads the {@link liveEnvelopes} Computable — the same shared-resource discovery tree that\n * feeds {@link pendingShares}. This is the single discovery mechanism: there is no separate\n * `ListUserResources` re-stream on every accept/reject. `refreshState()` is awaited first so a\n * just-granted envelope is observed (the tree's discovery poll may otherwise lag a freshly\n * landed grant). The tree is gRPC-only, so this is empty on a REST-connected client.\n */\n private async resolveLiveEnvelopes(): Promise<Map<ShareId, LiveEnvelope>> {\n await this.pendingSharesTree.refreshState();\n const live = (await this.liveEnvelopes.getValue()) ?? [];\n // Dedup by logical shareId (last writer wins — at most one live envelope per shareId).\n const map = new Map<ShareId, LiveEnvelope>();\n for (const e of live) map.set(e.data.shareId, e);\n return map;\n }\n\n /**\n * Accepts one or more pending shares: duplicates each share's projects into this user's\n * project list, records the decision per share, and (read-write share) writes the donor-visible\n * acceptance onto the envelope. Per-share failures (e.g. an expiry race) are collected, not\n * short-circuited — the rest still get accepted. Accept-all = pass every current pending shareId.\n *\n * `rename` resolves label collisions (same callback contract as {@link duplicateProject}), but\n * the source lives in the envelope tree, so accept calls the low-level mutator directly.\n */\n public async acceptShare(\n shareIds: ShareId[],\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<{ accepted: ProjectId[]; failed: { shareId: ShareId; error: string }[] }> {\n const live = await this.resolveLiveEnvelopes();\n const login = this.currentUserLogin;\n\n const accepted: ProjectId[] = [];\n const failed: { shareId: ShareId; error: string }[] = [];\n\n for (const shareId of shareIds) {\n const envelope = live.get(shareId);\n if (envelope === undefined) {\n failed.push({ shareId, error: \"Share is no longer available.\" });\n continue;\n }\n try {\n const now = Date.now();\n const createdRids = await this.pl.withWriteTx(\"MLAcceptShare\", async (tx) => {\n const created = await copyEnvelopeProjectsIntoList(\n tx,\n envelope.rid,\n this.projectListResourceId,\n rename,\n );\n\n // Record the decision on the acceptor's own SharingState, keyed on shareId.\n writeSharingDecision(tx, this.sharingStateResourceId, shareId, {\n decision: \"accepted\",\n timestamp: now,\n envelopeSharedAt: envelope.data.sharedAt,\n acceptedProjects: resourceIdsToStrings(created),\n });\n\n // Read-write share: write the donor-visible acceptance onto the envelope.\n if (login !== null && envelope.data.mode !== \"read-only\")\n writeEnvelopeAcceptance(tx, envelope.rid, login, \"accepted\", now);\n\n await tx.commit();\n return created;\n });\n for (const rid of createdRids) {\n const projectId = resourceIdToString(rid) as ProjectId;\n this.projectIdCache.set(projectId, rid);\n accepted.push(projectId);\n }\n } catch (e) {\n failed.push({ shareId, error: e instanceof Error ? e.message : String(e) });\n }\n }\n\n await Promise.all([this.projectListTree.refreshState(), this.sharingStateTree.refreshState()]);\n return { accepted, failed };\n }\n\n /** Records rejection of a pending share; it never surfaces again. */\n public async rejectShare(shareId: ShareId): Promise<void> {\n const live = await this.resolveLiveEnvelopes();\n const envelope = live.get(shareId);\n const login = this.currentUserLogin;\n const now = Date.now();\n\n await this.pl.withWriteTx(\"MLRejectShare\", async (tx) => {\n writeSharingDecision(tx, this.sharingStateResourceId, shareId, {\n decision: \"rejected\",\n timestamp: now,\n envelopeSharedAt: envelope?.data.sharedAt ?? now,\n acceptedProjects: [],\n });\n\n // Read-write share: write the donor-visible rejection onto the envelope (if still live).\n if (envelope !== undefined && login !== null && envelope.data.mode !== \"read-only\")\n writeEnvelopeAcceptance(tx, envelope.rid, login, \"rejected\", now);\n\n await tx.commit();\n });\n\n await this.sharingStateTree.refreshState();\n }\n\n //\n // Outbox cleanup (donor side)\n //\n\n private static readonly EnvelopeCleanupIntervalMs = 6 * 3600 * 1000; // every 6h\n private envelopeCleanupTimer: ReturnType<typeof setInterval> | undefined;\n\n /** On ML start and every 6h, delete envelopes whose immutable `expiresAt` has passed. */\n private startEnvelopeCleanup(): void {\n void this.runEnvelopeCleanup();\n this.envelopeCleanupTimer = setInterval(() => {\n void this.runEnvelopeCleanup();\n }, MiddleLayer.EnvelopeCleanupIntervalMs);\n // Don't keep the process alive solely for cleanup.\n this.envelopeCleanupTimer.unref?.();\n }\n\n /** Scans the donor's outbox and deletes expired envelopes (backend auto-revokes their grants).\n * Envelopes with `expiresAt: null` (share-with-everybody) are skipped. */\n private async runEnvelopeCleanup(): Promise<void> {\n try {\n const now = Date.now();\n const expired = await this.pl.withReadTx(\"MLEnvelopeCleanupScan\", async (tx) => {\n const data = await tx.getResourceData(this.sharingOutboxResourceId, true);\n const toDelete: { fieldName: string }[] = [];\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n const rd = await tx.getResourceData(f.value, false);\n if (rd.data === undefined) continue;\n const envData = decodeEnvelopeData(rd.data);\n if (envData.expiresAt === null) continue; // never expires\n if (envData.expiresAt <= now) toDelete.push({ fieldName: f.name });\n }\n return toDelete;\n });\n\n if (expired.length === 0) return;\n\n await this.pl.withWriteTx(\"MLEnvelopeCleanup\", async (tx) => {\n for (const { fieldName } of expired)\n tx.removeField(field(this.sharingOutboxResourceId, fieldName));\n await tx.commit();\n });\n\n await this.sharingOutboxTree.refreshState();\n } catch (e) {\n this.env.logger.warn(\n `envelope cleanup failed: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n if (this.envelopeCleanupTimer !== undefined) clearInterval(this.envelopeCleanupTimer);\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await Promise.all([\n this.projectListTree.terminate(),\n this.sharingOutboxTree.terminate(),\n this.sharingStateTree.terminate(),\n this.pendingSharesTree.terminate(),\n ]);\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n // apply MI_TREE_TRAVERSAL only when the embedder hasn't set an explicit mode\n if (\n ops.defaultTreeOptions.traversalMode === undefined &&\n getDebugFlags().treeTraversalMode !== undefined\n )\n ops.defaultTreeOptions.traversalMode = getDebugFlags().treeTraversalMode;\n\n const { projects, sharingOutbox, sharingState } = await pl.withWriteTx(\n \"MLInitialization\",\n async (tx) => {\n // Lazily create each clientRoot-attached singleton resource. Returns the existing\n // resource id if the field is already populated, otherwise creates + locks + sets it.\n const lazyInit = async (\n fieldName: string,\n type: { name: string; version: string },\n ): Promise<{ ref?: ResourceRef; existing?: SignedResourceId }> => {\n const f = field(tx.clientRoot, fieldName);\n tx.createField(f, \"Dynamic\");\n const fData = await tx.getField(f);\n if (isNullSignedResourceId(fData.value)) {\n const ref = tx.createEphemeral(type);\n tx.lock(ref);\n tx.setField(f, ref);\n return { ref };\n }\n return { existing: fData.value };\n };\n\n const projectsR = await lazyInit(ProjectsField, ProjectsResourceType);\n const outboxR = await lazyInit(SharingOutboxField, SharingOutboxResourceType);\n const stateR = await lazyInit(SharingStateField, SharingStateResourceType);\n\n await tx.commit();\n\n return {\n projects: projectsR.existing ?? (await projectsR.ref!.globalId),\n sharingState: stateR.existing ?? (await stateR.ref!.globalId),\n sharingOutbox: outboxR.existing ?? (await outboxR.ref!.globalId),\n };\n },\n );\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n // Project sharing trees and reactive views.\n const outgoingTC = await createOutgoingShares(pl, sharingOutbox, env);\n const sharingStateTree = await createSharingStateTree(pl, sharingState, env);\n const pendingSharesTree = await createPendingSharesTree(pl, env);\n const pendingShares = createPendingSharesComputable(\n pendingSharesTree,\n sharingStateTree,\n pl.userResources.authUser,\n );\n const liveEnvelopes = createLiveEnvelopesComputable(pendingSharesTree);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n sharingOutbox,\n sharingState,\n openedProjects,\n projectListTC.tree,\n outgoingTC.tree,\n sharingStateTree,\n pendingSharesTree,\n v2RegistryProvider,\n projectListTC.computable,\n outgoingTC.computable,\n pendingShares,\n liveEnvelopes,\n );\n }\n}\n"],"mappings":"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"}
@@ -9,6 +9,24 @@ const ProjectsResourceType = {
9
9
  name: "Projects",
10
10
  version: "1"
11
11
  };
12
+ /**
13
+ * Resolves the projects-list resource on the transaction's client root, lazily creating (and
14
+ * locking) an empty one when the {@link ProjectsField} is not yet populated. Returns its signed
15
+ * id. Used when writing into a root that may have no projects list yet, e.g. copying a project
16
+ * into another user's root during admin impersonation.
17
+ */
18
+ async function ensureProjectListRid(tx) {
19
+ const projectsField = (0, _milaboratories_pl_client.field)(tx.clientRoot, ProjectsField);
20
+ tx.createField(projectsField, "Dynamic");
21
+ const fData = await tx.getField(projectsField);
22
+ if ((0, _milaboratories_pl_client.isNullSignedResourceId)(fData.value)) {
23
+ const ref = tx.createEphemeral(ProjectsResourceType);
24
+ tx.lock(ref);
25
+ tx.setField(projectsField, ref);
26
+ return await ref.globalId;
27
+ }
28
+ return fData.value;
29
+ }
12
30
  const ProjectsListTreePruningFunction = (resource) => {
13
31
  if (!(0, _milaboratories_pl_client.resourceTypesEqual)(resource.type, ProjectsResourceType)) return [];
14
32
  return resource.fields;
@@ -52,6 +70,7 @@ exports.ProjectsField = ProjectsField;
52
70
  exports.ProjectsListTreePruningFunction = ProjectsListTreePruningFunction;
53
71
  exports.ProjectsResourceType = ProjectsResourceType;
54
72
  exports.createProjectList = createProjectList;
73
+ exports.ensureProjectListRid = ensureProjectListRid;
55
74
  exports.projectsListFieldFilter = projectsListFieldFilter;
56
75
 
57
76
  //# sourceMappingURL=project_list.cjs.map
@@ -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 { Filter, PlClient, ResourceType, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { resourceIdToString, resourceTypesEqual, treeFilter } 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 { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\n\nexport const ProjectsField = \"projects\";\nexport const ProjectsResourceType: ResourceType = { name: \"Projects\", version: \"1\" };\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 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 const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));\n const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));\n const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));\n const projectId = resourceIdToString(prj.id) as ProjectId;\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: oProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n result.sort((p) => -p.lastModified.valueOf());\n return result;\n }).withStableType();\n\n return { computable: c, tree };\n}\n"],"mappings":";;;;;;AAiBA,MAAa,gBAAgB;AAC7B,MAAa,uBAAqC;CAAE,MAAM;CAAY,SAAS;AAAI;AAEnF,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;CA4BA,OAAO;EAAE,YA1BCC,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,MAAM,SAA6B,CAAC;GAGpC,KAAK,MAAM,SAAS,KAAK,kBAAkB,GAAG;IAC5C,MAAM,MAAM,KAAK,SAAS,KAAK;IAC/B,IAAI,QAAQ,KAAA,GAAW;IACvB,MAAM,QAAA,GAAA,2BAAA,SAAA,CAAgB,IAAI,kBAA+BC,sBAAAA,cAAc,CAAC;IACxE,MAAM,WAAA,GAAA,2BAAA,SAAA,CAAmB,IAAI,kBAA0BC,sBAAAA,uBAAuB,CAAC;IAC/E,MAAM,gBAAA,GAAA,2BAAA,SAAA,CAAwB,IAAI,kBAA0BC,sBAAAA,4BAA4B,CAAC;IACzF,MAAM,aAAA,GAAA,0BAAA,mBAAA,CAA+B,IAAI,EAAE;IAC3C,OAAO,KAAK;KACV,IAAI;KACJ,SAAS,IAAI,KAAK,OAAO;KACzB,cAAc,IAAI,KAAK,YAAY;KACnC,QAAQ,UAAU,QAAQ,SAAS,KAAK;KACxC;IACF,CAAC;GACH;GACA,OAAO,MAAM,MAAM,CAAC,EAAE,aAAa,QAAQ,CAAC;GAC5C,OAAO;EACT,CAAC,CAAC,CAAC,eAEkB;EAAG;CAAK;AAC/B"}
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 { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\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 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 const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));\n const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));\n const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));\n const projectId = resourceIdToString(prj.id) as ProjectId;\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: oProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n result.sort((p) => -p.lastModified.valueOf());\n return result;\n }).withStableType();\n\n return { computable: c, tree };\n}\n"],"mappings":";;;;;;AA6BA,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;CA4BA,OAAO;EAAE,YA1BCC,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,MAAM,SAA6B,CAAC;GAGpC,KAAK,MAAM,SAAS,KAAK,kBAAkB,GAAG;IAC5C,MAAM,MAAM,KAAK,SAAS,KAAK;IAC/B,IAAI,QAAQ,KAAA,GAAW;IACvB,MAAM,QAAA,GAAA,2BAAA,SAAA,CAAgB,IAAI,kBAA+BC,sBAAAA,cAAc,CAAC;IACxE,MAAM,WAAA,GAAA,2BAAA,SAAA,CAAmB,IAAI,kBAA0BC,sBAAAA,uBAAuB,CAAC;IAC/E,MAAM,gBAAA,GAAA,2BAAA,SAAA,CAAwB,IAAI,kBAA0BC,sBAAAA,4BAA4B,CAAC;IACzF,MAAM,aAAA,GAAA,0BAAA,mBAAA,CAA+B,IAAI,EAAE;IAC3C,OAAO,KAAK;KACV,IAAI;KACJ,SAAS,IAAI,KAAK,OAAO;KACzB,cAAc,IAAI,KAAK,YAAY;KACnC,QAAQ,UAAU,QAAQ,SAAS,KAAK;KACxC;IACF,CAAC;GACH;GACA,OAAO,MAAM,MAAM,CAAC,EAAE,aAAa,QAAQ,CAAC;GAC5C,OAAO;EACT,CAAC,CAAC,CAAC,eAEkB;EAAG;CAAK;AAC/B"}
@@ -1,4 +1,4 @@
1
- import { PlClient, ResourceType, SignedResourceId } from "@milaboratories/pl-client";
1
+ import { PlClient, PlTransaction, ResourceType, SignedResourceId } from "@milaboratories/pl-client";
2
2
  import { WatchableValue } from "@milaboratories/computable";
3
3
 
4
4
  //#region src/middle_layer/project_list.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"project_list.d.ts","names":[],"sources":["../../src/middle_layer/project_list.ts"],"mappings":";;;;cAiBa,aAAA"}
1
+ {"version":3,"file":"project_list.d.ts","names":[],"sources":["../../src/middle_layer/project_list.ts"],"mappings":";;;;cA6Ba,aAAA"}
@@ -1,6 +1,6 @@
1
1
  import { ProjectCreatedTimestamp, ProjectLastModifiedTimestamp, ProjectMetaKey } from "../model/project_model.js";
2
2
  import { notEmpty } from "@milaboratories/ts-helpers";
3
- import { resourceIdToString, resourceTypesEqual, treeFilter } from "@milaboratories/pl-client";
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";
6
6
  //#region src/middle_layer/project_list.ts
@@ -9,6 +9,24 @@ const ProjectsResourceType = {
9
9
  name: "Projects",
10
10
  version: "1"
11
11
  };
12
+ /**
13
+ * Resolves the projects-list resource on the transaction's client root, lazily creating (and
14
+ * locking) an empty one when the {@link ProjectsField} is not yet populated. Returns its signed
15
+ * id. Used when writing into a root that may have no projects list yet, e.g. copying a project
16
+ * into another user's root during admin impersonation.
17
+ */
18
+ async function ensureProjectListRid(tx) {
19
+ const projectsField = field(tx.clientRoot, ProjectsField);
20
+ tx.createField(projectsField, "Dynamic");
21
+ const fData = await tx.getField(projectsField);
22
+ if (isNullSignedResourceId(fData.value)) {
23
+ const ref = tx.createEphemeral(ProjectsResourceType);
24
+ tx.lock(ref);
25
+ tx.setField(projectsField, ref);
26
+ return await ref.globalId;
27
+ }
28
+ return fData.value;
29
+ }
12
30
  const ProjectsListTreePruningFunction = (resource) => {
13
31
  if (!resourceTypesEqual(resource.type, ProjectsResourceType)) return [];
14
32
  return resource.fields;
@@ -48,6 +66,6 @@ async function createProjectList(pl, rid, openedProjects, env) {
48
66
  };
49
67
  }
50
68
  //#endregion
51
- export { ProjectsField, ProjectsListTreePruningFunction, ProjectsResourceType, createProjectList, projectsListFieldFilter };
69
+ export { ProjectsField, ProjectsListTreePruningFunction, ProjectsResourceType, createProjectList, ensureProjectListRid, projectsListFieldFilter };
52
70
 
53
71
  //# 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 { Filter, PlClient, ResourceType, SignedResourceId } from \"@milaboratories/pl-client\";\nimport { resourceIdToString, resourceTypesEqual, treeFilter } 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 { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\n\nexport const ProjectsField = \"projects\";\nexport const ProjectsResourceType: ResourceType = { name: \"Projects\", version: \"1\" };\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 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 const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));\n const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));\n const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));\n const projectId = resourceIdToString(prj.id) as ProjectId;\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: oProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n result.sort((p) => -p.lastModified.valueOf());\n return result;\n }).withStableType();\n\n return { computable: c, tree };\n}\n"],"mappings":";;;;;;AAiBA,MAAa,gBAAgB;AAC7B,MAAa,uBAAqC;CAAE,MAAM;CAAY,SAAS;AAAI;AAEnF,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;CA4BA,OAAO;EAAE,YA1BC,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,MAAM,SAA6B,CAAC;GAGpC,KAAK,MAAM,SAAS,KAAK,kBAAkB,GAAG;IAC5C,MAAM,MAAM,KAAK,SAAS,KAAK;IAC/B,IAAI,QAAQ,KAAA,GAAW;IACvB,MAAM,OAAO,SAAS,IAAI,kBAA+B,cAAc,CAAC;IACxE,MAAM,UAAU,SAAS,IAAI,kBAA0B,uBAAuB,CAAC;IAC/E,MAAM,eAAe,SAAS,IAAI,kBAA0B,4BAA4B,CAAC;IACzF,MAAM,YAAY,mBAAmB,IAAI,EAAE;IAC3C,OAAO,KAAK;KACV,IAAI;KACJ,SAAS,IAAI,KAAK,OAAO;KACzB,cAAc,IAAI,KAAK,YAAY;KACnC,QAAQ,UAAU,QAAQ,SAAS,KAAK;KACxC;IACF,CAAC;GACH;GACA,OAAO,MAAM,MAAM,CAAC,EAAE,aAAa,QAAQ,CAAC;GAC5C,OAAO;EACT,CAAC,CAAC,CAAC,eAEkB;EAAG;CAAK;AAC/B"}
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 { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { ProjectMeta } from \"@milaboratories/pl-model-middle-layer\";\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 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 const meta = notEmpty(prj.getKeyValueAsJson<ProjectMeta>(ProjectMetaKey));\n const created = notEmpty(prj.getKeyValueAsJson<number>(ProjectCreatedTimestamp));\n const lastModified = notEmpty(prj.getKeyValueAsJson<number>(ProjectLastModifiedTimestamp));\n const projectId = resourceIdToString(prj.id) as ProjectId;\n result.push({\n id: projectId,\n created: new Date(created),\n lastModified: new Date(lastModified),\n opened: oProjects.indexOf(projectId) >= 0,\n meta,\n });\n }\n result.sort((p) => -p.lastModified.valueOf());\n return result;\n }).withStableType();\n\n return { computable: c, tree };\n}\n"],"mappings":";;;;;;AA6BA,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;CA4BA,OAAO;EAAE,YA1BC,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,MAAM,SAA6B,CAAC;GAGpC,KAAK,MAAM,SAAS,KAAK,kBAAkB,GAAG;IAC5C,MAAM,MAAM,KAAK,SAAS,KAAK;IAC/B,IAAI,QAAQ,KAAA,GAAW;IACvB,MAAM,OAAO,SAAS,IAAI,kBAA+B,cAAc,CAAC;IACxE,MAAM,UAAU,SAAS,IAAI,kBAA0B,uBAAuB,CAAC;IAC/E,MAAM,eAAe,SAAS,IAAI,kBAA0B,4BAA4B,CAAC;IACzF,MAAM,YAAY,mBAAmB,IAAI,EAAE;IAC3C,OAAO,KAAK;KACV,IAAI;KACJ,SAAS,IAAI,KAAK,OAAO;KACzB,cAAc,IAAI,KAAK,YAAY;KACnC,QAAQ,UAAU,QAAQ,SAAS,KAAK;KACxC;IACF,CAAC;GACH;GACA,OAAO,MAAM,MAAM,CAAC,EAAE,aAAa,QAAQ,CAAC;GAC5C,OAAO;EACT,CAAC,CAAC,CAAC,eAEkB;EAAG;CAAK;AAC/B"}
@@ -23,6 +23,7 @@ exports.acceptanceField = require_sharing_model.acceptanceField;
23
23
  exports.acceptanceFieldLogin = require_sharing_model.acceptanceFieldLogin;
24
24
  exports.asShareId = require_sharing_model.asShareId;
25
25
  exports.canGrantToEveryone = require_sharing_model.canGrantToEveryone;
26
+ exports.canImpersonate = require_sharing_model.canImpersonate;
26
27
  exports.decisionField = require_sharing_model.decisionField;
27
28
  exports.decodeEnvelopeData = require_sharing_model.decodeEnvelopeData;
28
29
  exports.isAcceptanceField = require_sharing_model.isAcceptanceField;
@@ -1,4 +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, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId } from "./sharing_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";
3
3
  import { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromLocalTgz, FrontendFromLocalTgzData, FrontendFromLocalTgzResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec } from "./block_pack_spec.js";
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, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId };
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 };
@@ -1,4 +1,4 @@
1
1
  import { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectLastModifiedTimestamp, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey } from "./project_model.js";
2
2
  import { FrontendFromFolderResourceType, FrontendFromLocalTgzResourceType, FrontendFromUrlResourceType } from "./block_pack_spec.js";
3
- import { AcceptanceFieldPrefix, SharedEnvelopeResourceType, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, 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, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId };
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 };
@@ -40,6 +40,22 @@ function canGrantToEveryone(role) {
40
40
  default: return false;
41
41
  }
42
42
  }
43
+ /**
44
+ * Whether a role may impersonate another user: open/create another user's root and list
45
+ * the resources that user can access. Mirrors the backend's authorization rule
46
+ * `util/misecurity/role.go` `CanImpersonate` — true for controller and admin only. This is
47
+ * the admin gate for the "open another user's root" feature and is intentionally stricter
48
+ * than {@link canGrantToEveryone}, which also returns true for a regular user (a normal user
49
+ * may share their own projects, but must never be offered impersonation). `null` (no-auth
50
+ * mode) returns false.
51
+ */
52
+ function canImpersonate(role) {
53
+ switch (role) {
54
+ case _milaboratories_pl_client.Role.CONTROLLER:
55
+ case _milaboratories_pl_client.Role.ADMIN: return true;
56
+ default: return false;
57
+ }
58
+ }
43
59
  /** Dynamic field on SharingState, one per handled share, keyed by shareId. */
44
60
  const decisionField = (shareId) => `decision/${shareId}`;
45
61
  /** Dynamic field on SharedEnvelope, one per recipient who accepted or rejected, keyed
@@ -73,6 +89,7 @@ exports.acceptanceField = acceptanceField;
73
89
  exports.acceptanceFieldLogin = acceptanceFieldLogin;
74
90
  exports.asShareId = asShareId;
75
91
  exports.canGrantToEveryone = canGrantToEveryone;
92
+ exports.canImpersonate = canImpersonate;
76
93
  exports.decisionField = decisionField;
77
94
  exports.decodeEnvelopeData = decodeEnvelopeData;
78
95
  exports.isAcceptanceField = isAcceptanceField;
@@ -1 +1 @@
1
- {"version":3,"file":"sharing_model.cjs","names":["RoleEnum"],"sources":["../../src/model/sharing_model.ts"],"sourcesContent":["import type { ResourceType, Role } from \"@milaboratories/pl-client\";\nimport { Role as RoleEnum } from \"@milaboratories/pl-client\";\nimport type { Branded, ProjectId } from \"@milaboratories/pl-model-common\";\nimport { randomUUID } from \"node:crypto\";\n\n/**\n * Logical identity of a share, stable across replaces. A donor-generated UUID string,\n * branded so it cannot be silently confused with a project id, a login, or a raw field\n * name. Minted once with {@link newShareId}; every other site receives it (from decoded\n * {@link EnvelopeData} or by parsing a `decision/{shareId}` field name) and threads it\n * through unchanged.\n */\nexport type ShareId = Branded<string, \"ShareId\">;\n\n/** Mints a fresh {@link ShareId}. The single place a share's logical identity is created. */\nexport function newShareId(): ShareId {\n return randomUUID() as ShareId;\n}\n\n/** Brands a string already known to be a share id (e.g. parsed from a `decision/{shareId}`\n * field name) as a {@link ShareId}, without minting a new one. */\nexport function asShareId(id: string): ShareId {\n return id as ShareId;\n}\n\n//\n// Pl Model — Project Sharing\n//\n// All sharing structures are defined and managed by the middle layer; the\n// backend knows nothing about envelopes.\n//\n\n/** Field on the donor's clientRoot holding the {@link SharingOutboxResourceType} resource. */\nexport const SharingOutboxField = \"sharingOutbox\";\n/** Field on the acceptor's clientRoot holding the {@link SharingStateResourceType} resource. */\nexport const SharingStateField = \"sharingState\";\n\nexport const SharingOutboxResourceType: ResourceType = { name: \"SharingOutbox\", version: \"1\" };\nexport const SharedEnvelopeResourceType: ResourceType = { name: \"SharedEnvelope\", version: \"1\" };\nexport const SharingStateResourceType: ResourceType = { name: \"SharingState\", version: \"1\" };\n\nexport type EnvelopeMode = \"copy\" | \"read-only\" | \"collaboration\";\n\n/** Per-project decision on change, matching the UI labels: re-snapshot the live source (\"update\"),\n * carry the existing snapshot (\"keep\"), or drop the project from the pack (\"remove\"). */\nexport type ProjectChangeAction = \"keep\" | \"update\" | \"remove\";\n\n/** Key of the per-project envelope maps: a uuid minted per snapshot to name the `project/{uuid}`\n * field. Distinct from {@link ProjectId} — re-snapshotting one source yields a new uuid each time. */\nexport type ProjectFieldUuid = Branded<string, \"ProjectFieldUuid\">;\n\n/**\n * Whether a role may make a resource public (grant to everyone): true for controller,\n * admin, and user; false for workflow and unspecified. The middle layer carries no policy\n * of its own here — a crafted call still hits the backend's role + permission-ceiling gate.\n * `null` (no-auth mode) returns false.\n */\nexport function canGrantToEveryone(role: Role | null): boolean {\n switch (role) {\n case RoleEnum.CONTROLLER:\n case RoleEnum.ADMIN:\n case RoleEnum.USER:\n return true;\n default:\n return false;\n }\n}\n\n/** One project's snapshot inside an envelope, keyed by {@link ProjectFieldUuid} in {@link EnvelopeData.projects}. */\nexport interface EnvelopeProject {\n label: string; // carried so the pending-share UI renders without traversing into the project\n source: ProjectId; // donor's source projectId; supersedes a prior share and matches the snapshot to its live source on change\n updatedAt: number; // ms epoch of the last (re)snapshot\n}\n\n/** Immutable `data` on a SharedEnvelope, set at createEphemeral, never mutated. */\nexport interface EnvelopeData {\n schemaVersion: 1;\n shareId: ShareId; // donor-generated UUID; logical share identity, stable across changes\n sharedAt: number; // ms epoch; this instance's creation time — distinguishes instances of one shareId\n expiresAt: number | null; // ms epoch; sharedAt + ttl (default 14 days) for a targeted share; null for share-with-everybody (never expires)\n mode: EnvelopeMode; // what the acceptor's app should do with the contents\n sender: string; // donor login (informational; backend granted_by is authoritative)\n title: string; // display name shown to recipients; defaults to the first project's name\n projects: Record<ProjectFieldUuid, EnvelopeProject>; // contained projects, keyed by project field uuid\n}\n\n/** Dynamic field on SharingState, one per handled share, keyed by shareId. */\nexport const decisionField = (shareId: ShareId) => `decision/${shareId}`;\n\nexport interface SharingDecision {\n decision: \"accepted\" | \"rejected\";\n timestamp: number; // ms epoch — when the acceptor acted\n envelopeSharedAt: number; // the acted-on envelope instance's sharedAt — pins which instance was handled (paired with the shareId key; the resource id is never stored)\n acceptedProjects: string[]; // ids of the projects created in the acceptor's list ([] for a rejected share)\n}\n\n/** Dynamic field on SharedEnvelope, one per recipient who accepted or rejected, keyed\n * by recipient login. Written by the acceptor in read-write shares only (Copy & Share,\n * Live collaboration) — the acceptor's writable envelope grant is what permits the\n * write; read-only shares omit it. The donor reads these from its own outbox to see\n * who responded and when. Informational, not authoritative (a writable grant holder\n * could write under another login — same trust assumption as the sender field).\n * Copied forward when a share is changed. */\nexport const AcceptanceFieldPrefix = \"acceptance/\";\nexport const acceptanceField = (login: string) => `${AcceptanceFieldPrefix}${login}`;\nexport const isAcceptanceField = (name: string) => name.startsWith(AcceptanceFieldPrefix);\nexport const acceptanceFieldLogin = (name: string) => name.slice(AcceptanceFieldPrefix.length);\n\nexport interface EnvelopeAcceptance {\n action: \"accepted\" | \"rejected\";\n timestamp: number; // ms since epoch\n}\n\n/**\n * Single owner of the raw-data → {@link EnvelopeData} decode. The envelope's immutable `data`\n * blob is UTF-8 JSON set once at createEphemeral; every site that reads it from a raw resource\n * `data` byte buffer (the basic-resource read path) goes through here. The reactive tree-node\n * path uses `node.getDataAsJson<EnvelopeData>()`, which decodes the same JSON.\n */\nexport function decodeEnvelopeData(data: Uint8Array): EnvelopeData {\n return JSON.parse(Buffer.from(data).toString(\"utf-8\")) as EnvelopeData;\n}\n\n/**\n * Options for {@link MiddleLayer.shareProjects}.\n *\n * Recipients XOR everyone — two clean variants, not one struct with mutually exclusive\n * optional fields. The everyone variant issues a single make-public grant (the envelope's\n * `expiresAt` is set to `null`, so it never expires); the recipients variant grants each\n * named recipient and the envelope expires after the default TTL.\n */\nexport type ShareProjectsOptions =\n | {\n recipients: string[]; // recipient logins\n title: string; // display name shown to recipients; defaults to the first project's name\n mode: EnvelopeMode; // v1 UI always sends \"copy\"\n }\n | {\n everyone: true; // share with all users on the server\n /**\n * When true and an everyone-share of the same project already exists, refresh it under its\n * stable shareId (recipients who already accepted or rejected are not re-prompted) instead of\n * minting a new share. No-op when no prior everyone-share of the project exists. Callers that\n * don't care pass `false`.\n */\n replace: boolean;\n title: string;\n mode: EnvelopeMode;\n };\n"],"mappings":";;;;AAeA,SAAgB,aAAsB;CACpC,QAAA,GAAA,YAAA,WAAA,CAAkB;AACpB;;;AAIA,SAAgB,UAAU,IAAqB;CAC7C,OAAO;AACT;;AAUA,MAAa,qBAAqB;;AAElC,MAAa,oBAAoB;AAEjC,MAAa,4BAA0C;CAAE,MAAM;CAAiB,SAAS;AAAI;AAC7F,MAAa,6BAA2C;CAAE,MAAM;CAAkB,SAAS;AAAI;AAC/F,MAAa,2BAAyC;CAAE,MAAM;CAAgB,SAAS;AAAI;;;;;;;AAkB3F,SAAgB,mBAAmB,MAA4B;CAC7D,QAAQ,MAAR;EACE,KAAKA,0BAAAA,KAAS;EACd,KAAKA,0BAAAA,KAAS;EACd,KAAKA,0BAAAA,KAAS,MACZ,OAAO;EACT,SACE,OAAO;CACX;AACF;;AAsBA,MAAa,iBAAiB,YAAqB,YAAY;;;;;;;;AAgB/D,MAAa,wBAAwB;AACrC,MAAa,mBAAmB,UAAkB,GAAG,wBAAwB;AAC7E,MAAa,qBAAqB,SAAiB,KAAK,WAAW,qBAAqB;AACxF,MAAa,wBAAwB,SAAiB,KAAK,MAAM,EAA4B;;;;;;;AAa7F,SAAgB,mBAAmB,MAAgC;CACjE,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,OAAO,CAAC;AACvD"}
1
+ {"version":3,"file":"sharing_model.cjs","names":["RoleEnum"],"sources":["../../src/model/sharing_model.ts"],"sourcesContent":["import type { ResourceType, Role } from \"@milaboratories/pl-client\";\nimport { Role as RoleEnum } from \"@milaboratories/pl-client\";\nimport type { Branded, ProjectId } from \"@milaboratories/pl-model-common\";\nimport { randomUUID } from \"node:crypto\";\n\n/**\n * Logical identity of a share, stable across replaces. A donor-generated UUID string,\n * branded so it cannot be silently confused with a project id, a login, or a raw field\n * name. Minted once with {@link newShareId}; every other site receives it (from decoded\n * {@link EnvelopeData} or by parsing a `decision/{shareId}` field name) and threads it\n * through unchanged.\n */\nexport type ShareId = Branded<string, \"ShareId\">;\n\n/** Mints a fresh {@link ShareId}. The single place a share's logical identity is created. */\nexport function newShareId(): ShareId {\n return randomUUID() as ShareId;\n}\n\n/** Brands a string already known to be a share id (e.g. parsed from a `decision/{shareId}`\n * field name) as a {@link ShareId}, without minting a new one. */\nexport function asShareId(id: string): ShareId {\n return id as ShareId;\n}\n\n//\n// Pl Model — Project Sharing\n//\n// All sharing structures are defined and managed by the middle layer; the\n// backend knows nothing about envelopes.\n//\n\n/** Field on the donor's clientRoot holding the {@link SharingOutboxResourceType} resource. */\nexport const SharingOutboxField = \"sharingOutbox\";\n/** Field on the acceptor's clientRoot holding the {@link SharingStateResourceType} resource. */\nexport const SharingStateField = \"sharingState\";\n\nexport const SharingOutboxResourceType: ResourceType = { name: \"SharingOutbox\", version: \"1\" };\nexport const SharedEnvelopeResourceType: ResourceType = { name: \"SharedEnvelope\", version: \"1\" };\nexport const SharingStateResourceType: ResourceType = { name: \"SharingState\", version: \"1\" };\n\nexport type EnvelopeMode = \"copy\" | \"read-only\" | \"collaboration\";\n\n/** Per-project decision on change, matching the UI labels: re-snapshot the live source (\"update\"),\n * carry the existing snapshot (\"keep\"), or drop the project from the pack (\"remove\"). */\nexport type ProjectChangeAction = \"keep\" | \"update\" | \"remove\";\n\n/** Key of the per-project envelope maps: a uuid minted per snapshot to name the `project/{uuid}`\n * field. Distinct from {@link ProjectId} — re-snapshotting one source yields a new uuid each time. */\nexport type ProjectFieldUuid = Branded<string, \"ProjectFieldUuid\">;\n\n/**\n * Whether a role may make a resource public (grant to everyone): true for controller,\n * admin, and user; false for workflow and unspecified. The middle layer carries no policy\n * of its own here — a crafted call still hits the backend's role + permission-ceiling gate.\n * `null` (no-auth mode) returns false.\n */\nexport function canGrantToEveryone(role: Role | null): boolean {\n switch (role) {\n case RoleEnum.CONTROLLER:\n case RoleEnum.ADMIN:\n case RoleEnum.USER:\n return true;\n default:\n return false;\n }\n}\n\n/**\n * Whether a role may impersonate another user: open/create another user's root and list\n * the resources that user can access. Mirrors the backend's authorization rule\n * `util/misecurity/role.go` `CanImpersonate` — true for controller and admin only. This is\n * the admin gate for the \"open another user's root\" feature and is intentionally stricter\n * than {@link canGrantToEveryone}, which also returns true for a regular user (a normal user\n * may share their own projects, but must never be offered impersonation). `null` (no-auth\n * mode) returns false.\n */\nexport function canImpersonate(role: Role | null): boolean {\n switch (role) {\n case RoleEnum.CONTROLLER:\n case RoleEnum.ADMIN:\n return true;\n default:\n return false;\n }\n}\n\n/** One project's snapshot inside an envelope, keyed by {@link ProjectFieldUuid} in {@link EnvelopeData.projects}. */\nexport interface EnvelopeProject {\n label: string; // carried so the pending-share UI renders without traversing into the project\n source: ProjectId; // donor's source projectId; supersedes a prior share and matches the snapshot to its live source on change\n updatedAt: number; // ms epoch of the last (re)snapshot\n}\n\n/** Immutable `data` on a SharedEnvelope, set at createEphemeral, never mutated. */\nexport interface EnvelopeData {\n schemaVersion: 1;\n shareId: ShareId; // donor-generated UUID; logical share identity, stable across changes\n sharedAt: number; // ms epoch; this instance's creation time — distinguishes instances of one shareId\n expiresAt: number | null; // ms epoch; sharedAt + ttl (default 14 days) for a targeted share; null for share-with-everybody (never expires)\n mode: EnvelopeMode; // what the acceptor's app should do with the contents\n sender: string; // donor login (informational; backend granted_by is authoritative)\n title: string; // display name shown to recipients; defaults to the first project's name\n projects: Record<ProjectFieldUuid, EnvelopeProject>; // contained projects, keyed by project field uuid\n}\n\n/** Dynamic field on SharingState, one per handled share, keyed by shareId. */\nexport const decisionField = (shareId: ShareId) => `decision/${shareId}`;\n\nexport interface SharingDecision {\n decision: \"accepted\" | \"rejected\";\n timestamp: number; // ms epoch — when the acceptor acted\n envelopeSharedAt: number; // the acted-on envelope instance's sharedAt — pins which instance was handled (paired with the shareId key; the resource id is never stored)\n acceptedProjects: string[]; // ids of the projects created in the acceptor's list ([] for a rejected share)\n}\n\n/** Dynamic field on SharedEnvelope, one per recipient who accepted or rejected, keyed\n * by recipient login. Written by the acceptor in read-write shares only (Copy & Share,\n * Live collaboration) — the acceptor's writable envelope grant is what permits the\n * write; read-only shares omit it. The donor reads these from its own outbox to see\n * who responded and when. Informational, not authoritative (a writable grant holder\n * could write under another login — same trust assumption as the sender field).\n * Copied forward when a share is changed. */\nexport const AcceptanceFieldPrefix = \"acceptance/\";\nexport const acceptanceField = (login: string) => `${AcceptanceFieldPrefix}${login}`;\nexport const isAcceptanceField = (name: string) => name.startsWith(AcceptanceFieldPrefix);\nexport const acceptanceFieldLogin = (name: string) => name.slice(AcceptanceFieldPrefix.length);\n\nexport interface EnvelopeAcceptance {\n action: \"accepted\" | \"rejected\";\n timestamp: number; // ms since epoch\n}\n\n/**\n * Single owner of the raw-data → {@link EnvelopeData} decode. The envelope's immutable `data`\n * blob is UTF-8 JSON set once at createEphemeral; every site that reads it from a raw resource\n * `data` byte buffer (the basic-resource read path) goes through here. The reactive tree-node\n * path uses `node.getDataAsJson<EnvelopeData>()`, which decodes the same JSON.\n */\nexport function decodeEnvelopeData(data: Uint8Array): EnvelopeData {\n return JSON.parse(Buffer.from(data).toString(\"utf-8\")) as EnvelopeData;\n}\n\n/**\n * Options for {@link MiddleLayer.shareProjects}.\n *\n * Recipients XOR everyone — two clean variants, not one struct with mutually exclusive\n * optional fields. The everyone variant issues a single make-public grant (the envelope's\n * `expiresAt` is set to `null`, so it never expires); the recipients variant grants each\n * named recipient and the envelope expires after the default TTL.\n */\nexport type ShareProjectsOptions =\n | {\n recipients: string[]; // recipient logins\n title: string; // display name shown to recipients; defaults to the first project's name\n mode: EnvelopeMode; // v1 UI always sends \"copy\"\n }\n | {\n everyone: true; // share with all users on the server\n /**\n * When true and an everyone-share of the same project already exists, refresh it under its\n * stable shareId (recipients who already accepted or rejected are not re-prompted) instead of\n * minting a new share. No-op when no prior everyone-share of the project exists. Callers that\n * don't care pass `false`.\n */\n replace: boolean;\n title: string;\n mode: EnvelopeMode;\n };\n"],"mappings":";;;;AAeA,SAAgB,aAAsB;CACpC,QAAA,GAAA,YAAA,WAAA,CAAkB;AACpB;;;AAIA,SAAgB,UAAU,IAAqB;CAC7C,OAAO;AACT;;AAUA,MAAa,qBAAqB;;AAElC,MAAa,oBAAoB;AAEjC,MAAa,4BAA0C;CAAE,MAAM;CAAiB,SAAS;AAAI;AAC7F,MAAa,6BAA2C;CAAE,MAAM;CAAkB,SAAS;AAAI;AAC/F,MAAa,2BAAyC;CAAE,MAAM;CAAgB,SAAS;AAAI;;;;;;;AAkB3F,SAAgB,mBAAmB,MAA4B;CAC7D,QAAQ,MAAR;EACE,KAAKA,0BAAAA,KAAS;EACd,KAAKA,0BAAAA,KAAS;EACd,KAAKA,0BAAAA,KAAS,MACZ,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;AAWA,SAAgB,eAAe,MAA4B;CACzD,QAAQ,MAAR;EACE,KAAKA,0BAAAA,KAAS;EACd,KAAKA,0BAAAA,KAAS,OACZ,OAAO;EACT,SACE,OAAO;CACX;AACF;;AAsBA,MAAa,iBAAiB,YAAqB,YAAY;;;;;;;;AAgB/D,MAAa,wBAAwB;AACrC,MAAa,mBAAmB,UAAkB,GAAG,wBAAwB;AAC7E,MAAa,qBAAqB,SAAiB,KAAK,WAAW,qBAAqB;AACxF,MAAa,wBAAwB,SAAiB,KAAK,MAAM,EAA4B;;;;;;;AAa7F,SAAgB,mBAAmB,MAAgC;CACjE,OAAO,KAAK,MAAM,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,OAAO,CAAC;AACvD"}
@@ -36,6 +36,16 @@ type ProjectFieldUuid = Branded<string, "ProjectFieldUuid">;
36
36
  * `null` (no-auth mode) returns false.
37
37
  */
38
38
  declare function canGrantToEveryone(role: Role | null): boolean;
39
+ /**
40
+ * Whether a role may impersonate another user: open/create another user's root and list
41
+ * the resources that user can access. Mirrors the backend's authorization rule
42
+ * `util/misecurity/role.go` `CanImpersonate` — true for controller and admin only. This is
43
+ * the admin gate for the "open another user's root" feature and is intentionally stricter
44
+ * than {@link canGrantToEveryone}, which also returns true for a regular user (a normal user
45
+ * may share their own projects, but must never be offered impersonation). `null` (no-auth
46
+ * mode) returns false.
47
+ */
48
+ declare function canImpersonate(role: Role | null): boolean;
39
49
  /** One project's snapshot inside an envelope, keyed by {@link ProjectFieldUuid} in {@link EnvelopeData.projects}. */
40
50
  interface EnvelopeProject {
41
51
  label: string;
@@ -108,5 +118,5 @@ type ShareProjectsOptions = {
108
118
  mode: EnvelopeMode;
109
119
  };
110
120
  //#endregion
111
- export { AcceptanceFieldPrefix, EnvelopeAcceptance, EnvelopeData, EnvelopeMode, EnvelopeProject, ProjectChangeAction, ProjectFieldUuid, ShareId, ShareProjectsOptions, SharedEnvelopeResourceType, SharingDecision, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId };
121
+ export { AcceptanceFieldPrefix, EnvelopeAcceptance, EnvelopeData, EnvelopeMode, EnvelopeProject, ProjectChangeAction, ProjectFieldUuid, ShareId, ShareProjectsOptions, SharedEnvelopeResourceType, SharingDecision, SharingOutboxField, SharingOutboxResourceType, SharingStateField, SharingStateResourceType, acceptanceField, acceptanceFieldLogin, asShareId, canGrantToEveryone, canImpersonate, decisionField, decodeEnvelopeData, isAcceptanceField, newShareId };
112
122
  //# sourceMappingURL=sharing_model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sharing_model.d.ts","names":[],"sources":["../../src/model/sharing_model.ts"],"mappings":";;;;;;AAYA;;;;AAA6B;KAAjB,OAAA,GAAU,OAAO;;iBAGb,UAAA,IAAc,OAAO;;AAAA;iBAMrB,SAAA,CAAU,EAAA,WAAa,OAAO;;cAYjC,kBAAA;;cAEA,iBAAA;AAAA,cAEA,yBAAA,EAA2B,YAAsD;AAAA,cACjF,0BAAA,EAA4B,YAAuD;AAAA,cACnF,wBAAA,EAA0B,YAAqD;AAAA,KAEhF,YAAA;AARmB;AAE/B;AAF+B,KAYnB,mBAAA;;;KAIA,gBAAA,GAAmB,OAAO;AAZtC;;;;AAA8F;AAC9F;AADA,iBAoBgB,kBAAA,CAAmB,IAAiB,EAAX,IAAI;;UAY5B,eAAA;EACf,KAAA;EACA,MAAA,EAAQ,SAAS;EACjB,SAAA;AAAA;;UAIe,YAAA;EACf,aAAA;EACA,OAAA,EAAS,OAAA;EACT,QAAA;EACA,SAAA;EACA,IAAA,EAAM,YAAA;EACN,MAAA;EACA,KAAA;EACA,QAAA,EAAU,MAAA,CAAO,gBAAA,EAAkB,eAAA;AAAA;AAvCN;AAAA,cA2ClB,aAAA,GAAiB,OAAgB,EAAP,OAAO;AAAA,UAE7B,eAAA;EACf,QAAA;EACA,SAAA;EACA,gBAAA;EACA,gBAAA;AAAA;;;AArCkD;AAYpD;;;;cAmCa,qBAAA;AAAA,cACA,eAAA,GAAmB,KAAa;AAAA,cAChC,iBAAA,GAAqB,IAAY;AAAA,cACjC,oBAAA,GAAwB,IAAY;AAAA,UAEhC,kBAAA;EACf,MAAA;EACA,SAAS;AAAA;;;;;;;iBASK,kBAAA,CAAmB,IAAA,EAAM,UAAA,GAAa,YAAY;;;;;;;;;KAYtD,oBAAA;EAEN,UAAA;EACA,KAAA;EACA,IAAA,EAAM,YAAA;AAAA;EAGN,QAAA;EAvD8C;AAAA;AAIpD;;;;EA0DM,OAAA;EACA,KAAA;EACA,IAAA,EAAM,YAAY;AAAA"}
1
+ {"version":3,"file":"sharing_model.d.ts","names":[],"sources":["../../src/model/sharing_model.ts"],"mappings":";;;;;;AAYA;;;;AAA6B;KAAjB,OAAA,GAAU,OAAO;;iBAGb,UAAA,IAAc,OAAO;;AAAA;iBAMrB,SAAA,CAAU,EAAA,WAAa,OAAO;;cAYjC,kBAAA;;cAEA,iBAAA;AAAA,cAEA,yBAAA,EAA2B,YAAsD;AAAA,cACjF,0BAAA,EAA4B,YAAuD;AAAA,cACnF,wBAAA,EAA0B,YAAqD;AAAA,KAEhF,YAAA;AARmB;AAE/B;AAF+B,KAYnB,mBAAA;;;KAIA,gBAAA,GAAmB,OAAO;AAZtC;;;;AAA8F;AAC9F;AADA,iBAoBgB,kBAAA,CAAmB,IAAiB,EAAX,IAAI;;;AAnBmD;AAChG;;;;AAA4F;AAE5F;iBAoCgB,cAAA,CAAe,IAAiB,EAAX,IAAI;;UAWxB,eAAA;EACf,KAAA;EACA,MAAA,EAAQ,SAAS;EACjB,SAAA;AAAA;;UAIe,YAAA;EACf,aAAA;EACA,OAAA,EAAS,OAAA;EACT,QAAA;EACA,SAAA;EACA,IAAA,EAAM,YAAA;EACN,MAAA;EACA,KAAA;EACA,QAAA,EAAU,MAAA,CAAO,gBAAA,EAAkB,eAAA;AAAA;AA9Ce;AAAA,cAkDvC,aAAA,GAAiB,OAAgB,EAAP,OAAO;AAAA,UAE7B,eAAA;EACf,QAAA;EACA,SAAA;EACA,gBAAA;EACA,gBAAA;AAAA;;;;;;;;cAUW,qBAAA;AAAA,cACA,eAAA,GAAmB,KAAa;AAAA,cAChC,iBAAA,GAAqB,IAAY;AAAA,cACjC,oBAAA,GAAwB,IAAY;AAAA,UAEhC,kBAAA;EACf,MAAA;EACA,SAAS;AAAA;;;;;;;iBASK,kBAAA,CAAmB,IAAA,EAAM,UAAA,GAAa,YAAY;;;;;;;;;KAYtD,oBAAA;EAEN,UAAA;EACA,KAAA;EACA,IAAA,EAAM,YAAA;AAAA;EAGN,QAAA;EAnDiC;AAAO;AAE9C;;;;EAwDM,OAAA;EACA,KAAA;EACA,IAAA,EAAM,YAAY;AAAA"}