@boopkit/cap-users 0.1.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/capability.json +50 -0
- package/dist/account-management-service.d.ts +52 -0
- package/dist/account-management-service.d.ts.map +1 -0
- package/dist/account-management-service.js +140 -0
- package/dist/account-management-service.js.map +1 -0
- package/dist/account-property-service.d.ts +18 -0
- package/dist/account-property-service.d.ts.map +1 -0
- package/dist/account-property-service.js +19 -0
- package/dist/account-property-service.js.map +1 -0
- package/dist/account-service.d.ts +84 -0
- package/dist/account-service.d.ts.map +1 -0
- package/dist/account-service.js +136 -0
- package/dist/account-service.js.map +1 -0
- package/dist/auth-middleware.d.ts +50 -0
- package/dist/auth-middleware.d.ts.map +1 -0
- package/dist/auth-middleware.js +149 -0
- package/dist/auth-middleware.js.map +1 -0
- package/dist/auth-service.d.ts +112 -0
- package/dist/auth-service.d.ts.map +1 -0
- package/dist/auth-service.js +575 -0
- package/dist/auth-service.js.map +1 -0
- package/dist/identity-service.d.ts +48 -0
- package/dist/identity-service.d.ts.map +1 -0
- package/dist/identity-service.js +98 -0
- package/dist/identity-service.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/team-invitation-service.d.ts +61 -0
- package/dist/team-invitation-service.d.ts.map +1 -0
- package/dist/team-invitation-service.js +237 -0
- package/dist/team-invitation-service.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/user-property-service.d.ts +18 -0
- package/dist/user-property-service.d.ts.map +1 -0
- package/dist/user-property-service.js +19 -0
- package/dist/user-property-service.js.map +1 -0
- package/dist/user-service.d.ts +47 -0
- package/dist/user-service.d.ts.map +1 -0
- package/dist/user-service.js +39 -0
- package/dist/user-service.js.map +1 -0
- package/package.json +31 -0
- package/scaffold/prisma/schema/accounts.prisma +47 -0
- package/scaffold/prisma/schema/users.prisma +47 -0
- package/scaffold/reference/user-auth.ref.md +237 -0
- package/scaffold/src/controllers/auth.ts +158 -0
- package/scaffold/src/middleware/auth.ts +28 -0
- package/scaffold/src/routes/auth.ts +20 -0
- package/scaffold/src/services/accounts.service.ts +53 -0
- package/scaffold/src/services/users.service.ts +46 -0
- package/scaffold/styles/auth.css +120 -0
- package/scaffold/views/login.hbs +28 -0
- package/scaffold/views/partials/auth-nav.hbs +32 -0
- package/scaffold/views/signup.hbs +29 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createIdentityService = createIdentityService;
|
|
4
|
+
const types_js_1 = require("./types.js");
|
|
5
|
+
function toIdentity(row) {
|
|
6
|
+
const endedAt = row.endedAt;
|
|
7
|
+
return {
|
|
8
|
+
id: row.id,
|
|
9
|
+
userId: row.userId,
|
|
10
|
+
domain: row.domain,
|
|
11
|
+
key: row.key,
|
|
12
|
+
endedAt: endedAt == null ? null : (endedAt instanceof Date ? endedAt : new Date(endedAt)),
|
|
13
|
+
createdAt: row.createdAt instanceof Date ? row.createdAt : new Date(row.createdAt),
|
|
14
|
+
updatedAt: row.updatedAt instanceof Date ? row.updatedAt : new Date(row.updatedAt),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Compound unique key for (domain, key) lookups. */
|
|
18
|
+
function domainKeyWhere(domain, key) {
|
|
19
|
+
return { domain_key: { domain, key } };
|
|
20
|
+
}
|
|
21
|
+
/** Normalize key for domain-specific rules (email → lowercase). */
|
|
22
|
+
function normalizeKey(domain, key) {
|
|
23
|
+
return domain === 'email' ? key.toLowerCase() : key;
|
|
24
|
+
}
|
|
25
|
+
function isPrismaUniqueError(err) {
|
|
26
|
+
return (typeof err === 'object' &&
|
|
27
|
+
err !== null &&
|
|
28
|
+
'code' in err &&
|
|
29
|
+
err.code === 'P2002');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create an identity service for user identity management.
|
|
33
|
+
*
|
|
34
|
+
* Not entity-shaped — identities use (domain, key) compound unique
|
|
35
|
+
* instead of the standard entity pattern.
|
|
36
|
+
*/
|
|
37
|
+
function createIdentityService(model, options) {
|
|
38
|
+
return {
|
|
39
|
+
async create(userId, domain, key) {
|
|
40
|
+
const nKey = normalizeKey(domain, key);
|
|
41
|
+
try {
|
|
42
|
+
const row = await model.create({
|
|
43
|
+
data: { userId, domain, key: nKey },
|
|
44
|
+
});
|
|
45
|
+
const identity = toIdentity(row);
|
|
46
|
+
options?.onCreated?.(identity);
|
|
47
|
+
return identity;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (isPrismaUniqueError(err)) {
|
|
51
|
+
throw new types_js_1.DuplicateIdentityError(domain, key);
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
async findByDomainAndKey(domain, key) {
|
|
57
|
+
const row = await model.findUnique({ where: domainKeyWhere(domain, normalizeKey(domain, key)) });
|
|
58
|
+
if (!row)
|
|
59
|
+
return null;
|
|
60
|
+
const identity = toIdentity(row);
|
|
61
|
+
// Active-only: exclude ended identities
|
|
62
|
+
if (identity.endedAt !== null)
|
|
63
|
+
return null;
|
|
64
|
+
return identity;
|
|
65
|
+
},
|
|
66
|
+
async findByUserAndDomain(userId, domain) {
|
|
67
|
+
const row = await model.findFirst({ where: { userId, domain, endedAt: null } });
|
|
68
|
+
return row ? toIdentity(row) : null;
|
|
69
|
+
},
|
|
70
|
+
async findAllByUser(userId, opts) {
|
|
71
|
+
const where = { userId };
|
|
72
|
+
if (!opts?.includeEnded) {
|
|
73
|
+
where.endedAt = null;
|
|
74
|
+
}
|
|
75
|
+
const rows = await model.findMany({ where });
|
|
76
|
+
return rows.map(toIdentity);
|
|
77
|
+
},
|
|
78
|
+
async update(domain, key, newUserId) {
|
|
79
|
+
const row = await model.update({
|
|
80
|
+
where: domainKeyWhere(domain, normalizeKey(domain, key)),
|
|
81
|
+
data: { userId: newUserId },
|
|
82
|
+
});
|
|
83
|
+
const identity = toIdentity(row);
|
|
84
|
+
options?.onUpdated?.(identity);
|
|
85
|
+
return identity;
|
|
86
|
+
},
|
|
87
|
+
async endDate(domain, key) {
|
|
88
|
+
const row = await model.update({
|
|
89
|
+
where: domainKeyWhere(domain, normalizeKey(domain, key)),
|
|
90
|
+
data: { endedAt: new Date() },
|
|
91
|
+
});
|
|
92
|
+
const identity = toIdentity(row);
|
|
93
|
+
options?.onEnded?.(identity);
|
|
94
|
+
return identity;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=identity-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-service.js","sourceRoot":"","sources":["../src/identity-service.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @boopkit/cap-users — User data service factories.
|
|
3
|
+
*
|
|
4
|
+
* Provides typed CRUD factories for users, identities, and user properties,
|
|
5
|
+
* composing on top of @boopkit/cap-services primitives.
|
|
6
|
+
*/
|
|
7
|
+
export { DuplicateIdentityError, DuplicateAccountUserError } from './types.js';
|
|
8
|
+
export type { BaseUserInfo, User, UserIdentity, UserProfile, CredentialEntry, UserCredentials, AuthenticationMethod, UserAuthentication, TrustTier, AuthorizationGrant, UserAuthorization, BaseAccountInfo, BaseAccountUserInfo, Account, AccountUser, } from './types.js';
|
|
9
|
+
export { createUserService } from './user-service.js';
|
|
10
|
+
export type { UserService, UserServiceOptions } from './user-service.js';
|
|
11
|
+
export { createIdentityService } from './identity-service.js';
|
|
12
|
+
export type { IdentityService, IdentityServiceOptions, IdentityQueryOptions } from './identity-service.js';
|
|
13
|
+
export { createUserPropertyService } from './user-property-service.js';
|
|
14
|
+
export type { UserPropertyService, UserPropertyServiceOptions } from './user-property-service.js';
|
|
15
|
+
export { createAuthService, AuthValidationError, AuthError, generateToken, hashToken } from './auth-service.js';
|
|
16
|
+
export type { AuthService, AuthServiceDeps, AuthServiceOptions, AddAuthMethodOptions, SignupInput, SignupResult, LoginResult, VerifyEmailResult, } from './auth-service.js';
|
|
17
|
+
export { createAuthMiddleware } from './auth-middleware.js';
|
|
18
|
+
export type { AuthMiddleware, AuthMiddlewareDeps, AuthMiddlewareOptions, AuthLocals, } from './auth-middleware.js';
|
|
19
|
+
export { createAccountService } from './account-service.js';
|
|
20
|
+
export type { AccountService, AccountServiceOptions } from './account-service.js';
|
|
21
|
+
export { createAccountPropertyService } from './account-property-service.js';
|
|
22
|
+
export type { AccountPropertyService, AccountPropertyServiceOptions } from './account-property-service.js';
|
|
23
|
+
export { createAccountManagementService, AccountManagementError } from './account-management-service.js';
|
|
24
|
+
export type { AccountManagementService, AccountManagementServiceDeps, AccountManagementServiceOptions, ProvisionAccountInput, ProvisionAccountResult, TeamMember, } from './account-management-service.js';
|
|
25
|
+
export { createTeamInvitationService, TeamInvitationError, DEFAULT_INVITATION_TTL_MS, MAX_PENDING_INVITATIONS, MAX_RESENDS, } from './team-invitation-service.js';
|
|
26
|
+
export type { TeamInvitation, TeamInvitationsPropertyInfo, TeamInvitationService, TeamInvitationServiceDeps, TeamInvitationServiceOptions, AccountUserRole, } from './team-invitation-service.js';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC/E,YAAY,EACV,YAAY,EACZ,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE3G,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAElG,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChH,YAAY,EACV,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,UAAU,GACX,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElF,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,YAAY,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAE3G,OAAO,EAAE,8BAA8B,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACzG,YAAY,EACV,wBAAwB,EACxB,4BAA4B,EAC5B,+BAA+B,EAC/B,qBAAqB,EACrB,sBAAsB,EACtB,UAAU,GACX,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,EACvB,WAAW,GACZ,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,cAAc,EACd,2BAA2B,EAC3B,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @boopkit/cap-users — User data service factories.
|
|
4
|
+
*
|
|
5
|
+
* Provides typed CRUD factories for users, identities, and user properties,
|
|
6
|
+
* composing on top of @boopkit/cap-services primitives.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MAX_RESENDS = exports.MAX_PENDING_INVITATIONS = exports.DEFAULT_INVITATION_TTL_MS = exports.TeamInvitationError = exports.createTeamInvitationService = exports.AccountManagementError = exports.createAccountManagementService = exports.createAccountPropertyService = exports.createAccountService = exports.createAuthMiddleware = exports.hashToken = exports.generateToken = exports.AuthError = exports.AuthValidationError = exports.createAuthService = exports.createUserPropertyService = exports.createIdentityService = exports.createUserService = exports.DuplicateAccountUserError = exports.DuplicateIdentityError = void 0;
|
|
10
|
+
var types_js_1 = require("./types.js");
|
|
11
|
+
Object.defineProperty(exports, "DuplicateIdentityError", { enumerable: true, get: function () { return types_js_1.DuplicateIdentityError; } });
|
|
12
|
+
Object.defineProperty(exports, "DuplicateAccountUserError", { enumerable: true, get: function () { return types_js_1.DuplicateAccountUserError; } });
|
|
13
|
+
var user_service_js_1 = require("./user-service.js");
|
|
14
|
+
Object.defineProperty(exports, "createUserService", { enumerable: true, get: function () { return user_service_js_1.createUserService; } });
|
|
15
|
+
var identity_service_js_1 = require("./identity-service.js");
|
|
16
|
+
Object.defineProperty(exports, "createIdentityService", { enumerable: true, get: function () { return identity_service_js_1.createIdentityService; } });
|
|
17
|
+
var user_property_service_js_1 = require("./user-property-service.js");
|
|
18
|
+
Object.defineProperty(exports, "createUserPropertyService", { enumerable: true, get: function () { return user_property_service_js_1.createUserPropertyService; } });
|
|
19
|
+
var auth_service_js_1 = require("./auth-service.js");
|
|
20
|
+
Object.defineProperty(exports, "createAuthService", { enumerable: true, get: function () { return auth_service_js_1.createAuthService; } });
|
|
21
|
+
Object.defineProperty(exports, "AuthValidationError", { enumerable: true, get: function () { return auth_service_js_1.AuthValidationError; } });
|
|
22
|
+
Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return auth_service_js_1.AuthError; } });
|
|
23
|
+
Object.defineProperty(exports, "generateToken", { enumerable: true, get: function () { return auth_service_js_1.generateToken; } });
|
|
24
|
+
Object.defineProperty(exports, "hashToken", { enumerable: true, get: function () { return auth_service_js_1.hashToken; } });
|
|
25
|
+
var auth_middleware_js_1 = require("./auth-middleware.js");
|
|
26
|
+
Object.defineProperty(exports, "createAuthMiddleware", { enumerable: true, get: function () { return auth_middleware_js_1.createAuthMiddleware; } });
|
|
27
|
+
var account_service_js_1 = require("./account-service.js");
|
|
28
|
+
Object.defineProperty(exports, "createAccountService", { enumerable: true, get: function () { return account_service_js_1.createAccountService; } });
|
|
29
|
+
var account_property_service_js_1 = require("./account-property-service.js");
|
|
30
|
+
Object.defineProperty(exports, "createAccountPropertyService", { enumerable: true, get: function () { return account_property_service_js_1.createAccountPropertyService; } });
|
|
31
|
+
var account_management_service_js_1 = require("./account-management-service.js");
|
|
32
|
+
Object.defineProperty(exports, "createAccountManagementService", { enumerable: true, get: function () { return account_management_service_js_1.createAccountManagementService; } });
|
|
33
|
+
Object.defineProperty(exports, "AccountManagementError", { enumerable: true, get: function () { return account_management_service_js_1.AccountManagementError; } });
|
|
34
|
+
var team_invitation_service_js_1 = require("./team-invitation-service.js");
|
|
35
|
+
Object.defineProperty(exports, "createTeamInvitationService", { enumerable: true, get: function () { return team_invitation_service_js_1.createTeamInvitationService; } });
|
|
36
|
+
Object.defineProperty(exports, "TeamInvitationError", { enumerable: true, get: function () { return team_invitation_service_js_1.TeamInvitationError; } });
|
|
37
|
+
Object.defineProperty(exports, "DEFAULT_INVITATION_TTL_MS", { enumerable: true, get: function () { return team_invitation_service_js_1.DEFAULT_INVITATION_TTL_MS; } });
|
|
38
|
+
Object.defineProperty(exports, "MAX_PENDING_INVITATIONS", { enumerable: true, get: function () { return team_invitation_service_js_1.MAX_PENDING_INVITATIONS; } });
|
|
39
|
+
Object.defineProperty(exports, "MAX_RESENDS", { enumerable: true, get: function () { return team_invitation_service_js_1.MAX_RESENDS; } });
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,uCAA+E;AAAtE,kHAAA,sBAAsB,OAAA;AAAE,qHAAA,yBAAyB,OAAA;AAmB1D,qDAAsD;AAA7C,oHAAA,iBAAiB,OAAA;AAG1B,6DAA8D;AAArD,4HAAA,qBAAqB,OAAA;AAG9B,uEAAuE;AAA9D,qIAAA,yBAAyB,OAAA;AAGlC,qDAAgH;AAAvG,oHAAA,iBAAiB,OAAA;AAAE,sHAAA,mBAAmB,OAAA;AAAE,4GAAA,SAAS,OAAA;AAAE,gHAAA,aAAa,OAAA;AAAE,4GAAA,SAAS,OAAA;AAYpF,2DAA4D;AAAnD,0HAAA,oBAAoB,OAAA;AAQ7B,2DAA4D;AAAnD,0HAAA,oBAAoB,OAAA;AAG7B,6EAA6E;AAApE,2IAAA,4BAA4B,OAAA;AAGrC,iFAAyG;AAAhG,+IAAA,8BAA8B,OAAA;AAAE,uIAAA,sBAAsB,OAAA;AAU/D,2EAMsC;AALpC,yIAAA,2BAA2B,OAAA;AAC3B,iIAAA,mBAAmB,OAAA;AACnB,uIAAA,yBAAyB,OAAA;AACzB,qIAAA,uBAAuB,OAAA;AACvB,yHAAA,WAAW,OAAA"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { AccountPropertyService } from './account-property-service.js';
|
|
2
|
+
import type { AccountService } from './account-service.js';
|
|
3
|
+
import type { BaseAccountInfo, BaseAccountUserInfo } from './types.js';
|
|
4
|
+
import type { IdentityService } from './identity-service.js';
|
|
5
|
+
export declare const DEFAULT_INVITATION_TTL_MS: number;
|
|
6
|
+
export declare const MAX_PENDING_INVITATIONS = 20;
|
|
7
|
+
export declare const MAX_RESENDS = 3;
|
|
8
|
+
export declare const PRUNE_THRESHOLD = 100;
|
|
9
|
+
export declare const PRUNE_AGE_MS: number;
|
|
10
|
+
export type AccountUserRole = BaseAccountUserInfo['role'];
|
|
11
|
+
export interface TeamInvitation {
|
|
12
|
+
id: string;
|
|
13
|
+
token: string;
|
|
14
|
+
email: string;
|
|
15
|
+
role: AccountUserRole;
|
|
16
|
+
status: 'pending' | 'accepted' | 'cancelled' | 'expired';
|
|
17
|
+
invitedByUserId: number;
|
|
18
|
+
invitedByName: string;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
acceptedByUserId?: number;
|
|
22
|
+
acceptedAt?: string;
|
|
23
|
+
resendCount: number;
|
|
24
|
+
}
|
|
25
|
+
export interface TeamInvitationsPropertyInfo {
|
|
26
|
+
invitations: TeamInvitation[];
|
|
27
|
+
}
|
|
28
|
+
export declare class TeamInvitationError extends Error {
|
|
29
|
+
constructor(message: string);
|
|
30
|
+
}
|
|
31
|
+
export interface TeamInvitationService {
|
|
32
|
+
getInvitations(accountId: number): Promise<TeamInvitation[]>;
|
|
33
|
+
getPendingInvitations(accountId: number): Promise<TeamInvitation[]>;
|
|
34
|
+
createInvitation(accountId: number, email: string, role: AccountUserRole, invitedBy: {
|
|
35
|
+
userId: number;
|
|
36
|
+
name: string;
|
|
37
|
+
}): Promise<TeamInvitation>;
|
|
38
|
+
cancelInvitation(accountId: number, invitationId: string): Promise<TeamInvitation>;
|
|
39
|
+
resendInvitation(accountId: number, invitationId: string): Promise<TeamInvitation>;
|
|
40
|
+
/**
|
|
41
|
+
* Marks the invitation as accepted but does NOT add the user to the account.
|
|
42
|
+
* The caller must use `accountManagementService.addUser()` to complete the membership flow.
|
|
43
|
+
* @see integration test "invite → accept → join flow" for the full pattern
|
|
44
|
+
*/
|
|
45
|
+
acceptInvitation(accountId: number, token: string, acceptingUserId: number): Promise<TeamInvitation>;
|
|
46
|
+
findByToken(accountId: number, token: string): Promise<TeamInvitation | null>;
|
|
47
|
+
}
|
|
48
|
+
export interface TeamInvitationServiceDeps {
|
|
49
|
+
accountPropertyService: AccountPropertyService;
|
|
50
|
+
accountService: AccountService<BaseAccountInfo>;
|
|
51
|
+
identityService: IdentityService;
|
|
52
|
+
}
|
|
53
|
+
export interface TeamInvitationServiceOptions {
|
|
54
|
+
invitationTtl?: number;
|
|
55
|
+
maxPending?: number;
|
|
56
|
+
maxResends?: number;
|
|
57
|
+
pruneThreshold?: number;
|
|
58
|
+
pruneAgeMs?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare function createTeamInvitationService(deps: TeamInvitationServiceDeps, options?: TeamInvitationServiceOptions): TeamInvitationService;
|
|
61
|
+
//# sourceMappingURL=team-invitation-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-invitation-service.d.ts","sourceRoot":"","sources":["../src/team-invitation-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TeamInvitationError = exports.PRUNE_AGE_MS = exports.PRUNE_THRESHOLD = exports.MAX_RESENDS = exports.MAX_PENDING_INVITATIONS = exports.DEFAULT_INVITATION_TTL_MS = void 0;
|
|
4
|
+
exports.createTeamInvitationService = createTeamInvitationService;
|
|
5
|
+
const auth_service_js_1 = require("./auth-service.js");
|
|
6
|
+
const cap_services_1 = require("@boopkit/cap-services");
|
|
7
|
+
// ---------- Constants ----------
|
|
8
|
+
exports.DEFAULT_INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
9
|
+
exports.MAX_PENDING_INVITATIONS = 20;
|
|
10
|
+
exports.MAX_RESENDS = 3;
|
|
11
|
+
exports.PRUNE_THRESHOLD = 100;
|
|
12
|
+
exports.PRUNE_AGE_MS = 90 * 24 * 60 * 60 * 1000; // 90 days
|
|
13
|
+
const PROPERTY = 'team_invitations';
|
|
14
|
+
const SCHEMA = 'team-invitations-01';
|
|
15
|
+
const MAX_CONCURRENCY_RETRIES = 3;
|
|
16
|
+
const TERMINAL_STATUSES = new Set(['accepted', 'cancelled', 'expired']);
|
|
17
|
+
// ---------- Errors ----------
|
|
18
|
+
class TeamInvitationError extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'TeamInvitationError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.TeamInvitationError = TeamInvitationError;
|
|
25
|
+
// ---------- Factory ----------
|
|
26
|
+
function createTeamInvitationService(deps, options) {
|
|
27
|
+
const { accountPropertyService, accountService, identityService } = deps;
|
|
28
|
+
const invitationTtl = options?.invitationTtl ?? exports.DEFAULT_INVITATION_TTL_MS;
|
|
29
|
+
const maxPending = options?.maxPending ?? exports.MAX_PENDING_INVITATIONS;
|
|
30
|
+
const maxResends = options?.maxResends ?? exports.MAX_RESENDS;
|
|
31
|
+
const pruneThreshold = options?.pruneThreshold ?? exports.PRUNE_THRESHOLD;
|
|
32
|
+
const pruneAgeMs = options?.pruneAgeMs ?? exports.PRUNE_AGE_MS;
|
|
33
|
+
async function loadInvitations(accountId) {
|
|
34
|
+
const record = await accountPropertyService.get(accountId, PROPERTY);
|
|
35
|
+
return {
|
|
36
|
+
invitations: record?.info?.invitations ?? [],
|
|
37
|
+
revision: record?.revision,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Remove terminal-status invitations older than `pruneAgeMs` when the array
|
|
42
|
+
* exceeds `pruneThreshold`. Mutates the array in place for consistency with
|
|
43
|
+
* `applyLazyExpiry`. Returns true if any entries were removed.
|
|
44
|
+
*/
|
|
45
|
+
function pruneTerminalInvitations(invitations) {
|
|
46
|
+
if (invitations.length <= pruneThreshold)
|
|
47
|
+
return false;
|
|
48
|
+
const cutoff = new Date(Date.now() - pruneAgeMs).toISOString();
|
|
49
|
+
const before = invitations.length;
|
|
50
|
+
for (let i = invitations.length - 1; i >= 0; i--) {
|
|
51
|
+
const inv = invitations[i];
|
|
52
|
+
if (TERMINAL_STATUSES.has(inv.status) && inv.createdAt < cutoff) {
|
|
53
|
+
invitations.splice(i, 1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return invitations.length < before;
|
|
57
|
+
}
|
|
58
|
+
async function saveInvitations(accountId, invitations, expectedRevision) {
|
|
59
|
+
pruneTerminalInvitations(invitations);
|
|
60
|
+
await accountPropertyService.put(accountId, PROPERTY, { invitations }, { infoSchema: SCHEMA, expectedRevision });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Retry a read-modify-write operation on StaleEntityError (optimistic concurrency conflict).
|
|
64
|
+
*/
|
|
65
|
+
async function withRetry(fn) {
|
|
66
|
+
for (let attempt = 0; attempt <= MAX_CONCURRENCY_RETRIES; attempt++) {
|
|
67
|
+
try {
|
|
68
|
+
return await fn();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (err instanceof cap_services_1.StaleEntityError && attempt < MAX_CONCURRENCY_RETRIES) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// unreachable, but satisfies TypeScript
|
|
78
|
+
throw new Error('Retry loop exited unexpectedly');
|
|
79
|
+
}
|
|
80
|
+
/** Mark overdue pending invitations as expired. Returns true if any changed. */
|
|
81
|
+
function applyLazyExpiry(invitations) {
|
|
82
|
+
const now = new Date().toISOString();
|
|
83
|
+
let changed = false;
|
|
84
|
+
for (const inv of invitations) {
|
|
85
|
+
if (inv.status === 'pending' && inv.expiresAt < now) {
|
|
86
|
+
inv.status = 'expired';
|
|
87
|
+
changed = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return changed;
|
|
91
|
+
}
|
|
92
|
+
async function getInvitations(accountId) {
|
|
93
|
+
const snapshot = await loadInvitations(accountId);
|
|
94
|
+
const changed = applyLazyExpiry(snapshot.invitations);
|
|
95
|
+
if (changed) {
|
|
96
|
+
// Best-effort expiry flush; ignore conflicts since expiry is idempotent.
|
|
97
|
+
try {
|
|
98
|
+
await saveInvitations(accountId, snapshot.invitations, snapshot.revision);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (!(err instanceof cap_services_1.StaleEntityError))
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return snapshot.invitations;
|
|
106
|
+
}
|
|
107
|
+
async function getPendingInvitations(accountId) {
|
|
108
|
+
const invitations = await getInvitations(accountId);
|
|
109
|
+
return invitations.filter((inv) => inv.status === 'pending');
|
|
110
|
+
}
|
|
111
|
+
async function createInvitation(accountId, email, role, invitedBy) {
|
|
112
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
113
|
+
// Check if email already belongs to an account member (outside retry — stable data)
|
|
114
|
+
const users = await accountService.getUsers(accountId);
|
|
115
|
+
const memberEmails = await resolveUserEmails(users);
|
|
116
|
+
if (memberEmails.has(normalizedEmail)) {
|
|
117
|
+
throw new TeamInvitationError(`User ${normalizedEmail} is already a member of this account`);
|
|
118
|
+
}
|
|
119
|
+
return withRetry(async () => {
|
|
120
|
+
const snapshot = await loadInvitations(accountId);
|
|
121
|
+
applyLazyExpiry(snapshot.invitations);
|
|
122
|
+
const pending = snapshot.invitations.filter((inv) => inv.status === 'pending');
|
|
123
|
+
// Duplicate pending check
|
|
124
|
+
if (pending.some((inv) => inv.email === normalizedEmail)) {
|
|
125
|
+
throw new TeamInvitationError(`A pending invitation already exists for ${normalizedEmail}`);
|
|
126
|
+
}
|
|
127
|
+
// Max pending check
|
|
128
|
+
if (pending.length >= maxPending) {
|
|
129
|
+
throw new TeamInvitationError(`Maximum of ${maxPending} pending invitations reached`);
|
|
130
|
+
}
|
|
131
|
+
const now = new Date();
|
|
132
|
+
const invitation = {
|
|
133
|
+
id: (0, auth_service_js_1.generateToken)(),
|
|
134
|
+
token: (0, auth_service_js_1.generateToken)(),
|
|
135
|
+
email: normalizedEmail,
|
|
136
|
+
role,
|
|
137
|
+
status: 'pending',
|
|
138
|
+
invitedByUserId: invitedBy.userId,
|
|
139
|
+
invitedByName: invitedBy.name,
|
|
140
|
+
createdAt: now.toISOString(),
|
|
141
|
+
expiresAt: new Date(now.getTime() + invitationTtl).toISOString(),
|
|
142
|
+
resendCount: 0,
|
|
143
|
+
};
|
|
144
|
+
snapshot.invitations.push(invitation);
|
|
145
|
+
await saveInvitations(accountId, snapshot.invitations, snapshot.revision);
|
|
146
|
+
return invitation;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function cancelInvitation(accountId, invitationId) {
|
|
150
|
+
return withRetry(async () => {
|
|
151
|
+
const snapshot = await loadInvitations(accountId);
|
|
152
|
+
applyLazyExpiry(snapshot.invitations);
|
|
153
|
+
const inv = snapshot.invitations.find((i) => i.id === invitationId);
|
|
154
|
+
if (!inv)
|
|
155
|
+
throw new TeamInvitationError(`Invitation ${invitationId} not found`);
|
|
156
|
+
inv.status = 'cancelled';
|
|
157
|
+
await saveInvitations(accountId, snapshot.invitations, snapshot.revision);
|
|
158
|
+
return inv;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async function resendInvitation(accountId, invitationId) {
|
|
162
|
+
return withRetry(async () => {
|
|
163
|
+
const snapshot = await loadInvitations(accountId);
|
|
164
|
+
applyLazyExpiry(snapshot.invitations);
|
|
165
|
+
const inv = snapshot.invitations.find((i) => i.id === invitationId);
|
|
166
|
+
if (!inv)
|
|
167
|
+
throw new TeamInvitationError(`Invitation ${invitationId} not found`);
|
|
168
|
+
if (inv.status !== 'pending') {
|
|
169
|
+
throw new TeamInvitationError('Can only resend pending invitations');
|
|
170
|
+
}
|
|
171
|
+
if (inv.resendCount >= maxResends) {
|
|
172
|
+
throw new TeamInvitationError(`Maximum of ${maxResends} resends reached for this invitation`);
|
|
173
|
+
}
|
|
174
|
+
inv.token = (0, auth_service_js_1.generateToken)();
|
|
175
|
+
inv.expiresAt = new Date(Date.now() + invitationTtl).toISOString();
|
|
176
|
+
inv.resendCount += 1;
|
|
177
|
+
await saveInvitations(accountId, snapshot.invitations, snapshot.revision);
|
|
178
|
+
return inv;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Marks the invitation as accepted but does NOT add the user to the account.
|
|
183
|
+
* The caller must use `accountManagementService.addUser()` to complete the membership flow.
|
|
184
|
+
* @see integration test "invite → accept → join flow" for the full pattern
|
|
185
|
+
*/
|
|
186
|
+
async function acceptInvitation(accountId, token, acceptingUserId) {
|
|
187
|
+
return withRetry(async () => {
|
|
188
|
+
const snapshot = await loadInvitations(accountId);
|
|
189
|
+
applyLazyExpiry(snapshot.invitations);
|
|
190
|
+
const now = new Date().toISOString();
|
|
191
|
+
const inv = snapshot.invitations.find((i) => i.token === token && i.status === 'pending');
|
|
192
|
+
if (!inv) {
|
|
193
|
+
throw new TeamInvitationError('Invalid or expired invitation token');
|
|
194
|
+
}
|
|
195
|
+
if (inv.expiresAt < now) {
|
|
196
|
+
inv.status = 'expired';
|
|
197
|
+
await saveInvitations(accountId, snapshot.invitations, snapshot.revision);
|
|
198
|
+
throw new TeamInvitationError('Invitation has expired');
|
|
199
|
+
}
|
|
200
|
+
inv.status = 'accepted';
|
|
201
|
+
inv.acceptedByUserId = acceptingUserId;
|
|
202
|
+
inv.acceptedAt = now;
|
|
203
|
+
await saveInvitations(accountId, snapshot.invitations, snapshot.revision);
|
|
204
|
+
return inv;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async function findByToken(accountId, token) {
|
|
208
|
+
const all = await getInvitations(accountId);
|
|
209
|
+
return all.find((i) => i.token === token && i.status === 'pending') ?? null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Resolve emails for account members by looking up identities in the 'email' domain.
|
|
213
|
+
* Returns a set of lowercase email addresses.
|
|
214
|
+
*/
|
|
215
|
+
async function resolveUserEmails(users) {
|
|
216
|
+
const emails = new Set();
|
|
217
|
+
for (const u of users) {
|
|
218
|
+
const identities = await identityService.findAllByUser(u.userId);
|
|
219
|
+
for (const identity of identities) {
|
|
220
|
+
if (identity.domain === 'email') {
|
|
221
|
+
emails.add(identity.key.toLowerCase());
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return emails;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
getInvitations,
|
|
229
|
+
getPendingInvitations,
|
|
230
|
+
createInvitation,
|
|
231
|
+
cancelInvitation,
|
|
232
|
+
resendInvitation,
|
|
233
|
+
acceptInvitation,
|
|
234
|
+
findByToken,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=team-invitation-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-invitation-service.js","sourceRoot":"","sources":["../src/team-invitation-service.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Entity } from '@boopkit/cap-services';
|
|
2
|
+
/** Extend with project-specific user fields. */
|
|
3
|
+
export interface BaseUserInfo {
|
|
4
|
+
name?: string;
|
|
5
|
+
email?: string;
|
|
6
|
+
type: 'user' | 'service-user';
|
|
7
|
+
}
|
|
8
|
+
/** A stored user wrapping typed info with metadata. */
|
|
9
|
+
export type User<T extends BaseUserInfo = BaseUserInfo> = Entity<T>;
|
|
10
|
+
/** Identity linking a user to an auth mechanism. */
|
|
11
|
+
export interface UserIdentity {
|
|
12
|
+
id: number;
|
|
13
|
+
userId: number;
|
|
14
|
+
domain: string;
|
|
15
|
+
key: string;
|
|
16
|
+
endedAt: Date | null;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
updatedAt: Date;
|
|
19
|
+
}
|
|
20
|
+
/** Thrown when creating a duplicate (domain, key) identity. */
|
|
21
|
+
export declare class DuplicateIdentityError extends Error {
|
|
22
|
+
readonly domain: string;
|
|
23
|
+
readonly key: string;
|
|
24
|
+
constructor(domain: string, key: string);
|
|
25
|
+
}
|
|
26
|
+
/** User personal information (property: "profile"). */
|
|
27
|
+
export interface UserProfile {
|
|
28
|
+
name?: string;
|
|
29
|
+
email?: {
|
|
30
|
+
address: string;
|
|
31
|
+
verified: boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** A single credential entry keyed by strategy (property: "credentials"). */
|
|
35
|
+
export interface CredentialEntry {
|
|
36
|
+
strategy: string;
|
|
37
|
+
secret: string;
|
|
38
|
+
}
|
|
39
|
+
/** Auth credentials — strategy-keyed array, never plaintext (property: "credentials"). */
|
|
40
|
+
export type UserCredentials = CredentialEntry[];
|
|
41
|
+
/** A single accepted authentication method (property: "authentication"). */
|
|
42
|
+
export interface AuthenticationMethod {
|
|
43
|
+
strategy: string;
|
|
44
|
+
accepted: boolean;
|
|
45
|
+
refresh_token?: string;
|
|
46
|
+
access_token?: string;
|
|
47
|
+
access_token_expires_at?: number;
|
|
48
|
+
oauth_profile?: unknown;
|
|
49
|
+
}
|
|
50
|
+
/** Current auth state — array of accepted methods (property: "authentication"). */
|
|
51
|
+
export type UserAuthentication = AuthenticationMethod[];
|
|
52
|
+
/** Graduated trust tier for authorization. */
|
|
53
|
+
export type TrustTier = 'recognized' | 'authenticated' | 'verified';
|
|
54
|
+
/** A single per-device/per-key authorization grant. */
|
|
55
|
+
export interface AuthorizationGrant {
|
|
56
|
+
identity_key: string;
|
|
57
|
+
strategy: string;
|
|
58
|
+
trust: TrustTier;
|
|
59
|
+
granted_at: number;
|
|
60
|
+
expires_at: number;
|
|
61
|
+
granted_duration?: number;
|
|
62
|
+
}
|
|
63
|
+
/** Authorization state — array of per-device grants (property: "authorization"). */
|
|
64
|
+
export type UserAuthorization = AuthorizationGrant[];
|
|
65
|
+
/** Base info for an account entity. Extend with project-specific fields. */
|
|
66
|
+
export interface BaseAccountInfo {
|
|
67
|
+
name: string;
|
|
68
|
+
status: 'enabled' | 'disabled';
|
|
69
|
+
}
|
|
70
|
+
/** Base info for the account–user join record. */
|
|
71
|
+
export interface BaseAccountUserInfo {
|
|
72
|
+
status: 'enabled' | 'disabled' | 'pending';
|
|
73
|
+
role: 'viewer' | 'editor' | 'admin' | 'owner';
|
|
74
|
+
}
|
|
75
|
+
/** A stored account wrapping typed info with metadata. */
|
|
76
|
+
export type Account<T extends BaseAccountInfo = BaseAccountInfo> = Entity<T>;
|
|
77
|
+
/** A join record linking an account to a user. */
|
|
78
|
+
export interface AccountUser<T extends BaseAccountUserInfo = BaseAccountUserInfo> {
|
|
79
|
+
id: number;
|
|
80
|
+
accountId: number;
|
|
81
|
+
userId: number;
|
|
82
|
+
info: T;
|
|
83
|
+
createdAt: Date;
|
|
84
|
+
updatedAt: Date;
|
|
85
|
+
}
|
|
86
|
+
/** Thrown when adding a user to an account they're already a member of. */
|
|
87
|
+
export declare class DuplicateAccountUserError extends Error {
|
|
88
|
+
readonly accountId: number;
|
|
89
|
+
readonly userId: number;
|
|
90
|
+
constructor(accountId: number, userId: number);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DuplicateAccountUserError = exports.DuplicateIdentityError = void 0;
|
|
4
|
+
/** Thrown when creating a duplicate (domain, key) identity. */
|
|
5
|
+
class DuplicateIdentityError extends Error {
|
|
6
|
+
constructor(domain, key) {
|
|
7
|
+
super(`Duplicate identity: ${domain}/${key}`);
|
|
8
|
+
this.name = 'DuplicateIdentityError';
|
|
9
|
+
this.domain = domain;
|
|
10
|
+
this.key = key;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.DuplicateIdentityError = DuplicateIdentityError;
|
|
14
|
+
/** Thrown when adding a user to an account they're already a member of. */
|
|
15
|
+
class DuplicateAccountUserError extends Error {
|
|
16
|
+
constructor(accountId, userId) {
|
|
17
|
+
super(`User ${userId} is already a member of account ${accountId}`);
|
|
18
|
+
this.name = 'DuplicateAccountUserError';
|
|
19
|
+
this.accountId = accountId;
|
|
20
|
+
this.userId = userId;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.DuplicateAccountUserError = DuplicateAccountUserError;
|
|
24
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AA6BA,+DAA+D;AAC/D,MAAa,sBAAuB,SAAQ,KAAK;IAI/C,YAAY,MAAc,EAAE,GAAW;QACrC,KAAK,CAAC,uBAAuB,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAVD,wDAUC;AA2ED,2EAA2E;AAC3E,MAAa,yBAA0B,SAAQ,KAAK;IAIlD,YAAY,SAAiB,EAAE,MAAc;QAC3C,KAAK,CAAC,QAAQ,MAAM,mCAAmC,SAAS,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAVD,8DAUC"}
|