@aiquants/auth-directory-drizzle 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{AUTHZ_ACTIONS as Ie}from"@aiquants/authz-core";var Ce=(t,o)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${o}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${t}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function U(t,o,m,l){return new Set(t.filter(a=>o.has(a.roleId)&&a.appKey===m&&a.resourceKey===l).map(a=>a.action))}function W(t,o){return new Set(t.filter(m=>m.groupId===o).map(m=>m.roleId))}var j=t=>Ie.every(o=>t.has(o)),Y=(t,o)=>t.find(m=>m.roleId===o)?.roleKey??`#${o}`;function X(t,o,m,l){let a=[...o].find(p=>j(U(t,new Set([p]),m,l)));return a!==void 0?Y(t,a):[...o].filter(p=>U(t,new Set([p]),m,l).size>0).map(p=>Y(t,p)).join(" + ")}function je(t,o){return se(t,o)}function se(t,o){let m=t;return{...t,withTransaction:(l,a)=>m.withTransaction((d,p)=>l(se(d,o),p),a),async assignGroupRole(l,a,d,p){await m.withTransaction(async(g,w)=>{let n=await g.listGrants(l),y=W(await g.listGroupRoles(l),a);if(y.add(d),j(U(n,y,o.adminAppKey,o.adminResourceKey))){let e=await o.linkLookup.externalGroupKey(l,a,w);if(e!==null)throw new Error(o.message(X(n,y,o.adminAppKey,o.adminResourceKey),e))}await g.assignGroupRole(l,a,d,p)})},async createGrant(l,a,d){await m.withTransaction(async(p,g)=>{let n=(await p.listResources(l)).find(y=>y.id===a.resourceId);if(n?.appKey===o.adminAppKey&&n?.resourceKey===o.adminResourceKey){let y=await p.listGrants(l),e=await p.listGroupRoles(l);for(let u of new Set(e.filter(b=>b.roleId===a.roleId).map(b=>b.groupId))){let b=U(y,W(e,u),o.adminAppKey,o.adminResourceKey);if(b.add(a.action),!j(b))continue;let s=await o.linkLookup.externalGroupKey(l,u,g);if(s!==null)throw new Error(o.message(X(y,W(e,u),o.adminAppKey,o.adminResourceKey)||Y(y,a.roleId),s))}}await p.createGrant(l,a,d)})}}}async function Fe(t,o,m,l){let a=(await t.listGroupRoles(o)).filter(w=>w.groupId===m);if(a.length===0)return;let d=await t.listGrants(o),p=new Set(a.map(w=>w.roleId));if(!j(U(d,p,l.adminAppKey,l.adminResourceKey)))return;let g=a[0];if(g)throw new Error(l.message(X(d,p,l.adminAppKey,l.adminResourceKey),g.groupKey))}import{and as Se,eq as z}from"drizzle-orm";async function F(t,o,m){let{ledger:l,group:a}=o,{tenantId:d,externalUserId:p,email:g}=m;if(p===null&&g==="")return[];let w={ledgerId:l.id,groupId:l.groupId,tenantId:a.tenantId,externalUserId:l.externalUserId},n=async s=>(await t.select(w).from(l).innerJoin(a,Se(z(a.id,l.groupId),z(a.tenantId,d),z(a.isActive,!0))).where(s)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),y=s=>{let S=new Map;for(let I of s)S.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...S.values()].sort((I,A)=>I.ledgerId-A.ledgerId)},e=p===null?[]:await n(z(l.externalUserId,p));if(g==="")return y(e);let u=await n(z(l.email,g)),b=s=>p!==null&&s.externalUserId!==null&&s.externalUserId!==p;return y([...e,...u.filter(s=>!b(s))])}import{normalizeAuthEmail as Q}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as Ae}from"@aiquants/auth-directory-core";import{assertTenantId as M,assertTenantMatch as ve}from"@aiquants/authz-core";import{and as D,asc as de,count as Z,eq as h,isNull as ue}from"drizzle-orm";var ae=["direct","transitive"];function q(t){if(!ae.includes(t))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${ae.join(" | ")}, received ${JSON.stringify(t)}`);return t}function tr(t,o,m,l){let a=t,d=l.ensureConnection??(async()=>{}),p=l.policyCacheTtlMs??0;if(!Number.isFinite(p)||p<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(l.policyCacheTtlMs)}`);let g=new Map,w=r=>g.delete(r),{TDGroupExternalLink:n,TDGroupMemberEmail:y,TDTenantMembershipGroup:e,TDAllowlistGroup:u,TDDirectorySyncRequest:b}=o,{TMGroup:s,TDGroupMember:S}=m;async function I(r,i,c){if((await r.select({groupId:e.groupId}).from(e).where(D(h(e.tenantId,i),h(e.groupId,c),h(e.isBreakGlass,!0)))).length>0)throw new Error(`Group ${c} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function A(r,i,c){let[f]=await r.select({id:s.id}).from(s).where(D(h(s.id,c),h(s.tenantId,i)));if(!f)throw new Error(`Group not found in tenant: ${c}`)}function G(r,i){return{linkId:Number(r.linkId),groupId:Number(r.groupId),tenantId:String(r.tenantId),provider:String(r.provider),externalId:String(r.externalId),externalKey:String(r.externalKey),membershipMode:q(String(r.membershipMode)),isSyncEnabled:!!r.isSyncEnabled,...r.syncedAt?{syncedAt:new Date(r.syncedAt).toISOString()}:{},...r.syncError?{syncError:String(r.syncError)}:{},unresolvedCount:i}}async function _(r,i){let c=await r.select({groupId:y.groupId,n:Z()}).from(y).innerJoin(s,D(h(s.id,y.groupId),h(s.tenantId,i))).where(ue(y.resolvedUserId)).groupBy(y.groupId);return new Map(c.map(f=>[Number(f.groupId),Number(f.n)]))}return{async listLinks(r){M(r,"listLinks(tenantId)"),await d();let i=await a.select({linkId:n.id,groupId:n.groupId,tenantId:n.tenantId,provider:n.provider,externalId:n.externalId,externalKey:n.externalKey,membershipMode:n.membershipMode,isSyncEnabled:n.isSyncEnabled,syncedAt:n.syncedAt,syncError:n.syncError}).from(n).where(h(n.tenantId,r)).orderBy(de(n.groupId)),c=await _(a,r);return i.map(f=>(ve(r,String(f.tenantId),"listLinks(row.tenantId)"),G(f,c.get(Number(f.groupId))??0)))},async externalGroupKey(r,i,c){M(r,"externalGroupKey(tenantId)"),await d();let[f]=await(c??a).select({externalKey:n.externalKey}).from(n).where(D(h(n.groupId,i),h(n.tenantId,r)));return f?String(f.externalKey):null},async linkGroup(r,i,c,f){M(r,"linkGroup(tenantId)");let v=q(c.membershipMode),k=c.externalId.trim(),R=Q(c.externalKey);if(k===""||R==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await d(),await l.linkable.assertLinkableAndWrite(r,i,async V=>{let E=V;await A(E,r,i),await I(E,r,i),await E.insert(n).output().values({groupId:i,tenantId:r,provider:c.provider,externalId:k,externalKey:R,membershipMode:v,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:f})}),w(r)},async unlinkGroup(r,i){M(r,"unlinkGroup(tenantId)"),await d(),await a.transaction(async c=>{await A(c,r,i),await c.delete(y).where(h(y.groupId,i)),await c.delete(u).where(D(h(u.groupId,i),h(u.tenantId,r))),await c.delete(n).where(D(h(n.groupId,i),h(n.tenantId,r)))}),w(r)},async setSyncEnabled(r,i,c,f){M(r,"setSyncEnabled(tenantId)"),await d(),await a.update(n).set({isSyncEnabled:c,updatedAt:new Date,updatedBy:f}).output().where(D(h(n.groupId,i),h(n.tenantId,r)))},async requestSync(r,i,c){M(r,"requestSync(tenantId)"),await d(),await a.transaction(async f=>{i!==null&&await A(f,r,i),await f.insert(b).output().values({tenantId:r,groupId:i,requestedBy:c,requestedAt:new Date,createdAt:new Date,createdBy:c})})},async getStatus(r){M(r,"getStatus(tenantId)"),await d();let i=await a.select({groupId:n.groupId,isSyncEnabled:n.isSyncEnabled,syncedAt:n.syncedAt,syncError:n.syncError}).from(n).where(h(n.tenantId,r)),[c]=await a.select({n:Z()}).from(b).where(D(h(b.tenantId,r),ue(b.finishedAt))),f=i.filter(E=>!!E.isSyncEnabled),v=f.map(E=>E.syncedAt?new Date(E.syncedAt).getTime():null),k=v.length>0&&v.every(E=>E!==null)?Math.min(...v):null,V=i.length>0&&f.length===0||f.length>0&&(k===null||Date.now()-k>l.stalenessThresholdSec*1e3);return{isEnabled:l.isEnabled,provider:l.provider,linkedGroupCount:i.length,...k!==null?{oldestSyncedAt:new Date(k).toISOString()}:{},isStale:V,pausedGroupCount:i.length-f.length,stalenessThresholdSec:l.stalenessThresholdSec,erroredGroupIds:i.filter(E=>!!E.syncError).map(E=>Number(E.groupId)).sort((E,fe)=>E-fe),pendingRequestCount:Number(c?.n??0)}},async loadTenantMembershipPolicy(r){M(r,"loadTenantMembershipPolicy(tenantId)");let i=p>0?g.get(r):void 0;if(i&&Date.now()-i.loadedAt<p)return i.policy;await d();let c=await a.select({groupId:e.groupId,isBreakGlass:e.isBreakGlass,externalLinkId:n.id}).from(e).innerJoin(s,D(h(s.id,e.groupId),h(s.tenantId,r))).leftJoin(n,h(n.groupId,e.groupId)).where(h(e.tenantId,r));if(c.length===0){let R={kind:"no-proof-required"};return p>0&&g.set(r,{policy:R,loadedAt:Date.now()}),R}let f=c.map(R=>Number(R.groupId)),v=c.filter(R=>!!R.isBreakGlass);for(let R of v)if(R.externalLinkId!==null)throw new Error(`Break-glass group ${Number(R.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let k={kind:"require-proof",proofGroupIds:f,breakGlassGroupIds:v.map(R=>Number(R.groupId))};return Ae(k,`tenant ${r}`),p>0&&g.set(r,{policy:k,loadedAt:Date.now()}),k},async setTenantMembershipGroup(r,i,c,f){M(r,"setTenantMembershipGroup(tenantId)"),await d(),await a.transaction(async v=>{if(await A(v,r,i),c){let[R]=await v.select({id:n.id}).from(n).where(D(h(n.tenantId,r),h(n.groupId,i)));if(R)throw new Error(`Group ${i} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}if((await v.select({groupId:e.groupId}).from(e).where(D(h(e.tenantId,r),h(e.groupId,i)))).length>0){await v.update(e).set({isBreakGlass:c,updatedAt:new Date,updatedBy:f}).output().where(D(h(e.tenantId,r),h(e.groupId,i))),w(r);return}await v.insert(e).output().values({tenantId:r,groupId:i,isBreakGlass:c,createdAt:new Date,createdBy:f})}),w(r)},async removeTenantMembershipGroup(r,i){M(r,"removeTenantMembershipGroup(tenantId)"),await d(),await a.transaction(async c=>{let v=(await c.select({groupId:e.groupId,isBreakGlass:e.isBreakGlass}).from(e).where(h(e.tenantId,r))).filter(k=>Number(k.groupId)!==i);if(v.length>0&&!v.some(k=>!!k.isBreakGlass))throw new Error(`Removing group ${i} would leave tenant ${r} with a membership proof but no break-glass path.`);await c.delete(e).where(D(h(e.tenantId,r),h(e.groupId,i)))}),w(r)},async setGroupActive(r,i,c,f){M(r,"setGroupActive(tenantId)"),await d(),await a.update(s).set({isActive:c,updatedAt:new Date,updatedBy:f}).output().where(D(h(s.id,i),h(s.tenantId,r)))},async listLedgerGroupIds(r,i){if(M(r,"listLedgerGroupIds(tenantId)"),!l.isEnabled)return[];await d();let c=await F(a,{ledger:y,group:s},{tenantId:r,externalUserId:i.externalUserId?.trim()||null,email:Q(i.email)});return[...new Set(c.map(f=>f.groupId))].sort((f,v)=>f-v)},async isEmailAllowedByGroup(r){if(!l.isEnabled)return!1;let i=Q(r);if(i==="")return!1;await d();let[c]=await a.select({id:y.id}).from(y).innerJoin(u,h(u.groupId,y.groupId)).innerJoin(s,D(h(s.id,y.groupId),h(s.isActive,!0))).where(h(y.email,i));return!!c},async listAllowlistGroups(r){return M(r,"listAllowlistGroups(tenantId)"),await d(),(await a.select({id:u.id,groupId:u.groupId,groupKey:s.groupKey,groupName:s.name,description:u.description}).from(u).innerJoin(s,D(h(s.id,u.groupId),h(s.tenantId,r))).where(h(u.tenantId,r)).orderBy(de(s.name))).map(c=>({id:Number(c.id),type:"group",groupId:Number(c.groupId),groupKey:String(c.groupKey),groupName:String(c.groupName),...c.description?{description:String(c.description)}:{},owner:{kind:"tenant",tenantId:r},readOnly:!1}))},async addAllowlistGroup(r,i,c,f){M(r,"addAllowlistGroup(tenantId)"),await d(),await a.transaction(async v=>{await A(v,r,i);let[k]=await v.select({id:n.id}).from(n).where(D(h(n.groupId,i),h(n.tenantId,r)));if(!k)throw new Error(`Group ${i} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await v.insert(u).output().values({tenantId:r,groupId:i,description:c??null,createdAt:new Date,createdBy:f})})},async removeAllowlistGroup(r,i){M(r,"removeAllowlistGroup(tenantId)"),await d(),await a.delete(u).where(D(h(u.id,i),h(u.tenantId,r)))},async countProjectedMembers(r,i){M(r,"countProjectedMembers(tenantId)"),await d();let[c]=await a.select({n:Z()}).from(S).innerJoin(s,D(h(s.id,S.groupId),h(s.tenantId,r))).where(h(S.groupId,i));return Number(c?.n??0)}}}import{normalizeAuthEmail as Te}from"@aiquants/auth-core";import{assertTenantId as De,assertTenantMatch as xe}from"@aiquants/authz-core";async function sr(t,o){let m=De(o.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(o.userId)||o.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(o.userId)}`);let l=Te(o.email),a=o.externalUserId?.trim()||null;if(a===null&&l==="")return{addedGroupIds:[],matchedLedgerIds:[]};let d=await t.findLedgerMatches({tenantId:m,externalUserId:a,email:l});if(d.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let n of d)xe(m,n.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let p=[...new Set(d.map(n=>n.ledgerId))].sort((n,y)=>n-y),g=[...new Set(d.map(n=>n.groupId))].sort((n,y)=>n-y);return{addedGroupIds:[...await t.attachUserToGroups({tenantId:m,userId:o.userId,ledgerIds:p,groupIds:g})].sort((n,y)=>n-y),matchedLedgerIds:p}}import{assertTenantId as pe}from"@aiquants/authz-core";import{and as ee,eq as re,inArray as H}from"drizzle-orm";var le=2100;var dr=500;function L(t,o=500){if(!Number.isInteger(o)||o<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] chunk size must be a positive integer, received ${String(o)}`);let m=[];for(let l=0;l<t.length;l+=o)m.push(t.slice(l,l+o));return m}function O(t,o,m=100){if(!Number.isInteger(o)||o<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] parametersPerItem must be a positive integer, received ${String(o)}`);if(!Number.isInteger(m)||m<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] reservedParameters must be a non-negative integer, received ${String(m)}`);let l=2100-m,a=Math.floor(l/o);if(a<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] a single item binds ${o} parameters, which exceeds the ${l} available in one statement`);return L(t,a)}var ke=4,Ge=100;function yr(t,o,m,l){let a=t,d=l.ensureConnection??(async()=>{}),p=l.now??(()=>new Date),g=o.TDGroupMemberEmail,{TMGroup:w,TDGroupMember:n}=m;return{async findLedgerMatches(y){return pe(y.tenantId,"findLedgerMatches(tenantId)"),await d(),F(a,{ledger:g,group:w},y)},async attachUserToGroups({tenantId:y,userId:e,ledgerIds:u,groupIds:b}){return pe(y,"attachUserToGroups(tenantId)"),await d(),u.length===0?[]:a.transaction(async s=>{let S=new Set;for(let r of L(b)){let i=await s.select({id:w.id}).from(w).where(ee(H(w.id,r),re(w.tenantId,y),re(w.isActive,!0)));for(let c of i)S.add(Number(c.id))}let I=b.filter(r=>S.has(r));if(I.length===0)return[];let A=new Set;for(let r of L(I)){let i=await s.select({groupId:n.groupId}).from(n).where(ee(re(n.userId,e),H(n.groupId,r)));for(let c of i)A.add(Number(c.groupId))}let G=I.filter(r=>!A.has(r)),_=Math.max(1,2100-Ge-I.length);for(let r of L(u,_))await s.update(g).set({resolvedUserId:e}).output().where(ee(H(g.id,r),H(g.groupId,I)));if(G.length>0){let r=p();for(let i of O(G,ke))await s.insert(n).output().values(i.map(c=>({groupId:c,userId:e,createdAt:r,createdBy:l.actor})))}return G})}}}import{AUTHZ_TENANT_COLUMN as B}from"@aiquants/authz-drizzle";import{bigint as P,bit as ce,datetime2 as $,foreignKey as J,int as Re,mssqlSchema as Ee,nvarchar as T,primaryKey as N,uniqueIndex as C}from"drizzle-orm/mssql-core";var Me="provider",wr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TMAllowlist"];function fr(t,o){let m=Ee(t),l=()=>({createdAt:$("created_at"),createdBy:T("created_by",{length:50}),updatedAt:$("updated_at"),updatedBy:T("updated_by",{length:50})}),a=()=>({createdAt:$("created_at"),createdBy:T("created_by",{length:50})}),d=m.table("TDGroupExternalLink",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),tenantId:T(B,{length:64}).notNull(),provider:T(Me,{length:32}).notNull(),externalId:T("external_id",{length:256}).notNull(),externalKey:T("external_key",{length:320}).notNull(),membershipMode:T("membership_mode",{length:16}).notNull(),isSyncEnabled:ce("is_sync_enabled").notNull(),syncedAt:$("synced_at"),syncError:T("sync_error",{length:1e3}),vanishedObservations:Re("vanished_observations").notNull(),...l()},e=>[N({name:`${t}_TDGroupExternalLink_pk`,columns:[e.id]}),C(`${t}_TDGroupExternalLink_group_uq`).on(e.groupId),C(`${t}_TDGroupExternalLink_external_uq`).on(e.tenantId,e.provider,e.externalId),J({name:`${t}_TDGroupExternalLink_group_id_fk`,columns:[e.groupId],foreignColumns:[o.groupTable.id]})]),p=m.table("TDGroupMemberEmail",{id:P("id",{mode:"number"}).identity().notNull(),groupId:P("group_id",{mode:"number"}).notNull(),email:T("email",{length:320}).notNull(),externalUserId:T("external_user_id",{length:128}),resolvedUserId:P("resolved_user_id",{mode:"number"}),observedAt:$("observed_at").notNull(),...l()},e=>[N({name:`${t}_TDGroupMemberEmail_pk`,columns:[e.id]}),C(`${t}_TDGroupMemberEmail_group_email_uq`).on(e.groupId,e.email),J({name:`${t}_TDGroupMemberEmail_group_id_fk`,columns:[e.groupId],foreignColumns:[o.groupTable.id]})]),g=m.table("TDTenantMembershipGroup",{tenantId:T(B,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),isBreakGlass:ce("is_break_glass").notNull(),...a()},e=>[N({name:`${t}_TDTenantMembershipGroup_pk`,columns:[e.tenantId,e.groupId]}),J({name:`${t}_TDTenantMembershipGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[o.groupTable.id]})]),w=m.table("TDAllowlistGroup",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(B,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}).notNull(),description:T("description",{length:512}),...a()},e=>[N({name:`${t}_TDAllowlistGroup_pk`,columns:[e.id]}),C(`${t}_TDAllowlistGroup_tenant_group_uq`).on(e.tenantId,e.groupId),J({name:`${t}_TDAllowlistGroup_group_id_fk`,columns:[e.groupId],foreignColumns:[o.groupTable.id]})]),n=m.table("TDDirectorySyncRequest",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(B,{length:64}).notNull(),groupId:P("group_id",{mode:"number"}),requestedBy:T("requested_by",{length:320}).notNull(),requestedAt:$("requested_at").notNull(),pickedAt:$("picked_at"),finishedAt:$("finished_at"),result:T("result",{length:32}),error:T("error",{length:2e3}),...a()},e=>[N({name:`${t}_TDDirectorySyncRequest_pk`,columns:[e.id]})]),y=m.table("TMAllowlist",{id:P("id",{mode:"number"}).identity().notNull(),tenantId:T(B,{length:64}).notNull(),pattern:T("pattern",{length:256}).notNull(),type:T("type",{length:32}).notNull(),description:T("description",{length:512}),...a()},e=>[N({name:`${t}_TMAllowlist_pk`,columns:[e.id]}),C(`${t}_TMAllowlist_pattern_uq`).on(e.tenantId,e.pattern)]);return{TDGroupExternalLink:d,TDGroupMemberEmail:p,TDTenantMembershipGroup:g,TDAllowlistGroup:w,TDDirectorySyncRequest:n,TMAllowlist:y}}import{assertBlastRadiusPolicy as Pe,reconcile as _e}from"@aiquants/auth-directory-core";var te=(t,o=20)=>t.length<=o?t.join(", "):`${t.slice(0,o).join(", ")} \u307B\u304B ${t.length-o} \u4EF6`,me={upstreamUnreadable:t=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${t}`,localWriteFailed:t=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${t}`,resolverIncomplete:(t,o)=>`\u4E0A\u6D41\u304C ${t} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${o} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(t,o,m)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${t})\u3002\u9023\u7D9A ${o} \u56DE\u76EE\u3067\u3059\u3002${m} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(t,o)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${o} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${t})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:t=>{if(t.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059 (${te(t.offendingEmails)})\u3002`;let o=t.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",m=t.axis==="membership"?t.withheldProjectionRemoves.map(String):t.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${o}\u304C ${t.population} \u4EBA\u4E2D ${t.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${t.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${t.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${te(m)}\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalsRefused:t=>`${t.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${te(t.map(o=>String(o.userId)))})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(t,o)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${t} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${o} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var ye=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider"];function xr(t){let{directory:o,repository:m,policy:l,now:a,logger:d}=t,p=t.messages??me;if(Pe(l),!Number.isInteger(t.vanishedThreshold)||t.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(t.vanishedThreshold)}`);if(typeof t.provider!="string"||t.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function g(e,u){try{await m.recordFailure(e,u)}catch(b){d.error(`[auth-directory] could not record the failure for ${e.externalKey}:`,b)}}async function w(e){try{let u=await o.getGroup(e.externalId);return u===null?{kind:"missing"}:{kind:"ok",group:u,members:await o.listGroupMembers(u,e.membershipMode)}}catch(u){return{kind:"failed",error:u instanceof Error?u:new Error(String(u))}}}async function n(e){let u=await m.recordVanishedObservation(e,p.groupMissing(e.externalKey,e.vanishedObservations+1,t.vanishedThreshold));return u<t.vanishedThreshold?(d.warn(`[auth-directory] ${e.externalKey} missing upstream (${u}/${t.vanishedThreshold})`),{kind:"missing",link:e,observations:u}):(await m.markGroupInactive(e),await g(e,p.groupVanished(e.externalKey,u)),d.warn(`[auth-directory] ${e.externalKey} deactivated after ${u} consecutive absences`),{kind:"vanished",link:e})}async function y(e){if(e.provider!==t.provider)return await g(e,p.foreignProvider(e.provider,t.provider)),d.warn(`[auth-directory] skipping ${e.externalKey}: belongs to provider ${e.provider}`),{kind:"foreign-provider",link:e};let u=await w(e);if(u.kind==="missing")return n(e);if(u.kind==="failed")return await g(e,p.upstreamUnreadable(u.error.message)),d.error(`[auth-directory] upstream read failed for ${e.externalKey}:`,u.error),{kind:"upstream-failed",link:e,error:u.error};try{let[b,s]=await Promise.all([m.loadGroupSnapshot(e),m.resolveMembers(u.members)]);if(s.length!==u.members.length){let _=p.resolverIncomplete(u.members.length,s.length);return await g(e,_),d.error(`[auth-directory] ${_}`),{kind:"local-failed",link:e,error:new Error(_)}}let S=_e(b,s,l);if(S.aborted)return await g(e,p.planRefused(S.aborted)),d.warn(`[auth-directory] plan refused for ${e.externalKey}: ${S.aborted.reason}`),{kind:"aborted",link:e,plan:S};let I=S.ledgerUpserts.length>0||S.ledgerDeletes.length>0||S.projectionAdds.length>0||S.projectionRemoves.length>0,A=a(),G={refusals:[]};return I&&(G=await m.applyPlan(e,S,A)),await m.recordSuccess(e,u.group,A),G.refusals.length>0&&(await g(e,p.removalsRefused(G.refusals)),d.warn(`[auth-directory] ${G.refusals.length} removal(s) refused for ${e.externalKey}`)),S.unresolvedEmails.length>0&&d.info(`[auth-directory] ${e.externalKey}: ${S.unresolvedEmails.length} upstream member(s) have no local user yet`),I?{kind:"applied",link:e,plan:S}:{kind:"unchanged",link:e}}catch(b){let s=b instanceof Error?b:new Error(String(b));return await g(e,p.localWriteFailed(s.message)),d.error(`[auth-directory] local write failed for ${e.externalKey}:`,s),{kind:"local-failed",link:e,error:s}}}return{syncOne:y,async run(e={}){let u=await m.listSyncableLinks(e),b=[];for(let S of u)try{b.push(await y(S))}catch(I){let A=I instanceof Error?I:new Error(String(I));d.error(`[auth-directory] ${S.externalKey} ended abnormally:`,A),b.push({kind:"local-failed",link:S,error:A})}let s=b.filter(S=>ye.includes(S.kind)).length;return d.info(`[auth-directory] run finished: ${b.length} link(s), ${s} unhealthy`),{outcomes:b,unhealthyCount:s}}}}import{assertTenantId as Le}from"@aiquants/authz-core";import{and as $e,asc as ge,eq as he,isNull as ne}from"drizzle-orm";var oe=2e3;function Er(t,o,m){let l=t,a=m.ensureConnection??(async()=>{}),d=o.TDDirectorySyncRequest,p=m.maxErrorLength??oe;if(!Number.isInteger(p)||p<1||p>oe)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${oe}, received ${String(m.maxErrorLength)}`);return{async claim(){await a();let g=await l.select({id:d.id}).from(d).where(ne(d.pickedAt)).orderBy(ge(d.id));for(let w of g){let y=(await l.update(d).set({pickedAt:m.now()}).output().where($e(he(d.id,Number(w.id)),ne(d.pickedAt))))?.[0];if(!y)continue;let e=String(y.tenant_id??y.tenantId??""),u=y.group_id??y.groupId??null;return{id:Number(y.id),tenantId:Le(e,"claim(row.tenantId)"),groupId:u===null?null:Number(u),requestedBy:String(y.requested_by??y.requestedBy??"")}}return null},async finish(g,w){await a();let n=w.error?w.error.slice(0,p):null;await l.update(d).set({finishedAt:m.now(),result:w.result,error:n}).output().where(he(d.id,g))},async listStranded(g){return await a(),(await l.select({id:d.id,pickedAt:d.pickedAt}).from(d).where(ne(d.finishedAt)).orderBy(ge(d.id))).filter(n=>n.pickedAt!==null&&new Date(n.pickedAt)<g).map(n=>Number(n.id))}}}import{normalizeAuthEmail as be}from"@aiquants/auth-core";import{assertTenantId as we,assertTenantMatch as Ke}from"@aiquants/authz-core";import{and as K,eq as x,inArray as ie,isNull as Ne,notInArray as Ue,or as ze}from"drizzle-orm";var qe=7,Oe=4;function Nr(t,o,m,l){let a=t,d=l.ensureConnection??(async()=>{}),{TDGroupExternalLink:p,TDGroupMemberEmail:g}=o,{TMGroup:w,TDGroupMember:n}=m,y=l.actor;return{async listSyncableLinks(e){await d();let u=[x(p.isSyncEnabled,!0)];return e.tenantId!==void 0&&u.push(x(p.tenantId,we(e.tenantId,"listSyncableLinks(tenantId)"))),e.groupId!==void 0&&u.push(x(p.groupId,e.groupId)),(await a.select({linkId:p.id,groupId:p.groupId,linkTenantId:p.tenantId,groupTenantId:w.tenantId,groupKey:w.groupKey,isGroupActive:w.isActive,provider:p.provider,externalId:p.externalId,externalKey:p.externalKey,membershipMode:p.membershipMode,vanishedObservations:p.vanishedObservations}).from(p).innerJoin(w,x(w.id,p.groupId)).where(K(...u))).map(s=>(Ke(String(s.groupTenantId),String(s.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(s.linkId),groupId:Number(s.groupId),groupKey:String(s.groupKey),tenantId:String(s.groupTenantId),provider:String(s.provider),externalId:String(s.externalId),externalKey:String(s.externalKey),membershipMode:q(String(s.membershipMode)),isGroupActive:!!s.isGroupActive,vanishedObservations:Number(s.vanishedObservations??0)}))},async loadGroupSnapshot(e){await d();let[u,b]=await Promise.all([a.select({email:g.email,externalUserId:g.externalUserId,resolvedUserId:g.resolvedUserId}).from(g).where(x(g.groupId,e.groupId)),a.select({userId:n.userId}).from(n).where(x(n.groupId,e.groupId))]);return{ledger:u.map(s=>({email:String(s.email),...s.externalUserId?{externalUserId:String(s.externalUserId)}:{},resolvedUserId:s.resolvedUserId===null?null:Number(s.resolvedUserId)})),projectedUserIds:b.map(s=>Number(s.userId))}},resolveMembers(e){return l.memberResolver.resolve(e)},async applyPlan(e,u,b){we(e.tenantId,"applyPlan(link.tenantId)"),await d(),(u.ledgerUpserts.length>0||u.projectionAdds.length>0)&&await a.transaction(async I=>{let A=u.ledgerUpserts.map(r=>({...r,email:be(r.email)})),G=new Map;for(let r of L(A.map(i=>i.email))){let i=await I.select({id:g.id,email:g.email}).from(g).where(K(x(g.groupId,e.groupId),ie(g.email,r)));for(let c of i)G.set(String(c.email),Number(c.id))}let _=A.filter(r=>!G.has(r.email));for(let r of O(_,qe))await I.insert(g).output().values(r.map(i=>({groupId:e.groupId,email:i.email,externalUserId:i.externalUserId??null,resolvedUserId:i.resolvedUserId,observedAt:b,createdAt:b,createdBy:y})));for(let r of A){let i=G.get(r.email);i!==void 0&&await I.update(g).set({externalUserId:r.externalUserId??null,resolvedUserId:r.resolvedUserId,observedAt:b,updatedAt:b,updatedBy:y}).output().where(x(g.id,i))}for(let r of O(u.projectionAdds,Oe)){let i=new Set((await I.select({userId:n.userId}).from(n).where(K(x(n.groupId,e.groupId),ie(n.userId,r)))).map(f=>Number(f.userId))),c=r.filter(f=>!i.has(f));c.length>0&&await I.insert(n).output().values(c.map(f=>({groupId:e.groupId,userId:f,createdAt:b,createdBy:y})))}});let s=[];for(let I of u.projectionRemoves)try{await l.lockoutGuard.guardedWrite(e.tenantId,{removeGroupMembership:{groupId:e.groupId,userId:I}},"remove-group-member",A=>A.delete(n).where(K(x(n.groupId,e.groupId),x(n.userId,I))))}catch(A){s.push({userId:I,applied:!1,refusal:A instanceof Error?A.message:String(A)})}let S=s.map(I=>I.userId);return u.ledgerDeletes.length>0&&await a.transaction(async I=>{for(let A of L(u.ledgerDeletes)){let G=S.length>0?ze(Ne(g.resolvedUserId),Ue(g.resolvedUserId,S)):void 0;await I.delete(g).where(K(x(g.groupId,e.groupId),ie(g.email,A),...G?[G]:[]))}}),{refusals:s}},async recordSuccess(e,u,b){await d(),await a.transaction(async s=>{await s.update(p).set({syncedAt:b,syncError:null,vanishedObservations:0,externalId:u.externalId,externalKey:be(u.externalKey),updatedAt:b,updatedBy:y}).output().where(x(p.id,e.linkId)),e.isGroupActive||await s.update(w).set({isActive:!0,updatedAt:b,updatedBy:y}).output().where(K(x(w.id,e.groupId),x(w.tenantId,e.tenantId)))})},async recordFailure(e,u){await d(),await a.update(p).set({syncError:u.slice(0,1e3),updatedAt:new Date,updatedBy:y}).output().where(x(p.id,e.linkId))},async recordVanishedObservation(e,u){await d();let b=e.vanishedObservations+1;return await a.update(p).set({vanishedObservations:b,syncError:u.slice(0,1e3),updatedAt:new Date,updatedBy:y}).output().where(x(p.id,e.linkId)),b},async markGroupInactive(e){await d(),await a.update(w).set({isActive:!1,updatedAt:new Date,updatedBy:y}).output().where(K(x(w.id,e.groupId),x(w.tenantId,e.tenantId)))}}}export{Me as AUTH_DIRECTORY_PROVIDER_COLUMN,wr as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,dr as MAX_IN_LIST,le as MAX_PARAMETERS,ae as MEMBERSHIP_MODES,ye as UNHEALTHY_SYNC_OUTCOMES,Fe as assertGroupMayBeLinked,q as assertMembershipMode,L as chunk,O as chunkByParameters,tr as createDirectoryLinkStore,xr as createDirectorySync,Nr as createDirectorySyncRepository,yr as createPromotionRepository,Er as createSyncRequestQueue,Ce as defaultExternalGroupGuardMessage,me as defaultSyncMessages,fr as defineAuthDirectorySchema,F as findLedgerRowsForIdentity,sr as promoteLedgerForUser,je as withExternalGroupGuards};
1
+ import{assertTenantId as Ae}from"@aiquants/authz-core";import{and as Q,asc as Le,eq as N}from"drizzle-orm";function O(e){return e instanceof Date?e:new Date(/(?:Z|[+-]\d{2}:?\d{2})$/.test(e)?e:`${e.replace(" ","T")}Z`)}var X=2100,Ie=100,Se=500;function K(e,n=500){if(!Number.isInteger(n)||n<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] chunk size must be a positive integer, received ${String(n)}`);let y=[];for(let u=0;u<e.length;u+=n)y.push(e.slice(u,u+n));return y}function U(e,n,y=100){if(!Number.isInteger(n)||n<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] parametersPerItem must be a positive integer, received ${String(n)}`);if(!Number.isInteger(y)||y<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] reservedParameters must be a non-negative integer, received ${String(y)}`);let u=2100-y,d=Math.floor(u/n);if(d<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] a single item binds ${n} parameters, which exceeds the ${u} available in one statement`);return K(e,d)}var $e=10,$={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},ve=2147483647;function Ke(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>ve?ve:n}function W(e,n,y){let u=e.trim();if(u==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${y} must not be blank`);if(u.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${y} exceeds its column length (${n}); truncating it would change which rows it owns`);return u}function pr(e,n,y){let u=e,d=y.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,g=n.TMDirectoryCatalogObservation,h=w=>{let a=new Map;for(let p of w)a.set(p.externalId,p);return[...a.values()]};return{async replace(w,a,p){await d();let r=W(Ae(w,"replace(tenantId)"),$.tenantId,"tenantId"),t=W(a,$.provider,"provider"),i=W(y.actor,$.actor,"actor"),l=y.now(),f=h(p.map(b=>({tenantId:r,provider:t,externalId:b.externalId.slice(0,$.externalId),externalKey:b.externalKey.slice(0,$.externalKey),name:b.name.slice(0,$.name),description:b.description===void 0?null:b.description.slice(0,$.description),memberCount:Ke(b.memberCount),observedAt:l,createdAt:l,createdBy:i})));return await u.transaction(async b=>{await b.delete(s).where(Q(N(s.tenantId,r),N(s.provider,t)));for(let k of U(f,$e))await b.insert(s).values(k);await b.delete(g).where(Q(N(g.tenantId,r),N(g.provider,t))),await b.insert(g).values([{tenantId:r,provider:t,observedAt:l,entryCount:f.length,createdAt:l,createdBy:i,updatedAt:l,updatedBy:i}])}),f.length}}}function mr(e,n,y={}){let u=e,d=y.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,g=n.TMDirectoryCatalogObservation;return{async read(h,w){await d();let a=W(Ae(h,"read(tenantId)"),$.tenantId,"tenantId"),p=W(w,$.provider,"provider"),r=await u.select({observedAt:g.observedAt,entryCount:g.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(g).leftJoin(s,Q(N(s.tenantId,g.tenantId),N(s.provider,g.provider))).where(Q(N(g.tenantId,a),N(g.provider,p))).orderBy(Le(s.name)),t=r.filter(f=>f.externalId!==null).map(f=>({externalId:String(f.externalId),externalKey:String(f.externalKey),name:String(f.name),...f.description===null?{}:{description:f.description},...f.memberCount===null?{}:{memberCount:Number(f.memberCount)}})),i=r[0];if(i===void 0)return{groups:t,observedAt:null};if(Number(i.entryCount)!==t.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${a}, ${p}): the observation records ${i.entryCount} entries but ${t.length} rows are present`);let l=O(i.observedAt);if(!Number.isFinite(l.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${a}, ${p}) has an unreadable observed_at`);return{groups:t,observedAt:l}}}}import{AUTHZ_ACTIONS as Ne}from"@aiquants/authz-core";var hr=(e,n)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${n}) \u306B\u306F\u7BA1\u7406\u30ED\u30FC\u30EB (${e}) \u3092\u6301\u305F\u305B\u3089\u308C\u307E\u305B\u3093\u3002\u4E0A\u6D41\u5074\u306E\u6240\u5C5E\u5909\u66F4\u304C\u305D\u306E\u307E\u307E\u7BA1\u7406\u8005\u306E\u5897\u6E1B\u306B\u306A\u308B\u305F\u3081\u3001\u7BA1\u7406\u8005\u7D4C\u8DEF\u306F\u30ED\u30FC\u30AB\u30EB\u7BA1\u7406\u306E\u30B0\u30EB\u30FC\u30D7\u306B\u9650\u5B9A\u3057\u3066\u3044\u307E\u3059\u3002`;function F(e,n,y,u){return new Set(e.filter(d=>n.has(d.roleId)&&d.appKey===y&&d.resourceKey===u).map(d=>d.action))}function Z(e,n){return new Set(e.filter(y=>y.groupId===n).map(y=>y.roleId))}var H=e=>Ne.every(n=>e.has(n)),re=(e,n)=>e.find(y=>y.roleId===n)?.roleKey??`#${n}`;function ee(e,n,y,u){let d=[...n].find(g=>H(F(e,new Set([g]),y,u)));return d!==void 0?re(e,d):[...n].filter(g=>F(e,new Set([g]),y,u).size>0).map(g=>re(e,g)).join(" + ")}function br(e,n){return Te(e,n)}function Te(e,n){let y=e;return{...e,withTransaction:(u,d)=>y.withTransaction((s,g)=>u(Te(s,n),g),d),async assignGroupRole(u,d,s,g){await y.withTransaction(async(h,w)=>{let a=await h.listGrants(u),p=Z(await h.listGroupRoles(u),d);if(p.add(s),H(F(a,p,n.adminAppKey,n.adminResourceKey))){let r=await n.linkLookup.externalGroupKey(u,d,w);if(r!==null)throw new Error(n.message(ee(a,p,n.adminAppKey,n.adminResourceKey),r))}await h.assignGroupRole(u,d,s,g)})},async updateGrant(u,d,s,g){let h=s.action;if(h===void 0)return y.updateGrant(u,d,s,g);await y.withTransaction(async(w,a)=>{let p=await w.listGrants(u),r=p.find(l=>l.id===d);if(r===void 0)return w.updateGrant(u,d,s,g);let i=(await w.listResources(u)).find(l=>l.id===r.resourceId);if(i?.appKey===n.adminAppKey&&i?.resourceKey===n.adminResourceKey){let l=await w.listGroupRoles(u);for(let f of new Set(l.filter(b=>b.roleId===r.roleId).map(b=>b.groupId))){let b=Z(l,f),k=F(p.filter(x=>x.id!==d),b,n.adminAppKey,n.adminResourceKey);if(b.has(r.roleId)&&k.add(h),!H(k))continue;let v=await n.linkLookup.externalGroupKey(u,f,a);if(v!==null)throw new Error(n.message(ee(p,b,n.adminAppKey,n.adminResourceKey)||re(p,r.roleId),v))}}await w.updateGrant(u,d,s,g)})},async createGrant(u,d,s){await y.withTransaction(async(g,h)=>{let a=(await g.listResources(u)).find(p=>p.id===d.resourceId);if(a?.appKey===n.adminAppKey&&a?.resourceKey===n.adminResourceKey){let p=await g.listGrants(u),r=await g.listGroupRoles(u);for(let t of new Set(r.filter(i=>i.roleId===d.roleId).map(i=>i.groupId))){let i=F(p,Z(r,t),n.adminAppKey,n.adminResourceKey);if(i.add(d.action),!H(i))continue;let l=await n.linkLookup.externalGroupKey(u,t,h);if(l!==null)throw new Error(n.message(ee(p,Z(r,t),n.adminAppKey,n.adminResourceKey)||re(p,d.roleId),l))}}await g.createGrant(u,d,s)})}}}async function fr(e,n,y,u){let d=(await e.listGroupRoles(n)).filter(w=>w.groupId===y);if(d.length===0)return;let s=await e.listGrants(n),g=new Set(d.map(w=>w.roleId));if(!H(F(s,g,u.adminAppKey,u.adminResourceKey)))return;let h=d[0];if(h)throw new Error(u.message(ee(s,g,u.adminAppKey,u.adminResourceKey),h.groupKey))}import{and as qe,eq as J}from"drizzle-orm";async function te(e,n,y){let{ledger:u,group:d}=n,{tenantId:s,externalUserId:g,email:h}=y;if(g===null&&h==="")return[];let w={ledgerId:u.id,groupId:u.groupId,tenantId:d.tenantId,externalUserId:u.externalUserId},a=async l=>(await e.select(w).from(u).innerJoin(d,qe(J(d.id,u.groupId),J(d.tenantId,s),J(d.isActive,!0))).where(l)).map(b=>({ledgerId:Number(b.ledgerId),groupId:Number(b.groupId),tenantId:String(b.tenantId),externalUserId:b.externalUserId===null?null:String(b.externalUserId)})),p=l=>{let f=new Map;for(let b of l)f.set(b.ledgerId,{ledgerId:b.ledgerId,groupId:b.groupId,tenantId:b.tenantId});return[...f.values()].sort((b,k)=>b.ledgerId-k.ledgerId)},r=g===null?[]:await a(J(u.externalUserId,g));if(h==="")return p(r);let t=await a(J(u.email,h)),i=l=>l.externalUserId!==null&&l.externalUserId!==g;return p([...r,...t.filter(l=>!i(l))])}import{normalizeAuthEmail as le}from"@aiquants/auth-core";import{assertTenantMembershipPolicy as Oe}from"@aiquants/auth-directory-core";import{assertTenantId as C,assertTenantMatch as Ue}from"@aiquants/authz-core";import{and as R,asc as xe,count as ce,eq as S,isNull as ne}from"drizzle-orm";var De=["direct","transitive"];function V(e){if(!De.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${De.join(" | ")}, received ${JSON.stringify(e)}`);return e}function ze(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function Mr(e,n,y,u){let d=e,s=u.ensureConnection??(async()=>{}),g=u.policyCacheTtlMs??0;if(!Number.isFinite(g)||g<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(u.policyCacheTtlMs)}`);let h=new Map,w=o=>h.delete(o),{TDGroupExternalLink:a,TDGroupMemberEmail:p,TDTenantMembershipGroup:r,TDAllowlistGroup:t,TDDirectorySyncRequest:i}=n,{TMGroup:l,TDGroupMember:f}=y;async function b(o,c,m){if((await o.select({groupId:r.groupId}).from(r).where(R(S(r.tenantId,c),S(r.groupId,m),S(r.isBreakGlass,!0)))).length>0)throw new Error(`Group ${m} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function k(o,c,m){let[I]=await o.select({id:l.id}).from(l).where(R(S(l.id,m),S(l.tenantId,c)));if(!I)throw new Error(`Group not found in tenant: ${m}`)}function v(o,c){return{linkId:Number(o.linkId),groupId:Number(o.groupId),tenantId:String(o.tenantId),provider:String(o.provider),externalId:String(o.externalId),externalKey:String(o.externalKey),membershipMode:V(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:O(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:c}}async function x(o,c){let m=await o.select({groupId:p.groupId,n:ce()}).from(p).innerJoin(l,R(S(l.id,p.groupId),S(l.tenantId,c))).where(ne(p.resolvedUserId)).groupBy(p.groupId);return new Map(m.map(I=>[Number(I.groupId),Number(I.n)]))}return{async listLinks(o){C(o,"listLinks(tenantId)"),await s();let c=await d.select({linkId:a.id,groupId:a.groupId,tenantId:a.tenantId,provider:a.provider,externalId:a.externalId,externalKey:a.externalKey,membershipMode:a.membershipMode,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where(S(a.tenantId,o)).orderBy(xe(a.groupId)),m=await x(d,o);return c.map(I=>(Ue(o,String(I.tenantId),"listLinks(row.tenantId)"),v(I,m.get(Number(I.groupId))??0)))},async externalGroupKey(o,c,m){C(o,"externalGroupKey(tenantId)"),await s();let[I]=await(m??d).select({externalKey:a.externalKey}).from(a).where(R(S(a.groupId,c),S(a.tenantId,o)));return I?String(I.externalKey):null},async linkGroup(o,c,m,I){C(o,"linkGroup(tenantId)");let A=V(m.membershipMode),D=m.externalId.trim(),G=le(m.externalKey);if(D===""||G==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await u.linkable.assertLinkableAndWrite(o,c,async Y=>{let M=Y;await k(M,o,c),await b(M,o,c),await M.insert(a).output().values({groupId:c,tenantId:o,provider:m.provider,externalId:D,externalKey:G,membershipMode:A,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:I})}),w(o)},async unlinkGroup(o,c){C(o,"unlinkGroup(tenantId)"),await s(),await d.transaction(async m=>{await k(m,o,c),await m.delete(p).where(S(p.groupId,c)),await m.delete(t).where(R(S(t.groupId,c),S(t.tenantId,o))),await m.delete(a).where(R(S(a.groupId,c),S(a.tenantId,o)))}),w(o)},async setSyncEnabled(o,c,m,I){C(o,"setSyncEnabled(tenantId)"),await s(),await d.update(a).set({isSyncEnabled:m,updatedAt:new Date,updatedBy:I}).output().where(R(S(a.groupId,c),S(a.tenantId,o)))},async requestSync(o,c,m){return C(o,"requestSync(tenantId)"),await s(),d.transaction(async I=>{if(c!==null&&await k(I,o,c),(await I.select({id:i.id}).from(i).where(R(S(i.tenantId,o),c===null?ne(i.groupId):S(i.groupId,c),ne(i.pickedAt)))).length>0)return"already-pending";try{await I.insert(i).output().values({tenantId:o,groupId:c,requestedBy:m,requestedAt:new Date,createdAt:new Date,createdBy:m})}catch(D){if(!ze(D))throw D;return"already-pending"}return"queued"})},async getStatus(o){C(o,"getStatus(tenantId)"),await s();let c=await d.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where(S(a.tenantId,o)),[m]=await d.select({n:ce()}).from(i).where(R(S(i.tenantId,o),ne(i.finishedAt))),I=c.filter(M=>!!M.isSyncEnabled),A=I.map(M=>M.syncedAt?O(M.syncedAt).getTime():null),D=A.length>0&&A.every(M=>M!==null)?Math.min(...A):null,Y=c.length>0&&I.length===0||I.length>0&&(D===null||Date.now()-D>u.stalenessThresholdSec*1e3);return{isEnabled:u.isEnabled,provider:u.provider,linkedGroupCount:c.length,...D!==null?{oldestSyncedAt:new Date(D).toISOString()}:{},isStale:Y,pausedGroupCount:c.length-I.length,stalenessThresholdSec:u.stalenessThresholdSec,erroredGroupIds:c.filter(M=>!!M.syncError).map(M=>Number(M.groupId)).sort((M,Pe)=>M-Pe),pendingRequestCount:Number(m?.n??0)}},async loadTenantMembershipPolicy(o){C(o,"loadTenantMembershipPolicy(tenantId)");let c=g>0?h.get(o):void 0;if(c&&Date.now()-c.loadedAt<g)return c.policy;await s();let m=await d.select({groupId:r.groupId,isBreakGlass:r.isBreakGlass,externalLinkId:a.id}).from(r).innerJoin(l,R(S(l.id,r.groupId),S(l.tenantId,o))).leftJoin(a,R(S(a.groupId,r.groupId),S(a.tenantId,o))).where(S(r.tenantId,o));if(m.length===0){let G={kind:"no-proof-required"};return g>0&&h.set(o,{policy:G,loadedAt:Date.now()}),G}let I=m.map(G=>Number(G.groupId)),A=m.filter(G=>!!G.isBreakGlass);for(let G of A)if(G.externalLinkId!==null)throw new Error(`Break-glass group ${Number(G.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let D={kind:"require-proof",proofGroupIds:I,breakGlassGroupIds:A.map(G=>Number(G.groupId))};return Oe(D,`tenant ${o}`),g>0&&h.set(o,{policy:D,loadedAt:Date.now()}),D},async setTenantMembershipGroup(o,c,m,I){C(o,"setTenantMembershipGroup(tenantId)"),await s(),await d.transaction(async A=>{if(await k(A,o,c),m){let[G]=await A.select({id:a.id}).from(a).where(R(S(a.tenantId,o),S(a.groupId,c)));if(G)throw new Error(`Group ${c} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let D=await A.select({groupId:r.groupId}).from(r).where(R(S(r.tenantId,o),S(r.groupId,c)));if(!m&&!(await A.select({groupId:r.groupId,isBreakGlass:r.isBreakGlass}).from(r).where(S(r.tenantId,o))).filter(M=>Number(M.groupId)!==c).some(M=>!!M.isBreakGlass))throw new Error(`Declaring group ${c} as ordinary proof would leave tenant ${o} with a membership proof but no break-glass path; declare a locally-managed break-glass group first.`);if(D.length>0){await A.update(r).set({isBreakGlass:m,updatedAt:new Date,updatedBy:I}).output().where(R(S(r.tenantId,o),S(r.groupId,c)));return}await A.insert(r).output().values({tenantId:o,groupId:c,isBreakGlass:m,createdAt:new Date,createdBy:I})}),w(o)},async removeTenantMembershipGroup(o,c){C(o,"removeTenantMembershipGroup(tenantId)"),await s(),await d.transaction(async m=>{let A=(await m.select({groupId:r.groupId,isBreakGlass:r.isBreakGlass}).from(r).where(S(r.tenantId,o))).filter(D=>Number(D.groupId)!==c);if(A.length>0&&!A.some(D=>!!D.isBreakGlass))throw new Error(`Removing group ${c} would leave tenant ${o} with a membership proof but no break-glass path.`);await m.delete(r).where(R(S(r.tenantId,o),S(r.groupId,c)))}),w(o)},async setGroupActive(o,c,m,I){C(o,"setGroupActive(tenantId)"),await s(),await d.update(l).set({isActive:m,updatedAt:new Date,updatedBy:I}).output().where(R(S(l.id,c),S(l.tenantId,o)))},async listLedgerGroupIds(o,c){if(C(o,"listLedgerGroupIds(tenantId)"),!u.isEnabled)return[];await s();let m=await te(d,{ledger:p,group:l},{tenantId:o,externalUserId:c.externalUserId?.trim()||null,email:le(c.email)});return[...new Set(m.map(I=>I.groupId))].sort((I,A)=>I-A)},async isEmailAllowedByGroup(o){if(!u.isEnabled)return!1;let c=le(o);if(c==="")return!1;await s();let[m]=await d.select({id:p.id}).from(p).innerJoin(t,S(t.groupId,p.groupId)).innerJoin(l,R(S(l.id,p.groupId),S(l.isActive,!0))).where(S(p.email,c));return!!m},async listAllowlistGroups(o){return C(o,"listAllowlistGroups(tenantId)"),await s(),(await d.select({id:t.id,groupId:t.groupId,groupKey:l.groupKey,groupName:l.name,description:t.description}).from(t).innerJoin(l,R(S(l.id,t.groupId),S(l.tenantId,o))).where(S(t.tenantId,o)).orderBy(xe(l.name))).map(m=>({id:Number(m.id),type:"group",groupId:Number(m.groupId),groupKey:String(m.groupKey),groupName:String(m.groupName),...m.description?{description:String(m.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,c,m,I){C(o,"addAllowlistGroup(tenantId)"),await s(),await d.transaction(async A=>{await k(A,o,c);let[D]=await A.select({id:a.id}).from(a).where(R(S(a.groupId,c),S(a.tenantId,o)));if(!D)throw new Error(`Group ${c} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await A.insert(t).output().values({tenantId:o,groupId:c,description:m??null,createdAt:new Date,createdBy:I})})},async removeAllowlistGroup(o,c){C(o,"removeAllowlistGroup(tenantId)"),await s(),await d.delete(t).where(R(S(t.id,c),S(t.tenantId,o)))},async countProjectedMembers(o,c){C(o,"countProjectedMembers(tenantId)"),await s();let[m]=await d.select({n:ce()}).from(f).innerJoin(l,R(S(l.id,f.groupId),S(l.tenantId,o))).where(S(f.groupId,c));return Number(m?.n??0)}}}import{normalizeAuthEmail as Be}from"@aiquants/auth-core";import{assertTenantId as Fe,assertTenantMatch as je}from"@aiquants/authz-core";async function Pr(e,n){let y=Fe(n.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(n.userId)||n.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(n.userId)}`);let u=Be(n.email),d=n.externalUserId?.trim()||null;if(d===null&&u==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:y,externalUserId:d,email:u});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let a of s)je(y,a.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let g=[...new Set(s.map(a=>a.ledgerId))].sort((a,p)=>a-p),h=[...new Set(s.map(a=>a.groupId))].sort((a,p)=>a-p);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:y,userId:n.userId,ledgerIds:g,groupIds:h})].sort((a,p)=>a-p),matchedLedgerIds:g}}import{assertTenantId as ke}from"@aiquants/authz-core";import{and as pe,eq as me,inArray as oe}from"drizzle-orm";var We=4,He=100;function Or(e,n,y,u){let d=e,s=u.ensureConnection??(async()=>{}),g=u.now??(()=>new Date),h=n.TDGroupMemberEmail,{TMGroup:w,TDGroupMember:a}=y;return{async findLedgerMatches(p){return ke(p.tenantId,"findLedgerMatches(tenantId)"),await s(),te(d,{ledger:h,group:w},p)},async attachUserToGroups({tenantId:p,userId:r,ledgerIds:t,groupIds:i}){return ke(p,"attachUserToGroups(tenantId)"),await s(),t.length===0?[]:d.transaction(async l=>{let f=new Set;for(let o of K(i)){let c=await l.select({id:w.id}).from(w).where(pe(oe(w.id,o),me(w.tenantId,p),me(w.isActive,!0)));for(let m of c)f.add(Number(m.id))}let b=i.filter(o=>f.has(o));if(b.length===0)return[];let k=new Set;for(let o of K(b)){let c=await l.select({groupId:a.groupId}).from(a).where(pe(me(a.userId,r),oe(a.groupId,o)));for(let m of c)k.add(Number(m.groupId))}let v=b.filter(o=>!k.has(o)),x=Math.max(1,2100-He-b.length);for(let o of K(t,x))await l.update(h).set({resolvedUserId:r}).output().where(pe(oe(h.id,o),oe(h.groupId,b)));if(v.length>0){let o=g();for(let c of U(v,We))await l.insert(a).output().values(c.map(m=>({groupId:m,userId:r,createdAt:o,createdBy:u.actor})))}return v})}}}import{AUTHZ_TENANT_COLUMN as z}from"@aiquants/authz-drizzle";import{bigint as _,bit as Ee,datetime2 as L,foreignKey as ie,int as ye,mssqlSchema as Je,nvarchar as T,primaryKey as q,uniqueIndex as B}from"drizzle-orm/mssql-core";var ge="provider",Fr=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function jr(e,n){let y=Je(e),u=()=>({createdAt:L("created_at"),createdBy:T("created_by",{length:50}),updatedAt:L("updated_at"),updatedBy:T("updated_by",{length:50})}),d=()=>({createdAt:L("created_at"),createdBy:T("created_by",{length:50})}),s=y.table("TDGroupExternalLink",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),tenantId:T(z,{length:64}).notNull(),provider:T(ge,{length:32}).notNull(),externalId:T("external_id",{length:256}).notNull(),externalKey:T("external_key",{length:320}).notNull(),membershipMode:T("membership_mode",{length:16}).notNull(),isSyncEnabled:Ee("is_sync_enabled").notNull(),syncedAt:L("synced_at"),syncError:T("sync_error",{length:1e3}),vanishedObservations:ye("vanished_observations").notNull(),...u()},i=>[q({name:`${e}_TDGroupExternalLink_pk`,columns:[i.id]}),B(`${e}_TDGroupExternalLink_group_uq`).on(i.groupId),B(`${e}_TDGroupExternalLink_external_uq`).on(i.tenantId,i.provider,i.externalId),ie({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[i.groupId],foreignColumns:[n.groupTable.id]})]),g=y.table("TDGroupMemberEmail",{id:_("id",{mode:"number"}).identity().notNull(),groupId:_("group_id",{mode:"number"}).notNull(),email:T("email",{length:320}).notNull(),externalUserId:T("external_user_id",{length:128}),resolvedUserId:_("resolved_user_id",{mode:"number"}),observedAt:L("observed_at").notNull(),...u()},i=>[q({name:`${e}_TDGroupMemberEmail_pk`,columns:[i.id]}),B(`${e}_TDGroupMemberEmail_group_email_uq`).on(i.groupId,i.email),ie({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[i.groupId],foreignColumns:[n.groupTable.id]})]),h=y.table("TDTenantMembershipGroup",{tenantId:T(z,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),isBreakGlass:Ee("is_break_glass").notNull(),...d()},i=>[q({name:`${e}_TDTenantMembershipGroup_pk`,columns:[i.tenantId,i.groupId]}),ie({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[i.groupId],foreignColumns:[n.groupTable.id]})]),w=y.table("TDAllowlistGroup",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:T(z,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}).notNull(),description:T("description",{length:512}),...d()},i=>[q({name:`${e}_TDAllowlistGroup_pk`,columns:[i.id]}),B(`${e}_TDAllowlistGroup_tenant_group_uq`).on(i.tenantId,i.groupId),ie({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[i.groupId],foreignColumns:[n.groupTable.id]})]),a=y.table("TDDirectorySyncRequest",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:T(z,{length:64}).notNull(),groupId:_("group_id",{mode:"number"}),requestedBy:T("requested_by",{length:320}).notNull(),requestedAt:L("requested_at").notNull(),pickedAt:L("picked_at"),finishedAt:L("finished_at"),result:T("result",{length:32}),error:T("error",{length:2e3}),...d()},i=>[q({name:`${e}_TDDirectorySyncRequest_pk`,columns:[i.id]})]),p=y.table("TMAllowlist",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:T(z,{length:64}).notNull(),pattern:T("pattern",{length:256}).notNull(),type:T("type",{length:32}).notNull(),description:T("description",{length:512}),...d()},i=>[q({name:`${e}_TMAllowlist_pk`,columns:[i.id]}),B(`${e}_TMAllowlist_pattern_uq`).on(i.tenantId,i.pattern)]),r=y.table("TDDirectoryCatalog",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:T(z,{length:64}).notNull(),provider:T(ge,{length:32}).notNull(),externalId:T("external_id",{length:256}).notNull(),externalKey:T("external_key",{length:320}).notNull(),name:T("name",{length:256}).notNull(),description:T("description",{length:1e3}),memberCount:ye("member_count"),observedAt:L("observed_at").notNull(),...d()},i=>[q({name:`${e}_TDDirectoryCatalog_pk`,columns:[i.id]}),B(`${e}_TDDirectoryCatalog_external_uq`).on(i.tenantId,i.provider,i.externalId)]),t=y.table("TMDirectoryCatalogObservation",{id:_("id",{mode:"number"}).identity().notNull(),tenantId:T(z,{length:64}).notNull(),provider:T(ge,{length:32}).notNull(),observedAt:L("observed_at").notNull(),entryCount:ye("entry_count").notNull(),...u()},i=>[q({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[i.id]}),B(`${e}_TMDirectoryCatalogObservation_target_uq`).on(i.tenantId,i.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:g,TDTenantMembershipGroup:h,TDAllowlistGroup:w,TDDirectorySyncRequest:a,TDDirectoryCatalog:r,TMDirectoryCatalogObservation:t,TMAllowlist:p}}import{assertBlastRadiusPolicy as Ve,reconcile as Ye}from"@aiquants/auth-directory-core";function ae(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let n=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof n!="number"?"unknown":n===401||n===403?"denied":n===429||n===408?"rateLimited":n>=500?"upstreamError":"unknown"}var Re=e=>`${e.length} \u4EF6`,he={denied:"\u8CC7\u683C\u60C5\u5831\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",rateLimited:"\u547C\u3073\u51FA\u3057\u304C\u5236\u9650\u3055\u308C\u3066\u3044\u307E\u3059",upstreamError:"\u76F8\u624B\u5074\u304C\u5185\u90E8\u30A8\u30E9\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F",timeout:"\u5FDC\u7B54\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F",localWriteError:"\u66F8\u304D\u8FBC\u307F\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",unknown:"\u8A73\u7D30\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059"},Me={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${he[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${he[e]})\u3002`,resolverIncomplete:(e,n)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${n} \u4EF6\u3067\u3057\u305F\u3002\u4EF6\u6570\u304C\u6E1B\u308B\u7A81\u5408\u306F\u3001\u89E3\u6C7A\u3067\u304D\u306A\u304B\u3063\u305F\u4EBA\u7269\u3092\u300C\u4E0A\u6D41\u304B\u3089\u6D88\u3048\u305F\u300D\u3068\u3057\u3066\u524A\u9664\u3057\u3066\u3057\u307E\u3046\u305F\u3081\u4E2D\u65AD\u3057\u307E\u3057\u305F\u3002\u89E3\u6C7A\u3067\u304D\u306A\u3044\u30E1\u30F3\u30D0\u30FC\u306F\u7701\u7565\u305B\u305A resolvedUserId: null \u3067\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`,groupMissing:(e,n,y)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${n} \u56DE\u76EE\u3067\u3059\u3002${y} \u56DE\u306B\u9054\u3057\u305F\u6642\u70B9\u3067\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3059\u3002`,groupVanished:(e,n)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${n} \u56DE\u9023\u7D9A\u3067\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F (${e})\u3002\u30B0\u30EB\u30FC\u30D7\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\u3002\u4E0A\u6D41\u304C\u5FA9\u65E7\u3059\u308C\u3070\u6B21\u56DE\u306E\u540C\u671F\u3067\u81EA\u52D5\u7684\u306B\u518D\u6709\u52B9\u5316\u3055\u308C\u307E\u3059\u3002`,planRefused:e=>{if(e.reason==="invalid-upstream-data")return`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: \u4E0A\u6D41\u306E\u30E1\u30F3\u30D0\u30FC\u4E00\u89A7\u306B\u6271\u3048\u306A\u3044\u30A2\u30C9\u30EC\u30B9\u304C ${Re(e.offendingEmails)} \u542B\u307E\u308C\u3066\u3044\u307E\u3059\u3002\u5BFE\u8C61\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059\u3002`;let n=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",y=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${n}\u304C ${e.population} \u4EBA\u4E2D ${e.removalCount} \u4EBA\u306B\u306A\u308A\u307E\u3059`,`(\u4E0A\u9650: ${e.limits.maxRemovalCount} \u4EF6 / \u6BD4\u7387 ${e.limits.maxRemovalRatio})\u3002`,`\u5DEE\u3057\u6B62\u3081\u305F\u5BFE\u8C61: ${Re(y)} (\u4E00\u89A7\u306F\u30B5\u30FC\u30D0\u30FC\u306E\u30ED\u30B0\u306B\u51FA\u3066\u3044\u307E\u3059)\u3002`,"\u4E0A\u6D41\u5074\u306E\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u610F\u56F3\u3057\u305F\u5909\u66F4\u3067\u3042\u308C\u3070\u3001\u3053\u306E\u8EF8\u306E\u4E0A\u9650\u3060\u3051\u3092\u4E00\u6642\u7684\u306B\u5F15\u304D\u4E0A\u3052\u3066\u518D\u5B9F\u884C\u3057\u307E\u3059\u3002"].join(" ")},removalWritesFailed:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u66F8\u304D\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F (${[...new Set(e.map(n=>he[n.cause]))].join(" / ")})\u3002\u6B21\u56DE\u306E\u5B9F\u884C\u3067\u518D\u8A66\u884C\u3057\u307E\u3059\u3002`,removalsRefused:e=>`${e.length} \u4EF6\u306E\u6240\u5C5E\u89E3\u9664\u304C\u6700\u5F8C\u306E\u7BA1\u7406\u8005\u30AC\u30FC\u30C9\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F (\u30E6\u30FC\u30B6\u30FC ${e.map(n=>n.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,n)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${n} \u7528\u3067\u3059\u3002\u5BFE\u5FDC\u3059\u308B\u540C\u671F\u30B8\u30E7\u30D6\u304B\u3089\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`};var be=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],Ge=[...be,"already-vanished"];function Qr(e){let{directory:n,repository:y,policy:u,now:d,logger:s}=e,g=e.messages??Me;if(Ve(u),!Number.isInteger(e.vanishedThreshold)||e.vanishedThreshold<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] vanishedThreshold must be a positive integer, received ${String(e.vanishedThreshold)}`);if(typeof e.provider!="string"||e.provider.trim()==="")throw new TypeError("[@aiquants/auth-directory-drizzle] provider must be a non-empty string so links of other providers can be told apart");async function h(r,t){try{await y.recordFailure(r,t)}catch(i){s.error(`[auth-directory] could not record the failure for ${r.externalKey}:`,i)}}async function w(r){try{let t=await n.getGroup(r.externalId);return t===null?{kind:"missing"}:{kind:"ok",group:t,members:await n.listGroupMembers(t,r.membershipMode)}}catch(t){return{kind:"failed",error:t instanceof Error?t:new Error(String(t))}}}async function a(r){if(r.vanishedObservations>=e.vanishedThreshold&&!r.isGroupActive)return s.warn(`[auth-directory] ${r.externalKey} still missing upstream (already deactivated after ${r.vanishedObservations} absences)`),{kind:"already-vanished",link:r};let t=await y.recordVanishedObservation(r,g.groupMissing(r.externalKey,r.vanishedObservations+1,e.vanishedThreshold));return t<e.vanishedThreshold?(s.warn(`[auth-directory] ${r.externalKey} missing upstream (${t}/${e.vanishedThreshold})`),{kind:"missing",link:r,observations:t}):(await y.markGroupInactive(r),await h(r,g.groupVanished(r.externalKey,t)),s.warn(`[auth-directory] ${r.externalKey} deactivated after ${t} consecutive absences`),{kind:"vanished",link:r})}async function p(r){if(r.provider!==e.provider)return await h(r,g.foreignProvider(r.provider,e.provider)),s.warn(`[auth-directory] skipping ${r.externalKey}: belongs to provider ${r.provider}`),{kind:"foreign-provider",link:r};let t=await w(r);if(t.kind==="missing")return a(r);if(t.kind==="failed")return await h(r,g.upstreamUnreadable(ae(t.error))),s.error(`[auth-directory] upstream read failed for ${r.externalKey}:`,t.error),{kind:"upstream-failed",link:r,error:t.error};try{await y.resolveExternalIdentity(r,t.group);let[i,l]=await Promise.all([y.loadGroupSnapshot(r),y.resolveMembers(t.members)]);if(l.length!==t.members.length){let x=g.resolverIncomplete(t.members.length,l.length);return await h(r,x),s.error(`[auth-directory] ${x}`),{kind:"local-failed",link:r,error:new Error(x)}}let f=Ye(i,l,u);if(f.aborted){await h(r,g.planRefused(f.aborted));let x=f.aborted.reason==="invalid-upstream-data"?f.aborted.offendingEmails:f.aborted.axis==="membership"?f.aborted.withheldProjectionRemoves.map(String):f.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${r.externalKey}: ${f.aborted.reason}; withheld: ${x.join(", ")}`),{kind:"aborted",link:r,plan:f}}let b=f.ledgerUpserts.length>0||f.ledgerDeletes.length>0||f.projectionAdds.length>0||f.projectionRemoves.length>0,k=d(),v={refusals:[],failures:[]};return b&&(v=await y.applyPlan(r,f,k)),await y.recordSuccess(r,t.group,k),v.refusals.length>0&&(await h(r,g.removalsRefused(v.refusals)),s.warn(`[auth-directory] ${v.refusals.length} removal(s) refused for ${r.externalKey}: users ${v.refusals.map(x=>x.userId).join(", ")}`)),v.failures.length>0&&(await h(r,g.removalWritesFailed(v.failures)),s.warn(`[auth-directory] ${v.failures.length} removal(s) could not be written for ${r.externalKey}: users ${v.failures.map(x=>x.userId).join(", ")}`)),f.unresolvedEmails.length>0&&s.info(`[auth-directory] ${r.externalKey}: ${f.unresolvedEmails.length} upstream member(s) have no local user yet`),v.refusals.length>0||v.failures.length>0?{kind:"not-converged",link:r,plan:f,refusals:v.refusals,failures:v.failures}:b?{kind:"applied",link:r,plan:f}:{kind:"unchanged",link:r}}catch(i){let l=i instanceof Error?i:new Error(String(i));return await h(r,g.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${r.externalKey}:`,l),{kind:"local-failed",link:r,error:l}}}return{syncOne:p,async run(r){let t=await y.listSyncableLinks(r),i=[];for(let b of t)try{i.push(await p(b))}catch(k){let v=k instanceof Error?k:new Error(String(k));s.error(`[auth-directory] ${b.externalKey} ended abnormally:`,v),i.push({kind:"local-failed",link:b,error:v})}let l=i.filter(b=>Ge.includes(b.kind)).length,f=i.filter(b=>be.includes(b.kind)).length;return s.info(`[auth-directory] run finished: ${i.length} link(s), ${l} unhealthy (${f} new)`),{outcomes:i,unhealthyCount:l,newlyUnhealthyCount:f}}}}import{assertTenantId as Xe}from"@aiquants/authz-core";import{and as se,asc as Ce,eq as j,isNull as ue}from"drizzle-orm";var Qe="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",fe=2e3;function nt(e,n,y){let u=e,d=y.ensureConnection??(async()=>{}),s=n.TDDirectorySyncRequest,g=Xe(y.tenantId,"createSyncRequestQueue(tenantId)"),h=y.maxErrorLength??fe;if(!Number.isInteger(h)||h<1||h>fe)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${fe}, received ${String(y.maxErrorLength)}`);let w={async claim(){await d();let a=await u.select({id:s.id}).from(s).where(se(j(s.tenantId,g),ue(s.pickedAt))).orderBy(Ce(s.id));for(let p of a){let t=(await u.update(s).set({pickedAt:y.now()}).output().where(se(j(s.tenantId,g),j(s.id,Number(p.id)),ue(s.pickedAt))))?.[0];if(!t)continue;if(t.requestedBy===void 0||t.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(t.id)}`);let i=t.groupId??null;return{id:Number(t.id),tenantId:g,groupId:i===null?null:Number(i),requestedBy:String(t.requestedBy)}}return null},async finish(a,p){await d();let r=p.error?p.error.slice(0,h):null;await u.update(s).set({finishedAt:y.now(),result:p.result,error:r}).output().where(se(j(s.tenantId,g),j(s.id,a),ue(s.finishedAt)))},async listStranded(a){await d();let p=await u.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where(se(j(s.tenantId,g),ue(s.finishedAt))).orderBy(Ce(s.id)),r=t=>{let i=O(t);return Number.isNaN(i.getTime())||i<a};return{stranded:p.filter(t=>t.pickedAt!==null&&r(t.pickedAt)).map(t=>Number(t.id)),unclaimed:p.filter(t=>t.pickedAt===null&&r(t.requestedAt)).map(t=>Number(t.id))}},async abandonStranded(a){let{stranded:p}=await w.listStranded(a),r=[];for(let t of p)try{await w.finish(t,{result:"failed",error:Qe}),r.push(t)}catch(i){y.onCloseFailed?.(t,i)}return r}};return w}import{normalizeAuthEmail as de}from"@aiquants/auth-core";import{assertTenantId as _e,assertTenantMatch as Ze}from"@aiquants/authz-core";import{and as P,eq as E,inArray as we,isNull as er,notInArray as rr,or as tr}from"drizzle-orm";var nr=7,or=4;function ir(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function ct(e,n,y,u){let d=e,s=u.ensureConnection??(async()=>{}),g=u.now??(()=>new Date),{TDGroupExternalLink:h,TDGroupMemberEmail:w}=n,{TMGroup:a,TDGroupMember:p}=y,r=u.actor;return{async listSyncableLinks(t){await s();let i=_e(t.tenantId,"listSyncableLinks(tenantId)"),l=[E(h.isSyncEnabled,!0),E(h.tenantId,i)];return t.groupId!==void 0&&l.push(E(h.groupId,t.groupId)),(await d.select({linkId:h.id,groupId:h.groupId,linkTenantId:h.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:h.provider,externalId:h.externalId,externalKey:h.externalKey,membershipMode:h.membershipMode,vanishedObservations:h.vanishedObservations}).from(h).innerJoin(a,P(E(a.id,h.groupId),E(a.tenantId,i))).where(P(...l))).map(b=>(Ze(String(b.groupTenantId),String(b.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(b.linkId),groupId:Number(b.groupId),groupKey:String(b.groupKey),tenantId:String(b.groupTenantId),provider:String(b.provider),externalId:String(b.externalId),externalKey:String(b.externalKey),membershipMode:V(String(b.membershipMode)),isGroupActive:!!b.isGroupActive,vanishedObservations:Number(b.vanishedObservations??0)}))},async loadGroupSnapshot(t){await s();let[i,l]=await Promise.all([d.select({email:w.email,externalUserId:w.externalUserId,resolvedUserId:w.resolvedUserId}).from(w).where(E(w.groupId,t.groupId)),d.select({userId:p.userId}).from(p).where(E(p.groupId,t.groupId))]);return{ledger:i.map(f=>({email:String(f.email),...f.externalUserId?{externalUserId:String(f.externalUserId)}:{},resolvedUserId:f.resolvedUserId===null?null:Number(f.resolvedUserId)})),projectedUserIds:l.map(f=>Number(f.userId))}},resolveMembers(t){return u.memberResolver.resolve(t)},async applyPlan(t,i,l){_e(t.tenantId,"applyPlan(link.tenantId)"),await s(),(i.ledgerUpserts.length>0||i.projectionAdds.length>0)&&await d.transaction(async v=>{let x=i.ledgerUpserts.map(m=>({...m,email:de(m.email)})),o=new Map;for(let m of K(x.map(I=>I.email))){let I=await v.select({id:w.id,email:w.email}).from(w).where(P(E(w.groupId,t.groupId),we(w.email,m)));for(let A of I)o.set(String(A.email),Number(A.id))}let c=x.filter(m=>!o.has(m.email));for(let m of U(c,nr))await v.insert(w).output().values(m.map(I=>({groupId:t.groupId,email:I.email,externalUserId:I.externalUserId??null,resolvedUserId:I.resolvedUserId,observedAt:l,createdAt:l,createdBy:r})));for(let m of x){let I=o.get(m.email);I!==void 0&&await v.update(w).set({externalUserId:m.externalUserId??null,resolvedUserId:m.resolvedUserId,observedAt:l,updatedAt:l,updatedBy:r}).output().where(E(w.id,I))}for(let m of U(i.projectionAdds,or)){let I=new Set((await v.select({userId:p.userId}).from(p).where(P(E(p.groupId,t.groupId),we(p.userId,m)))).map(D=>Number(D.userId))),A=m.filter(D=>!I.has(D));A.length>0&&await v.insert(p).output().values(A.map(D=>({groupId:t.groupId,userId:D,createdAt:l,createdBy:r})))}});let f=[],b=[];for(let v of i.projectionRemoves)try{await u.lockoutGuard.guardedWrite(t.tenantId,{removeGroupMembership:{groupId:t.groupId,userId:v}},"remove-group-member",x=>x.delete(p).where(P(E(p.groupId,t.groupId),E(p.userId,v))))}catch(x){ir(x)?f.push({userId:v,applied:!1,refusal:x.message}):b.push({userId:v,cause:ae(x)})}let k=f.map(v=>v.userId);if(i.ledgerDeletes.length>0){let v=Math.max(1,1999-k.length);await d.transaction(async x=>{for(let o of K(i.ledgerDeletes,Math.min(500,v))){let c=k.length>0?tr(er(w.resolvedUserId),rr(w.resolvedUserId,k)):void 0;await x.delete(w).where(P(E(w.groupId,t.groupId),we(w.email,o),...c?[c]:[]))}})}return{refusals:f,failures:b}},async resolveExternalIdentity(t,i){await s(),!(t.externalId===i.externalId&&t.externalKey===de(i.externalKey))&&await d.update(h).set({externalId:i.externalId,externalKey:de(i.externalKey)}).output().where(P(E(h.id,t.linkId),E(h.tenantId,t.tenantId)))},async recordSuccess(t,i,l){await s(),await d.transaction(async f=>{await f.update(h).set({syncedAt:l,syncError:null,vanishedObservations:0,externalId:i.externalId,externalKey:de(i.externalKey),updatedAt:l,updatedBy:r}).output().where(P(E(h.id,t.linkId),E(h.tenantId,t.tenantId))),t.isGroupActive||await f.update(a).set({isActive:!0,updatedAt:l,updatedBy:r}).output().where(P(E(a.id,t.groupId),E(a.tenantId,t.tenantId)))})},async recordFailure(t,i){await s(),await d.update(h).set({syncError:i.slice(0,1e3),updatedAt:g(),updatedBy:r}).output().where(P(E(h.id,t.linkId),E(h.tenantId,t.tenantId)))},async recordVanishedObservation(t,i){await s();let l=t.vanishedObservations+1;return await d.update(h).set({vanishedObservations:l,syncError:i.slice(0,1e3),updatedAt:g(),updatedBy:r}).output().where(P(E(h.id,t.linkId),E(h.tenantId,t.tenantId))),l},async markGroupInactive(t){await s(),await d.update(a).set({isActive:!1,updatedAt:g(),updatedBy:r}).output().where(P(E(a.id,t.groupId),E(a.tenantId,t.tenantId),E(a.isActive,!0)))}}}export{ge as AUTH_DIRECTORY_PROVIDER_COLUMN,Fr as AUTH_DIRECTORY_TENANT_SCOPED_TABLES,Se as MAX_IN_LIST,X as MAX_PARAMETERS,De as MEMBERSHIP_MODES,be as NEWLY_UNHEALTHY_SYNC_OUTCOMES,Ie as RESERVED_PARAMETERS,Qe as STRANDED_REASON,Ge as UNHEALTHY_SYNC_OUTCOMES,fr as assertGroupMayBeLinked,V as assertMembershipMode,K as chunk,U as chunkByParameters,ae as classifySyncFailure,mr as createDirectoryCatalogReader,pr as createDirectoryCatalogWriter,Mr as createDirectoryLinkStore,Qr as createDirectorySync,ct as createDirectorySyncRepository,Or as createPromotionRepository,nt as createSyncRequestQueue,hr as defaultExternalGroupGuardMessage,Me as defaultSyncMessages,jr as defineAuthDirectorySchema,te as findLedgerRowsForIdentity,Pr as promoteLedgerForUser,O as toInstant,br as withExternalGroupGuards};
2
2
  //# sourceMappingURL=index.mjs.map