@tearleads/client-sdk 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/organizations/organizationGroupPresentation.d.ts +1 -12
- package/data/documents/shared/syncResponses.js +5 -7
- package/data/documents/shared/syncResponses.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelGrantPersistence.js +2 -1
- package/data/persistence/organizations/organizationReadModelGrantPersistence.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelMemberLoad.d.ts +9 -0
- package/data/persistence/organizations/organizationReadModelMemberLoad.js +29 -21
- package/data/persistence/organizations/organizationReadModelMemberLoad.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelMembershipPersistence.js +91 -46
- package/data/persistence/organizations/organizationReadModelMembershipPersistence.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelPersistence.js +95 -73
- package/data/persistence/organizations/organizationReadModelPersistence.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelProjection.js +78 -89
- package/data/persistence/organizations/organizationReadModelProjection.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelPurge.js +14 -15
- package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
- package/data/sqlite/organizationReadModelSchema.js +23 -28
- package/data/sqlite/organizationReadModelSchema.js.map +1 -1
- package/data/sqlite/organizationReadModelTableRegistry.d.ts +116 -263
- package/data/sqlite/organizationReadModelTableRegistry.js +10 -7
- package/data/sqlite/organizationReadModelTableRegistry.js.map +1 -1
- package/data/sqlite/schema.d.ts +116 -263
- package/data/sqlite/sqliteBindBudget.js +30 -0
- package/data/sqlite/sqliteBindBudget.js.map +1 -0
- package/internal/validators/documentSyncRefinements.js +5 -0
- package/internal/validators/documentSyncRefinements.js.map +1 -1
- package/internal/validators/operation/documentSync.d.ts +3 -0
- package/internal/validators/operation/organizations.d.ts +0 -16
- package/internal/validators/response/documentSyncSchema.js +35 -1
- package/internal/validators/response/documentSyncSchema.js.map +1 -1
- package/internal/validators/response/index.d.ts +1 -1
- package/internal/validators/response/index.js.map +1 -1
- package/internal/validators/response/organizationReadModel.d.ts +10 -48
- package/internal/validators/response/organizationReadModel.js +9 -1
- package/internal/validators/response/organizationReadModel.js.map +1 -1
- package/package.json +1 -1
- package/workflows/documents/syncContentKeys.js +4 -9
- package/workflows/documents/syncContentKeys.js.map +1 -1
- package/workflows/organizations/readModel.d.ts +8 -3
- package/workflows/organizations/readModel.js.map +1 -1
- package/workflows/organizations/readModelProjection.d.ts +2 -12
- package/workflows/organizations/readModelProjection.js +1 -1
- package/workflows/organizations/readModelProjection.js.map +1 -1
- package/workflows/sync/remoteReset.js +1 -3
- package/workflows/sync/remoteReset.js.map +1 -1
- package/data/persistence/organizations/organizationReadModelPolicyHeadPersistence.js +0 -64
- package/data/persistence/organizations/organizationReadModelPolicyHeadPersistence.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationReadModelMembershipPersistence.js","sourceRoot":"","sources":["organizationReadModelMembershipPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n OrganizationGroupMemberResponse,\n OrganizationReadModelGroupMembershipsResponse,\n} from \"@tearleads/validators/response\";\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport {\n organizationReadModelGroupMembers,\n organizationReadModelGroupMemberships,\n organizationReadModelGroups,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\nconst GROUP_MEMBERSHIP_BATCH_SIZE = 90;\nconst GROUP_MEMBER_INSERT_BATCH_SIZE = 30;\n\nexport class OrganizationReadModelBindingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OrganizationReadModelBindingError\";\n }\n}\n\nfunction membershipMemberKey(\n member: Pick<OrganizationGroupMemberResponse, \"userId\">,\n): string {\n return member.userId;\n}\n\nfunction assertUniqueMembershipRows(\n lane: OrganizationReadModelGroupMembershipsResponse,\n): void {\n const groupIds = lane.groups.map((group) => group.groupId);\n if (new Set(groupIds).size !== groupIds.length) {\n throw new Error(\n \"Organization read-model group memberships contain duplicate groups\",\n );\n }\n if (new Set(lane.deletedGroupIds).size !== lane.deletedGroupIds.length) {\n throw new Error(\n \"Organization read-model group memberships contain duplicate deletions\",\n );\n }\n const deletedGroupIds = new Set(lane.deletedGroupIds);\n if (groupIds.some((groupId) => deletedGroupIds.has(groupId))) {\n throw new Error(\n \"Organization read-model group membership is both replaced and deleted\",\n );\n }\n for (const group of lane.groups) {\n const memberKeys = group.members.map(membershipMemberKey);\n if (new Set(memberKeys).size !== memberKeys.length) {\n throw new Error(\n \"Organization read-model group membership contains duplicate members\",\n );\n }\n }\n}\n\ninterface ApplyGroupMembershipsLaneInput {\n readonly lane: OrganizationReadModelGroupMembershipsResponse;\n readonly organizationId: string;\n readonly replaceAll: boolean;\n readonly tx: ClientSQLiteTransactionScope;\n}\n\nasync function clearMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n await input.tx\n .delete(organizationReadModelGroupMembers)\n .where(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroupMemberships)\n .where(\n eq(\n organizationReadModelGroupMemberships.organizationId,\n input.organizationId,\n ),\n )\n .run();\n}\n\nasync function deleteMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n groupIds: string[],\n): Promise<void> {\n for (\n let index = 0;\n index < groupIds.length;\n index += GROUP_MEMBERSHIP_BATCH_SIZE\n ) {\n const groupIdBatch = groupIds.slice(\n index,\n index + GROUP_MEMBERSHIP_BATCH_SIZE,\n );\n await input.tx\n .delete(organizationReadModelGroupMembers)\n .where(\n and(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n inArray(organizationReadModelGroupMembers.groupId, groupIdBatch),\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroupMemberships)\n .where(\n and(\n eq(\n organizationReadModelGroupMemberships.organizationId,\n input.organizationId,\n ),\n inArray(organizationReadModelGroupMemberships.groupId, groupIdBatch),\n ),\n )\n .run();\n }\n}\n\nasync function removeExistingMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n if (input.replaceAll) {\n if (input.lane.deletedGroupIds.length > 0) {\n throw new Error(\"Organization membership snapshot contains deletions\");\n }\n return clearMembershipRows(input);\n }\n const replacedGroupIds = input.lane.groups.map((group) => group.groupId);\n const removedGroupIds = [...replacedGroupIds, ...input.lane.deletedGroupIds];\n if (removedGroupIds.length > 0) {\n await deleteMembershipRows(input, removedGroupIds);\n }\n}\n\nasync function insertMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n if (input.lane.groups.length === 0) {\n return;\n }\n const membershipRows = input.lane.groups.map((group) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n stateHash: group.stateHash,\n }));\n for (\n let index = 0;\n index < membershipRows.length;\n index += GROUP_MEMBERSHIP_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelGroupMemberships)\n .values(membershipRows.slice(index, index + GROUP_MEMBERSHIP_BATCH_SIZE))\n .run();\n }\n\n const memberRows = input.lane.groups.flatMap((group) =>\n group.members.map((member, sortOrder) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n userId: member.userId,\n sortOrder,\n stateHash: group.stateHash,\n role: member.role,\n signingKeyFingerprint: member.signingKeyFingerprint,\n signingPublicKey: member.signingPublicKey,\n encapsulationPublicKey: member.encapsulationPublicKey,\n encapsulationKeyFingerprint: member.encapsulationKeyFingerprint,\n })),\n );\n for (\n let index = 0;\n index < memberRows.length;\n index += GROUP_MEMBER_INSERT_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelGroupMembers)\n .values(memberRows.slice(index, index + GROUP_MEMBER_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nexport async function applyGroupMembershipsLane(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n assertUniqueMembershipRows(input.lane);\n await removeExistingMembershipRows(input);\n await insertMembershipRows(input);\n}\n\nexport async function assertStoredGroupMembershipBindings(input: {\n readonly memberGroupId: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const groups = await input.tx\n .select({\n groupId: organizationReadModelGroups.groupId,\n memberCount: organizationReadModelGroups.memberCount,\n stateHash: organizationReadModelGroups.stateHash,\n })\n .from(organizationReadModelGroups)\n .where(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n );\n const heads = await input.tx\n .select({\n groupId: organizationReadModelGroupMemberships.groupId,\n stateHash: organizationReadModelGroupMemberships.stateHash,\n })\n .from(organizationReadModelGroupMemberships)\n .where(\n eq(\n organizationReadModelGroupMemberships.organizationId,\n input.organizationId,\n ),\n );\n const members = await input.tx\n .select({\n groupId: organizationReadModelGroupMembers.groupId,\n stateHash: organizationReadModelGroupMembers.stateHash,\n })\n .from(organizationReadModelGroupMembers)\n .where(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n );\n const headsByGroupId = new Map(heads.map((head) => [head.groupId, head]));\n const expectedHeadIds = new Set([\n input.memberGroupId,\n ...groups.flatMap((group) => (group.stateHash ? [group.groupId] : [])),\n ]);\n if (\n heads.length !== expectedHeadIds.size ||\n heads.some((head) => !expectedHeadIds.has(head.groupId))\n ) {\n throw new OrganizationReadModelBindingError(\n \"Organization group membership coverage is inconsistent\",\n );\n }\n\n const memberCountsByGroupId = new Map<string, number>();\n for (const member of members) {\n const head = headsByGroupId.get(member.groupId);\n if (!head || head.stateHash !== member.stateHash) {\n throw new OrganizationReadModelBindingError(\n \"Organization group membership member state is inconsistent\",\n );\n }\n memberCountsByGroupId.set(\n member.groupId,\n (memberCountsByGroupId.get(member.groupId) ?? 0) + 1,\n );\n }\n\n for (const group of groups) {\n const head = headsByGroupId.get(group.groupId);\n if (group.stateHash === null) {\n continue;\n }\n if (\n !head ||\n head.stateHash !== group.stateHash ||\n group.memberCount === null ||\n group.memberCount !== (memberCountsByGroupId.get(group.groupId) ?? 0)\n ) {\n throw new OrganizationReadModelBindingError(\n \"Organization group membership state hash is inconsistent\",\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"organizationReadModelMembershipPersistence.js","sourceRoot":"","sources":["organizationReadModelMembershipPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n OrganizationReadModelGroupMemberResponse,\n OrganizationReadModelGroupMembershipsResponse,\n} from \"@tearleads/validators/response\";\nimport { and, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n organizationReadModelGroupMembers,\n organizationReadModelGroups,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\n// Group ID lists bind one value per group; the head-clearing update also\n// binds its null value beside the organization ID.\nconst GROUP_MEMBERSHIP_BATCH_SIZE = sqliteRowsPerStatement(1, 2);\n// Each head binds two values in its CASE arm and one in the IN list, plus the\n// organization ID.\nconst GROUP_HEAD_UPDATE_BATCH_SIZE = sqliteRowsPerStatement(3, 1);\nconst GROUP_MEMBER_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelGroupMembers,\n);\n\nexport class OrganizationReadModelBindingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OrganizationReadModelBindingError\";\n }\n}\n\nfunction membershipCoverageError(): OrganizationReadModelBindingError {\n return new OrganizationReadModelBindingError(\n \"Organization group membership coverage is inconsistent\",\n );\n}\n\nfunction membershipMemberKey(\n member: Pick<OrganizationReadModelGroupMemberResponse, \"userId\">,\n): string {\n return member.userId;\n}\n\nfunction assertUniqueMembershipRows(\n lane: OrganizationReadModelGroupMembershipsResponse,\n): void {\n const groupIds = lane.groups.map((group) => group.groupId);\n if (new Set(groupIds).size !== groupIds.length) {\n throw new Error(\n \"Organization read-model group memberships contain duplicate groups\",\n );\n }\n if (new Set(lane.deletedGroupIds).size !== lane.deletedGroupIds.length) {\n throw new Error(\n \"Organization read-model group memberships contain duplicate deletions\",\n );\n }\n const deletedGroupIds = new Set(lane.deletedGroupIds);\n if (groupIds.some((groupId) => deletedGroupIds.has(groupId))) {\n throw new Error(\n \"Organization read-model group membership is both replaced and deleted\",\n );\n }\n for (const group of lane.groups) {\n const memberKeys = group.members.map(membershipMemberKey);\n if (new Set(memberKeys).size !== memberKeys.length) {\n throw new Error(\n \"Organization read-model group membership contains duplicate members\",\n );\n }\n }\n}\n\ninterface ApplyGroupMembershipsLaneInput {\n /** The reserved Members head before this lane applies. */\n readonly currentMemberGroupStateHash: string | null;\n readonly lane: OrganizationReadModelGroupMembershipsResponse;\n readonly memberGroupId: string;\n readonly organizationId: string;\n readonly replaceAll: boolean;\n readonly tx: ClientSQLiteTransactionScope;\n}\n\nasync function clearMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n await input.tx\n .delete(organizationReadModelGroupMembers)\n .where(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .update(organizationReadModelGroups)\n .set({ membersStateHash: null })\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n}\n\nasync function deleteMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n groupIds: string[],\n): Promise<void> {\n for (\n let index = 0;\n index < groupIds.length;\n index += GROUP_MEMBERSHIP_BATCH_SIZE\n ) {\n const groupIdBatch = groupIds.slice(\n index,\n index + GROUP_MEMBERSHIP_BATCH_SIZE,\n );\n await input.tx\n .delete(organizationReadModelGroupMembers)\n .where(\n and(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n inArray(organizationReadModelGroupMembers.groupId, groupIdBatch),\n ),\n )\n .run();\n await input.tx\n .update(organizationReadModelGroups)\n .set({ membersStateHash: null })\n .where(\n and(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n inArray(organizationReadModelGroups.groupId, groupIdBatch),\n ),\n )\n .run();\n }\n}\n\nasync function removeExistingMembershipRows(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n if (input.replaceAll) {\n if (input.lane.deletedGroupIds.length > 0) {\n throw new Error(\"Organization membership snapshot contains deletions\");\n }\n return clearMembershipRows(input);\n }\n const replacedGroupIds = input.lane.groups.map((group) => group.groupId);\n const removedGroupIds = [...replacedGroupIds, ...input.lane.deletedGroupIds];\n if (removedGroupIds.length > 0) {\n await deleteMembershipRows(input, removedGroupIds);\n }\n}\n\n/**\n * Record each visible group's membership head on its catalog row. A head for\n * a group the catalog does not list cannot be bound and fails the page. The\n * reserved Members head binds to the state row instead; the catalog normally\n * omits Members, so its row is updated only when one exists.\n */\nasync function writeVisibleGroupHeads(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n const memberGroupHead = input.lane.groups.find(\n (group) => group.groupId === input.memberGroupId,\n );\n if (memberGroupHead) {\n await input.tx\n .update(organizationReadModelGroups)\n .set({ membersStateHash: memberGroupHead.stateHash })\n .where(\n and(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n eq(organizationReadModelGroups.groupId, input.memberGroupId),\n ),\n )\n .run();\n }\n const heads = input.lane.groups.filter(\n (group) => group.groupId !== input.memberGroupId,\n );\n for (\n let index = 0;\n index < heads.length;\n index += GROUP_HEAD_UPDATE_BATCH_SIZE\n ) {\n const batch = heads.slice(index, index + GROUP_HEAD_UPDATE_BATCH_SIZE);\n const arms = sql.join(\n batch.map((head) => sql`WHEN ${head.groupId} THEN ${head.stateHash}`),\n sql` `,\n );\n const updated = await input.tx\n .update(organizationReadModelGroups)\n .set({\n membersStateHash: sql`CASE ${organizationReadModelGroups.groupId} ${arms} END`,\n })\n .where(\n and(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n inArray(\n organizationReadModelGroups.groupId,\n batch.map((head) => head.groupId),\n ),\n ),\n )\n .returning({ groupId: organizationReadModelGroups.groupId });\n if (updated.length !== batch.length) {\n throw membershipCoverageError();\n }\n }\n}\n\nasync function insertMemberRows(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<void> {\n const memberRows = input.lane.groups.flatMap((group) =>\n group.members.map((member, sortOrder) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n userId: member.userId,\n sortOrder,\n stateHash: group.stateHash,\n role: member.role,\n })),\n );\n for (\n let index = 0;\n index < memberRows.length;\n index += GROUP_MEMBER_INSERT_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelGroupMembers)\n .values(memberRows.slice(index, index + GROUP_MEMBER_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nfunction nextMemberGroupStateHash(\n input: ApplyGroupMembershipsLaneInput,\n): string | null {\n const head = input.lane.groups.find(\n (group) => group.groupId === input.memberGroupId,\n );\n if (head) {\n return head.stateHash;\n }\n return input.replaceAll ||\n input.lane.deletedGroupIds.includes(input.memberGroupId)\n ? null\n : input.currentMemberGroupStateHash;\n}\n\n/**\n * Apply one groupMemberships lane and return the reserved Members head, which\n * the caller persists on the organization state row.\n */\nexport async function applyGroupMembershipsLane(\n input: ApplyGroupMembershipsLaneInput,\n): Promise<string | null> {\n assertUniqueMembershipRows(input.lane);\n await removeExistingMembershipRows(input);\n await writeVisibleGroupHeads(input);\n await insertMemberRows(input);\n return nextMemberGroupStateHash(input);\n}\n\ninterface StoredGroupHead {\n readonly groupId: string;\n readonly memberCount: number | null;\n readonly membersStateHash: string | null;\n readonly stateHash: string | null;\n}\n\nfunction membershipHeadsByGroupId(input: {\n readonly groups: readonly StoredGroupHead[];\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string | null;\n}): Map<string, string> {\n if (input.memberGroupStateHash === null) {\n throw membershipCoverageError();\n }\n const heads = new Map([[input.memberGroupId, input.memberGroupStateHash]]);\n for (const group of input.groups) {\n if (group.membersStateHash === null) {\n continue;\n }\n // A head is expected exactly for stateful visible groups; a listed\n // Members row must carry the same head as the state row.\n const isMemberGroup = group.groupId === input.memberGroupId;\n if (\n group.stateHash === null ||\n (isMemberGroup\n ? group.membersStateHash !== input.memberGroupStateHash\n : heads.has(group.groupId))\n ) {\n throw membershipCoverageError();\n }\n heads.set(group.groupId, group.membersStateHash);\n }\n return heads;\n}\n\nexport async function assertStoredGroupMembershipBindings(input: {\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string | null;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const groups = await input.tx\n .select({\n groupId: organizationReadModelGroups.groupId,\n memberCount: organizationReadModelGroups.memberCount,\n membersStateHash: organizationReadModelGroups.membersStateHash,\n stateHash: organizationReadModelGroups.stateHash,\n })\n .from(organizationReadModelGroups)\n .where(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n );\n const members = await input.tx\n .select({\n groupId: organizationReadModelGroupMembers.groupId,\n stateHash: organizationReadModelGroupMembers.stateHash,\n })\n .from(organizationReadModelGroupMembers)\n .where(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n );\n const headsByGroupId = membershipHeadsByGroupId({ ...input, groups });\n\n const memberCountsByGroupId = new Map<string, number>();\n for (const member of members) {\n if (headsByGroupId.get(member.groupId) !== member.stateHash) {\n throw new OrganizationReadModelBindingError(\n \"Organization group membership member state is inconsistent\",\n );\n }\n memberCountsByGroupId.set(\n member.groupId,\n (memberCountsByGroupId.get(member.groupId) ?? 0) + 1,\n );\n }\n\n for (const group of groups) {\n if (group.stateHash === null) {\n continue;\n }\n if (\n group.membersStateHash !== group.stateHash ||\n group.memberCount === null ||\n group.memberCount !== (memberCountsByGroupId.get(group.groupId) ?? 0)\n ) {\n throw new OrganizationReadModelBindingError(\n \"Organization group membership state hash is inconsistent\",\n );\n }\n }\n}\n"]}
|
|
@@ -3,17 +3,17 @@ import { invalidatePrincipalRecovery } from "../../principals/principalRecoveryE
|
|
|
3
3
|
import { organizationDataUsageTables } from "../../sqlite/organizationDataUsageSchema.js";
|
|
4
4
|
import { organizationReadModelDirectoryUsers, organizationReadModelGroups, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
|
|
5
5
|
import { organizationReadModelTables } from "../../sqlite/schema.js";
|
|
6
|
+
import { sqliteInsertBatchSize } from "../../sqlite/sqliteBindBudget.js";
|
|
6
7
|
import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
|
|
7
8
|
import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
|
|
8
9
|
import { clearOrganizationPresentationDenialInTransaction } from "./organizationPresentationDenialPersistence.js";
|
|
9
10
|
import { applyOrganizationReadModelGrantsLane } from "./organizationReadModelGrantPersistence.js";
|
|
10
|
-
import { applyGroupMembershipsLane, assertStoredGroupMembershipBindings, } from "./organizationReadModelMembershipPersistence.js";
|
|
11
|
-
import { applyOrganizationPolicyLane, replaceGroupPolicyHeads, } from "./organizationReadModelPolicyHeadPersistence.js";
|
|
11
|
+
import { applyGroupMembershipsLane, assertStoredGroupMembershipBindings, OrganizationReadModelBindingError, } from "./organizationReadModelMembershipPersistence.js";
|
|
12
12
|
import { loadOrganizationReadModelProjectionInTransaction, } from "./organizationReadModelProjection.js";
|
|
13
13
|
import { loadWithOrganizationReadModelIntegrityPurge, pruneInactiveOrganizationRequestersInTransaction, purgeOrganizationReadModelProjectionInTransaction, } from "./organizationReadModelPurge.js";
|
|
14
14
|
import { assertOrganizationReadModelResponse } from "./organizationReadModelResponseAssertions.js";
|
|
15
|
-
const DIRECTORY_INSERT_BATCH_SIZE =
|
|
16
|
-
const GROUP_INSERT_BATCH_SIZE =
|
|
15
|
+
const DIRECTORY_INSERT_BATCH_SIZE = sqliteInsertBatchSize(organizationReadModelDirectoryUsers);
|
|
16
|
+
const GROUP_INSERT_BATCH_SIZE = sqliteInsertBatchSize(organizationReadModelGroups);
|
|
17
17
|
async function replaceDirectoryLane(input) {
|
|
18
18
|
await input.tx
|
|
19
19
|
.delete(organizationReadModelDirectoryUsers)
|
|
@@ -73,43 +73,35 @@ async function upsertRequester(input) {
|
|
|
73
73
|
tx: input.tx,
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
/** The directory lane is the authoritative roster: requester rows for users
|
|
77
|
-
* it no longer lists as active must fall with it. */
|
|
78
|
-
async function pruneRequestersForDirectoryLane(input) {
|
|
79
|
-
await pruneInactiveOrganizationRequestersInTransaction({
|
|
80
|
-
activeUserIds: input.directory.users
|
|
81
|
-
.filter((user) => user.status === "active")
|
|
82
|
-
.map((user) => user.userId),
|
|
83
|
-
organizationId: input.organizationId,
|
|
84
|
-
retainUserId: input.currentUserId,
|
|
85
|
-
tx: input.tx,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
76
|
async function replaceGroupsLane(input) {
|
|
77
|
+
// Hydrated names and delivered membership heads outlive a catalog
|
|
78
|
+
// replacement; the groupMemberships lane rebinds any head that changed.
|
|
89
79
|
const previousGroups = await input.tx
|
|
90
80
|
.select({
|
|
91
81
|
groupId: organizationReadModelGroups.groupId,
|
|
82
|
+
membersStateHash: organizationReadModelGroups.membersStateHash,
|
|
92
83
|
name: organizationReadModelGroups.name,
|
|
93
84
|
})
|
|
94
85
|
.from(organizationReadModelGroups)
|
|
95
86
|
.where(eq(organizationReadModelGroups.organizationId, input.organizationId));
|
|
96
|
-
const
|
|
87
|
+
const previousById = new Map(previousGroups.map((group) => [group.groupId, group]));
|
|
97
88
|
await input.tx
|
|
98
89
|
.delete(organizationReadModelGroups)
|
|
99
90
|
.where(eq(organizationReadModelGroups.organizationId, input.organizationId))
|
|
100
91
|
.run();
|
|
101
|
-
await replaceGroupPolicyHeads(input);
|
|
102
92
|
const rows = input.groups.groups.map((group, sortOrder) => ({
|
|
103
93
|
organizationId: input.organizationId,
|
|
104
94
|
groupId: group.groupId,
|
|
105
95
|
sortOrder,
|
|
106
|
-
name:
|
|
96
|
+
name: previousById.get(group.groupId)?.name ?? "",
|
|
107
97
|
createdAt: group.createdAt,
|
|
108
98
|
isBuiltin: group.isBuiltin,
|
|
109
99
|
stateHash: group.currentState?.stateHash ?? null,
|
|
110
100
|
stateVersion: group.currentState?.version ?? null,
|
|
111
101
|
keyEpoch: group.currentState?.keyEpoch ?? null,
|
|
102
|
+
keyFingerprint: group.currentState?.keyFingerprint ?? null,
|
|
112
103
|
memberCount: group.currentState?.memberCount ?? null,
|
|
104
|
+
membersStateHash: previousById.get(group.groupId)?.membersStateHash ?? null,
|
|
113
105
|
}));
|
|
114
106
|
for (let index = 0; index < rows.length; index += GROUP_INSERT_BATCH_SIZE) {
|
|
115
107
|
await input.tx
|
|
@@ -122,13 +114,32 @@ async function loadCurrentState(tx, organizationId) {
|
|
|
122
114
|
const [current] = await tx
|
|
123
115
|
.select({
|
|
124
116
|
cursor: organizationReadModelState.cursor,
|
|
117
|
+
keyEpoch: organizationReadModelState.organizationKeyEpoch,
|
|
118
|
+
keyFingerprint: organizationReadModelState.organizationKeyFingerprint,
|
|
119
|
+
memberCount: organizationReadModelState.organizationMemberCount,
|
|
125
120
|
memberGroupId: organizationReadModelState.memberGroupId,
|
|
121
|
+
memberGroupStateHash: organizationReadModelState.memberGroupStateHash,
|
|
126
122
|
profileDocumentId: organizationReadModelState.profileDocumentId,
|
|
123
|
+
stateHash: organizationReadModelState.organizationStateHash,
|
|
124
|
+
version: organizationReadModelState.organizationStateVersion,
|
|
127
125
|
})
|
|
128
126
|
.from(organizationReadModelState)
|
|
129
127
|
.where(eq(organizationReadModelState.organizationId, organizationId))
|
|
130
128
|
.limit(1);
|
|
131
|
-
|
|
129
|
+
if (!current) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const { keyEpoch, keyFingerprint, memberCount, stateHash, version, ...rest } = current;
|
|
133
|
+
return {
|
|
134
|
+
...rest,
|
|
135
|
+
organizationHead: {
|
|
136
|
+
keyEpoch,
|
|
137
|
+
keyFingerprint,
|
|
138
|
+
memberCount,
|
|
139
|
+
stateHash,
|
|
140
|
+
version,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
132
143
|
}
|
|
133
144
|
function resolveApplyDisposition(input) {
|
|
134
145
|
const currentCursor = input.current?.cursor ?? null;
|
|
@@ -142,67 +153,92 @@ function resolveApplyDisposition(input) {
|
|
|
142
153
|
}
|
|
143
154
|
return null;
|
|
144
155
|
}
|
|
156
|
+
/** Apply every carried lane and return the reserved Members head. */
|
|
145
157
|
async function replaceResponseLanes(input) {
|
|
158
|
+
const { organizationId } = input.response;
|
|
146
159
|
const directory = input.response.lanes.directory;
|
|
147
160
|
if (directory) {
|
|
148
|
-
await replaceDirectoryLane({
|
|
149
|
-
directory,
|
|
150
|
-
organizationId: input.response.organizationId,
|
|
151
|
-
tx: input.tx,
|
|
152
|
-
});
|
|
161
|
+
await replaceDirectoryLane({ directory, organizationId, tx: input.tx });
|
|
153
162
|
}
|
|
154
163
|
const groups = input.response.lanes.groups;
|
|
155
164
|
if (groups) {
|
|
156
|
-
await replaceGroupsLane({
|
|
157
|
-
groups,
|
|
158
|
-
organizationId: input.response.organizationId,
|
|
159
|
-
tx: input.tx,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
const organizationPolicy = input.response.lanes.organizationPolicy;
|
|
163
|
-
if (organizationPolicy) {
|
|
164
|
-
await applyOrganizationPolicyLane({
|
|
165
|
-
organizationId: input.response.organizationId,
|
|
166
|
-
organizationPolicy,
|
|
167
|
-
tx: input.tx,
|
|
168
|
-
});
|
|
165
|
+
await replaceGroupsLane({ groups, organizationId, tx: input.tx });
|
|
169
166
|
}
|
|
170
167
|
const groupMemberships = input.response.lanes.groupMemberships;
|
|
171
|
-
|
|
172
|
-
await applyGroupMembershipsLane({
|
|
168
|
+
const memberGroupStateHash = groupMemberships
|
|
169
|
+
? await applyGroupMembershipsLane({
|
|
170
|
+
currentMemberGroupStateHash: input.current?.memberGroupStateHash ?? null,
|
|
173
171
|
lane: groupMemberships,
|
|
174
|
-
|
|
172
|
+
memberGroupId: input.memberGroupId,
|
|
173
|
+
organizationId,
|
|
175
174
|
replaceAll: input.response.mode === "snapshot",
|
|
176
175
|
tx: input.tx,
|
|
177
|
-
})
|
|
178
|
-
|
|
176
|
+
})
|
|
177
|
+
: (input.current?.memberGroupStateHash ?? null);
|
|
179
178
|
const grants = input.response.lanes.grants;
|
|
180
179
|
if (grants) {
|
|
181
180
|
await applyOrganizationReadModelGrantsLane({
|
|
182
181
|
lane: grants,
|
|
183
|
-
organizationId
|
|
182
|
+
organizationId,
|
|
184
183
|
tx: input.tx,
|
|
185
184
|
});
|
|
186
185
|
}
|
|
186
|
+
return memberGroupStateHash;
|
|
187
187
|
}
|
|
188
188
|
async function applyResponseLanes(input) {
|
|
189
|
-
await replaceResponseLanes(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
directory: directoryLane,
|
|
189
|
+
const memberGroupStateHash = await replaceResponseLanes(input);
|
|
190
|
+
// The directory lane is the authoritative roster, already persisted above:
|
|
191
|
+
// requester rows for users it no longer lists as active fall with it.
|
|
192
|
+
if (input.response.lanes.directory) {
|
|
193
|
+
await pruneInactiveOrganizationRequestersInTransaction({
|
|
195
194
|
organizationId: input.response.organizationId,
|
|
195
|
+
retainUserId: input.currentUserId,
|
|
196
196
|
tx: input.tx,
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
if (input.response.lanes.groups || input.response.lanes.groupMemberships) {
|
|
200
200
|
await assertStoredGroupMembershipBindings({
|
|
201
201
|
memberGroupId: input.memberGroupId,
|
|
202
|
+
memberGroupStateHash,
|
|
202
203
|
organizationId: input.response.organizationId,
|
|
203
204
|
tx: input.tx,
|
|
204
205
|
});
|
|
205
206
|
}
|
|
207
|
+
return memberGroupStateHash;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Write the cursor with the organization policy head and the reserved Members
|
|
211
|
+
* head in one row, so a failure anywhere in the page rolls them back together.
|
|
212
|
+
*/
|
|
213
|
+
async function upsertOrganizationReadModelState(input) {
|
|
214
|
+
const organizationHead = input.response.lanes.organizationPolicy?.currentState ??
|
|
215
|
+
input.current?.organizationHead;
|
|
216
|
+
if (!organizationHead || input.memberGroupStateHash === null) {
|
|
217
|
+
throw new OrganizationReadModelBindingError("Organization read-model policy or Members head is missing");
|
|
218
|
+
}
|
|
219
|
+
const directoryLane = input.response.lanes.directory;
|
|
220
|
+
const values = {
|
|
221
|
+
cursor: input.response.nextCursor,
|
|
222
|
+
profileDocumentId: directoryLane !== undefined
|
|
223
|
+
? directoryLane.profileDocumentId
|
|
224
|
+
: (input.current?.profileDocumentId ?? null),
|
|
225
|
+
memberGroupId: input.memberGroupId,
|
|
226
|
+
memberGroupStateHash: input.memberGroupStateHash,
|
|
227
|
+
organizationStateHash: organizationHead.stateHash,
|
|
228
|
+
organizationStateVersion: organizationHead.version,
|
|
229
|
+
organizationKeyEpoch: organizationHead.keyEpoch,
|
|
230
|
+
organizationKeyFingerprint: organizationHead.keyFingerprint,
|
|
231
|
+
organizationMemberCount: organizationHead.memberCount,
|
|
232
|
+
updatedAt: input.now,
|
|
233
|
+
};
|
|
234
|
+
await input.tx
|
|
235
|
+
.insert(organizationReadModelState)
|
|
236
|
+
.values({ ...values, organizationId: input.response.organizationId })
|
|
237
|
+
.onConflictDoUpdate({
|
|
238
|
+
target: organizationReadModelState.organizationId,
|
|
239
|
+
set: values,
|
|
240
|
+
})
|
|
241
|
+
.run();
|
|
206
242
|
}
|
|
207
243
|
function requireMemberGroupId(input) {
|
|
208
244
|
const memberGroupId = input.response.lanes.groups?.memberGroupId ?? input.current?.memberGroupId;
|
|
@@ -257,35 +293,21 @@ export async function applyOrganizationReadModelResponse(input) {
|
|
|
257
293
|
current,
|
|
258
294
|
response: input.response,
|
|
259
295
|
});
|
|
260
|
-
await applyResponseLanes({
|
|
296
|
+
const memberGroupStateHash = await applyResponseLanes({
|
|
297
|
+
current,
|
|
261
298
|
currentUserId: input.currentUserId,
|
|
262
299
|
memberGroupId,
|
|
263
300
|
response: input.response,
|
|
264
301
|
tx,
|
|
265
302
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
organizationId: input.response.organizationId,
|
|
269
|
-
cursor: input.response.nextCursor,
|
|
270
|
-
profileDocumentId: directoryLane !== undefined
|
|
271
|
-
? directoryLane.profileDocumentId
|
|
272
|
-
: (current?.profileDocumentId ?? null),
|
|
303
|
+
await upsertOrganizationReadModelState({
|
|
304
|
+
current,
|
|
273
305
|
memberGroupId,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.onConflictDoUpdate({
|
|
280
|
-
target: organizationReadModelState.organizationId,
|
|
281
|
-
set: {
|
|
282
|
-
cursor: stateRow.cursor,
|
|
283
|
-
profileDocumentId: stateRow.profileDocumentId,
|
|
284
|
-
memberGroupId: stateRow.memberGroupId,
|
|
285
|
-
updatedAt: stateRow.updatedAt,
|
|
286
|
-
},
|
|
287
|
-
})
|
|
288
|
-
.run();
|
|
306
|
+
memberGroupStateHash,
|
|
307
|
+
now,
|
|
308
|
+
response: input.response,
|
|
309
|
+
tx,
|
|
310
|
+
});
|
|
289
311
|
return "applied";
|
|
290
312
|
});
|
|
291
313
|
// An applied change follows an organization hint or mutation; in-flight
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationReadModelPersistence.js","sourceRoot":"","sources":["organizationReadModelPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ListOrganizationGroupsResponse,\n OrganizationReadModelDirectoryResponse,\n OrganizationReadModelResponse,\n} from \"@tearleads/validators/response\";\nimport { eq } from \"drizzle-orm\";\nimport { invalidatePrincipalRecovery } from \"../../principals/principalRecoveryEpoch\";\nimport { organizationDataUsageTables } from \"../../sqlite/organizationDataUsageSchema\";\nimport {\n organizationReadModelDirectoryUsers,\n organizationReadModelGroups,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { clearOrganizationPresentationDenialInTransaction } from \"./organizationPresentationDenialPersistence\";\nimport { applyOrganizationReadModelGrantsLane } from \"./organizationReadModelGrantPersistence\";\nimport {\n applyGroupMembershipsLane,\n assertStoredGroupMembershipBindings,\n} from \"./organizationReadModelMembershipPersistence\";\nimport {\n applyOrganizationPolicyLane,\n replaceGroupPolicyHeads,\n} from \"./organizationReadModelPolicyHeadPersistence\";\nimport {\n loadOrganizationReadModelProjectionInTransaction,\n type OrganizationReadModelProjection,\n} from \"./organizationReadModelProjection\";\nimport {\n loadWithOrganizationReadModelIntegrityPurge,\n pruneInactiveOrganizationRequestersInTransaction,\n purgeOrganizationReadModelProjectionInTransaction,\n} from \"./organizationReadModelPurge\";\nimport { assertOrganizationReadModelResponse } from \"./organizationReadModelResponseAssertions\";\n\nexport type { OrganizationReadModelProjection } from \"./organizationReadModelProjection\";\n\nconst DIRECTORY_INSERT_BATCH_SIZE = 64;\nconst GROUP_INSERT_BATCH_SIZE = 90;\n\ntype ApplyOrganizationReadModelResponseResult =\n | \"already-applied\"\n | \"applied\"\n | \"stale\";\n\ninterface CurrentOrganizationReadModelState {\n readonly cursor: string;\n readonly memberGroupId: string;\n readonly profileDocumentId: string | null;\n}\n\nasync function replaceDirectoryLane(input: {\n readonly directory: OrganizationReadModelDirectoryResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelDirectoryUsers)\n .where(\n eq(\n organizationReadModelDirectoryUsers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n\n const rows = input.directory.users.map((user, sortOrder) => ({\n organizationId: input.organizationId,\n userId: user.userId,\n sortOrder,\n signingKeyFingerprint: user.signingKeyFingerprint,\n signingPublicKey: user.signingPublicKey,\n encapsulationPublicKey: user.encapsulationPublicKey,\n encapsulationKeyFingerprint: user.encapsulationKeyFingerprint,\n isPersonalOrganizationOwner: user.isPersonalOrganizationOwner,\n createdAt: user.createdAt,\n status: user.status,\n profileDocumentId: user.profileDocumentId,\n joinedAt: user.joinedAt,\n updatedAt: user.updatedAt,\n disabledAt: user.disabledAt,\n disabledByUserId: user.disabledByUserId,\n }));\n for (\n let index = 0;\n index < rows.length;\n index += DIRECTORY_INSERT_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelDirectoryUsers)\n .values(rows.slice(index, index + DIRECTORY_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function upsertRequester(input: {\n readonly currentUserId: string;\n readonly isOrgAdmin: boolean;\n readonly now: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const requesterRow = {\n organizationId: input.organizationId,\n userId: input.currentUserId,\n isOrgAdmin: input.isOrgAdmin,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelRequesters)\n .values(requesterRow)\n .onConflictDoUpdate({\n target: [\n organizationReadModelRequesters.organizationId,\n organizationReadModelRequesters.userId,\n ],\n set: {\n isOrgAdmin: requesterRow.isOrgAdmin,\n updatedAt: requesterRow.updatedAt,\n },\n })\n .run();\n // Storing an authoritative response proves access: retire the durable\n // denial marker for this scope in the same transaction.\n await clearOrganizationPresentationDenialInTransaction({\n organizationId: input.organizationId,\n projection: \"readModel\",\n requesterUserId: input.currentUserId,\n tx: input.tx,\n });\n}\n\n/** The directory lane is the authoritative roster: requester rows for users\n * it no longer lists as active must fall with it. */\nasync function pruneRequestersForDirectoryLane(input: {\n readonly currentUserId: string;\n readonly directory: OrganizationReadModelDirectoryResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await pruneInactiveOrganizationRequestersInTransaction({\n activeUserIds: input.directory.users\n .filter((user) => user.status === \"active\")\n .map((user) => user.userId),\n organizationId: input.organizationId,\n retainUserId: input.currentUserId,\n tx: input.tx,\n });\n}\n\nasync function replaceGroupsLane(input: {\n readonly groups: ListOrganizationGroupsResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const previousGroups = await input.tx\n .select({\n groupId: organizationReadModelGroups.groupId,\n name: organizationReadModelGroups.name,\n })\n .from(organizationReadModelGroups)\n .where(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n );\n const names = new Map(\n previousGroups.map((group) => [group.groupId, group.name]),\n );\n await input.tx\n .delete(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n await replaceGroupPolicyHeads(input);\n\n const rows = input.groups.groups.map((group, sortOrder) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n sortOrder,\n name: names.get(group.groupId) ?? \"\",\n createdAt: group.createdAt,\n isBuiltin: group.isBuiltin,\n stateHash: group.currentState?.stateHash ?? null,\n stateVersion: group.currentState?.version ?? null,\n keyEpoch: group.currentState?.keyEpoch ?? null,\n memberCount: group.currentState?.memberCount ?? null,\n }));\n for (let index = 0; index < rows.length; index += GROUP_INSERT_BATCH_SIZE) {\n await input.tx\n .insert(organizationReadModelGroups)\n .values(rows.slice(index, index + GROUP_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function loadCurrentState(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<CurrentOrganizationReadModelState | null> {\n const [current] = await tx\n .select({\n cursor: organizationReadModelState.cursor,\n memberGroupId: organizationReadModelState.memberGroupId,\n profileDocumentId: organizationReadModelState.profileDocumentId,\n })\n .from(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, organizationId))\n .limit(1);\n return current ?? null;\n}\n\nfunction resolveApplyDisposition(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): ApplyOrganizationReadModelResponseResult | null {\n const currentCursor = input.current?.cursor ?? null;\n if (currentCursor !== input.requestedCursor) {\n return currentCursor === input.response.nextCursor\n ? \"already-applied\"\n : \"stale\";\n }\n if (!input.current && input.response.mode !== \"snapshot\") {\n throw new Error(\"Organization read-model delta requires a local snapshot\");\n }\n return null;\n}\n\nasync function replaceResponseLanes(input: {\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const directory = input.response.lanes.directory;\n if (directory) {\n await replaceDirectoryLane({\n directory,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n const groups = input.response.lanes.groups;\n if (groups) {\n await replaceGroupsLane({\n groups,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n const organizationPolicy = input.response.lanes.organizationPolicy;\n if (organizationPolicy) {\n await applyOrganizationPolicyLane({\n organizationId: input.response.organizationId,\n organizationPolicy,\n tx: input.tx,\n });\n }\n const groupMemberships = input.response.lanes.groupMemberships;\n if (groupMemberships) {\n await applyGroupMembershipsLane({\n lane: groupMemberships,\n organizationId: input.response.organizationId,\n replaceAll: input.response.mode === \"snapshot\",\n tx: input.tx,\n });\n }\n const grants = input.response.lanes.grants;\n if (grants) {\n await applyOrganizationReadModelGrantsLane({\n lane: grants,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n}\n\nasync function applyResponseLanes(input: {\n readonly currentUserId: string;\n readonly memberGroupId: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await replaceResponseLanes({ response: input.response, tx: input.tx });\n const directoryLane = input.response.lanes.directory;\n if (directoryLane) {\n await pruneRequestersForDirectoryLane({\n currentUserId: input.currentUserId,\n directory: directoryLane,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n if (input.response.lanes.groups || input.response.lanes.groupMemberships) {\n await assertStoredGroupMembershipBindings({\n memberGroupId: input.memberGroupId,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n}\n\nfunction requireMemberGroupId(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly response: OrganizationReadModelResponse;\n}): string {\n const memberGroupId =\n input.response.lanes.groups?.memberGroupId ?? input.current?.memberGroupId;\n if (!memberGroupId) {\n throw new Error(\"Organization read-model member group is missing\");\n }\n return memberGroupId;\n}\n\nexport async function loadOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n currentUserId: string,\n): Promise<OrganizationReadModelProjection | null> {\n return loadWithOrganizationReadModelIntegrityPurge({\n execSql,\n organizationId,\n load: (tx) =>\n loadOrganizationReadModelProjectionInTransaction({\n currentUserId,\n organizationId,\n tx,\n }),\n });\n}\n\nexport async function applyOrganizationReadModelResponse(input: {\n readonly currentUserId: string;\n readonly execSql: ExecSql;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): Promise<ApplyOrganizationReadModelResponseResult> {\n assertOrganizationReadModelResponse({\n currentUserId: input.currentUserId,\n response: input.response,\n });\n // The requester prune below also retires pruned users' usage projections.\n await ensureSqlTables(input.execSql, [\n ...organizationReadModelTables,\n ...organizationDataUsageTables,\n ]);\n\n const disposition = await getClientSQLitePersistenceRuntime(\n input.execSql,\n ).transaction(\n async (tx): Promise<ApplyOrganizationReadModelResponseResult> => {\n const current = await loadCurrentState(tx, input.response.organizationId);\n const disposition = resolveApplyDisposition({\n current,\n requestedCursor: input.requestedCursor,\n response: input.response,\n });\n if (disposition === \"stale\") {\n return disposition;\n }\n const now = new Date().toISOString();\n await upsertRequester({\n currentUserId: input.currentUserId,\n isOrgAdmin: input.response.currentUser.isOrgAdmin,\n now,\n organizationId: input.response.organizationId,\n tx,\n });\n if (disposition === \"already-applied\") {\n return disposition;\n }\n const memberGroupId = requireMemberGroupId({\n current,\n response: input.response,\n });\n await applyResponseLanes({\n currentUserId: input.currentUserId,\n memberGroupId,\n response: input.response,\n tx,\n });\n const directoryLane = input.response.lanes.directory;\n const stateRow = {\n organizationId: input.response.organizationId,\n cursor: input.response.nextCursor,\n profileDocumentId:\n directoryLane !== undefined\n ? directoryLane.profileDocumentId\n : (current?.profileDocumentId ?? null),\n memberGroupId,\n updatedAt: now,\n };\n await tx\n .insert(organizationReadModelState)\n .values(stateRow)\n .onConflictDoUpdate({\n target: organizationReadModelState.organizationId,\n set: {\n cursor: stateRow.cursor,\n profileDocumentId: stateRow.profileDocumentId,\n memberGroupId: stateRow.memberGroupId,\n updatedAt: stateRow.updatedAt,\n },\n })\n .run();\n\n return \"applied\";\n },\n );\n // An applied change follows an organization hint or mutation; in-flight\n // operations recover the directory again instead of reusing their memo.\n if (disposition === \"applied\")\n invalidatePrincipalRecovery(input.execSql, input.response.organizationId);\n return disposition;\n}\n\nexport async function purgeOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n options: { readonly onlyRequesterUserId?: string } = {},\n): Promise<void> {\n await ensureSqlTables(execSql, organizationReadModelTables);\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await purgeOrganizationReadModelProjectionInTransaction({\n onlyRequesterUserId: options.onlyRequesterUserId,\n organizationId,\n tx,\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"organizationReadModelPersistence.js","sourceRoot":"","sources":["organizationReadModelPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ListOrganizationGroupsResponse,\n OrganizationGroupCurrentStateResponse,\n OrganizationReadModelDirectoryResponse,\n OrganizationReadModelResponse,\n} from \"@tearleads/validators/response\";\nimport { eq } from \"drizzle-orm\";\nimport { invalidatePrincipalRecovery } from \"../../principals/principalRecoveryEpoch\";\nimport { organizationDataUsageTables } from \"../../sqlite/organizationDataUsageSchema\";\nimport {\n organizationReadModelDirectoryUsers,\n organizationReadModelGroups,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { clearOrganizationPresentationDenialInTransaction } from \"./organizationPresentationDenialPersistence\";\nimport { applyOrganizationReadModelGrantsLane } from \"./organizationReadModelGrantPersistence\";\nimport {\n applyGroupMembershipsLane,\n assertStoredGroupMembershipBindings,\n OrganizationReadModelBindingError,\n} from \"./organizationReadModelMembershipPersistence\";\nimport {\n loadOrganizationReadModelProjectionInTransaction,\n type OrganizationReadModelProjection,\n} from \"./organizationReadModelProjection\";\nimport {\n loadWithOrganizationReadModelIntegrityPurge,\n pruneInactiveOrganizationRequestersInTransaction,\n purgeOrganizationReadModelProjectionInTransaction,\n} from \"./organizationReadModelPurge\";\nimport { assertOrganizationReadModelResponse } from \"./organizationReadModelResponseAssertions\";\n\nexport type { OrganizationReadModelProjection } from \"./organizationReadModelProjection\";\n\nconst DIRECTORY_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelDirectoryUsers,\n);\nconst GROUP_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelGroups,\n);\n\ntype ApplyOrganizationReadModelResponseResult =\n | \"already-applied\"\n | \"applied\"\n | \"stale\";\n\ninterface CurrentOrganizationReadModelState {\n readonly cursor: string;\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string;\n readonly organizationHead: OrganizationGroupCurrentStateResponse;\n readonly profileDocumentId: string | null;\n}\n\nasync function replaceDirectoryLane(input: {\n readonly directory: OrganizationReadModelDirectoryResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelDirectoryUsers)\n .where(\n eq(\n organizationReadModelDirectoryUsers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n\n const rows = input.directory.users.map((user, sortOrder) => ({\n organizationId: input.organizationId,\n userId: user.userId,\n sortOrder,\n signingKeyFingerprint: user.signingKeyFingerprint,\n signingPublicKey: user.signingPublicKey,\n encapsulationPublicKey: user.encapsulationPublicKey,\n encapsulationKeyFingerprint: user.encapsulationKeyFingerprint,\n isPersonalOrganizationOwner: user.isPersonalOrganizationOwner,\n createdAt: user.createdAt,\n status: user.status,\n profileDocumentId: user.profileDocumentId,\n joinedAt: user.joinedAt,\n updatedAt: user.updatedAt,\n disabledAt: user.disabledAt,\n disabledByUserId: user.disabledByUserId,\n }));\n for (\n let index = 0;\n index < rows.length;\n index += DIRECTORY_INSERT_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelDirectoryUsers)\n .values(rows.slice(index, index + DIRECTORY_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function upsertRequester(input: {\n readonly currentUserId: string;\n readonly isOrgAdmin: boolean;\n readonly now: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const requesterRow = {\n organizationId: input.organizationId,\n userId: input.currentUserId,\n isOrgAdmin: input.isOrgAdmin,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelRequesters)\n .values(requesterRow)\n .onConflictDoUpdate({\n target: [\n organizationReadModelRequesters.organizationId,\n organizationReadModelRequesters.userId,\n ],\n set: {\n isOrgAdmin: requesterRow.isOrgAdmin,\n updatedAt: requesterRow.updatedAt,\n },\n })\n .run();\n // Storing an authoritative response proves access: retire the durable\n // denial marker for this scope in the same transaction.\n await clearOrganizationPresentationDenialInTransaction({\n organizationId: input.organizationId,\n projection: \"readModel\",\n requesterUserId: input.currentUserId,\n tx: input.tx,\n });\n}\n\nasync function replaceGroupsLane(input: {\n readonly groups: ListOrganizationGroupsResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n // Hydrated names and delivered membership heads outlive a catalog\n // replacement; the groupMemberships lane rebinds any head that changed.\n const previousGroups = await input.tx\n .select({\n groupId: organizationReadModelGroups.groupId,\n membersStateHash: organizationReadModelGroups.membersStateHash,\n name: organizationReadModelGroups.name,\n })\n .from(organizationReadModelGroups)\n .where(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n );\n const previousById = new Map(\n previousGroups.map((group) => [group.groupId, group]),\n );\n await input.tx\n .delete(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n\n const rows = input.groups.groups.map((group, sortOrder) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n sortOrder,\n name: previousById.get(group.groupId)?.name ?? \"\",\n createdAt: group.createdAt,\n isBuiltin: group.isBuiltin,\n stateHash: group.currentState?.stateHash ?? null,\n stateVersion: group.currentState?.version ?? null,\n keyEpoch: group.currentState?.keyEpoch ?? null,\n keyFingerprint: group.currentState?.keyFingerprint ?? null,\n memberCount: group.currentState?.memberCount ?? null,\n membersStateHash: previousById.get(group.groupId)?.membersStateHash ?? null,\n }));\n for (let index = 0; index < rows.length; index += GROUP_INSERT_BATCH_SIZE) {\n await input.tx\n .insert(organizationReadModelGroups)\n .values(rows.slice(index, index + GROUP_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function loadCurrentState(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<CurrentOrganizationReadModelState | null> {\n const [current] = await tx\n .select({\n cursor: organizationReadModelState.cursor,\n keyEpoch: organizationReadModelState.organizationKeyEpoch,\n keyFingerprint: organizationReadModelState.organizationKeyFingerprint,\n memberCount: organizationReadModelState.organizationMemberCount,\n memberGroupId: organizationReadModelState.memberGroupId,\n memberGroupStateHash: organizationReadModelState.memberGroupStateHash,\n profileDocumentId: organizationReadModelState.profileDocumentId,\n stateHash: organizationReadModelState.organizationStateHash,\n version: organizationReadModelState.organizationStateVersion,\n })\n .from(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, organizationId))\n .limit(1);\n if (!current) {\n return null;\n }\n const { keyEpoch, keyFingerprint, memberCount, stateHash, version, ...rest } =\n current;\n return {\n ...rest,\n organizationHead: {\n keyEpoch,\n keyFingerprint,\n memberCount,\n stateHash,\n version,\n },\n };\n}\n\nfunction resolveApplyDisposition(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): ApplyOrganizationReadModelResponseResult | null {\n const currentCursor = input.current?.cursor ?? null;\n if (currentCursor !== input.requestedCursor) {\n return currentCursor === input.response.nextCursor\n ? \"already-applied\"\n : \"stale\";\n }\n if (!input.current && input.response.mode !== \"snapshot\") {\n throw new Error(\"Organization read-model delta requires a local snapshot\");\n }\n return null;\n}\n\ninterface ResponseLanesInput {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly memberGroupId: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}\n\n/** Apply every carried lane and return the reserved Members head. */\nasync function replaceResponseLanes(\n input: ResponseLanesInput,\n): Promise<string | null> {\n const { organizationId } = input.response;\n const directory = input.response.lanes.directory;\n if (directory) {\n await replaceDirectoryLane({ directory, organizationId, tx: input.tx });\n }\n const groups = input.response.lanes.groups;\n if (groups) {\n await replaceGroupsLane({ groups, organizationId, tx: input.tx });\n }\n const groupMemberships = input.response.lanes.groupMemberships;\n const memberGroupStateHash = groupMemberships\n ? await applyGroupMembershipsLane({\n currentMemberGroupStateHash:\n input.current?.memberGroupStateHash ?? null,\n lane: groupMemberships,\n memberGroupId: input.memberGroupId,\n organizationId,\n replaceAll: input.response.mode === \"snapshot\",\n tx: input.tx,\n })\n : (input.current?.memberGroupStateHash ?? null);\n const grants = input.response.lanes.grants;\n if (grants) {\n await applyOrganizationReadModelGrantsLane({\n lane: grants,\n organizationId,\n tx: input.tx,\n });\n }\n return memberGroupStateHash;\n}\n\nasync function applyResponseLanes(\n input: ResponseLanesInput & { readonly currentUserId: string },\n): Promise<string | null> {\n const memberGroupStateHash = await replaceResponseLanes(input);\n // The directory lane is the authoritative roster, already persisted above:\n // requester rows for users it no longer lists as active fall with it.\n if (input.response.lanes.directory) {\n await pruneInactiveOrganizationRequestersInTransaction({\n organizationId: input.response.organizationId,\n retainUserId: input.currentUserId,\n tx: input.tx,\n });\n }\n if (input.response.lanes.groups || input.response.lanes.groupMemberships) {\n await assertStoredGroupMembershipBindings({\n memberGroupId: input.memberGroupId,\n memberGroupStateHash,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n return memberGroupStateHash;\n}\n\n/**\n * Write the cursor with the organization policy head and the reserved Members\n * head in one row, so a failure anywhere in the page rolls them back together.\n */\nasync function upsertOrganizationReadModelState(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string | null;\n readonly now: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const organizationHead =\n input.response.lanes.organizationPolicy?.currentState ??\n input.current?.organizationHead;\n if (!organizationHead || input.memberGroupStateHash === null) {\n throw new OrganizationReadModelBindingError(\n \"Organization read-model policy or Members head is missing\",\n );\n }\n const directoryLane = input.response.lanes.directory;\n const values = {\n cursor: input.response.nextCursor,\n profileDocumentId:\n directoryLane !== undefined\n ? directoryLane.profileDocumentId\n : (input.current?.profileDocumentId ?? null),\n memberGroupId: input.memberGroupId,\n memberGroupStateHash: input.memberGroupStateHash,\n organizationStateHash: organizationHead.stateHash,\n organizationStateVersion: organizationHead.version,\n organizationKeyEpoch: organizationHead.keyEpoch,\n organizationKeyFingerprint: organizationHead.keyFingerprint,\n organizationMemberCount: organizationHead.memberCount,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelState)\n .values({ ...values, organizationId: input.response.organizationId })\n .onConflictDoUpdate({\n target: organizationReadModelState.organizationId,\n set: values,\n })\n .run();\n}\n\nfunction requireMemberGroupId(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly response: OrganizationReadModelResponse;\n}): string {\n const memberGroupId =\n input.response.lanes.groups?.memberGroupId ?? input.current?.memberGroupId;\n if (!memberGroupId) {\n throw new Error(\"Organization read-model member group is missing\");\n }\n return memberGroupId;\n}\n\nexport async function loadOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n currentUserId: string,\n): Promise<OrganizationReadModelProjection | null> {\n return loadWithOrganizationReadModelIntegrityPurge({\n execSql,\n organizationId,\n load: (tx) =>\n loadOrganizationReadModelProjectionInTransaction({\n currentUserId,\n organizationId,\n tx,\n }),\n });\n}\n\nexport async function applyOrganizationReadModelResponse(input: {\n readonly currentUserId: string;\n readonly execSql: ExecSql;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): Promise<ApplyOrganizationReadModelResponseResult> {\n assertOrganizationReadModelResponse({\n currentUserId: input.currentUserId,\n response: input.response,\n });\n // The requester prune below also retires pruned users' usage projections.\n await ensureSqlTables(input.execSql, [\n ...organizationReadModelTables,\n ...organizationDataUsageTables,\n ]);\n\n const disposition = await getClientSQLitePersistenceRuntime(\n input.execSql,\n ).transaction(\n async (tx): Promise<ApplyOrganizationReadModelResponseResult> => {\n const current = await loadCurrentState(tx, input.response.organizationId);\n const disposition = resolveApplyDisposition({\n current,\n requestedCursor: input.requestedCursor,\n response: input.response,\n });\n if (disposition === \"stale\") {\n return disposition;\n }\n const now = new Date().toISOString();\n await upsertRequester({\n currentUserId: input.currentUserId,\n isOrgAdmin: input.response.currentUser.isOrgAdmin,\n now,\n organizationId: input.response.organizationId,\n tx,\n });\n if (disposition === \"already-applied\") {\n return disposition;\n }\n const memberGroupId = requireMemberGroupId({\n current,\n response: input.response,\n });\n const memberGroupStateHash = await applyResponseLanes({\n current,\n currentUserId: input.currentUserId,\n memberGroupId,\n response: input.response,\n tx,\n });\n await upsertOrganizationReadModelState({\n current,\n memberGroupId,\n memberGroupStateHash,\n now,\n response: input.response,\n tx,\n });\n\n return \"applied\";\n },\n );\n // An applied change follows an organization hint or mutation; in-flight\n // operations recover the directory again instead of reusing their memo.\n if (disposition === \"applied\")\n invalidatePrincipalRecovery(input.execSql, input.response.organizationId);\n return disposition;\n}\n\nexport async function purgeOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n options: { readonly onlyRequesterUserId?: string } = {},\n): Promise<void> {\n await ensureSqlTables(execSql, organizationReadModelTables);\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await purgeOrganizationReadModelProjectionInTransaction({\n onlyRequesterUserId: options.onlyRequesterUserId,\n organizationId,\n tx,\n });\n });\n}\n"]}
|