@aiquants/auth-directory-drizzle 0.7.0 → 0.8.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/README.md +7 -0
- package/dist/index.d.mts +38 -67
- package/dist/index.d.ts +38 -67
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -55,6 +55,13 @@ authorization admin UI uses. Removals are issued one at a time because `AdminRem
|
|
|
55
55
|
descriptor; adding a batch form to the authorization core would breach the package boundary, and the
|
|
56
56
|
count is already bounded by the reconciler's circuit breaker.
|
|
57
57
|
|
|
58
|
+
**Every transaction goes through one seam.** All nine of them are opened by `transactionRunner` in
|
|
59
|
+
`db.ts`, which runs the work again when — and only when — the server rolled it back
|
|
60
|
+
(see [`@aiquants/mssql`](../mssql/README.md) — **only the deadlock victim `1205` is run again**, and the closure is wrapped in `capture` so the ORM cannot destroy that evidence). Calling `db.transaction` directly
|
|
61
|
+
at each site means touching nine places to change the policy, and one of them eventually gets left
|
|
62
|
+
behind without saying so. Configure with `transactionRetry` on any factory's options; the closure
|
|
63
|
+
must keep its side effects inside the database, because a retry runs it from the start.
|
|
64
|
+
|
|
58
65
|
**A refusal does not abort the run.** Stopping at the first refused removal leaves the additions
|
|
59
66
|
committed, the earlier removals committed and the ledger untouched — and every later run recomputes
|
|
60
67
|
the same plan and dies at the same index, so one legitimately-guarded row blocks the whole group's
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthDirectoryCatalogGroup, AuthDirectoryCatalogSnapshot, AuthGroupExternalLink, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthAllowlistEntry } from '@aiquants/auth-core';
|
|
2
|
+
import { TransactionRetryOptions } from '@aiquants/mssql';
|
|
2
3
|
import * as drizzle_orm_mssql_core from 'drizzle-orm/mssql-core';
|
|
3
4
|
import { AnyMsSqlColumn } from 'drizzle-orm/mssql-core';
|
|
4
5
|
import { AuthzAdminStore } from '@aiquants/authz-core';
|
|
@@ -14,6 +15,7 @@ import { TenantMembershipPolicy, SyncAbort, AuthDirectoryMembershipMode as AuthD
|
|
|
14
15
|
* パラメータ (スキーマ・ロガー等) が配備ごとに違い、それを署名へ引き回すと利用側が
|
|
15
16
|
* `as any` を書く羽目になるためである。実体は実行時にこれらを満たす。
|
|
16
17
|
*/
|
|
18
|
+
|
|
17
19
|
type Rows<T> = PromiseLike<T[]>;
|
|
18
20
|
interface SelectChain<T> extends Rows<T> {
|
|
19
21
|
from(t: unknown): SelectChain<T>;
|
|
@@ -45,19 +47,35 @@ interface DirectoryDb {
|
|
|
45
47
|
}
|
|
46
48
|
/** Ensures a connection exists before the first statement. 最初の文の前に接続を確立する処理。 */
|
|
47
49
|
type EnsureConnection = () => Promise<void>;
|
|
50
|
+
/** Options every factory in this adapter shares. 本アダプタの各ファクトリが共通で受ける設定。 */
|
|
51
|
+
type TransactionalOptions = {
|
|
52
|
+
/**
|
|
53
|
+
* Retry policy for transactions SQL Server chose as the deadlock victim. See `@aiquants/mssql`.
|
|
54
|
+
* サーバがロールバックしたトランザクションの再試行方針。
|
|
55
|
+
*
|
|
56
|
+
* 鍵の範囲が重なる並行操作はデッドロックしうる。犠牲に選ばれた側はサーバによって
|
|
57
|
+
* ロールバックされる — 何もコミットされていないので、読み直して判定し直す再実行が正しい。
|
|
58
|
+
* 既定で 3 回試す。`{ attempts: 1 }` で再試行しない。
|
|
59
|
+
*/
|
|
60
|
+
transactionRetry?: TransactionRetryOptions;
|
|
61
|
+
};
|
|
48
62
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
63
|
+
* Builds the one function every transaction in this adapter goes through.
|
|
64
|
+
*
|
|
65
|
+
* 本アダプタの全トランザクションが通る、唯一の関数を組み立てる処理。
|
|
66
|
+
*
|
|
67
|
+
* 各呼び出し箇所で `db.transaction` を直に呼ぶと、再試行を足す・方針を変えるたびに 9 箇所を
|
|
68
|
+
* 触ることになり、いずれ 1 箇所だけ取り残される。入口を 1 つにする。
|
|
51
69
|
*
|
|
52
|
-
* ⚠️
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* 対処すると必ずどこかが漏れるので、1 つの関数に閉じる。
|
|
70
|
+
* ⚠️ **閉包を DB の外へ波及させないこと。** 再実行は毎回はじめから走るため、ロールバックで元に
|
|
71
|
+
* 戻らない副作用 (外部への送信・呼び出し元の配列への追加) を閉包へ入れてはならない。これは
|
|
72
|
+
* 再試行の有無に関わらず、トランザクションに要求される性質である。
|
|
56
73
|
*
|
|
57
|
-
* @param
|
|
58
|
-
* @
|
|
74
|
+
* @param db - 対象の DB 面。
|
|
75
|
+
* @param opts - 再試行方針を含む設定。
|
|
76
|
+
* @returns トランザクションを開いて閉包を走らせる関数。
|
|
59
77
|
*/
|
|
60
|
-
declare function
|
|
78
|
+
declare function transactionRunner(db: DirectoryDb, opts: TransactionalOptions): <T>(fn: (tx: DirectoryDb) => Promise<T>) => Promise<T>;
|
|
61
79
|
|
|
62
80
|
/** Minimal shape of a table this schema points a foreign key at. 外部キーの参照先に要る最小形。 */
|
|
63
81
|
type ReferencedTable = {
|
|
@@ -1375,7 +1393,7 @@ type DirectoryCatalogReaderOptions = {
|
|
|
1375
1393
|
* その場で `replace` を呼ぶ経路が「既に手元にあるもの」になり、上流を読めないプロセスが
|
|
1376
1394
|
* 観測時刻を詐称できてしまう。だから生成関数を 2 つに分けている。
|
|
1377
1395
|
*/
|
|
1378
|
-
type DirectoryCatalogWriterOptions = DirectoryCatalogReaderOptions & {
|
|
1396
|
+
type DirectoryCatalogWriterOptions = DirectoryCatalogReaderOptions & TransactionalOptions & {
|
|
1379
1397
|
/** Injected clock — one observation must carry one timestamp. 注入時計。 */
|
|
1380
1398
|
now: () => Date;
|
|
1381
1399
|
/** Who the audit columns record. 監査列に残す主体。 */
|
|
@@ -1625,7 +1643,7 @@ type LinkableAssertion = {
|
|
|
1625
1643
|
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1626
1644
|
};
|
|
1627
1645
|
/** Options for the link store. リンクストアの構成。 */
|
|
1628
|
-
type DirectoryLinkStoreOptions = {
|
|
1646
|
+
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1629
1647
|
/** Required — see {@link LinkableAssertion}. 必須 (忘れられる経路を作らないため)。 */
|
|
1630
1648
|
linkable: LinkableAssertion;
|
|
1631
1649
|
/** Establishes the connection before the first statement. 最初の文の前に接続を確立する処理。 */
|
|
@@ -1929,7 +1947,7 @@ declare function promoteLedgerForUser(repository: PromotionRepository, args: {
|
|
|
1929
1947
|
}): Promise<PromotionResult>;
|
|
1930
1948
|
|
|
1931
1949
|
/** Options for {@link createPromotionRepository}. 昇格リポジトリの生成オプション。 */
|
|
1932
|
-
type PromotionRepositoryOptions = {
|
|
1950
|
+
type PromotionRepositoryOptions = TransactionalOptions & {
|
|
1933
1951
|
/** Establishes the connection before the first statement. 最初の文の前に接続を確立する処理。 */
|
|
1934
1952
|
ensureConnection?: EnsureConnection;
|
|
1935
1953
|
/** Written to the audit columns of inserted membership rows. 挿入行の監査列に書く実行者。 */
|
|
@@ -1949,56 +1967,6 @@ type PromotionRepositoryOptions = {
|
|
|
1949
1967
|
*/
|
|
1950
1968
|
declare function createPromotionRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: PromotionRepositoryOptions): PromotionRepository;
|
|
1951
1969
|
|
|
1952
|
-
/**
|
|
1953
|
-
* SQL Server parameter-limit helpers shared by every batched statement.
|
|
1954
|
-
* 一括文が共有する SQL Server のパラメータ上限まわりのヘルパー。
|
|
1955
|
-
*
|
|
1956
|
-
* ⚠️ 上限は **行数ではなくパラメータ数** である。1 行あたり何個の値を束ねるかは文ごとに違うので、
|
|
1957
|
-
* 行数だけで分割すると、列の多い `VALUES` が静かに上限を越える。しかも越えるのは件数が閾値を
|
|
1958
|
-
* 跨いだ日だけで、それまでは同じコードが問題なく動いて見える。
|
|
1959
|
-
*/
|
|
1960
|
-
/**
|
|
1961
|
-
* Hard limit of parameters per statement. 1 文あたりのパラメータ数の上限。
|
|
1962
|
-
*
|
|
1963
|
-
* SQL Server の RPC 1 回あたりのパラメータ上限は 2100。数個は文自身の値 (`SET` 句・スカラー
|
|
1964
|
-
* 述語) が使うため、分割の基準にはここから余裕を引いた値を用いる。
|
|
1965
|
-
*/
|
|
1966
|
-
declare const MAX_PARAMETERS = 2100;
|
|
1967
|
-
/** Parameters reserved for the statement's own scalar values. 文自身のスカラー値のための予約枠。 */
|
|
1968
|
-
declare const RESERVED_PARAMETERS = 100;
|
|
1969
|
-
/**
|
|
1970
|
-
* Maximum ids per `IN` list of single-column predicates. 単列述語の `IN` 句 1 回あたりの最大 ID 数。
|
|
1971
|
-
*
|
|
1972
|
-
* 1 行 1 パラメータの用途 (`inArray(col, ids)`) 専用の既定値である。複数列を束ねる `VALUES` や、
|
|
1973
|
-
* 1 文に `IN` を 2 つ描画する `UPDATE` にそのまま使ってはならない — {@link chunkByParameters} を使う。
|
|
1974
|
-
*/
|
|
1975
|
-
declare const MAX_IN_LIST = 500;
|
|
1976
|
-
/**
|
|
1977
|
-
* Splits a list into chunks that fit one statement.
|
|
1978
|
-
* 1 文に収まる大きさへリストを分割する処理。
|
|
1979
|
-
*
|
|
1980
|
-
* @param items - Items to split. 分割対象。
|
|
1981
|
-
* @param size - Maximum chunk size. 1 チャンクの最大数。
|
|
1982
|
-
* @returns Chunks in the original order. 元の順序を保ったチャンク列。
|
|
1983
|
-
* @throws {TypeError} When the size is not a positive integer. 正の整数でない場合。
|
|
1984
|
-
*/
|
|
1985
|
-
declare function chunk<T>(items: readonly T[], size?: number): T[][];
|
|
1986
|
-
/**
|
|
1987
|
-
* Splits a list so each chunk fits the parameter budget of one statement.
|
|
1988
|
-
* 各チャンクが 1 文のパラメータ枠に収まるようリストを分割する処理。
|
|
1989
|
-
*
|
|
1990
|
-
* `VALUES` は 1 行につき列数ぶんのパラメータを束ねる。7 列の台帳行なら 500 行で 3500 個になり、
|
|
1991
|
-
* 上限 2100 を越えて **実行される前に** 拒否される。しかもその失敗は再試行しても同じ場所で起き、
|
|
1992
|
-
* そのグループは永久に台帳を持てない。
|
|
1993
|
-
*
|
|
1994
|
-
* @param items - Items to split. 分割対象。
|
|
1995
|
-
* @param parametersPerItem - Parameters each item binds. 1 件が束ねるパラメータ数。
|
|
1996
|
-
* @param reservedParameters - Parameters the statement itself binds. 文自身が束ねる数。
|
|
1997
|
-
* @returns Chunks in the original order. 元の順序を保ったチャンク列。
|
|
1998
|
-
* @throws {TypeError} When a single item cannot fit the budget. 1 件すら収まらない場合。
|
|
1999
|
-
*/
|
|
2000
|
-
declare function chunkByParameters<T>(items: readonly T[], parametersPerItem: number, reservedParameters?: number): T[][];
|
|
2001
|
-
|
|
2002
1970
|
/**
|
|
2003
1971
|
* @module sync-messages
|
|
2004
1972
|
* @description Operator-facing text the synchronization records against a link.
|
|
@@ -2463,11 +2431,14 @@ declare function createSyncRequestQueue(dbUnknown: unknown, tables: AuthDirector
|
|
|
2463
2431
|
/**
|
|
2464
2432
|
* Records how a claimed request ended. 取得済みの要求がどう終わったかを記録する処理。
|
|
2465
2433
|
*
|
|
2434
|
+
* ⚠️ 未完了の行にしか書かない。既に結末が記録されている行は **0 行更新** になるので、
|
|
2435
|
+
* 「書けたかどうか」を返す。返さないと、呼び出し側は閉じていない行を閉じたと報告する。
|
|
2436
|
+
*
|
|
2466
2437
|
* @param id - The claimed request id. 取得した要求の ID。
|
|
2467
2438
|
* @param outcome - Result and optional error text. 結果と (あれば) エラー文。
|
|
2468
|
-
* @returns
|
|
2439
|
+
* @returns True when this call recorded the outcome. この呼び出しが結末を記録した場合に true。
|
|
2469
2440
|
*/
|
|
2470
|
-
finish(id: number, outcome: SyncRequestResult): Promise<
|
|
2441
|
+
finish(id: number, outcome: SyncRequestResult): Promise<boolean>;
|
|
2471
2442
|
/**
|
|
2472
2443
|
* Lists requests that are not progressing. 進んでいない要求の一覧。
|
|
2473
2444
|
*
|
|
@@ -2499,7 +2470,7 @@ declare function createSyncRequestQueue(dbUnknown: unknown, tables: AuthDirector
|
|
|
2499
2470
|
*
|
|
2500
2471
|
* @param olderThan - Requests picked before this are considered abandoned.
|
|
2501
2472
|
* これより前に掴まれた要求を放棄と見なす締切時刻。
|
|
2502
|
-
* @returns The ids closed.
|
|
2473
|
+
* @returns The ids this call actually closed. この呼び出しが実際に閉じた要求の ID。
|
|
2503
2474
|
*/
|
|
2504
2475
|
abandonStranded(olderThan: Date): Promise<number[]>;
|
|
2505
2476
|
};
|
|
@@ -2518,7 +2489,7 @@ type MemberResolverPort = {
|
|
|
2518
2489
|
resolve(members: readonly DirectoryMember[]): Promise<ResolvedDirectoryMember[]>;
|
|
2519
2490
|
};
|
|
2520
2491
|
/** Options for the synchronization repository. 同期リポジトリの構成。 */
|
|
2521
|
-
type SyncRepositoryOptions = {
|
|
2492
|
+
type SyncRepositoryOptions = TransactionalOptions & {
|
|
2522
2493
|
ensureConnection?: EnsureConnection;
|
|
2523
2494
|
/**
|
|
2524
2495
|
* Wall-clock supplier. 実行の時刻を供給する処理。
|
|
@@ -2553,4 +2524,4 @@ type SyncRepositoryOptions = {
|
|
|
2553
2524
|
*/
|
|
2554
2525
|
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
2555
2526
|
|
|
2556
|
-
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRow, type DirectoryLinkStoreOptions, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion,
|
|
2527
|
+
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRow, type DirectoryLinkStoreOptions, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthDirectoryCatalogGroup, AuthDirectoryCatalogSnapshot, AuthGroupExternalLink, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthAllowlistEntry } from '@aiquants/auth-core';
|
|
2
|
+
import { TransactionRetryOptions } from '@aiquants/mssql';
|
|
2
3
|
import * as drizzle_orm_mssql_core from 'drizzle-orm/mssql-core';
|
|
3
4
|
import { AnyMsSqlColumn } from 'drizzle-orm/mssql-core';
|
|
4
5
|
import { AuthzAdminStore } from '@aiquants/authz-core';
|
|
@@ -14,6 +15,7 @@ import { TenantMembershipPolicy, SyncAbort, AuthDirectoryMembershipMode as AuthD
|
|
|
14
15
|
* パラメータ (スキーマ・ロガー等) が配備ごとに違い、それを署名へ引き回すと利用側が
|
|
15
16
|
* `as any` を書く羽目になるためである。実体は実行時にこれらを満たす。
|
|
16
17
|
*/
|
|
18
|
+
|
|
17
19
|
type Rows<T> = PromiseLike<T[]>;
|
|
18
20
|
interface SelectChain<T> extends Rows<T> {
|
|
19
21
|
from(t: unknown): SelectChain<T>;
|
|
@@ -45,19 +47,35 @@ interface DirectoryDb {
|
|
|
45
47
|
}
|
|
46
48
|
/** Ensures a connection exists before the first statement. 最初の文の前に接続を確立する処理。 */
|
|
47
49
|
type EnsureConnection = () => Promise<void>;
|
|
50
|
+
/** Options every factory in this adapter shares. 本アダプタの各ファクトリが共通で受ける設定。 */
|
|
51
|
+
type TransactionalOptions = {
|
|
52
|
+
/**
|
|
53
|
+
* Retry policy for transactions SQL Server chose as the deadlock victim. See `@aiquants/mssql`.
|
|
54
|
+
* サーバがロールバックしたトランザクションの再試行方針。
|
|
55
|
+
*
|
|
56
|
+
* 鍵の範囲が重なる並行操作はデッドロックしうる。犠牲に選ばれた側はサーバによって
|
|
57
|
+
* ロールバックされる — 何もコミットされていないので、読み直して判定し直す再実行が正しい。
|
|
58
|
+
* 既定で 3 回試す。`{ attempts: 1 }` で再試行しない。
|
|
59
|
+
*/
|
|
60
|
+
transactionRetry?: TransactionRetryOptions;
|
|
61
|
+
};
|
|
48
62
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
63
|
+
* Builds the one function every transaction in this adapter goes through.
|
|
64
|
+
*
|
|
65
|
+
* 本アダプタの全トランザクションが通る、唯一の関数を組み立てる処理。
|
|
66
|
+
*
|
|
67
|
+
* 各呼び出し箇所で `db.transaction` を直に呼ぶと、再試行を足す・方針を変えるたびに 9 箇所を
|
|
68
|
+
* 触ることになり、いずれ 1 箇所だけ取り残される。入口を 1 つにする。
|
|
51
69
|
*
|
|
52
|
-
* ⚠️
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* 対処すると必ずどこかが漏れるので、1 つの関数に閉じる。
|
|
70
|
+
* ⚠️ **閉包を DB の外へ波及させないこと。** 再実行は毎回はじめから走るため、ロールバックで元に
|
|
71
|
+
* 戻らない副作用 (外部への送信・呼び出し元の配列への追加) を閉包へ入れてはならない。これは
|
|
72
|
+
* 再試行の有無に関わらず、トランザクションに要求される性質である。
|
|
56
73
|
*
|
|
57
|
-
* @param
|
|
58
|
-
* @
|
|
74
|
+
* @param db - 対象の DB 面。
|
|
75
|
+
* @param opts - 再試行方針を含む設定。
|
|
76
|
+
* @returns トランザクションを開いて閉包を走らせる関数。
|
|
59
77
|
*/
|
|
60
|
-
declare function
|
|
78
|
+
declare function transactionRunner(db: DirectoryDb, opts: TransactionalOptions): <T>(fn: (tx: DirectoryDb) => Promise<T>) => Promise<T>;
|
|
61
79
|
|
|
62
80
|
/** Minimal shape of a table this schema points a foreign key at. 外部キーの参照先に要る最小形。 */
|
|
63
81
|
type ReferencedTable = {
|
|
@@ -1375,7 +1393,7 @@ type DirectoryCatalogReaderOptions = {
|
|
|
1375
1393
|
* その場で `replace` を呼ぶ経路が「既に手元にあるもの」になり、上流を読めないプロセスが
|
|
1376
1394
|
* 観測時刻を詐称できてしまう。だから生成関数を 2 つに分けている。
|
|
1377
1395
|
*/
|
|
1378
|
-
type DirectoryCatalogWriterOptions = DirectoryCatalogReaderOptions & {
|
|
1396
|
+
type DirectoryCatalogWriterOptions = DirectoryCatalogReaderOptions & TransactionalOptions & {
|
|
1379
1397
|
/** Injected clock — one observation must carry one timestamp. 注入時計。 */
|
|
1380
1398
|
now: () => Date;
|
|
1381
1399
|
/** Who the audit columns record. 監査列に残す主体。 */
|
|
@@ -1625,7 +1643,7 @@ type LinkableAssertion = {
|
|
|
1625
1643
|
assertLinkableAndWrite(tenantId: string, groupId: number, write: (runner: unknown) => Promise<void>): Promise<void>;
|
|
1626
1644
|
};
|
|
1627
1645
|
/** Options for the link store. リンクストアの構成。 */
|
|
1628
|
-
type DirectoryLinkStoreOptions = {
|
|
1646
|
+
type DirectoryLinkStoreOptions = TransactionalOptions & {
|
|
1629
1647
|
/** Required — see {@link LinkableAssertion}. 必須 (忘れられる経路を作らないため)。 */
|
|
1630
1648
|
linkable: LinkableAssertion;
|
|
1631
1649
|
/** Establishes the connection before the first statement. 最初の文の前に接続を確立する処理。 */
|
|
@@ -1929,7 +1947,7 @@ declare function promoteLedgerForUser(repository: PromotionRepository, args: {
|
|
|
1929
1947
|
}): Promise<PromotionResult>;
|
|
1930
1948
|
|
|
1931
1949
|
/** Options for {@link createPromotionRepository}. 昇格リポジトリの生成オプション。 */
|
|
1932
|
-
type PromotionRepositoryOptions = {
|
|
1950
|
+
type PromotionRepositoryOptions = TransactionalOptions & {
|
|
1933
1951
|
/** Establishes the connection before the first statement. 最初の文の前に接続を確立する処理。 */
|
|
1934
1952
|
ensureConnection?: EnsureConnection;
|
|
1935
1953
|
/** Written to the audit columns of inserted membership rows. 挿入行の監査列に書く実行者。 */
|
|
@@ -1949,56 +1967,6 @@ type PromotionRepositoryOptions = {
|
|
|
1949
1967
|
*/
|
|
1950
1968
|
declare function createPromotionRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: PromotionRepositoryOptions): PromotionRepository;
|
|
1951
1969
|
|
|
1952
|
-
/**
|
|
1953
|
-
* SQL Server parameter-limit helpers shared by every batched statement.
|
|
1954
|
-
* 一括文が共有する SQL Server のパラメータ上限まわりのヘルパー。
|
|
1955
|
-
*
|
|
1956
|
-
* ⚠️ 上限は **行数ではなくパラメータ数** である。1 行あたり何個の値を束ねるかは文ごとに違うので、
|
|
1957
|
-
* 行数だけで分割すると、列の多い `VALUES` が静かに上限を越える。しかも越えるのは件数が閾値を
|
|
1958
|
-
* 跨いだ日だけで、それまでは同じコードが問題なく動いて見える。
|
|
1959
|
-
*/
|
|
1960
|
-
/**
|
|
1961
|
-
* Hard limit of parameters per statement. 1 文あたりのパラメータ数の上限。
|
|
1962
|
-
*
|
|
1963
|
-
* SQL Server の RPC 1 回あたりのパラメータ上限は 2100。数個は文自身の値 (`SET` 句・スカラー
|
|
1964
|
-
* 述語) が使うため、分割の基準にはここから余裕を引いた値を用いる。
|
|
1965
|
-
*/
|
|
1966
|
-
declare const MAX_PARAMETERS = 2100;
|
|
1967
|
-
/** Parameters reserved for the statement's own scalar values. 文自身のスカラー値のための予約枠。 */
|
|
1968
|
-
declare const RESERVED_PARAMETERS = 100;
|
|
1969
|
-
/**
|
|
1970
|
-
* Maximum ids per `IN` list of single-column predicates. 単列述語の `IN` 句 1 回あたりの最大 ID 数。
|
|
1971
|
-
*
|
|
1972
|
-
* 1 行 1 パラメータの用途 (`inArray(col, ids)`) 専用の既定値である。複数列を束ねる `VALUES` や、
|
|
1973
|
-
* 1 文に `IN` を 2 つ描画する `UPDATE` にそのまま使ってはならない — {@link chunkByParameters} を使う。
|
|
1974
|
-
*/
|
|
1975
|
-
declare const MAX_IN_LIST = 500;
|
|
1976
|
-
/**
|
|
1977
|
-
* Splits a list into chunks that fit one statement.
|
|
1978
|
-
* 1 文に収まる大きさへリストを分割する処理。
|
|
1979
|
-
*
|
|
1980
|
-
* @param items - Items to split. 分割対象。
|
|
1981
|
-
* @param size - Maximum chunk size. 1 チャンクの最大数。
|
|
1982
|
-
* @returns Chunks in the original order. 元の順序を保ったチャンク列。
|
|
1983
|
-
* @throws {TypeError} When the size is not a positive integer. 正の整数でない場合。
|
|
1984
|
-
*/
|
|
1985
|
-
declare function chunk<T>(items: readonly T[], size?: number): T[][];
|
|
1986
|
-
/**
|
|
1987
|
-
* Splits a list so each chunk fits the parameter budget of one statement.
|
|
1988
|
-
* 各チャンクが 1 文のパラメータ枠に収まるようリストを分割する処理。
|
|
1989
|
-
*
|
|
1990
|
-
* `VALUES` は 1 行につき列数ぶんのパラメータを束ねる。7 列の台帳行なら 500 行で 3500 個になり、
|
|
1991
|
-
* 上限 2100 を越えて **実行される前に** 拒否される。しかもその失敗は再試行しても同じ場所で起き、
|
|
1992
|
-
* そのグループは永久に台帳を持てない。
|
|
1993
|
-
*
|
|
1994
|
-
* @param items - Items to split. 分割対象。
|
|
1995
|
-
* @param parametersPerItem - Parameters each item binds. 1 件が束ねるパラメータ数。
|
|
1996
|
-
* @param reservedParameters - Parameters the statement itself binds. 文自身が束ねる数。
|
|
1997
|
-
* @returns Chunks in the original order. 元の順序を保ったチャンク列。
|
|
1998
|
-
* @throws {TypeError} When a single item cannot fit the budget. 1 件すら収まらない場合。
|
|
1999
|
-
*/
|
|
2000
|
-
declare function chunkByParameters<T>(items: readonly T[], parametersPerItem: number, reservedParameters?: number): T[][];
|
|
2001
|
-
|
|
2002
1970
|
/**
|
|
2003
1971
|
* @module sync-messages
|
|
2004
1972
|
* @description Operator-facing text the synchronization records against a link.
|
|
@@ -2463,11 +2431,14 @@ declare function createSyncRequestQueue(dbUnknown: unknown, tables: AuthDirector
|
|
|
2463
2431
|
/**
|
|
2464
2432
|
* Records how a claimed request ended. 取得済みの要求がどう終わったかを記録する処理。
|
|
2465
2433
|
*
|
|
2434
|
+
* ⚠️ 未完了の行にしか書かない。既に結末が記録されている行は **0 行更新** になるので、
|
|
2435
|
+
* 「書けたかどうか」を返す。返さないと、呼び出し側は閉じていない行を閉じたと報告する。
|
|
2436
|
+
*
|
|
2466
2437
|
* @param id - The claimed request id. 取得した要求の ID。
|
|
2467
2438
|
* @param outcome - Result and optional error text. 結果と (あれば) エラー文。
|
|
2468
|
-
* @returns
|
|
2439
|
+
* @returns True when this call recorded the outcome. この呼び出しが結末を記録した場合に true。
|
|
2469
2440
|
*/
|
|
2470
|
-
finish(id: number, outcome: SyncRequestResult): Promise<
|
|
2441
|
+
finish(id: number, outcome: SyncRequestResult): Promise<boolean>;
|
|
2471
2442
|
/**
|
|
2472
2443
|
* Lists requests that are not progressing. 進んでいない要求の一覧。
|
|
2473
2444
|
*
|
|
@@ -2499,7 +2470,7 @@ declare function createSyncRequestQueue(dbUnknown: unknown, tables: AuthDirector
|
|
|
2499
2470
|
*
|
|
2500
2471
|
* @param olderThan - Requests picked before this are considered abandoned.
|
|
2501
2472
|
* これより前に掴まれた要求を放棄と見なす締切時刻。
|
|
2502
|
-
* @returns The ids closed.
|
|
2473
|
+
* @returns The ids this call actually closed. この呼び出しが実際に閉じた要求の ID。
|
|
2503
2474
|
*/
|
|
2504
2475
|
abandonStranded(olderThan: Date): Promise<number[]>;
|
|
2505
2476
|
};
|
|
@@ -2518,7 +2489,7 @@ type MemberResolverPort = {
|
|
|
2518
2489
|
resolve(members: readonly DirectoryMember[]): Promise<ResolvedDirectoryMember[]>;
|
|
2519
2490
|
};
|
|
2520
2491
|
/** Options for the synchronization repository. 同期リポジトリの構成。 */
|
|
2521
|
-
type SyncRepositoryOptions = {
|
|
2492
|
+
type SyncRepositoryOptions = TransactionalOptions & {
|
|
2522
2493
|
ensureConnection?: EnsureConnection;
|
|
2523
2494
|
/**
|
|
2524
2495
|
* Wall-clock supplier. 実行の時刻を供給する処理。
|
|
@@ -2553,4 +2524,4 @@ type SyncRepositoryOptions = {
|
|
|
2553
2524
|
*/
|
|
2554
2525
|
declare function createDirectorySyncRepository(dbUnknown: unknown, tables: AuthDirectoryTables, authzTables: AuthzTables, opts: SyncRepositoryOptions): DirectorySyncRepository;
|
|
2555
2526
|
|
|
2556
|
-
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRow, type DirectoryLinkStoreOptions, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion,
|
|
2527
|
+
export { AUTH_DIRECTORY_PROVIDER_COLUMN, AUTH_DIRECTORY_TENANT_SCOPED_TABLES, type ApplyReport, type AuthDirectoryTables, type ClaimedSyncRequest, type DeleteChain, type DirectoryCatalogEntry, type DirectoryCatalogReaderOptions, type DirectoryCatalogSnapshot, type DirectoryCatalogWriterOptions, type DirectoryDb, type DirectoryLinkRow, type DirectoryLinkStoreOptions, type DirectorySyncConfig, type DirectorySyncRepository, type DirectorySyncRunResult, type EnsureConnection, type ExternalGroupGuardOptions, type ExternalLinkLookup, type InsertChain, type LedgerIdentityRow, type LedgerIdentityTables, type LinkGroupInput, type LinkableAssertion, MEMBERSHIP_MODES, type MemberResolverPort, NEWLY_UNHEALTHY_SYNC_OUTCOMES, type PromotionRepository, type PromotionRepositoryOptions, type PromotionResult, type ReferencedTable, type RemovalFailure, type RemovalOutcome, STRANDED_REASON, type SelectChain, type StrandedRequests, type SyncFailureCause, type SyncLink, type SyncLoggerPort, type SyncMessages, type SyncOutcome, type SyncRepositoryOptions, type SyncRequestOutcome, type SyncRequestQueueOptions, type SyncRequestResult, type SyncScope, type TransactionalOptions, UNHEALTHY_SYNC_OUTCOMES, type UpdateChain, assertGroupMayBeLinked, assertMembershipMode, classifySyncFailure, createDirectoryCatalogReader, createDirectoryCatalogWriter, createDirectoryLinkStore, createDirectorySync, createDirectorySyncRepository, createPromotionRepository, createSyncRequestQueue, defaultExternalGroupGuardMessage, defaultSyncMessages, defineAuthDirectorySchema, findLedgerRowsForIdentity, promoteLedgerForUser, transactionRunner, withExternalGroupGuards };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var ie=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var xe=Object.prototype.hasOwnProperty;var ke=(e,t)=>{for(var m in t)ie(e,m,{get:t[m],enumerable:!0})},Ee=(e,t,m,d)=>{if(t&&typeof t=="object"||typeof t=="function")for(let u of De(t))!xe.call(e,u)&&u!==m&&ie(e,u,{get:()=>t[u],enumerable:!(d=Te(t,u))||d.enumerable});return e};var Re=e=>Ee(ie({},"__esModule",{value:!0}),e);var Xe={};ke(Xe,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>te,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>Be,MAX_IN_LIST:()=>ye,MAX_PARAMETERS:()=>Y,MEMBERSHIP_MODES:()=>se,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>ne,RESERVED_PARAMETERS:()=>me,STRANDED_REASON:()=>ve,UNHEALTHY_SYNC_OUTCOMES:()=>ce,assertGroupMayBeLinked:()=>$e,assertMembershipMode:()=>z,chunk:()=>L,chunkByParameters:()=>N,classifySyncFailure:()=>W,createDirectoryCatalogReader:()=>_e,createDirectoryCatalogWriter:()=>Ce,createDirectoryLinkStore:()=>Ne,createDirectorySync:()=>je,createDirectorySyncRepository:()=>Ye,createPromotionRepository:()=>ze,createSyncRequestQueue:()=>We,defaultExternalGroupGuardMessage:()=>Pe,defaultSyncMessages:()=>le,defineAuthDirectorySchema:()=>Fe,findLedgerRowsForIdentity:()=>j,promoteLedgerForUser:()=>qe,toInstant:()=>K,withExternalGroupGuards:()=>Le});module.exports=Re(Xe);var ae=require("@aiquants/authz-core"),_=require("drizzle-orm");function K(e){return e instanceof Date?e:new Date(/(?:Z|[+-]\d{2}:?\d{2})$/.test(e)?e:`${e.replace(" ","T")}Z`)}var Y=2100,me=100,ye=500;function L(e,t=500){if(!Number.isInteger(t)||t<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] chunk size must be a positive integer, received ${String(t)}`);let m=[];for(let d=0;d<e.length;d+=t)m.push(e.slice(d,d+t));return m}function N(e,t,m=100){if(!Number.isInteger(t)||t<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] parametersPerItem must be a positive integer, received ${String(t)}`);if(!Number.isInteger(m)||m<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] reservedParameters must be a non-negative integer, received ${String(m)}`);let d=2100-m,u=Math.floor(d/t);if(u<1)throw new TypeError(`[@aiquants/auth-directory-drizzle] a single item binds ${t} parameters, which exceeds the ${d} available in one statement`);return L(e,u)}var Me=10,$={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},ge=2147483647;function Ge(e){if(e===void 0||!Number.isFinite(e))return null;let t=Math.trunc(e);return t<0?null:t>ge?ge:t}function B(e,t,m){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} must not be blank`);if(d.length>t)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${m} exceeds its column length (${t}); truncating it would change which rows it owns`);return d}function Ce(e,t,m){let d=e,u=m.ensureConnection??(async()=>{}),s=t.TDDirectoryCatalog,b=t.TMDirectoryCatalogObservation,f=S=>{let a=new Map;for(let g of S)a.set(g.externalId,g);return[...a.values()]};return{async replace(S,a,g){await u();let r=B((0,ae.assertTenantId)(S,"replace(tenantId)"),$.tenantId,"tenantId"),n=B(a,$.provider,"provider"),i=B(m.actor,$.actor,"actor"),l=m.now(),I=f(g.map(w=>({tenantId:r,provider:n,externalId:w.externalId.slice(0,$.externalId),externalKey:w.externalKey.slice(0,$.externalKey),name:w.name.slice(0,$.name),description:w.description===void 0?null:w.description.slice(0,$.description),memberCount:Ge(w.memberCount),observedAt:l,createdAt:l,createdBy:i})));return await d.transaction(async w=>{await w.delete(s).where((0,_.and)((0,_.eq)(s.tenantId,r),(0,_.eq)(s.provider,n)));for(let E of N(I,Me))await w.insert(s).values(E);await w.delete(b).where((0,_.and)((0,_.eq)(b.tenantId,r),(0,_.eq)(b.provider,n))),await w.insert(b).values([{tenantId:r,provider:n,observedAt:l,entryCount:I.length,createdAt:l,createdBy:i,updatedAt:l,updatedBy:i}])}),I.length}}}function _e(e,t,m={}){let d=e,u=m.ensureConnection??(async()=>{}),s=t.TDDirectoryCatalog,b=t.TMDirectoryCatalogObservation;return{async read(f,S){await u();let a=B((0,ae.assertTenantId)(f,"read(tenantId)"),$.tenantId,"tenantId"),g=B(S,$.provider,"provider"),r=await d.select({observedAt:b.observedAt,entryCount:b.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(b).leftJoin(s,(0,_.and)((0,_.eq)(s.tenantId,b.tenantId),(0,_.eq)(s.provider,b.provider))).where((0,_.and)((0,_.eq)(b.tenantId,a),(0,_.eq)(b.provider,g))).orderBy((0,_.asc)(s.name)),n=r.filter(I=>I.externalId!==null).map(I=>({externalId:String(I.externalId),externalKey:String(I.externalKey),name:String(I.name),...I.description===null?{}:{description:I.description},...I.memberCount===null?{}:{memberCount:Number(I.memberCount)}})),i=r[0];if(i===void 0)return{groups:n,observedAt:null};if(Number(i.entryCount)!==n.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${a}, ${g}): the observation records ${i.entryCount} entries but ${n.length} rows are present`);let l=K(i.observedAt);if(!Number.isFinite(l.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${a}, ${g}) has an unreadable observed_at`);return{groups:n,observedAt:l}}}}var he=require("@aiquants/authz-core"),Pe=(e,t)=>`\u5916\u90E8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u9023\u643A\u30B0\u30EB\u30FC\u30D7 (${t}) \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 U(e,t,m,d){return new Set(e.filter(u=>t.has(u.roleId)&&u.appKey===m&&u.resourceKey===d).map(u=>u.action))}function X(e,t){return new Set(e.filter(m=>m.groupId===t).map(m=>m.roleId))}var F=e=>he.AUTHZ_ACTIONS.every(t=>e.has(t)),Z=(e,t)=>e.find(m=>m.roleId===t)?.roleKey??`#${t}`;function Q(e,t,m,d){let u=[...t].find(b=>F(U(e,new Set([b]),m,d)));return u!==void 0?Z(e,u):[...t].filter(b=>U(e,new Set([b]),m,d).size>0).map(b=>Z(e,b)).join(" + ")}function Le(e,t){return be(e,t)}function be(e,t){let m=e;return{...e,withTransaction:(d,u)=>m.withTransaction((s,b)=>d(be(s,t),b),u),async assignGroupRole(d,u,s,b){await m.withTransaction(async(f,S)=>{let a=await f.listGrants(d),g=X(await f.listGroupRoles(d),u);if(g.add(s),F(U(a,g,t.adminAppKey,t.adminResourceKey))){let r=await t.linkLookup.externalGroupKey(d,u,S);if(r!==null)throw new Error(t.message(Q(a,g,t.adminAppKey,t.adminResourceKey),r))}await f.assignGroupRole(d,u,s,b)})},async updateGrant(d,u,s,b){let f=s.action;if(f===void 0)return m.updateGrant(d,u,s,b);await m.withTransaction(async(S,a)=>{let g=await S.listGrants(d),r=g.find(l=>l.id===u);if(r===void 0)return S.updateGrant(d,u,s,b);let i=(await S.listResources(d)).find(l=>l.id===r.resourceId);if(i?.appKey===t.adminAppKey&&i?.resourceKey===t.adminResourceKey){let l=await S.listGroupRoles(d);for(let I of new Set(l.filter(w=>w.roleId===r.roleId).map(w=>w.groupId))){let w=X(l,I),E=U(g.filter(k=>k.id!==u),w,t.adminAppKey,t.adminResourceKey);if(w.has(r.roleId)&&E.add(f),!F(E))continue;let T=await t.linkLookup.externalGroupKey(d,I,a);if(T!==null)throw new Error(t.message(Q(g,w,t.adminAppKey,t.adminResourceKey)||Z(g,r.roleId),T))}}await S.updateGrant(d,u,s,b)})},async createGrant(d,u,s){await m.withTransaction(async(b,f)=>{let a=(await b.listResources(d)).find(g=>g.id===u.resourceId);if(a?.appKey===t.adminAppKey&&a?.resourceKey===t.adminResourceKey){let g=await b.listGrants(d),r=await b.listGroupRoles(d);for(let n of new Set(r.filter(i=>i.roleId===u.roleId).map(i=>i.groupId))){let i=U(g,X(r,n),t.adminAppKey,t.adminResourceKey);if(i.add(u.action),!F(i))continue;let l=await t.linkLookup.externalGroupKey(d,n,f);if(l!==null)throw new Error(t.message(Q(g,X(r,n),t.adminAppKey,t.adminResourceKey)||Z(g,u.roleId),l))}}await b.createGrant(d,u,s)})}}}async function $e(e,t,m,d){let u=(await e.listGroupRoles(t)).filter(S=>S.groupId===m);if(u.length===0)return;let s=await e.listGrants(t),b=new Set(u.map(S=>S.roleId));if(!F(U(s,b,d.adminAppKey,d.adminResourceKey)))return;let f=u[0];if(f)throw new Error(d.message(Q(s,b,d.adminAppKey,d.adminResourceKey),f.groupKey))}var q=require("drizzle-orm");async function j(e,t,m){let{ledger:d,group:u}=t,{tenantId:s,externalUserId:b,email:f}=m;if(b===null&&f==="")return[];let S={ledgerId:d.id,groupId:d.groupId,tenantId:u.tenantId,externalUserId:d.externalUserId},a=async l=>(await e.select(S).from(d).innerJoin(u,(0,q.and)((0,q.eq)(u.id,d.groupId),(0,q.eq)(u.tenantId,s),(0,q.eq)(u.isActive,!0))).where(l)).map(w=>({ledgerId:Number(w.ledgerId),groupId:Number(w.groupId),tenantId:String(w.tenantId),externalUserId:w.externalUserId===null?null:String(w.externalUserId)})),g=l=>{let I=new Map;for(let w of l)I.set(w.ledgerId,{ledgerId:w.ledgerId,groupId:w.groupId,tenantId:w.tenantId});return[...I.values()].sort((w,E)=>w.ledgerId-E.ledgerId)},r=b===null?[]:await a((0,q.eq)(d.externalUserId,b));if(f==="")return g(r);let n=await a((0,q.eq)(d.email,f)),i=l=>l.externalUserId!==null&&l.externalUserId!==b;return g([...r,...n.filter(l=>!i(l))])}var ee=require("@aiquants/auth-core"),fe=require("@aiquants/auth-directory-core"),G=require("@aiquants/authz-core"),c=require("drizzle-orm");var se=["direct","transitive"];function z(e){if(!se.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${se.join(" | ")}, received ${JSON.stringify(e)}`);return e}function Ke(e){let t=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return t===2601||t===2627}function Ne(e,t,m,d){let u=e,s=d.ensureConnection??(async()=>{}),b=d.policyCacheTtlMs??0;if(!Number.isFinite(b)||b<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let f=new Map,S=o=>f.delete(o),{TDGroupExternalLink:a,TDGroupMemberEmail:g,TDTenantMembershipGroup:r,TDAllowlistGroup:n,TDDirectorySyncRequest:i}=t,{TMGroup:l,TDGroupMember:I}=m;async function w(o,y,h){if((await o.select({groupId:r.groupId}).from(r).where((0,c.and)((0,c.eq)(r.tenantId,y),(0,c.eq)(r.groupId,h),(0,c.eq)(r.isBreakGlass,!0)))).length>0)throw new Error(`Group ${h} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function E(o,y,h){let[v]=await o.select({id:l.id}).from(l).where((0,c.and)((0,c.eq)(l.id,h),(0,c.eq)(l.tenantId,y)));if(!v)throw new Error(`Group not found in tenant: ${h}`)}function T(o,y){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:z(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:K(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:y}}async function k(o,y){let h=await o.select({groupId:g.groupId,n:(0,c.count)()}).from(g).innerJoin(l,(0,c.and)((0,c.eq)(l.id,g.groupId),(0,c.eq)(l.tenantId,y))).where((0,c.isNull)(g.resolvedUserId)).groupBy(g.groupId);return new Map(h.map(v=>[Number(v.groupId),Number(v.n)]))}return{async listLinks(o){(0,G.assertTenantId)(o,"listLinks(tenantId)"),await s();let y=await u.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((0,c.eq)(a.tenantId,o)).orderBy((0,c.asc)(a.groupId)),h=await k(u,o);return y.map(v=>((0,G.assertTenantMatch)(o,String(v.tenantId),"listLinks(row.tenantId)"),T(v,h.get(Number(v.groupId))??0)))},async externalGroupKey(o,y,h){(0,G.assertTenantId)(o,"externalGroupKey(tenantId)"),await s();let[v]=await(h??u).select({externalKey:a.externalKey}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,o)));return v?String(v.externalKey):null},async linkGroup(o,y,h,v){(0,G.assertTenantId)(o,"linkGroup(tenantId)");let D=z(h.membershipMode),x=h.externalId.trim(),C=(0,ee.normalizeAuthEmail)(h.externalKey);if(x===""||C==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(o,y,async V=>{let R=V;await E(R,o,y),await w(R,o,y),await R.insert(a).output().values({groupId:y,tenantId:o,provider:h.provider,externalId:x,externalKey:C,membershipMode:D,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:v})}),S(o)},async unlinkGroup(o,y){(0,G.assertTenantId)(o,"unlinkGroup(tenantId)"),await s(),await u.transaction(async h=>{await E(h,o,y),await h.delete(g).where((0,c.eq)(g.groupId,y)),await h.delete(n).where((0,c.and)((0,c.eq)(n.groupId,y),(0,c.eq)(n.tenantId,o))),await h.delete(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,o)))}),S(o)},async setSyncEnabled(o,y,h,v){(0,G.assertTenantId)(o,"setSyncEnabled(tenantId)"),await s(),await u.update(a).set({isSyncEnabled:h,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,o)))},async requestSync(o,y,h){return(0,G.assertTenantId)(o,"requestSync(tenantId)"),await s(),u.transaction(async v=>{if(y!==null&&await E(v,o,y),(await v.select({id:i.id}).from(i).where((0,c.and)((0,c.eq)(i.tenantId,o),y===null?(0,c.isNull)(i.groupId):(0,c.eq)(i.groupId,y),(0,c.isNull)(i.pickedAt)))).length>0)return"already-pending";try{await v.insert(i).output().values({tenantId:o,groupId:y,requestedBy:h,requestedAt:new Date,createdAt:new Date,createdBy:h})}catch(x){if(!Ke(x))throw x;return"already-pending"}return"queued"})},async getStatus(o){(0,G.assertTenantId)(o,"getStatus(tenantId)"),await s();let y=await u.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,o)),[h]=await u.select({n:(0,c.count)()}).from(i).where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.isNull)(i.finishedAt))),v=y.filter(R=>!!R.isSyncEnabled),D=v.map(R=>R.syncedAt?K(R.syncedAt).getTime():null),x=D.length>0&&D.every(R=>R!==null)?Math.min(...D):null,V=y.length>0&&v.length===0||v.length>0&&(x===null||Date.now()-x>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:y.length,...x!==null?{oldestSyncedAt:new Date(x).toISOString()}:{},isStale:V,pausedGroupCount:y.length-v.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:y.filter(R=>!!R.syncError).map(R=>Number(R.groupId)).sort((R,Ae)=>R-Ae),pendingRequestCount:Number(h?.n??0)}},async loadTenantMembershipPolicy(o){(0,G.assertTenantId)(o,"loadTenantMembershipPolicy(tenantId)");let y=b>0?f.get(o):void 0;if(y&&Date.now()-y.loadedAt<b)return y.policy;await s();let h=await u.select({groupId:r.groupId,isBreakGlass:r.isBreakGlass,externalLinkId:a.id}).from(r).innerJoin(l,(0,c.and)((0,c.eq)(l.id,r.groupId),(0,c.eq)(l.tenantId,o))).leftJoin(a,(0,c.and)((0,c.eq)(a.groupId,r.groupId),(0,c.eq)(a.tenantId,o))).where((0,c.eq)(r.tenantId,o));if(h.length===0){let C={kind:"no-proof-required"};return b>0&&f.set(o,{policy:C,loadedAt:Date.now()}),C}let v=h.map(C=>Number(C.groupId)),D=h.filter(C=>!!C.isBreakGlass);for(let C of D)if(C.externalLinkId!==null)throw new Error(`Break-glass group ${Number(C.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let x={kind:"require-proof",proofGroupIds:v,breakGlassGroupIds:D.map(C=>Number(C.groupId))};return(0,fe.assertTenantMembershipPolicy)(x,`tenant ${o}`),b>0&&f.set(o,{policy:x,loadedAt:Date.now()}),x},async setTenantMembershipGroup(o,y,h,v){(0,G.assertTenantId)(o,"setTenantMembershipGroup(tenantId)"),await s(),await u.transaction(async D=>{if(await E(D,o,y),h){let[C]=await D.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.tenantId,o),(0,c.eq)(a.groupId,y)));if(C)throw new Error(`Group ${y} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let x=await D.select({groupId:r.groupId}).from(r).where((0,c.and)((0,c.eq)(r.tenantId,o),(0,c.eq)(r.groupId,y)));if(!h&&!(await D.select({groupId:r.groupId,isBreakGlass:r.isBreakGlass}).from(r).where((0,c.eq)(r.tenantId,o))).filter(R=>Number(R.groupId)!==y).some(R=>!!R.isBreakGlass))throw new Error(`Declaring group ${y} 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(x.length>0){await D.update(r).set({isBreakGlass:h,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(r.tenantId,o),(0,c.eq)(r.groupId,y)));return}await D.insert(r).output().values({tenantId:o,groupId:y,isBreakGlass:h,createdAt:new Date,createdBy:v})}),S(o)},async removeTenantMembershipGroup(o,y){(0,G.assertTenantId)(o,"removeTenantMembershipGroup(tenantId)"),await s(),await u.transaction(async h=>{let D=(await h.select({groupId:r.groupId,isBreakGlass:r.isBreakGlass}).from(r).where((0,c.eq)(r.tenantId,o))).filter(x=>Number(x.groupId)!==y);if(D.length>0&&!D.some(x=>!!x.isBreakGlass))throw new Error(`Removing group ${y} would leave tenant ${o} with a membership proof but no break-glass path.`);await h.delete(r).where((0,c.and)((0,c.eq)(r.tenantId,o),(0,c.eq)(r.groupId,y)))}),S(o)},async setGroupActive(o,y,h,v){(0,G.assertTenantId)(o,"setGroupActive(tenantId)"),await s(),await u.update(l).set({isActive:h,updatedAt:new Date,updatedBy:v}).output().where((0,c.and)((0,c.eq)(l.id,y),(0,c.eq)(l.tenantId,o)))},async listLedgerGroupIds(o,y){if((0,G.assertTenantId)(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let h=await j(u,{ledger:g,group:l},{tenantId:o,externalUserId:y.externalUserId?.trim()||null,email:(0,ee.normalizeAuthEmail)(y.email)});return[...new Set(h.map(v=>v.groupId))].sort((v,D)=>v-D)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let y=(0,ee.normalizeAuthEmail)(o);if(y==="")return!1;await s();let[h]=await u.select({id:g.id}).from(g).innerJoin(n,(0,c.eq)(n.groupId,g.groupId)).innerJoin(l,(0,c.and)((0,c.eq)(l.id,g.groupId),(0,c.eq)(l.isActive,!0))).where((0,c.eq)(g.email,y));return!!h},async listAllowlistGroups(o){return(0,G.assertTenantId)(o,"listAllowlistGroups(tenantId)"),await s(),(await u.select({id:n.id,groupId:n.groupId,groupKey:l.groupKey,groupName:l.name,description:n.description}).from(n).innerJoin(l,(0,c.and)((0,c.eq)(l.id,n.groupId),(0,c.eq)(l.tenantId,o))).where((0,c.eq)(n.tenantId,o)).orderBy((0,c.asc)(l.name))).map(h=>({id:Number(h.id),type:"group",groupId:Number(h.groupId),groupKey:String(h.groupKey),groupName:String(h.groupName),...h.description?{description:String(h.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,y,h,v){(0,G.assertTenantId)(o,"addAllowlistGroup(tenantId)"),await s(),await u.transaction(async D=>{await E(D,o,y);let[x]=await D.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.groupId,y),(0,c.eq)(a.tenantId,o)));if(!x)throw new Error(`Group ${y} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await D.insert(n).output().values({tenantId:o,groupId:y,description:h??null,createdAt:new Date,createdBy:v})})},async removeAllowlistGroup(o,y){(0,G.assertTenantId)(o,"removeAllowlistGroup(tenantId)"),await s(),await u.delete(n).where((0,c.and)((0,c.eq)(n.id,y),(0,c.eq)(n.tenantId,o)))},async countProjectedMembers(o,y){(0,G.assertTenantId)(o,"countProjectedMembers(tenantId)"),await s();let[h]=await u.select({n:(0,c.count)()}).from(I).innerJoin(l,(0,c.and)((0,c.eq)(l.id,I.groupId),(0,c.eq)(l.tenantId,o))).where((0,c.eq)(I.groupId,y));return Number(h?.n??0)}}}var we=require("@aiquants/auth-core"),re=require("@aiquants/authz-core");async function qe(e,t){let m=(0,re.assertTenantId)(t.tenantId,"promoteLedgerForUser(tenantId)");if(!Number.isInteger(t.userId)||t.userId<=0)throw new TypeError(`[@aiquants/auth-directory-drizzle] userId must be a positive integer, received ${String(t.userId)}`);let d=(0,we.normalizeAuthEmail)(t.email),u=t.externalUserId?.trim()||null;if(u===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:m,externalUserId:u,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let a of s)(0,re.assertTenantMatch)(m,a.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let b=[...new Set(s.map(a=>a.ledgerId))].sort((a,g)=>a-g),f=[...new Set(s.map(a=>a.groupId))].sort((a,g)=>a-g);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:m,userId:t.userId,ledgerIds:b,groupIds:f})].sort((a,g)=>a-g),matchedLedgerIds:b}}var ue=require("@aiquants/authz-core"),P=require("drizzle-orm");var Oe=4,Ue=100;function ze(e,t,m,d){let u=e,s=d.ensureConnection??(async()=>{}),b=d.now??(()=>new Date),f=t.TDGroupMemberEmail,{TMGroup:S,TDGroupMember:a}=m;return{async findLedgerMatches(g){return(0,ue.assertTenantId)(g.tenantId,"findLedgerMatches(tenantId)"),await s(),j(u,{ledger:f,group:S},g)},async attachUserToGroups({tenantId:g,userId:r,ledgerIds:n,groupIds:i}){return(0,ue.assertTenantId)(g,"attachUserToGroups(tenantId)"),await s(),n.length===0?[]:u.transaction(async l=>{let I=new Set;for(let o of L(i)){let y=await l.select({id:S.id}).from(S).where((0,P.and)((0,P.inArray)(S.id,o),(0,P.eq)(S.tenantId,g),(0,P.eq)(S.isActive,!0)));for(let h of y)I.add(Number(h.id))}let w=i.filter(o=>I.has(o));if(w.length===0)return[];let E=new Set;for(let o of L(w)){let y=await l.select({groupId:a.groupId}).from(a).where((0,P.and)((0,P.eq)(a.userId,r),(0,P.inArray)(a.groupId,o)));for(let h of y)E.add(Number(h.groupId))}let T=w.filter(o=>!E.has(o)),k=Math.max(1,2100-Ue-w.length);for(let o of L(n,k))await l.update(f).set({resolvedUserId:r}).output().where((0,P.and)((0,P.inArray)(f.id,o),(0,P.inArray)(f.groupId,w)));if(T.length>0){let o=b();for(let y of N(T,Oe))await l.insert(a).output().values(y.map(h=>({groupId:h,userId:r,createdAt:o,createdBy:d.actor})))}return T})}}}var O=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),te="provider",Be=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function Fe(e,t){let m=(0,p.mssqlSchema)(e),d=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50}),updatedAt:(0,p.datetime2)("updated_at"),updatedBy:(0,p.nvarchar)("updated_by",{length:50})}),u=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50})}),s=m.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(te,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),membershipMode:(0,p.nvarchar)("membership_mode",{length:16}).notNull(),isSyncEnabled:(0,p.bit)("is_sync_enabled").notNull(),syncedAt:(0,p.datetime2)("synced_at"),syncError:(0,p.nvarchar)("sync_error",{length:1e3}),vanishedObservations:(0,p.int)("vanished_observations").notNull(),...d()},i=>[(0,p.primaryKey)({name:`${e}_TDGroupExternalLink_pk`,columns:[i.id]}),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_group_uq`).on(i.groupId),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_external_uq`).on(i.tenantId,i.provider,i.externalId),(0,p.foreignKey)({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[i.groupId],foreignColumns:[t.groupTable.id]})]),b=m.table("TDGroupMemberEmail",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),email:(0,p.nvarchar)("email",{length:320}).notNull(),externalUserId:(0,p.nvarchar)("external_user_id",{length:128}),resolvedUserId:(0,p.bigint)("resolved_user_id",{mode:"number"}),observedAt:(0,p.datetime2)("observed_at").notNull(),...d()},i=>[(0,p.primaryKey)({name:`${e}_TDGroupMemberEmail_pk`,columns:[i.id]}),(0,p.uniqueIndex)(`${e}_TDGroupMemberEmail_group_email_uq`).on(i.groupId,i.email),(0,p.foreignKey)({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[i.groupId],foreignColumns:[t.groupTable.id]})]),f=m.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),isBreakGlass:(0,p.bit)("is_break_glass").notNull(),...u()},i=>[(0,p.primaryKey)({name:`${e}_TDTenantMembershipGroup_pk`,columns:[i.tenantId,i.groupId]}),(0,p.foreignKey)({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[i.groupId],foreignColumns:[t.groupTable.id]})]),S=m.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...u()},i=>[(0,p.primaryKey)({name:`${e}_TDAllowlistGroup_pk`,columns:[i.id]}),(0,p.uniqueIndex)(`${e}_TDAllowlistGroup_tenant_group_uq`).on(i.tenantId,i.groupId),(0,p.foreignKey)({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[i.groupId],foreignColumns:[t.groupTable.id]})]),a=m.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}),requestedBy:(0,p.nvarchar)("requested_by",{length:320}).notNull(),requestedAt:(0,p.datetime2)("requested_at").notNull(),pickedAt:(0,p.datetime2)("picked_at"),finishedAt:(0,p.datetime2)("finished_at"),result:(0,p.nvarchar)("result",{length:32}),error:(0,p.nvarchar)("error",{length:2e3}),...u()},i=>[(0,p.primaryKey)({name:`${e}_TDDirectorySyncRequest_pk`,columns:[i.id]})]),g=m.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),pattern:(0,p.nvarchar)("pattern",{length:256}).notNull(),type:(0,p.nvarchar)("type",{length:32}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...u()},i=>[(0,p.primaryKey)({name:`${e}_TMAllowlist_pk`,columns:[i.id]}),(0,p.uniqueIndex)(`${e}_TMAllowlist_pattern_uq`).on(i.tenantId,i.pattern)]),r=m.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(te,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),name:(0,p.nvarchar)("name",{length:256}).notNull(),description:(0,p.nvarchar)("description",{length:1e3}),memberCount:(0,p.int)("member_count"),observedAt:(0,p.datetime2)("observed_at").notNull(),...u()},i=>[(0,p.primaryKey)({name:`${e}_TDDirectoryCatalog_pk`,columns:[i.id]}),(0,p.uniqueIndex)(`${e}_TDDirectoryCatalog_external_uq`).on(i.tenantId,i.provider,i.externalId)]),n=m.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(O.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(te,{length:32}).notNull(),observedAt:(0,p.datetime2)("observed_at").notNull(),entryCount:(0,p.int)("entry_count").notNull(),...d()},i=>[(0,p.primaryKey)({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[i.id]}),(0,p.uniqueIndex)(`${e}_TMDirectoryCatalogObservation_target_uq`).on(i.tenantId,i.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:b,TDTenantMembershipGroup:f,TDAllowlistGroup:S,TDDirectorySyncRequest:a,TDDirectoryCatalog:r,TMDirectoryCatalogObservation:n,TMAllowlist:g}}var oe=require("@aiquants/auth-directory-core");function W(e){if(e instanceof Error&&(e.name==="AbortError"||e.name==="TimeoutError"))return"timeout";let t=typeof e=="object"&&e!==null&&"status"in e?e.status:void 0;return typeof t!="number"?"unknown":t===401||t===403?"denied":t===429||t===408?"rateLimited":t>=500?"upstreamError":"unknown"}var Ie=e=>`${e.length} \u4EF6`,de={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"},le={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${de[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${de[e]})\u3002`,resolverIncomplete:(e,t)=>`\u4E0A\u6D41\u304C ${e} \u4EF6\u306E\u30E1\u30F3\u30D0\u30FC\u3092\u8FD4\u3057\u307E\u3057\u305F\u304C\u3001\u7A81\u5408\u7D50\u679C\u306F ${t} \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,t,m)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093 (${e})\u3002\u9023\u7D9A ${t} \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:(e,t)=>`\u4E0A\u6D41\u3067\u30B0\u30EB\u30FC\u30D7\u304C ${t} \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 ${Ie(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 t=e.axis==="membership"?"\u6A29\u9650\u3092\u5931\u3046\u4EBA\u6570":"\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308B\u4EBA\u6570",m=e.axis==="membership"?e.withheldProjectionRemoves.map(String):e.withheldLedgerDeletes;return[`\u540C\u671F\u3092\u4E2D\u65AD\u3057\u307E\u3057\u305F: ${t}\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: ${Ie(m)} (\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(t=>de[t.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(t=>t.userId).join(", ")})\u3002\u6B8B\u308A\u306E\u5909\u66F4\u306F\u9069\u7528\u6E08\u307F\u3067\u3059\u3002`,foreignProvider:(e,t)=>`\u3053\u306E\u30EA\u30F3\u30AF\u306E\u4F9B\u7D66\u5143\u306F ${e} \u3067\u3059\u304C\u3001\u5B9F\u884C\u4E2D\u306E\u540C\u671F\u306F ${t} \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 ne=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],ce=[...ne,"already-vanished"];function je(e){let{directory:t,repository:m,policy:d,now:u,logger:s}=e,b=e.messages??le;if((0,oe.assertBlastRadiusPolicy)(d),!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 f(r,n){try{await m.recordFailure(r,n)}catch(i){s.error(`[auth-directory] could not record the failure for ${r.externalKey}:`,i)}}async function S(r){try{let n=await t.getGroup(r.externalId);return n===null?{kind:"missing"}:{kind:"ok",group:n,members:await t.listGroupMembers(n,r.membershipMode)}}catch(n){return{kind:"failed",error:n instanceof Error?n:new Error(String(n))}}}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 n=await m.recordVanishedObservation(r,b.groupMissing(r.externalKey,r.vanishedObservations+1,e.vanishedThreshold));return n<e.vanishedThreshold?(s.warn(`[auth-directory] ${r.externalKey} missing upstream (${n}/${e.vanishedThreshold})`),{kind:"missing",link:r,observations:n}):(await m.markGroupInactive(r),await f(r,b.groupVanished(r.externalKey,n)),s.warn(`[auth-directory] ${r.externalKey} deactivated after ${n} consecutive absences`),{kind:"vanished",link:r})}async function g(r){if(r.provider!==e.provider)return await f(r,b.foreignProvider(r.provider,e.provider)),s.warn(`[auth-directory] skipping ${r.externalKey}: belongs to provider ${r.provider}`),{kind:"foreign-provider",link:r};let n=await S(r);if(n.kind==="missing")return a(r);if(n.kind==="failed")return await f(r,b.upstreamUnreadable(W(n.error))),s.error(`[auth-directory] upstream read failed for ${r.externalKey}:`,n.error),{kind:"upstream-failed",link:r,error:n.error};try{await m.resolveExternalIdentity(r,n.group);let[i,l]=await Promise.all([m.loadGroupSnapshot(r),m.resolveMembers(n.members)]);if(l.length!==n.members.length){let k=b.resolverIncomplete(n.members.length,l.length);return await f(r,k),s.error(`[auth-directory] ${k}`),{kind:"local-failed",link:r,error:new Error(k)}}let I=(0,oe.reconcile)(i,l,d);if(I.aborted){await f(r,b.planRefused(I.aborted));let k=I.aborted.reason==="invalid-upstream-data"?I.aborted.offendingEmails:I.aborted.axis==="membership"?I.aborted.withheldProjectionRemoves.map(String):I.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${r.externalKey}: ${I.aborted.reason}; withheld: ${k.join(", ")}`),{kind:"aborted",link:r,plan:I}}let w=I.ledgerUpserts.length>0||I.ledgerDeletes.length>0||I.projectionAdds.length>0||I.projectionRemoves.length>0,E=u(),T={refusals:[],failures:[]};return w&&(T=await m.applyPlan(r,I,E)),await m.recordSuccess(r,n.group,E),T.refusals.length>0&&(await f(r,b.removalsRefused(T.refusals)),s.warn(`[auth-directory] ${T.refusals.length} removal(s) refused for ${r.externalKey}: users ${T.refusals.map(k=>k.userId).join(", ")}`)),T.failures.length>0&&(await f(r,b.removalWritesFailed(T.failures)),s.warn(`[auth-directory] ${T.failures.length} removal(s) could not be written for ${r.externalKey}: users ${T.failures.map(k=>k.userId).join(", ")}`)),I.unresolvedEmails.length>0&&s.info(`[auth-directory] ${r.externalKey}: ${I.unresolvedEmails.length} upstream member(s) have no local user yet`),T.refusals.length>0||T.failures.length>0?{kind:"not-converged",link:r,plan:I,refusals:T.refusals,failures:T.failures}:w?{kind:"applied",link:r,plan:I}:{kind:"unchanged",link:r}}catch(i){let l=i instanceof Error?i:new Error(String(i));return await f(r,b.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${r.externalKey}:`,l),{kind:"local-failed",link:r,error:l}}}return{syncOne:g,async run(r){let n=await m.listSyncableLinks(r),i=[];for(let w of n)try{i.push(await g(w))}catch(E){let T=E instanceof Error?E:new Error(String(E));s.error(`[auth-directory] ${w.externalKey} ended abnormally:`,T),i.push({kind:"local-failed",link:w,error:T})}let l=i.filter(w=>ce.includes(w.kind)).length,I=i.filter(w=>ne.includes(w.kind)).length;return s.info(`[auth-directory] run finished: ${i.length} link(s), ${l} unhealthy (${I} new)`),{outcomes:i,unhealthyCount:l,newlyUnhealthyCount:I}}}}var Se=require("@aiquants/authz-core"),M=require("drizzle-orm");var ve="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",pe=2e3;function We(e,t,m){let d=e,u=m.ensureConnection??(async()=>{}),s=t.TDDirectorySyncRequest,b=(0,Se.assertTenantId)(m.tenantId,"createSyncRequestQueue(tenantId)"),f=m.maxErrorLength??pe;if(!Number.isInteger(f)||f<1||f>pe)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${pe}, received ${String(m.maxErrorLength)}`);let S={async claim(){await u();let a=await d.select({id:s.id}).from(s).where((0,M.and)((0,M.eq)(s.tenantId,b),(0,M.isNull)(s.pickedAt))).orderBy((0,M.asc)(s.id));for(let g of a){let n=(await d.update(s).set({pickedAt:m.now()}).output().where((0,M.and)((0,M.eq)(s.tenantId,b),(0,M.eq)(s.id,Number(g.id)),(0,M.isNull)(s.pickedAt))))?.[0];if(!n)continue;if(n.requestedBy===void 0||n.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(n.id)}`);let i=n.groupId??null;return{id:Number(n.id),tenantId:b,groupId:i===null?null:Number(i),requestedBy:String(n.requestedBy)}}return null},async finish(a,g){await u();let r=g.error?g.error.slice(0,f):null;await d.update(s).set({finishedAt:m.now(),result:g.result,error:r}).output().where((0,M.and)((0,M.eq)(s.tenantId,b),(0,M.eq)(s.id,a),(0,M.isNull)(s.finishedAt)))},async listStranded(a){await u();let g=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where((0,M.and)((0,M.eq)(s.tenantId,b),(0,M.isNull)(s.finishedAt))).orderBy((0,M.asc)(s.id)),r=n=>{let i=K(n);return Number.isNaN(i.getTime())||i<a};return{stranded:g.filter(n=>n.pickedAt!==null&&r(n.pickedAt)).map(n=>Number(n.id)),unclaimed:g.filter(n=>n.pickedAt===null&&r(n.requestedAt)).map(n=>Number(n.id))}},async abandonStranded(a){let{stranded:g}=await S.listStranded(a),r=[];for(let n of g)try{await S.finish(n,{result:"failed",error:ve}),r.push(n)}catch(i){m.onCloseFailed?.(n,i)}return r}};return S}var H=require("@aiquants/auth-core"),J=require("@aiquants/authz-core"),A=require("drizzle-orm");var He=7,Je=4;function Ve(e){if(!(e instanceof Error))return!1;let t=e;return t.number===void 0&&t.code===void 0&&e.name==="Error"}function Ye(e,t,m,d){let u=e,s=d.ensureConnection??(async()=>{}),b=d.now??(()=>new Date),{TDGroupExternalLink:f,TDGroupMemberEmail:S}=t,{TMGroup:a,TDGroupMember:g}=m,r=d.actor;return{async listSyncableLinks(n){await s();let i=(0,J.assertTenantId)(n.tenantId,"listSyncableLinks(tenantId)"),l=[(0,A.eq)(f.isSyncEnabled,!0),(0,A.eq)(f.tenantId,i)];return n.groupId!==void 0&&l.push((0,A.eq)(f.groupId,n.groupId)),(await u.select({linkId:f.id,groupId:f.groupId,linkTenantId:f.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:f.provider,externalId:f.externalId,externalKey:f.externalKey,membershipMode:f.membershipMode,vanishedObservations:f.vanishedObservations}).from(f).innerJoin(a,(0,A.and)((0,A.eq)(a.id,f.groupId),(0,A.eq)(a.tenantId,i))).where((0,A.and)(...l))).map(w=>((0,J.assertTenantMatch)(String(w.groupTenantId),String(w.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(w.linkId),groupId:Number(w.groupId),groupKey:String(w.groupKey),tenantId:String(w.groupTenantId),provider:String(w.provider),externalId:String(w.externalId),externalKey:String(w.externalKey),membershipMode:z(String(w.membershipMode)),isGroupActive:!!w.isGroupActive,vanishedObservations:Number(w.vanishedObservations??0)}))},async loadGroupSnapshot(n){await s();let[i,l]=await Promise.all([u.select({email:S.email,externalUserId:S.externalUserId,resolvedUserId:S.resolvedUserId}).from(S).where((0,A.eq)(S.groupId,n.groupId)),u.select({userId:g.userId}).from(g).where((0,A.eq)(g.groupId,n.groupId))]);return{ledger:i.map(I=>({email:String(I.email),...I.externalUserId?{externalUserId:String(I.externalUserId)}:{},resolvedUserId:I.resolvedUserId===null?null:Number(I.resolvedUserId)})),projectedUserIds:l.map(I=>Number(I.userId))}},resolveMembers(n){return d.memberResolver.resolve(n)},async applyPlan(n,i,l){(0,J.assertTenantId)(n.tenantId,"applyPlan(link.tenantId)"),await s(),(i.ledgerUpserts.length>0||i.projectionAdds.length>0)&&await u.transaction(async T=>{let k=i.ledgerUpserts.map(h=>({...h,email:(0,H.normalizeAuthEmail)(h.email)})),o=new Map;for(let h of L(k.map(v=>v.email))){let v=await T.select({id:S.id,email:S.email}).from(S).where((0,A.and)((0,A.eq)(S.groupId,n.groupId),(0,A.inArray)(S.email,h)));for(let D of v)o.set(String(D.email),Number(D.id))}let y=k.filter(h=>!o.has(h.email));for(let h of N(y,He))await T.insert(S).output().values(h.map(v=>({groupId:n.groupId,email:v.email,externalUserId:v.externalUserId??null,resolvedUserId:v.resolvedUserId,observedAt:l,createdAt:l,createdBy:r})));for(let h of k){let v=o.get(h.email);v!==void 0&&await T.update(S).set({externalUserId:h.externalUserId??null,resolvedUserId:h.resolvedUserId,observedAt:l,updatedAt:l,updatedBy:r}).output().where((0,A.eq)(S.id,v))}for(let h of N(i.projectionAdds,Je)){let v=new Set((await T.select({userId:g.userId}).from(g).where((0,A.and)((0,A.eq)(g.groupId,n.groupId),(0,A.inArray)(g.userId,h)))).map(x=>Number(x.userId))),D=h.filter(x=>!v.has(x));D.length>0&&await T.insert(g).output().values(D.map(x=>({groupId:n.groupId,userId:x,createdAt:l,createdBy:r})))}});let I=[],w=[];for(let T of i.projectionRemoves)try{await d.lockoutGuard.guardedWrite(n.tenantId,{removeGroupMembership:{groupId:n.groupId,userId:T}},"remove-group-member",k=>k.delete(g).where((0,A.and)((0,A.eq)(g.groupId,n.groupId),(0,A.eq)(g.userId,T))))}catch(k){Ve(k)?I.push({userId:T,applied:!1,refusal:k.message}):w.push({userId:T,cause:W(k)})}let E=I.map(T=>T.userId);if(i.ledgerDeletes.length>0){let T=Math.max(1,1999-E.length);await u.transaction(async k=>{for(let o of L(i.ledgerDeletes,Math.min(500,T))){let y=E.length>0?(0,A.or)((0,A.isNull)(S.resolvedUserId),(0,A.notInArray)(S.resolvedUserId,E)):void 0;await k.delete(S).where((0,A.and)((0,A.eq)(S.groupId,n.groupId),(0,A.inArray)(S.email,o),...y?[y]:[]))}})}return{refusals:I,failures:w}},async resolveExternalIdentity(n,i){await s(),!(n.externalId===i.externalId&&n.externalKey===(0,H.normalizeAuthEmail)(i.externalKey))&&await u.update(f).set({externalId:i.externalId,externalKey:(0,H.normalizeAuthEmail)(i.externalKey)}).output().where((0,A.and)((0,A.eq)(f.id,n.linkId),(0,A.eq)(f.tenantId,n.tenantId)))},async recordSuccess(n,i,l){await s(),await u.transaction(async I=>{await I.update(f).set({syncedAt:l,syncError:null,vanishedObservations:0,externalId:i.externalId,externalKey:(0,H.normalizeAuthEmail)(i.externalKey),updatedAt:l,updatedBy:r}).output().where((0,A.and)((0,A.eq)(f.id,n.linkId),(0,A.eq)(f.tenantId,n.tenantId))),n.isGroupActive||await I.update(a).set({isActive:!0,updatedAt:l,updatedBy:r}).output().where((0,A.and)((0,A.eq)(a.id,n.groupId),(0,A.eq)(a.tenantId,n.tenantId)))})},async recordFailure(n,i){await s(),await u.update(f).set({syncError:i.slice(0,1e3),updatedAt:b(),updatedBy:r}).output().where((0,A.and)((0,A.eq)(f.id,n.linkId),(0,A.eq)(f.tenantId,n.tenantId)))},async recordVanishedObservation(n,i){await s();let l=n.vanishedObservations+1;return await u.update(f).set({vanishedObservations:l,syncError:i.slice(0,1e3),updatedAt:b(),updatedBy:r}).output().where((0,A.and)((0,A.eq)(f.id,n.linkId),(0,A.eq)(f.tenantId,n.tenantId))),l},async markGroupInactive(n){await s(),await u.update(a).set({isActive:!1,updatedAt:b(),updatedBy:r}).output().where((0,A.and)((0,A.eq)(a.id,n.groupId),(0,A.eq)(a.tenantId,n.tenantId),(0,A.eq)(a.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,MAX_IN_LIST,MAX_PARAMETERS,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,RESERVED_PARAMETERS,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,chunk,chunkByParameters,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createPromotionRepository,createSyncRequestQueue,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,toInstant,withExternalGroupGuards});
|
|
1
|
+
"use strict";var ae=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Re=Object.getOwnPropertyNames;var Ee=Object.prototype.hasOwnProperty;var Ge=(e,n)=>{for(var g in n)ae(e,g,{get:n[g],enumerable:!0})},Me=(e,n,g,d)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of Re(n))!Ee.call(e,l)&&l!==g&&ae(e,l,{get:()=>n[l],enumerable:!(d=ke(n,l))||d.enumerable});return e};var Ce=e=>Me(ae({},"__esModule",{value:!0}),e);var er={};Ge(er,{AUTH_DIRECTORY_PROVIDER_COLUMN:()=>ne,AUTH_DIRECTORY_TENANT_SCOPED_TABLES:()=>We,MEMBERSHIP_MODES:()=>ue,NEWLY_UNHEALTHY_SYNC_OUTCOMES:()=>oe,STRANDED_REASON:()=>Ae,UNHEALTHY_SYNC_OUTCOMES:()=>me,assertGroupMayBeLinked:()=>$e,assertMembershipMode:()=>B,classifySyncFailure:()=>H,createDirectoryCatalogReader:()=>Ke,createDirectoryCatalogWriter:()=>Le,createDirectoryLinkStore:()=>Ue,createDirectorySync:()=>Je,createDirectorySyncRepository:()=>Ze,createPromotionRepository:()=>je,createSyncRequestQueue:()=>Ye,defaultExternalGroupGuardMessage:()=>Oe,defaultSyncMessages:()=>pe,defineAuthDirectorySchema:()=>He,findLedgerRowsForIdentity:()=>W,promoteLedgerForUser:()=>ze,transactionRunner:()=>O,withExternalGroupGuards:()=>Ne});module.exports=Ce(er);var se=require("@aiquants/authz-core"),Q=require("@aiquants/mssql"),_=require("drizzle-orm");var ge=require("@aiquants/mssql");function O(e,n){return g=>(0,ge.runWithTransactionRetry)(d=>e.transaction(l=>d(()=>g(l))),n.transactionRetry)}var _e=10,N={tenantId:64,provider:32,externalId:256,externalKey:320,name:256,description:1e3,actor:50},he=2147483647;function Pe(e){if(e===void 0||!Number.isFinite(e))return null;let n=Math.trunc(e);return n<0?null:n>he?he:n}function F(e,n,g){let d=e.trim();if(d==="")throw new TypeError(`[@aiquants/auth-directory-drizzle] ${g} must not be blank`);if(d.length>n)throw new TypeError(`[@aiquants/auth-directory-drizzle] ${g} exceeds its column length (${n}); truncating it would change which rows it owns`);return d}function Le(e,n,g){let d=e,l=g.ensureConnection??(async()=>{}),s=O(d,g),f=n.TDDirectoryCatalog,v=n.TMDirectoryCatalogObservation,w=b=>{let a=new Map;for(let t of b)a.set(t.externalId,t);return[...a.values()]};return{async replace(b,a,t){await l();let i=F((0,se.assertTenantId)(b,"replace(tenantId)"),N.tenantId,"tenantId"),r=F(a,N.provider,"provider"),h=F(g.actor,N.actor,"actor"),u=g.now(),I=w(t.map(A=>({tenantId:i,provider:r,externalId:A.externalId.slice(0,N.externalId),externalKey:A.externalKey.slice(0,N.externalKey),name:A.name.slice(0,N.name),description:A.description===void 0?null:A.description.slice(0,N.description),memberCount:Pe(A.memberCount),observedAt:u,createdAt:u,createdBy:h})));return await s(async A=>{await A.delete(f).where((0,_.and)((0,_.eq)(f.tenantId,i),(0,_.eq)(f.provider,r)));for(let D of(0,Q.chunkByParameters)(I,_e))await A.insert(f).values(D);await A.delete(v).where((0,_.and)((0,_.eq)(v.tenantId,i),(0,_.eq)(v.provider,r))),await A.insert(v).values([{tenantId:i,provider:r,observedAt:u,entryCount:I.length,createdAt:u,createdBy:h,updatedAt:u,updatedBy:h}])}),I.length}}}function Ke(e,n,g={}){let d=e,l=g.ensureConnection??(async()=>{}),s=n.TDDirectoryCatalog,f=n.TMDirectoryCatalogObservation;return{async read(v,w){await l();let b=F((0,se.assertTenantId)(v,"read(tenantId)"),N.tenantId,"tenantId"),a=F(w,N.provider,"provider"),t=await d.select({observedAt:f.observedAt,entryCount:f.entryCount,externalId:s.externalId,externalKey:s.externalKey,name:s.name,description:s.description,memberCount:s.memberCount}).from(f).leftJoin(s,(0,_.and)((0,_.eq)(s.tenantId,f.tenantId),(0,_.eq)(s.provider,f.provider))).where((0,_.and)((0,_.eq)(f.tenantId,b),(0,_.eq)(f.provider,a))).orderBy((0,_.asc)(s.name)),i=t.filter(u=>u.externalId!==null).map(u=>({externalId:String(u.externalId),externalKey:String(u.externalKey),name:String(u.name),...u.description===null?{}:{description:u.description},...u.memberCount===null?{}:{memberCount:Number(u.memberCount)}})),r=t[0];if(r===void 0)return{groups:i,observedAt:null};if(Number(r.entryCount)!==i.length)throw new Error(`[@aiquants/auth-directory-drizzle] catalog is inconsistent for (${b}, ${a}): the observation records ${r.entryCount} entries but ${i.length} rows are present`);let h=(0,Q.toInstant)(r.observedAt);if(!Number.isFinite(h.getTime()))throw new Error(`[@aiquants/auth-directory-drizzle] catalog observation for (${b}, ${a}) has an unreadable observed_at`);return{groups:i,observedAt:h}}}}var be=require("@aiquants/authz-core"),Oe=(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 z(e,n,g,d){return new Set(e.filter(l=>n.has(l.roleId)&&l.appKey===g&&l.resourceKey===d).map(l=>l.action))}function X(e,n){return new Set(e.filter(g=>g.groupId===n).map(g=>g.roleId))}var j=e=>be.AUTHZ_ACTIONS.every(n=>e.has(n)),ee=(e,n)=>e.find(g=>g.roleId===n)?.roleKey??`#${n}`;function Z(e,n,g,d){let l=[...n].find(f=>j(z(e,new Set([f]),g,d)));return l!==void 0?ee(e,l):[...n].filter(f=>z(e,new Set([f]),g,d).size>0).map(f=>ee(e,f)).join(" + ")}function Ne(e,n){return fe(e,n)}function fe(e,n){let g=e;return{...e,withTransaction:(d,l)=>g.withTransaction((s,f)=>d(fe(s,n),f),l),async assignGroupRole(d,l,s,f){await g.withTransaction(async(v,w)=>{let b=await v.listGrants(d),a=X(await v.listGroupRoles(d),l);if(a.add(s),j(z(b,a,n.adminAppKey,n.adminResourceKey))){let t=await n.linkLookup.externalGroupKey(d,l,w);if(t!==null)throw new Error(n.message(Z(b,a,n.adminAppKey,n.adminResourceKey),t))}await v.assignGroupRole(d,l,s,f)})},async updateGrant(d,l,s,f){let v=s.action;if(v===void 0)return g.updateGrant(d,l,s,f);await g.withTransaction(async(w,b)=>{let a=await w.listGrants(d),t=a.find(h=>h.id===l);if(t===void 0)return w.updateGrant(d,l,s,f);let r=(await w.listResources(d)).find(h=>h.id===t.resourceId);if(r?.appKey===n.adminAppKey&&r?.resourceKey===n.adminResourceKey){let h=await w.listGroupRoles(d);for(let u of new Set(h.filter(I=>I.roleId===t.roleId).map(I=>I.groupId))){let I=X(h,u),A=z(a.filter(x=>x.id!==l),I,n.adminAppKey,n.adminResourceKey);if(I.has(t.roleId)&&A.add(v),!j(A))continue;let D=await n.linkLookup.externalGroupKey(d,u,b);if(D!==null)throw new Error(n.message(Z(a,I,n.adminAppKey,n.adminResourceKey)||ee(a,t.roleId),D))}}await w.updateGrant(d,l,s,f)})},async createGrant(d,l,s){await g.withTransaction(async(f,v)=>{let b=(await f.listResources(d)).find(a=>a.id===l.resourceId);if(b?.appKey===n.adminAppKey&&b?.resourceKey===n.adminResourceKey){let a=await f.listGrants(d),t=await f.listGroupRoles(d);for(let i of new Set(t.filter(r=>r.roleId===l.roleId).map(r=>r.groupId))){let r=z(a,X(t,i),n.adminAppKey,n.adminResourceKey);if(r.add(l.action),!j(r))continue;let h=await n.linkLookup.externalGroupKey(d,i,v);if(h!==null)throw new Error(n.message(Z(a,X(t,i),n.adminAppKey,n.adminResourceKey)||ee(a,l.roleId),h))}}await f.createGrant(d,l,s)})}}}async function $e(e,n,g,d){let l=(await e.listGroupRoles(n)).filter(w=>w.groupId===g);if(l.length===0)return;let s=await e.listGrants(n),f=new Set(l.map(w=>w.roleId));if(!j(z(s,f,d.adminAppKey,d.adminResourceKey)))return;let v=l[0];if(v)throw new Error(d.message(Z(s,f,d.adminAppKey,d.adminResourceKey),v.groupKey))}var $=require("drizzle-orm");async function W(e,n,g){let{ledger:d,group:l}=n,{tenantId:s,externalUserId:f,email:v}=g;if(f===null&&v==="")return[];let w={ledgerId:d.id,groupId:d.groupId,tenantId:l.tenantId,externalUserId:d.externalUserId},b=async h=>(await e.select(w).from(d).innerJoin(l,(0,$.and)((0,$.eq)(l.id,d.groupId),(0,$.eq)(l.tenantId,s),(0,$.eq)(l.isActive,!0))).where(h)).map(I=>({ledgerId:Number(I.ledgerId),groupId:Number(I.groupId),tenantId:String(I.tenantId),externalUserId:I.externalUserId===null?null:String(I.externalUserId)})),a=h=>{let u=new Map;for(let I of h)u.set(I.ledgerId,{ledgerId:I.ledgerId,groupId:I.groupId,tenantId:I.tenantId});return[...u.values()].sort((I,A)=>I.ledgerId-A.ledgerId)},t=f===null?[]:await b((0,$.eq)(d.externalUserId,f));if(v==="")return a(t);let i=await b((0,$.eq)(d.email,v)),r=h=>h.externalUserId!==null&&h.externalUserId!==f;return a([...t,...i.filter(h=>!r(h))])}var re=require("@aiquants/auth-core"),we=require("@aiquants/auth-directory-core"),M=require("@aiquants/authz-core"),de=require("@aiquants/mssql"),c=require("drizzle-orm");var ue=["direct","transitive"];function B(e){if(!ue.includes(e))throw new TypeError(`[@aiquants/auth-directory-drizzle] membershipMode must be one of ${ue.join(" | ")}, received ${JSON.stringify(e)}`);return e}function qe(e){let n=typeof e=="object"&&e!==null&&"number"in e?e.number:void 0;return n===2601||n===2627}function Ue(e,n,g,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),v=d.policyCacheTtlMs??0;if(!Number.isFinite(v)||v<0)throw new TypeError(`[@aiquants/auth-directory-drizzle] policyCacheTtlMs must be a non-negative number, received ${String(d.policyCacheTtlMs)}`);let w=new Map,b=o=>w.delete(o),{TDGroupExternalLink:a,TDGroupMemberEmail:t,TDTenantMembershipGroup:i,TDAllowlistGroup:r,TDDirectorySyncRequest:h}=n,{TMGroup:u,TDGroupMember:I}=g;async function A(o,m,y){if((await o.select({groupId:i.groupId}).from(i).where((0,c.and)((0,c.eq)(i.tenantId,m),(0,c.eq)(i.groupId,y),(0,c.eq)(i.isBreakGlass,!0)))).length>0)throw new Error(`Group ${y} is a break-glass proof group; a break-glass proof path must be one the synchronization can never touch.`)}async function D(o,m,y){let[S]=await o.select({id:u.id}).from(u).where((0,c.and)((0,c.eq)(u.id,y),(0,c.eq)(u.tenantId,m)));if(!S)throw new Error(`Group not found in tenant: ${y}`)}function x(o,m){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:B(String(o.membershipMode)),isSyncEnabled:!!o.isSyncEnabled,...o.syncedAt?{syncedAt:(0,de.toInstant)(o.syncedAt).toISOString()}:{},...o.syncError?{syncError:String(o.syncError)}:{},unresolvedCount:m}}async function P(o,m){let y=await o.select({groupId:t.groupId,n:(0,c.count)()}).from(t).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.tenantId,m))).where((0,c.isNull)(t.resolvedUserId)).groupBy(t.groupId);return new Map(y.map(S=>[Number(S.groupId),Number(S.n)]))}return{async listLinks(o){(0,M.assertTenantId)(o,"listLinks(tenantId)"),await s();let m=await l.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((0,c.eq)(a.tenantId,o)).orderBy((0,c.asc)(a.groupId)),y=await P(l,o);return m.map(S=>((0,M.assertTenantMatch)(o,String(S.tenantId),"listLinks(row.tenantId)"),x(S,y.get(Number(S.groupId))??0)))},async externalGroupKey(o,m,y){(0,M.assertTenantId)(o,"externalGroupKey(tenantId)"),await s();let[S]=await(y??l).select({externalKey:a.externalKey}).from(a).where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)));return S?String(S.externalKey):null},async linkGroup(o,m,y,S){(0,M.assertTenantId)(o,"linkGroup(tenantId)");let k=B(y.membershipMode),R=y.externalId.trim(),C=(0,re.normalizeAuthEmail)(y.externalKey);if(R===""||C==="")throw new TypeError("[@aiquants/auth-directory-drizzle] externalId and externalKey must be non-empty");await s(),await d.linkable.assertLinkableAndWrite(o,m,async V=>{let E=V;await D(E,o,m),await A(E,o,m),await E.insert(a).output().values({groupId:m,tenantId:o,provider:y.provider,externalId:R,externalKey:C,membershipMode:k,isSyncEnabled:!0,vanishedObservations:0,createdAt:new Date,createdBy:S})}),b(o)},async unlinkGroup(o,m){(0,M.assertTenantId)(o,"unlinkGroup(tenantId)"),await s(),await f(async y=>{await D(y,o,m),await y.delete(t).where((0,c.eq)(t.groupId,m)),await y.delete(r).where((0,c.and)((0,c.eq)(r.groupId,m),(0,c.eq)(r.tenantId,o))),await y.delete(a).where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)))}),b(o)},async setSyncEnabled(o,m,y,S){(0,M.assertTenantId)(o,"setSyncEnabled(tenantId)"),await s(),await l.update(a).set({isSyncEnabled:y,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)))},async requestSync(o,m,y){return(0,M.assertTenantId)(o,"requestSync(tenantId)"),await s(),l.transaction(async S=>{if(m!==null&&await D(S,o,m),(await S.select({id:h.id}).from(h).where((0,c.and)((0,c.eq)(h.tenantId,o),m===null?(0,c.isNull)(h.groupId):(0,c.eq)(h.groupId,m),(0,c.isNull)(h.pickedAt)))).length>0)return"already-pending";try{await S.insert(h).output().values({tenantId:o,groupId:m,requestedBy:y,requestedAt:new Date,createdAt:new Date,createdBy:y})}catch(R){if(!qe(R))throw R;return"already-pending"}return"queued"})},async getStatus(o){(0,M.assertTenantId)(o,"getStatus(tenantId)"),await s();let m=await l.select({groupId:a.groupId,isSyncEnabled:a.isSyncEnabled,syncedAt:a.syncedAt,syncError:a.syncError}).from(a).where((0,c.eq)(a.tenantId,o)),[y]=await l.select({n:(0,c.count)()}).from(h).where((0,c.and)((0,c.eq)(h.tenantId,o),(0,c.isNull)(h.finishedAt))),S=m.filter(E=>!!E.isSyncEnabled),k=S.map(E=>E.syncedAt?(0,de.toInstant)(E.syncedAt).getTime():null),R=k.length>0&&k.every(E=>E!==null)?Math.min(...k):null,V=m.length>0&&S.length===0||S.length>0&&(R===null||Date.now()-R>d.stalenessThresholdSec*1e3);return{isEnabled:d.isEnabled,provider:d.provider,linkedGroupCount:m.length,...R!==null?{oldestSyncedAt:new Date(R).toISOString()}:{},isStale:V,pausedGroupCount:m.length-S.length,stalenessThresholdSec:d.stalenessThresholdSec,erroredGroupIds:m.filter(E=>!!E.syncError).map(E=>Number(E.groupId)).sort((E,xe)=>E-xe),pendingRequestCount:Number(y?.n??0)}},async loadTenantMembershipPolicy(o){(0,M.assertTenantId)(o,"loadTenantMembershipPolicy(tenantId)");let m=v>0?w.get(o):void 0;if(m&&Date.now()-m.loadedAt<v)return m.policy;await s();let y=await l.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass,externalLinkId:a.id}).from(i).innerJoin(u,(0,c.and)((0,c.eq)(u.id,i.groupId),(0,c.eq)(u.tenantId,o))).leftJoin(a,(0,c.and)((0,c.eq)(a.groupId,i.groupId),(0,c.eq)(a.tenantId,o))).where((0,c.eq)(i.tenantId,o));if(y.length===0){let C={kind:"no-proof-required"};return v>0&&w.set(o,{policy:C,loadedAt:Date.now()}),C}let S=y.map(C=>Number(C.groupId)),k=y.filter(C=>!!C.isBreakGlass);for(let C of k)if(C.externalLinkId!==null)throw new Error(`Break-glass group ${Number(C.groupId)} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`);let R={kind:"require-proof",proofGroupIds:S,breakGlassGroupIds:k.map(C=>Number(C.groupId))};return(0,we.assertTenantMembershipPolicy)(R,`tenant ${o}`),v>0&&w.set(o,{policy:R,loadedAt:Date.now()}),R},async setTenantMembershipGroup(o,m,y,S){(0,M.assertTenantId)(o,"setTenantMembershipGroup(tenantId)"),await s(),await f(async k=>{if(await D(k,o,m),y){let[C]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.tenantId,o),(0,c.eq)(a.groupId,m)));if(C)throw new Error(`Group ${m} is externally sourced; a break-glass proof path must be one the synchronization can never touch.`)}let R=await k.select({groupId:i.groupId}).from(i).where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.eq)(i.groupId,m)));if(!y&&!(await k.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where((0,c.eq)(i.tenantId,o))).filter(E=>Number(E.groupId)!==m).some(E=>!!E.isBreakGlass))throw new Error(`Declaring group ${m} 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(R.length>0){await k.update(i).set({isBreakGlass:y,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.eq)(i.groupId,m)));return}await k.insert(i).output().values({tenantId:o,groupId:m,isBreakGlass:y,createdAt:new Date,createdBy:S})}),b(o)},async removeTenantMembershipGroup(o,m){(0,M.assertTenantId)(o,"removeTenantMembershipGroup(tenantId)"),await s(),await f(async y=>{let k=(await y.select({groupId:i.groupId,isBreakGlass:i.isBreakGlass}).from(i).where((0,c.eq)(i.tenantId,o))).filter(R=>Number(R.groupId)!==m);if(k.length>0&&!k.some(R=>!!R.isBreakGlass))throw new Error(`Removing group ${m} would leave tenant ${o} with a membership proof but no break-glass path.`);await y.delete(i).where((0,c.and)((0,c.eq)(i.tenantId,o),(0,c.eq)(i.groupId,m)))}),b(o)},async setGroupActive(o,m,y,S){(0,M.assertTenantId)(o,"setGroupActive(tenantId)"),await s(),await l.update(u).set({isActive:y,updatedAt:new Date,updatedBy:S}).output().where((0,c.and)((0,c.eq)(u.id,m),(0,c.eq)(u.tenantId,o)))},async listLedgerGroupIds(o,m){if((0,M.assertTenantId)(o,"listLedgerGroupIds(tenantId)"),!d.isEnabled)return[];await s();let y=await W(l,{ledger:t,group:u},{tenantId:o,externalUserId:m.externalUserId?.trim()||null,email:(0,re.normalizeAuthEmail)(m.email)});return[...new Set(y.map(S=>S.groupId))].sort((S,k)=>S-k)},async isEmailAllowedByGroup(o){if(!d.isEnabled)return!1;let m=(0,re.normalizeAuthEmail)(o);if(m==="")return!1;await s();let[y]=await l.select({id:t.id}).from(t).innerJoin(r,(0,c.eq)(r.groupId,t.groupId)).innerJoin(u,(0,c.and)((0,c.eq)(u.id,t.groupId),(0,c.eq)(u.isActive,!0))).where((0,c.eq)(t.email,m));return!!y},async listAllowlistGroups(o){return(0,M.assertTenantId)(o,"listAllowlistGroups(tenantId)"),await s(),(await l.select({id:r.id,groupId:r.groupId,groupKey:u.groupKey,groupName:u.name,description:r.description}).from(r).innerJoin(u,(0,c.and)((0,c.eq)(u.id,r.groupId),(0,c.eq)(u.tenantId,o))).where((0,c.eq)(r.tenantId,o)).orderBy((0,c.asc)(u.name))).map(y=>({id:Number(y.id),type:"group",groupId:Number(y.groupId),groupKey:String(y.groupKey),groupName:String(y.groupName),...y.description?{description:String(y.description)}:{},owner:{kind:"tenant",tenantId:o},readOnly:!1}))},async addAllowlistGroup(o,m,y,S){(0,M.assertTenantId)(o,"addAllowlistGroup(tenantId)"),await s(),await f(async k=>{await D(k,o,m);let[R]=await k.select({id:a.id}).from(a).where((0,c.and)((0,c.eq)(a.groupId,m),(0,c.eq)(a.tenantId,o)));if(!R)throw new Error(`Group ${m} is locally managed; only externally-sourced groups can gate sign-in, because the decision reads the upstream member ledger.`);await k.insert(r).output().values({tenantId:o,groupId:m,description:y??null,createdAt:new Date,createdBy:S})})},async removeAllowlistGroup(o,m){(0,M.assertTenantId)(o,"removeAllowlistGroup(tenantId)"),await s(),await l.delete(r).where((0,c.and)((0,c.eq)(r.id,m),(0,c.eq)(r.tenantId,o)))},async countProjectedMembers(o,m){(0,M.assertTenantId)(o,"countProjectedMembers(tenantId)"),await s();let[y]=await l.select({n:(0,c.count)()}).from(I).innerJoin(u,(0,c.and)((0,c.eq)(u.id,I.groupId),(0,c.eq)(u.tenantId,o))).where((0,c.eq)(I.groupId,m));return Number(y?.n??0)}}}var Ie=require("@aiquants/auth-core"),te=require("@aiquants/authz-core");async function ze(e,n){let g=(0,te.assertTenantId)(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 d=(0,Ie.normalizeAuthEmail)(n.email),l=n.externalUserId?.trim()||null;if(l===null&&d==="")return{addedGroupIds:[],matchedLedgerIds:[]};let s=await e.findLedgerMatches({tenantId:g,externalUserId:l,email:d});if(s.length===0)return{addedGroupIds:[],matchedLedgerIds:[]};for(let b of s)(0,te.assertTenantMatch)(g,b.tenantId,"promoteLedgerForUser(findLedgerMatches row)");let f=[...new Set(s.map(b=>b.ledgerId))].sort((b,a)=>b-a),v=[...new Set(s.map(b=>b.groupId))].sort((b,a)=>b-a);return{addedGroupIds:[...await e.attachUserToGroups({tenantId:g,userId:n.userId,ledgerIds:f,groupIds:v})].sort((b,a)=>b-a),matchedLedgerIds:f}}var le=require("@aiquants/authz-core"),q=require("@aiquants/mssql"),L=require("drizzle-orm");var Be=4,Fe=100;function je(e,n,g,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),v=d.now??(()=>new Date),w=n.TDGroupMemberEmail,{TMGroup:b,TDGroupMember:a}=g;return{async findLedgerMatches(t){return(0,le.assertTenantId)(t.tenantId,"findLedgerMatches(tenantId)"),await s(),W(l,{ledger:w,group:b},t)},async attachUserToGroups({tenantId:t,userId:i,ledgerIds:r,groupIds:h}){return(0,le.assertTenantId)(t,"attachUserToGroups(tenantId)"),await s(),r.length===0?[]:f(async u=>{let I=new Set;for(let o of(0,q.chunk)(h)){let m=await u.select({id:b.id}).from(b).where((0,L.and)((0,L.inArray)(b.id,o),(0,L.eq)(b.tenantId,t),(0,L.eq)(b.isActive,!0)));for(let y of m)I.add(Number(y.id))}let A=h.filter(o=>I.has(o));if(A.length===0)return[];let D=new Set;for(let o of(0,q.chunk)(A)){let m=await u.select({groupId:a.groupId}).from(a).where((0,L.and)((0,L.eq)(a.userId,i),(0,L.inArray)(a.groupId,o)));for(let y of m)D.add(Number(y.groupId))}let x=A.filter(o=>!D.has(o)),P=Math.max(1,q.MAX_PARAMETERS-Fe-A.length);for(let o of(0,q.chunk)(r,P))await u.update(w).set({resolvedUserId:i}).output().where((0,L.and)((0,L.inArray)(w.id,o),(0,L.inArray)(w.groupId,A)));if(x.length>0){let o=v();for(let m of(0,q.chunkByParameters)(x,Be))await u.insert(a).output().values(m.map(y=>({groupId:y,userId:i,createdAt:o,createdBy:d.actor})))}return x})}}}var U=require("@aiquants/authz-drizzle"),p=require("drizzle-orm/mssql-core"),ne="provider",We=["TDGroupExternalLink","TDTenantMembershipGroup","TDAllowlistGroup","TDDirectorySyncRequest","TDDirectoryCatalog","TMDirectoryCatalogObservation","TMAllowlist"];function He(e,n){let g=(0,p.mssqlSchema)(e),d=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50}),updatedAt:(0,p.datetime2)("updated_at"),updatedBy:(0,p.nvarchar)("updated_by",{length:50})}),l=()=>({createdAt:(0,p.datetime2)("created_at"),createdBy:(0,p.nvarchar)("created_by",{length:50})}),s=g.table("TDGroupExternalLink",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),membershipMode:(0,p.nvarchar)("membership_mode",{length:16}).notNull(),isSyncEnabled:(0,p.bit)("is_sync_enabled").notNull(),syncedAt:(0,p.datetime2)("synced_at"),syncError:(0,p.nvarchar)("sync_error",{length:1e3}),vanishedObservations:(0,p.int)("vanished_observations").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupExternalLink_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_group_uq`).on(r.groupId),(0,p.uniqueIndex)(`${e}_TDGroupExternalLink_external_uq`).on(r.tenantId,r.provider,r.externalId),(0,p.foreignKey)({name:`${e}_TDGroupExternalLink_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),f=g.table("TDGroupMemberEmail",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),email:(0,p.nvarchar)("email",{length:320}).notNull(),externalUserId:(0,p.nvarchar)("external_user_id",{length:128}),resolvedUserId:(0,p.bigint)("resolved_user_id",{mode:"number"}),observedAt:(0,p.datetime2)("observed_at").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TDGroupMemberEmail_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDGroupMemberEmail_group_email_uq`).on(r.groupId,r.email),(0,p.foreignKey)({name:`${e}_TDGroupMemberEmail_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),v=g.table("TDTenantMembershipGroup",{tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),isBreakGlass:(0,p.bit)("is_break_glass").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDTenantMembershipGroup_pk`,columns:[r.tenantId,r.groupId]}),(0,p.foreignKey)({name:`${e}_TDTenantMembershipGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),w=g.table("TDAllowlistGroup",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDAllowlistGroup_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDAllowlistGroup_tenant_group_uq`).on(r.tenantId,r.groupId),(0,p.foreignKey)({name:`${e}_TDAllowlistGroup_group_id_fk`,columns:[r.groupId],foreignColumns:[n.groupTable.id]})]),b=g.table("TDDirectorySyncRequest",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),groupId:(0,p.bigint)("group_id",{mode:"number"}),requestedBy:(0,p.nvarchar)("requested_by",{length:320}).notNull(),requestedAt:(0,p.datetime2)("requested_at").notNull(),pickedAt:(0,p.datetime2)("picked_at"),finishedAt:(0,p.datetime2)("finished_at"),result:(0,p.nvarchar)("result",{length:32}),error:(0,p.nvarchar)("error",{length:2e3}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectorySyncRequest_pk`,columns:[r.id]})]),a=g.table("TMAllowlist",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),pattern:(0,p.nvarchar)("pattern",{length:256}).notNull(),type:(0,p.nvarchar)("type",{length:32}).notNull(),description:(0,p.nvarchar)("description",{length:512}),...l()},r=>[(0,p.primaryKey)({name:`${e}_TMAllowlist_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMAllowlist_pattern_uq`).on(r.tenantId,r.pattern)]),t=g.table("TDDirectoryCatalog",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),externalId:(0,p.nvarchar)("external_id",{length:256}).notNull(),externalKey:(0,p.nvarchar)("external_key",{length:320}).notNull(),name:(0,p.nvarchar)("name",{length:256}).notNull(),description:(0,p.nvarchar)("description",{length:1e3}),memberCount:(0,p.int)("member_count"),observedAt:(0,p.datetime2)("observed_at").notNull(),...l()},r=>[(0,p.primaryKey)({name:`${e}_TDDirectoryCatalog_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TDDirectoryCatalog_external_uq`).on(r.tenantId,r.provider,r.externalId)]),i=g.table("TMDirectoryCatalogObservation",{id:(0,p.bigint)("id",{mode:"number"}).identity().notNull(),tenantId:(0,p.nvarchar)(U.AUTHZ_TENANT_COLUMN,{length:64}).notNull(),provider:(0,p.nvarchar)(ne,{length:32}).notNull(),observedAt:(0,p.datetime2)("observed_at").notNull(),entryCount:(0,p.int)("entry_count").notNull(),...d()},r=>[(0,p.primaryKey)({name:`${e}_TMDirectoryCatalogObservation_pk`,columns:[r.id]}),(0,p.uniqueIndex)(`${e}_TMDirectoryCatalogObservation_target_uq`).on(r.tenantId,r.provider)]);return{TDGroupExternalLink:s,TDGroupMemberEmail:f,TDTenantMembershipGroup:v,TDAllowlistGroup:w,TDDirectorySyncRequest:b,TDDirectoryCatalog:t,TMDirectoryCatalogObservation:i,TMAllowlist:a}}var ie=require("@aiquants/auth-directory-core");function H(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 ve=e=>`${e.length} \u4EF6`,ce={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"},pe={upstreamUnreadable:e=>`\u4E0A\u6D41\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ce[e]})\u3002`,localWriteFailed:e=>`\u30ED\u30FC\u30AB\u30EB\u3078\u306E\u66F8\u304D\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F (${ce[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,g)=>`\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${g} \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 ${ve(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",g=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: ${ve(g)} (\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=>ce[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 oe=["aborted","missing","vanished","upstream-failed","local-failed","foreign-provider","not-converged"],me=[...oe,"already-vanished"];function Je(e){let{directory:n,repository:g,policy:d,now:l,logger:s}=e,f=e.messages??pe;if((0,ie.assertBlastRadiusPolicy)(d),!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 v(t,i){try{await g.recordFailure(t,i)}catch(r){s.error(`[auth-directory] could not record the failure for ${t.externalKey}:`,r)}}async function w(t){try{let i=await n.getGroup(t.externalId);return i===null?{kind:"missing"}:{kind:"ok",group:i,members:await n.listGroupMembers(i,t.membershipMode)}}catch(i){return{kind:"failed",error:i instanceof Error?i:new Error(String(i))}}}async function b(t){if(t.vanishedObservations>=e.vanishedThreshold&&!t.isGroupActive)return s.warn(`[auth-directory] ${t.externalKey} still missing upstream (already deactivated after ${t.vanishedObservations} absences)`),{kind:"already-vanished",link:t};let i=await g.recordVanishedObservation(t,f.groupMissing(t.externalKey,t.vanishedObservations+1,e.vanishedThreshold));return i<e.vanishedThreshold?(s.warn(`[auth-directory] ${t.externalKey} missing upstream (${i}/${e.vanishedThreshold})`),{kind:"missing",link:t,observations:i}):(await g.markGroupInactive(t),await v(t,f.groupVanished(t.externalKey,i)),s.warn(`[auth-directory] ${t.externalKey} deactivated after ${i} consecutive absences`),{kind:"vanished",link:t})}async function a(t){if(t.provider!==e.provider)return await v(t,f.foreignProvider(t.provider,e.provider)),s.warn(`[auth-directory] skipping ${t.externalKey}: belongs to provider ${t.provider}`),{kind:"foreign-provider",link:t};let i=await w(t);if(i.kind==="missing")return b(t);if(i.kind==="failed")return await v(t,f.upstreamUnreadable(H(i.error))),s.error(`[auth-directory] upstream read failed for ${t.externalKey}:`,i.error),{kind:"upstream-failed",link:t,error:i.error};try{await g.resolveExternalIdentity(t,i.group);let[r,h]=await Promise.all([g.loadGroupSnapshot(t),g.resolveMembers(i.members)]);if(h.length!==i.members.length){let x=f.resolverIncomplete(i.members.length,h.length);return await v(t,x),s.error(`[auth-directory] ${x}`),{kind:"local-failed",link:t,error:new Error(x)}}let u=(0,ie.reconcile)(r,h,d);if(u.aborted){await v(t,f.planRefused(u.aborted));let x=u.aborted.reason==="invalid-upstream-data"?u.aborted.offendingEmails:u.aborted.axis==="membership"?u.aborted.withheldProjectionRemoves.map(String):u.aborted.withheldLedgerDeletes;return s.warn(`[auth-directory] plan refused for ${t.externalKey}: ${u.aborted.reason}; withheld: ${x.join(", ")}`),{kind:"aborted",link:t,plan:u}}let I=u.ledgerUpserts.length>0||u.ledgerDeletes.length>0||u.projectionAdds.length>0||u.projectionRemoves.length>0,A=l(),D={refusals:[],failures:[]};return I&&(D=await g.applyPlan(t,u,A)),await g.recordSuccess(t,i.group,A),D.refusals.length>0&&(await v(t,f.removalsRefused(D.refusals)),s.warn(`[auth-directory] ${D.refusals.length} removal(s) refused for ${t.externalKey}: users ${D.refusals.map(x=>x.userId).join(", ")}`)),D.failures.length>0&&(await v(t,f.removalWritesFailed(D.failures)),s.warn(`[auth-directory] ${D.failures.length} removal(s) could not be written for ${t.externalKey}: users ${D.failures.map(x=>x.userId).join(", ")}`)),u.unresolvedEmails.length>0&&s.info(`[auth-directory] ${t.externalKey}: ${u.unresolvedEmails.length} upstream member(s) have no local user yet`),D.refusals.length>0||D.failures.length>0?{kind:"not-converged",link:t,plan:u,refusals:D.refusals,failures:D.failures}:I?{kind:"applied",link:t,plan:u}:{kind:"unchanged",link:t}}catch(r){let h=r instanceof Error?r:new Error(String(r));return await v(t,f.localWriteFailed("localWriteError")),s.error(`[auth-directory] local write failed for ${t.externalKey}:`,h),{kind:"local-failed",link:t,error:h}}}return{syncOne:a,async run(t){let i=await g.listSyncableLinks(t),r=[];for(let I of i)try{r.push(await a(I))}catch(A){let D=A instanceof Error?A:new Error(String(A));s.error(`[auth-directory] ${I.externalKey} ended abnormally:`,D),r.push({kind:"local-failed",link:I,error:D})}let h=r.filter(I=>me.includes(I.kind)).length,u=r.filter(I=>oe.includes(I.kind)).length;return s.info(`[auth-directory] run finished: ${r.length} link(s), ${h} unhealthy (${u} new)`),{outcomes:r,unhealthyCount:h,newlyUnhealthyCount:u}}}}var Se=require("@aiquants/authz-core"),Te=require("@aiquants/mssql"),G=require("drizzle-orm"),Ae="The job that claimed this request ended without recording an outcome; whether the synchronization ran is unknown.",ye=2e3;function Ye(e,n,g){let d=e,l=g.ensureConnection??(async()=>{}),s=n.TDDirectorySyncRequest,f=(0,Se.assertTenantId)(g.tenantId,"createSyncRequestQueue(tenantId)"),v=g.maxErrorLength??ye;if(!Number.isInteger(v)||v<1||v>ye)throw new TypeError(`[@aiquants/auth-directory-drizzle] maxErrorLength must be an integer within 1..${ye}, received ${String(g.maxErrorLength)}`);let w={async claim(){await l();let b=await d.select({id:s.id}).from(s).where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.isNull)(s.pickedAt))).orderBy((0,G.asc)(s.id));for(let a of b){let i=(await d.update(s).set({pickedAt:g.now()}).output().where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.eq)(s.id,Number(a.id)),(0,G.isNull)(s.pickedAt))))?.[0];if(!i)continue;if(i.requestedBy===void 0||i.requestedBy===null)throw new TypeError(`[@aiquants/auth-directory-drizzle] the driver returned a row without requestedBy for request ${String(i.id)}`);let r=i.groupId??null;return{id:Number(i.id),tenantId:f,groupId:r===null?null:Number(r),requestedBy:String(i.requestedBy)}}return null},async finish(b,a){await l();let t=a.error?a.error.slice(0,v):null;return!!(await d.update(s).set({finishedAt:g.now(),result:a.result,error:t}).output().where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.eq)(s.id,b),(0,G.isNull)(s.finishedAt))))?.length},async listStranded(b){await l();let a=await d.select({id:s.id,pickedAt:s.pickedAt,requestedAt:s.requestedAt}).from(s).where((0,G.and)((0,G.eq)(s.tenantId,f),(0,G.isNull)(s.finishedAt))).orderBy((0,G.asc)(s.id)),t=i=>{let r=(0,Te.toInstant)(i);return Number.isNaN(r.getTime())||r<b};return{stranded:a.filter(i=>i.pickedAt!==null&&t(i.pickedAt)).map(i=>Number(i.id)),unclaimed:a.filter(i=>i.pickedAt===null&&t(i.requestedAt)).map(i=>Number(i.id))}},async abandonStranded(b){let{stranded:a}=await w.listStranded(b),t=[];for(let i of a)try{await w.finish(i,{result:"failed",error:Ae})&&t.push(i)}catch(r){g.onCloseFailed?.(i,r)}return t}};return w}var J=require("@aiquants/auth-core"),Y=require("@aiquants/authz-core"),K=require("@aiquants/mssql"),T=require("drizzle-orm");var Ve=7,Qe=4,De=1e3;function Xe(e){if(!(e instanceof Error))return!1;let n=e;return n.number===void 0&&n.code===void 0&&e.name==="Error"}function Ze(e,n,g,d){let l=e,s=d.ensureConnection??(async()=>{}),f=O(l,d),v=d.now??(()=>new Date),{TDGroupExternalLink:w,TDGroupMemberEmail:b}=n,{TMGroup:a,TDGroupMember:t}=g,i=d.actor;return{async listSyncableLinks(r){await s();let h=(0,Y.assertTenantId)(r.tenantId,"listSyncableLinks(tenantId)"),u=[(0,T.eq)(w.isSyncEnabled,!0),(0,T.eq)(w.tenantId,h)];return r.groupId!==void 0&&u.push((0,T.eq)(w.groupId,r.groupId)),(await l.select({linkId:w.id,groupId:w.groupId,linkTenantId:w.tenantId,groupTenantId:a.tenantId,groupKey:a.groupKey,isGroupActive:a.isActive,provider:w.provider,externalId:w.externalId,externalKey:w.externalKey,membershipMode:w.membershipMode,vanishedObservations:w.vanishedObservations}).from(w).innerJoin(a,(0,T.and)((0,T.eq)(a.id,w.groupId),(0,T.eq)(a.tenantId,h))).where((0,T.and)(...u))).map(A=>((0,Y.assertTenantMatch)(String(A.groupTenantId),String(A.linkTenantId),"listSyncableLinks(link.tenantId vs TMGroup.tenant_id)"),{linkId:Number(A.linkId),groupId:Number(A.groupId),groupKey:String(A.groupKey),tenantId:String(A.groupTenantId),provider:String(A.provider),externalId:String(A.externalId),externalKey:String(A.externalKey),membershipMode:B(String(A.membershipMode)),isGroupActive:!!A.isGroupActive,vanishedObservations:Number(A.vanishedObservations??0)}))},async loadGroupSnapshot(r){await s();let[h,u]=await Promise.all([l.select({email:b.email,externalUserId:b.externalUserId,resolvedUserId:b.resolvedUserId}).from(b).where((0,T.eq)(b.groupId,r.groupId)),l.select({userId:t.userId}).from(t).where((0,T.eq)(t.groupId,r.groupId))]);return{ledger:h.map(I=>({email:String(I.email),...I.externalUserId?{externalUserId:String(I.externalUserId)}:{},resolvedUserId:I.resolvedUserId===null?null:Number(I.resolvedUserId)})),projectedUserIds:u.map(I=>Number(I.userId))}},resolveMembers(r){return d.memberResolver.resolve(r)},async applyPlan(r,h,u){(0,Y.assertTenantId)(r.tenantId,"applyPlan(link.tenantId)"),await s(),(h.ledgerUpserts.length>0||h.projectionAdds.length>0)&&await f(async x=>{let P=h.ledgerUpserts.map(y=>({...y,email:(0,J.normalizeAuthEmail)(y.email)})),o=new Map;for(let y of(0,K.chunk)(P.map(S=>S.email))){let S=await x.select({id:b.id,email:b.email}).from(b).where((0,T.and)((0,T.eq)(b.groupId,r.groupId),(0,T.inArray)(b.email,y)));for(let k of S)o.set(String(k.email),Number(k.id))}let m=P.filter(y=>!o.has(y.email));for(let y of(0,K.chunkByParameters)(m,Ve))await x.insert(b).output().values(y.map(S=>({groupId:r.groupId,email:S.email,externalUserId:S.externalUserId??null,resolvedUserId:S.resolvedUserId,observedAt:u,createdAt:u,createdBy:i})));for(let y of P){let S=o.get(y.email);S!==void 0&&await x.update(b).set({externalUserId:y.externalUserId??null,resolvedUserId:y.resolvedUserId,observedAt:u,updatedAt:u,updatedBy:i}).output().where((0,T.eq)(b.id,S))}for(let y of(0,K.chunkByParameters)(h.projectionAdds,Qe)){let S=new Set((await x.select({userId:t.userId}).from(t).where((0,T.and)((0,T.eq)(t.groupId,r.groupId),(0,T.inArray)(t.userId,y)))).map(R=>Number(R.userId))),k=y.filter(R=>!S.has(R));k.length>0&&await x.insert(t).output().values(k.map(R=>({groupId:r.groupId,userId:R,createdAt:u,createdBy:i})))}});let I=[],A=[];for(let x of h.projectionRemoves)try{await d.lockoutGuard.guardedWrite(r.tenantId,{removeGroupMembership:{groupId:r.groupId,userId:x}},"remove-group-member",P=>P.delete(t).where((0,T.and)((0,T.eq)(t.groupId,r.groupId),(0,T.eq)(t.userId,x))))}catch(P){Xe(P)?I.push({userId:x,applied:!1,refusal:P.message}):A.push({userId:x,cause:H(P)})}let D=I.map(x=>x.userId);if(h.ledgerDeletes.length>0){let x=Math.max(1,K.MAX_PARAMETERS-K.RESERVED_PARAMETERS-1-D.length);await f(async P=>{for(let o of(0,K.chunk)(h.ledgerDeletes,Math.min(K.MAX_IN_LIST,x))){let m=D.length>0?(0,T.or)((0,T.isNull)(b.resolvedUserId),(0,T.notInArray)(b.resolvedUserId,D)):void 0;await P.delete(b).where((0,T.and)((0,T.eq)(b.groupId,r.groupId),(0,T.inArray)(b.email,o),...m?[m]:[]))}})}return{refusals:I,failures:A}},async resolveExternalIdentity(r,h){await s(),!(r.externalId===h.externalId&&r.externalKey===(0,J.normalizeAuthEmail)(h.externalKey))&&await l.update(w).set({externalId:h.externalId,externalKey:(0,J.normalizeAuthEmail)(h.externalKey)}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId)))},async recordSuccess(r,h,u){await s(),await f(async I=>{await I.update(w).set({syncedAt:u,syncError:null,vanishedObservations:0,externalId:h.externalId,externalKey:(0,J.normalizeAuthEmail)(h.externalKey),updatedAt:u,updatedBy:i}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId))),r.isGroupActive||await I.update(a).set({isActive:!0,updatedAt:u,updatedBy:i}).output().where((0,T.and)((0,T.eq)(a.id,r.groupId),(0,T.eq)(a.tenantId,r.tenantId)))})},async recordFailure(r,h){await s(),await l.update(w).set({syncError:h.slice(0,De),updatedAt:v(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId)))},async recordVanishedObservation(r,h){await s();let u=r.vanishedObservations+1;return await l.update(w).set({vanishedObservations:u,syncError:h.slice(0,De),updatedAt:v(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(w.id,r.linkId),(0,T.eq)(w.tenantId,r.tenantId))),u},async markGroupInactive(r){await s(),await l.update(a).set({isActive:!1,updatedAt:v(),updatedBy:i}).output().where((0,T.and)((0,T.eq)(a.id,r.groupId),(0,T.eq)(a.tenantId,r.tenantId),(0,T.eq)(a.isActive,!0)))}}}0&&(module.exports={AUTH_DIRECTORY_PROVIDER_COLUMN,AUTH_DIRECTORY_TENANT_SCOPED_TABLES,MEMBERSHIP_MODES,NEWLY_UNHEALTHY_SYNC_OUTCOMES,STRANDED_REASON,UNHEALTHY_SYNC_OUTCOMES,assertGroupMayBeLinked,assertMembershipMode,classifySyncFailure,createDirectoryCatalogReader,createDirectoryCatalogWriter,createDirectoryLinkStore,createDirectorySync,createDirectorySyncRepository,createPromotionRepository,createSyncRequestQueue,defaultExternalGroupGuardMessage,defaultSyncMessages,defineAuthDirectorySchema,findLedgerRowsForIdentity,promoteLedgerForUser,transactionRunner,withExternalGroupGuards});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|