@tangle-network/agent-app 0.42.3 → 0.42.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/InvitationsPanel-ZKAUYZYU.js +8 -0
- package/dist/InviteAcceptPage-P3QJC3C6.js +7 -0
- package/dist/{MembersPanel-4CGUQRPF.js → MembersPanel-FOVHWUQ3.js} +2 -2
- package/dist/MembersPanel-FOVHWUQ3.js.map +1 -0
- package/dist/{access-ChAHG4Tp.d.ts → access-BZ81Btt5.d.ts} +1 -1
- package/dist/chunk-F7D7YCUL.js +206 -0
- package/dist/chunk-F7D7YCUL.js.map +1 -0
- package/dist/{chunk-WOVCWPFF.js → chunk-FJUOBNIY.js} +1 -1
- package/dist/{chunk-3G334FVA.js → chunk-GNL3MG5J.js} +4 -3
- package/dist/chunk-GNL3MG5J.js.map +1 -0
- package/dist/chunk-SWUVTGMR.js +262 -0
- package/dist/chunk-SWUVTGMR.js.map +1 -0
- package/dist/{chunk-535V6BH6.js → chunk-VCPZ3HTN.js} +44 -4
- package/dist/chunk-VCPZ3HTN.js.map +1 -0
- package/dist/chunk-WEBBJBDH.js +58 -0
- package/dist/chunk-WEBBJBDH.js.map +1 -0
- package/dist/design-canvas-react/index.js +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/invitations-CZ5G2tIn.d.ts +56 -0
- package/dist/invitations-schema-DaMO4hZE.d.ts +280 -0
- package/dist/{roles-CLtYKHHl.d.ts → roles-BC1n4t37.d.ts} +1 -1
- package/dist/teams/drizzle.d.ts +4 -3
- package/dist/teams/drizzle.js +31 -1
- package/dist/teams/drizzle.js.map +1 -1
- package/dist/teams/index.d.ts +2 -1
- package/dist/teams/index.js +16 -0
- package/dist/teams/invitations-api.d.ts +169 -0
- package/dist/teams/invitations-api.js +338 -0
- package/dist/teams/invitations-api.js.map +1 -0
- package/dist/teams/members-api.d.ts +2 -2
- package/dist/teams/members-api.js +5 -255
- package/dist/teams/members-api.js.map +1 -1
- package/dist/teams-react/index.d.ts +66 -7
- package/dist/teams-react/index.js +6 -2
- package/dist/teams-react/lazy.d.ts +6 -4
- package/dist/teams-react/lazy.js +6 -2
- package/dist/teams-react/lazy.js.map +1 -1
- package/dist/web-react/index.d.ts +17 -1
- package/dist/web-react/index.js +101 -19
- package/dist/web-react/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/InviteAcceptPage-EUOGZO2X.js +0 -7
- package/dist/chunk-3G334FVA.js.map +0 -1
- package/dist/chunk-535V6BH6.js.map +0 -1
- /package/dist/{InviteAcceptPage-EUOGZO2X.js.map → InvitationsPanel-ZKAUYZYU.js.map} +0 -0
- /package/dist/{MembersPanel-4CGUQRPF.js.map → InviteAcceptPage-P3QJC3C6.js.map} +0 -0
- /package/dist/{chunk-WOVCWPFF.js.map → chunk-FJUOBNIY.js.map} +0 -0
package/dist/teams/drizzle.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { T as TeamDatabase, a as TeamTables, O as OrganizationRow, b as OrganizationMemberRow } from '../access-
|
|
2
|
-
export { C as CreateAccessOptions, c as CreateOrganizationAccessOptions, d as CreateTeamTablesOptions, e as OrganizationAccess, f as OrganizationAccessApi, g as TeamParentTable, U as UserWorkspaceSummary, W as WorkspaceAccess, h as WorkspaceAccessApi, i as WorkspaceAccessTable, j as WorkspaceMemberRow, k as createOrganizationAccess, l as createTeamTables, m as createWorkspaceAccess } from '../access-
|
|
3
|
-
|
|
1
|
+
import { T as TeamDatabase, a as TeamTables, O as OrganizationRow, b as OrganizationMemberRow } from '../access-BZ81Btt5.js';
|
|
2
|
+
export { C as CreateAccessOptions, c as CreateOrganizationAccessOptions, d as CreateTeamTablesOptions, e as OrganizationAccess, f as OrganizationAccessApi, g as TeamParentTable, U as UserWorkspaceSummary, W as WorkspaceAccess, h as WorkspaceAccessApi, i as WorkspaceAccessTable, j as WorkspaceMemberRow, k as createOrganizationAccess, l as createTeamTables, m as createWorkspaceAccess } from '../access-BZ81Btt5.js';
|
|
3
|
+
export { C as CreateWorkspaceInvitationTableOptions, W as WorkspaceInvitationRow, a as WorkspaceInvitationTables, c as createWorkspaceInvitationTable } from '../invitations-schema-DaMO4hZE.js';
|
|
4
|
+
import { O as OrganizationRole } from '../roles-BC1n4t37.js';
|
|
4
5
|
import 'drizzle-orm/sqlite-core';
|
|
5
6
|
|
|
6
7
|
/**
|
package/dist/teams/drizzle.js
CHANGED
|
@@ -54,6 +54,35 @@ function createTeamTables(opts) {
|
|
|
54
54
|
return { organizations, organizationMembers, workspaceMembers };
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// src/teams/drizzle/invitations-schema.ts
|
|
58
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
59
|
+
import { index as index2, integer as integer2, sqliteTable as sqliteTable2, text as text2, uniqueIndex as uniqueIndex2 } from "drizzle-orm/sqlite-core";
|
|
60
|
+
function createWorkspaceInvitationTable(opts) {
|
|
61
|
+
const { userTable, workspaceTable, organizationTable } = opts;
|
|
62
|
+
const workspaceInvitations = sqliteTable2("workspace_invitation", {
|
|
63
|
+
id: text2("id").primaryKey().default(sql2`(lower(hex(randomblob(16))))`),
|
|
64
|
+
workspaceId: text2("workspace_id").notNull().references(() => workspaceTable.id, { onDelete: "cascade" }),
|
|
65
|
+
organizationId: text2("organization_id").notNull().references(() => organizationTable.id, { onDelete: "cascade" }),
|
|
66
|
+
email: text2("email").notNull(),
|
|
67
|
+
invitedByUserId: text2("invited_by_user_id").notNull().references(() => userTable.id, { onDelete: "cascade" }),
|
|
68
|
+
permissions: text2("permissions", { enum: ["admin", "editor", "viewer"] }).notNull().default("editor"),
|
|
69
|
+
token: text2("token").notNull().unique(),
|
|
70
|
+
status: text2("status", { enum: ["pending", "accepted", "expired", "revoked"] }).notNull().default("pending"),
|
|
71
|
+
emailStatus: text2("email_status", { enum: ["not_sent", "sent", "failed"] }).notNull().default("not_sent"),
|
|
72
|
+
expiresAt: integer2("expires_at", { mode: "timestamp" }).notNull(),
|
|
73
|
+
createdAt: integer2("created_at", { mode: "timestamp" }).notNull().default(sql2`(unixepoch())`),
|
|
74
|
+
acceptedAt: integer2("accepted_at", { mode: "timestamp" }),
|
|
75
|
+
revokedAt: integer2("revoked_at", { mode: "timestamp" }),
|
|
76
|
+
lastSentAt: integer2("last_sent_at", { mode: "timestamp" })
|
|
77
|
+
}, (table) => [
|
|
78
|
+
uniqueIndex2("uniq_workspace_invitation_token").on(table.token),
|
|
79
|
+
index2("idx_workspace_invitation_workspace").on(table.workspaceId, table.createdAt),
|
|
80
|
+
index2("idx_workspace_invitation_email").on(table.email),
|
|
81
|
+
index2("idx_workspace_invitation_workspace_email_status").on(table.workspaceId, table.email, table.status)
|
|
82
|
+
]);
|
|
83
|
+
return { workspaceInvitations };
|
|
84
|
+
}
|
|
85
|
+
|
|
57
86
|
// src/teams/drizzle/access.ts
|
|
58
87
|
import { and, desc, eq, isNotNull } from "drizzle-orm";
|
|
59
88
|
function createWorkspaceAccess(opts) {
|
|
@@ -189,6 +218,7 @@ export {
|
|
|
189
218
|
createEnsurePersonalOrganization,
|
|
190
219
|
createOrganizationAccess,
|
|
191
220
|
createTeamTables,
|
|
192
|
-
createWorkspaceAccess
|
|
221
|
+
createWorkspaceAccess,
|
|
222
|
+
createWorkspaceInvitationTable
|
|
193
223
|
};
|
|
194
224
|
//# sourceMappingURL=drizzle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/teams/drizzle/schema.ts","../../src/teams/drizzle/access.ts","../../src/teams/drizzle/personal-organization.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the teams tables. The product owns the user and\n * workspace tables; this factory creates the tenancy/membership tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics. Column names, types,\n * defaults, enums, and indexes mirror gtm's hand-rolled tables so a product\n * with those tables can adopt the factory without rewriting rows.\n *\n * The three tables and their roles:\n * - `organization` — the TENANT/ownership primitive. `kind` is 'personal'\n * (one auto-created per user; see ensurePersonalOrganization) or 'team'.\n * This is what owns workspaces and what billing/seats attach to.\n * - `organizationMember` — who belongs to an org and at what org role.\n * - `workspaceMember` — the additive invite/member surface: per-workspace\n * access grants, including pending email-only invites carrying a token.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/teams/drizzle` sub-subpath. The pure `./teams` leaf imports none of this,\n * so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type TeamParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\nexport interface CreateTeamTablesOptions {\n /** The product's user table — org/member rows reference `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — workspace members reference `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nexport function createTeamTables(opts: CreateTeamTablesOptions) {\n const { userTable, workspaceTable } = opts\n\n const organizations = sqliteTable('organization', {\n id: hexId(),\n name: text('name').notNull(),\n slug: text('slug').notNull().unique(),\n kind: text('kind', { enum: ['personal', 'team'] }).notNull().default('personal'),\n createdBy: text('created_by').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n index('idx_organization_created_by').on(table.createdBy),\n ])\n\n const organizationMembers = sqliteTable('organization_member', {\n id: hexId(),\n organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'member', 'billing'] }).notNull().default('member'),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_org_member_user').on(table.organizationId, table.userId),\n index('idx_org_member_user').on(table.userId),\n ])\n\n const workspaceMembers = sqliteTable('workspace_member', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationMemberId: text('organization_member_id').references(() => organizationMembers.id, { onDelete: 'cascade' }),\n userId: text('user_id').references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'editor', 'viewer'] }).notNull().default('editor'),\n invitedBy: text('invited_by'),\n inviteEmail: text('invite_email'),\n inviteToken: text('invite_token'),\n invitedAt: integer('invited_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_member_org_member').on(table.workspaceId, table.organizationMemberId),\n index('idx_workspace_member_user').on(table.workspaceId, table.userId),\n index('idx_member_user').on(table.userId),\n uniqueIndex('idx_member_invite_token').on(table.inviteToken),\n ])\n\n return { organizations, organizationMembers, workspaceMembers }\n}\n\nexport type TeamTables = ReturnType<typeof createTeamTables>\n\nexport type OrganizationRow = TeamTables['organizations']['$inferSelect']\nexport type OrganizationMemberRow = TeamTables['organizationMembers']['$inferSelect']\nexport type WorkspaceMemberRow = TeamTables['workspaceMembers']['$inferSelect']\n","/**\n * RBAC access builders over the teams tables. `createWorkspaceAccess` and\n * `createOrganizationAccess` close over the product's `db`, the tables from\n * `createTeamTables`, and the product's `workspace` table, returning the exact\n * `getWorkspaceAccess` / `requireWorkspaceAccess` / `listUserWorkspaces` /\n * `getOrganizationAccess` / `requireOrganizationAccess` functions a consumer\n * already calls — so adoption is a one-line import swap, every call site stays\n * identical.\n *\n * Defense in depth: org owners/admins are workspace owners across the org;\n * everyone else gets their explicit per-workspace role. Effective role is the\n * fold in `resolveWorkspaceRole` (pure, from `../roles`). Every query pins\n * `userId`, so a leaked id can never read across the tenancy boundary — it\n * surfaces as \"not found\".\n *\n * Driver-agnostic: builders are awaited, never `.run()`/`.all()`, so\n * better-sqlite3, D1, and libsql handles all behave identically.\n */\n\nimport { and, desc, eq, isNotNull } from 'drizzle-orm'\nimport type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'\nimport {\n type OrganizationRole,\n type WorkspaceRole,\n hasOrganizationRole,\n hasWorkspaceRole,\n resolveWorkspaceRole,\n} from '../roles'\nimport type { TeamParentTable } from './schema'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\n/** Any SQLite drizzle database — `any` erases driver-specific generics so\n * better-sqlite3, D1, and libsql handles all fit. */\nexport type TeamDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>\n\n/**\n * The product's workspace table, narrowed to the columns the access joins read.\n * Adopters pass their real drizzle workspace table — it carries these columns\n * (gtm's does); the type only asserts the minimum the joins touch.\n */\nexport interface WorkspaceAccessTable {\n id: any\n organizationId: any\n name: any\n updatedAt: any\n}\n\nexport interface CreateAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The product's workspace table (the FK target passed to createTeamTables). */\n workspaceTable: TeamParentTable & WorkspaceAccessTable\n}\n\nexport interface WorkspaceAccess {\n workspace: Record<string, unknown>\n organization: OrganizationRow\n organizationMember: OrganizationMemberRow\n role: WorkspaceRole\n}\n\nexport interface UserWorkspaceSummary extends Record<string, unknown> {\n organizationId: string\n organizationName: string\n role: WorkspaceRole\n}\n\nexport interface WorkspaceAccessApi {\n getWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess | null>\n requireWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess>\n listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]>\n}\n\nexport function createWorkspaceAccess(opts: CreateAccessOptions): WorkspaceAccessApi {\n const { db, tables, workspaceTable } = opts\n const { organizations, organizationMembers, workspaceMembers } = tables\n const workspaces = workspaceTable\n\n async function getWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess | null> {\n const [row] = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n acceptedAt: workspaceMembers.acceptedAt,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n .where(eq(workspaces.id, workspaceId))\n .limit(1)\n\n if (!row) return null\n\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return null\n if (!hasWorkspaceRole(role, minRole)) return null\n\n return {\n workspace: row.workspace as Record<string, unknown>,\n organization: row.organization,\n organizationMember: row.organizationMember,\n role,\n }\n }\n\n async function requireWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess> {\n const access = await getWorkspaceAccess(workspaceId, userId, minRole)\n if (!access) throw new Response('Workspace not found', { status: 404 })\n return access\n }\n\n async function listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]> {\n const rows = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n // Most recently active workspace first, so callers taking rows[0] as the\n // default pick the freshest one.\n .orderBy(desc(workspaces.updatedAt))\n\n return rows.flatMap((row: any) => {\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return []\n return [{\n ...(row.workspace as Record<string, unknown>),\n organizationId: row.organization.id,\n organizationName: row.organization.name,\n role,\n } as UserWorkspaceSummary]\n })\n }\n\n return { getWorkspaceAccess, requireWorkspaceAccess, listUserWorkspaces }\n}\n\nexport interface OrganizationAccess {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\nexport interface OrganizationAccessApi {\n getOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess | null>\n requireOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess>\n}\n\nexport interface CreateOrganizationAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\nexport function createOrganizationAccess(opts: CreateOrganizationAccessOptions): OrganizationAccessApi {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n async function getOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess | null> {\n const [row] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.organizationId, organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .limit(1)\n\n if (!row) return null\n const role = row.member.role as OrganizationRole\n if (!hasOrganizationRole(role, minRole)) return null\n return { organization: row.organization, member: row.member, role }\n }\n\n async function requireOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess> {\n const access = await getOrganizationAccess(organizationId, userId, minRole)\n if (!access) throw new Response('Organization not found', { status: 404 })\n return access\n }\n\n return { getOrganizationAccess, requireOrganizationAccess }\n}\n","/**\n * `ensurePersonalOrganization` — the substrate that makes solo-user adoption\n * work. Every user gets exactly one auto-created `kind: 'personal'` org with\n * the user as `owner` member. That org is the tenant that owns their\n * workspaces, so role resolution (`getWorkspaceAccess`) has an org row to read\n * even before any team/invite exists.\n *\n * Idempotent by construction: the org `slug` is derived from the user id\n * (`personal-<id>`) and upserted; the membership upserts on the\n * (organizationId, userId) unique index. Concurrent calls converge — no\n * duplicate personal orgs, no duplicate owner rows.\n *\n * ADOPTION CONTRACT: call this once per user at first authenticated entry\n * (e.g. right after sign-in / session bootstrap) BEFORE the first\n * `getWorkspaceAccess`. Role resolution requires the org membership to exist;\n * an adopter that skips this will see `null` access for a brand-new user with\n * no org row. There is no implicit creation inside the access builders — that\n * keeps reads side-effect-free; provisioning is this explicit call.\n */\n\nimport { and, eq } from 'drizzle-orm'\nimport type { OrganizationRole } from '../roles'\nimport type { TeamDatabase } from './access'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\nexport interface EnsurePersonalOrganizationUser {\n id: string\n name?: string | null\n email?: string | null\n}\n\nexport interface PersonalOrganizationResult {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\nexport interface CreatePersonalOrganizationOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\nexport function createEnsurePersonalOrganization(opts: CreatePersonalOrganizationOptions) {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n return async function ensurePersonalOrganization(\n user: EnsurePersonalOrganizationUser,\n ): Promise<PersonalOrganizationResult> {\n const [existing] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.userId, user.id),\n eq(organizations.kind, 'personal'),\n ))\n .limit(1)\n\n if (existing) {\n return {\n organization: existing.organization,\n member: existing.member,\n role: existing.member.role as OrganizationRole,\n }\n }\n\n const orgName = user.name?.trim() || user.email?.split('@')[0] || 'Personal'\n const slug = `personal-${user.id}`\n const [organization] = await db\n .insert(organizations)\n .values({\n name: `${orgName}'s Organization`,\n slug,\n kind: 'personal',\n createdBy: user.id,\n })\n .onConflictDoUpdate({\n target: organizations.slug,\n set: { updatedAt: new Date() },\n })\n .returning()\n\n // A single-row upsert with .returning() always yields exactly one row.\n const org = organization as OrganizationRow\n\n const [member] = await db\n .insert(organizationMembers)\n .values({\n organizationId: org.id,\n userId: user.id,\n role: 'owner',\n })\n .onConflictDoUpdate({\n target: [organizationMembers.organizationId, organizationMembers.userId],\n set: { role: 'owner', updatedAt: new Date() },\n })\n .returning()\n\n return { organization: org, member: member as OrganizationMemberRow, role: 'owner' }\n }\n}\n"],"mappings":"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","names":["and","eq"]}
|
|
1
|
+
{"version":3,"sources":["../../src/teams/drizzle/schema.ts","../../src/teams/drizzle/invitations-schema.ts","../../src/teams/drizzle/access.ts","../../src/teams/drizzle/personal-organization.ts"],"sourcesContent":["/**\n * Drizzle schema factory for the teams tables. The product owns the user and\n * workspace tables; this factory creates the tenancy/membership tables and\n * wires their foreign keys into the passed-in tables so the whole graph lives\n * in one drizzle schema with real cascade semantics. Column names, types,\n * defaults, enums, and indexes mirror gtm's hand-rolled tables so a product\n * with those tables can adopt the factory without rewriting rows.\n *\n * The three tables and their roles:\n * - `organization` — the TENANT/ownership primitive. `kind` is 'personal'\n * (one auto-created per user; see ensurePersonalOrganization) or 'team'.\n * This is what owns workspaces and what billing/seats attach to.\n * - `organizationMember` — who belongs to an org and at what org role.\n * - `workspaceMember` — the additive invite/member surface: per-workspace\n * access grants, including pending email-only invites carrying a token.\n *\n * Imports `drizzle-orm` at module top — that is WHY this lives behind the\n * `/teams/drizzle` sub-subpath. The pure `./teams` leaf imports none of this,\n * so a consumer that never touches the DB never pulls the optional peer.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { AnySQLiteColumn, AnySQLiteTable } from 'drizzle-orm/sqlite-core'\n\n/** A product table referenced by FK — only the `id` column is touched. */\nexport type TeamParentTable = AnySQLiteTable & { id: AnySQLiteColumn }\n\nexport interface CreateTeamTablesOptions {\n /** The product's user table — org/member rows reference `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — workspace members reference `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n}\n\nconst hexId = () => text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`)\n\nconst createdAt = () => integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nconst updatedAt = () => integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`)\n\nexport function createTeamTables(opts: CreateTeamTablesOptions) {\n const { userTable, workspaceTable } = opts\n\n const organizations = sqliteTable('organization', {\n id: hexId(),\n name: text('name').notNull(),\n slug: text('slug').notNull().unique(),\n kind: text('kind', { enum: ['personal', 'team'] }).notNull().default('personal'),\n createdBy: text('created_by').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n index('idx_organization_created_by').on(table.createdBy),\n ])\n\n const organizationMembers = sqliteTable('organization_member', {\n id: hexId(),\n organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),\n userId: text('user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'member', 'billing'] }).notNull().default('member'),\n createdAt: createdAt(),\n updatedAt: updatedAt(),\n }, (table) => [\n uniqueIndex('uniq_org_member_user').on(table.organizationId, table.userId),\n index('idx_org_member_user').on(table.userId),\n ])\n\n const workspaceMembers = sqliteTable('workspace_member', {\n id: hexId(),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationMemberId: text('organization_member_id').references(() => organizationMembers.id, { onDelete: 'cascade' }),\n userId: text('user_id').references(() => userTable.id, { onDelete: 'cascade' }),\n role: text('role', { enum: ['owner', 'admin', 'editor', 'viewer'] }).notNull().default('editor'),\n invitedBy: text('invited_by'),\n inviteEmail: text('invite_email'),\n inviteToken: text('invite_token'),\n invitedAt: integer('invited_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_member_org_member').on(table.workspaceId, table.organizationMemberId),\n index('idx_workspace_member_user').on(table.workspaceId, table.userId),\n index('idx_member_user').on(table.userId),\n uniqueIndex('idx_member_invite_token').on(table.inviteToken),\n ])\n\n return { organizations, organizationMembers, workspaceMembers }\n}\n\nexport type TeamTables = ReturnType<typeof createTeamTables>\n\nexport type OrganizationRow = TeamTables['organizations']['$inferSelect']\nexport type OrganizationMemberRow = TeamTables['organizationMembers']['$inferSelect']\nexport type WorkspaceMemberRow = TeamTables['workspaceMembers']['$inferSelect']\n","/**\n * Drizzle schema factory for the dedicated workspace-invitation table. Separate\n * from `createTeamTables` on purpose: an app opts into the rich email-invitation\n * lifecycle (status / expiry / resend / revoke / preview) by calling this, and an\n * app that doesn't want it never grows the table — `createTeamTables`'s output and\n * migrations stay untouched.\n *\n * The product owns the user and workspace tables; `organizations` comes from\n * `createTeamTables`. Pass all three so the invitation's FKs wire into the same\n * drizzle schema with real cascade semantics. Call this AFTER `createTeamTables`\n * (so `organizations` exists) — the lazy `.references(() => ...)` closures make\n * the ordering within one module safe.\n *\n * Columns / enums / indexes mirror creative-agent's validated hand-rolled table,\n * so an app already running that table adopts this factory with no row rewrite.\n */\n\nimport { sql } from 'drizzle-orm'\nimport { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'\nimport type { TeamParentTable } from './schema'\n\nexport interface CreateWorkspaceInvitationTableOptions {\n /** The product's user table — `invitedByUserId` references `userTable.id`. */\n userTable: TeamParentTable\n /** The product's workspace table — `workspaceId` references `workspaceTable.id`. */\n workspaceTable: TeamParentTable\n /** The `organizations` table from `createTeamTables` — `organizationId` references it. */\n organizationTable: TeamParentTable\n}\n\nexport function createWorkspaceInvitationTable(opts: CreateWorkspaceInvitationTableOptions) {\n const { userTable, workspaceTable, organizationTable } = opts\n\n const workspaceInvitations = sqliteTable('workspace_invitation', {\n id: text('id').primaryKey().default(sql`(lower(hex(randomblob(16))))`),\n workspaceId: text('workspace_id').notNull().references(() => workspaceTable.id, { onDelete: 'cascade' }),\n organizationId: text('organization_id').notNull().references(() => organizationTable.id, { onDelete: 'cascade' }),\n email: text('email').notNull(),\n invitedByUserId: text('invited_by_user_id').notNull().references(() => userTable.id, { onDelete: 'cascade' }),\n permissions: text('permissions', { enum: ['admin', 'editor', 'viewer'] }).notNull().default('editor'),\n token: text('token').notNull().unique(),\n status: text('status', { enum: ['pending', 'accepted', 'expired', 'revoked'] }).notNull().default('pending'),\n emailStatus: text('email_status', { enum: ['not_sent', 'sent', 'failed'] }).notNull().default('not_sent'),\n expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),\n createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(unixepoch())`),\n acceptedAt: integer('accepted_at', { mode: 'timestamp' }),\n revokedAt: integer('revoked_at', { mode: 'timestamp' }),\n lastSentAt: integer('last_sent_at', { mode: 'timestamp' }),\n }, (table) => [\n uniqueIndex('uniq_workspace_invitation_token').on(table.token),\n index('idx_workspace_invitation_workspace').on(table.workspaceId, table.createdAt),\n index('idx_workspace_invitation_email').on(table.email),\n index('idx_workspace_invitation_workspace_email_status').on(table.workspaceId, table.email, table.status),\n ])\n\n return { workspaceInvitations }\n}\n\nexport type WorkspaceInvitationTables = ReturnType<typeof createWorkspaceInvitationTable>\nexport type WorkspaceInvitationRow = WorkspaceInvitationTables['workspaceInvitations']['$inferSelect']\n","/**\n * RBAC access builders over the teams tables. `createWorkspaceAccess` and\n * `createOrganizationAccess` close over the product's `db`, the tables from\n * `createTeamTables`, and the product's `workspace` table, returning the exact\n * `getWorkspaceAccess` / `requireWorkspaceAccess` / `listUserWorkspaces` /\n * `getOrganizationAccess` / `requireOrganizationAccess` functions a consumer\n * already calls — so adoption is a one-line import swap, every call site stays\n * identical.\n *\n * Defense in depth: org owners/admins are workspace owners across the org;\n * everyone else gets their explicit per-workspace role. Effective role is the\n * fold in `resolveWorkspaceRole` (pure, from `../roles`). Every query pins\n * `userId`, so a leaked id can never read across the tenancy boundary — it\n * surfaces as \"not found\".\n *\n * Driver-agnostic: builders are awaited, never `.run()`/`.all()`, so\n * better-sqlite3, D1, and libsql handles all behave identically.\n */\n\nimport { and, desc, eq, isNotNull } from 'drizzle-orm'\nimport type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'\nimport {\n type OrganizationRole,\n type WorkspaceRole,\n hasOrganizationRole,\n hasWorkspaceRole,\n resolveWorkspaceRole,\n} from '../roles'\nimport type { TeamParentTable } from './schema'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\n/** Any SQLite drizzle database — `any` erases driver-specific generics so\n * better-sqlite3, D1, and libsql handles all fit. */\nexport type TeamDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>\n\n/**\n * The product's workspace table, narrowed to the columns the access joins read.\n * Adopters pass their real drizzle workspace table — it carries these columns\n * (gtm's does); the type only asserts the minimum the joins touch.\n */\nexport interface WorkspaceAccessTable {\n id: any\n organizationId: any\n name: any\n updatedAt: any\n}\n\nexport interface CreateAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The product's workspace table (the FK target passed to createTeamTables). */\n workspaceTable: TeamParentTable & WorkspaceAccessTable\n}\n\nexport interface WorkspaceAccess {\n workspace: Record<string, unknown>\n organization: OrganizationRow\n organizationMember: OrganizationMemberRow\n role: WorkspaceRole\n}\n\nexport interface UserWorkspaceSummary extends Record<string, unknown> {\n organizationId: string\n organizationName: string\n role: WorkspaceRole\n}\n\nexport interface WorkspaceAccessApi {\n getWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess | null>\n requireWorkspaceAccess(workspaceId: string, userId: string, minRole?: WorkspaceRole): Promise<WorkspaceAccess>\n listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]>\n}\n\nexport function createWorkspaceAccess(opts: CreateAccessOptions): WorkspaceAccessApi {\n const { db, tables, workspaceTable } = opts\n const { organizations, organizationMembers, workspaceMembers } = tables\n const workspaces = workspaceTable\n\n async function getWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess | null> {\n const [row] = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n acceptedAt: workspaceMembers.acceptedAt,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n .where(eq(workspaces.id, workspaceId))\n .limit(1)\n\n if (!row) return null\n\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return null\n if (!hasWorkspaceRole(role, minRole)) return null\n\n return {\n workspace: row.workspace as Record<string, unknown>,\n organization: row.organization,\n organizationMember: row.organizationMember,\n role,\n }\n }\n\n async function requireWorkspaceAccess(\n workspaceId: string,\n userId: string,\n minRole: WorkspaceRole = 'viewer',\n ): Promise<WorkspaceAccess> {\n const access = await getWorkspaceAccess(workspaceId, userId, minRole)\n if (!access) throw new Response('Workspace not found', { status: 404 })\n return access\n }\n\n async function listUserWorkspaces(userId: string): Promise<UserWorkspaceSummary[]> {\n const rows = await db\n .select({\n workspace: workspaces,\n organization: organizations,\n organizationMember: organizationMembers,\n projectRole: workspaceMembers.role,\n })\n .from(workspaces)\n .innerJoin(organizations, eq(organizations.id, workspaces.organizationId))\n .innerJoin(organizationMembers, and(\n eq(organizationMembers.organizationId, workspaces.organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .leftJoin(workspaceMembers, and(\n eq(workspaceMembers.workspaceId, workspaces.id),\n eq(workspaceMembers.organizationMemberId, organizationMembers.id),\n isNotNull(workspaceMembers.acceptedAt),\n ))\n // Most recently active workspace first, so callers taking rows[0] as the\n // default pick the freshest one.\n .orderBy(desc(workspaces.updatedAt))\n\n return rows.flatMap((row: any) => {\n const role = resolveWorkspaceRole(row.organizationMember.role, row.projectRole as WorkspaceRole | null)\n if (!role) return []\n return [{\n ...(row.workspace as Record<string, unknown>),\n organizationId: row.organization.id,\n organizationName: row.organization.name,\n role,\n } as UserWorkspaceSummary]\n })\n }\n\n return { getWorkspaceAccess, requireWorkspaceAccess, listUserWorkspaces }\n}\n\nexport interface OrganizationAccess {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\nexport interface OrganizationAccessApi {\n getOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess | null>\n requireOrganizationAccess(organizationId: string, userId: string, minRole?: OrganizationRole): Promise<OrganizationAccess>\n}\n\nexport interface CreateOrganizationAccessOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\nexport function createOrganizationAccess(opts: CreateOrganizationAccessOptions): OrganizationAccessApi {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n async function getOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess | null> {\n const [row] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.organizationId, organizationId),\n eq(organizationMembers.userId, userId),\n ))\n .limit(1)\n\n if (!row) return null\n const role = row.member.role as OrganizationRole\n if (!hasOrganizationRole(role, minRole)) return null\n return { organization: row.organization, member: row.member, role }\n }\n\n async function requireOrganizationAccess(\n organizationId: string,\n userId: string,\n minRole: OrganizationRole = 'member',\n ): Promise<OrganizationAccess> {\n const access = await getOrganizationAccess(organizationId, userId, minRole)\n if (!access) throw new Response('Organization not found', { status: 404 })\n return access\n }\n\n return { getOrganizationAccess, requireOrganizationAccess }\n}\n","/**\n * `ensurePersonalOrganization` — the substrate that makes solo-user adoption\n * work. Every user gets exactly one auto-created `kind: 'personal'` org with\n * the user as `owner` member. That org is the tenant that owns their\n * workspaces, so role resolution (`getWorkspaceAccess`) has an org row to read\n * even before any team/invite exists.\n *\n * Idempotent by construction: the org `slug` is derived from the user id\n * (`personal-<id>`) and upserted; the membership upserts on the\n * (organizationId, userId) unique index. Concurrent calls converge — no\n * duplicate personal orgs, no duplicate owner rows.\n *\n * ADOPTION CONTRACT: call this once per user at first authenticated entry\n * (e.g. right after sign-in / session bootstrap) BEFORE the first\n * `getWorkspaceAccess`. Role resolution requires the org membership to exist;\n * an adopter that skips this will see `null` access for a brand-new user with\n * no org row. There is no implicit creation inside the access builders — that\n * keeps reads side-effect-free; provisioning is this explicit call.\n */\n\nimport { and, eq } from 'drizzle-orm'\nimport type { OrganizationRole } from '../roles'\nimport type { TeamDatabase } from './access'\nimport type { OrganizationMemberRow, OrganizationRow, TeamTables } from './schema'\n\nexport interface EnsurePersonalOrganizationUser {\n id: string\n name?: string | null\n email?: string | null\n}\n\nexport interface PersonalOrganizationResult {\n organization: OrganizationRow\n member: OrganizationMemberRow\n role: OrganizationRole\n}\n\nexport interface CreatePersonalOrganizationOptions {\n db: TeamDatabase\n tables: TeamTables\n}\n\nexport function createEnsurePersonalOrganization(opts: CreatePersonalOrganizationOptions) {\n const { db, tables } = opts\n const { organizations, organizationMembers } = tables\n\n return async function ensurePersonalOrganization(\n user: EnsurePersonalOrganizationUser,\n ): Promise<PersonalOrganizationResult> {\n const [existing] = await db\n .select({\n organization: organizations,\n member: organizationMembers,\n })\n .from(organizationMembers)\n .innerJoin(organizations, eq(organizations.id, organizationMembers.organizationId))\n .where(and(\n eq(organizationMembers.userId, user.id),\n eq(organizations.kind, 'personal'),\n ))\n .limit(1)\n\n if (existing) {\n return {\n organization: existing.organization,\n member: existing.member,\n role: existing.member.role as OrganizationRole,\n }\n }\n\n const orgName = user.name?.trim() || user.email?.split('@')[0] || 'Personal'\n const slug = `personal-${user.id}`\n const [organization] = await db\n .insert(organizations)\n .values({\n name: `${orgName}'s Organization`,\n slug,\n kind: 'personal',\n createdBy: user.id,\n })\n .onConflictDoUpdate({\n target: organizations.slug,\n set: { updatedAt: new Date() },\n })\n .returning()\n\n // A single-row upsert with .returning() always yields exactly one row.\n const org = organization as OrganizationRow\n\n const [member] = await db\n .insert(organizationMembers)\n .values({\n organizationId: org.id,\n userId: user.id,\n role: 'owner',\n })\n .onConflictDoUpdate({\n target: [organizationMembers.organizationId, organizationMembers.userId],\n set: { role: 'owner', updatedAt: new Date() },\n })\n .returning()\n\n return { organization: org, member: member as OrganizationMemberRow, role: 'owner' }\n }\n}\n"],"mappings":"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","names":["sql","index","integer","sqliteTable","text","uniqueIndex","and","eq"]}
|
package/dist/teams/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { a as ASSIGNABLE_WORKSPACE_ROLES, A as AssignableWorkspaceRole, b as ORGANIZATION_ROLES, c as ORGANIZATION_ROLE_RANK, O as OrganizationRole, S as SandboxWorkspaceRole, W as WORKSPACE_ROLES, d as WORKSPACE_ROLE_RANK, e as WorkspaceCollaborationAccess, f as WorkspaceRole, g as canManageWorkspaceMemberRole, h as hasOrganizationRole, i as hasWorkspaceRole, j as isAssignableWorkspaceRole, o as organizationRoleGrantsWorkspaceOwner, r as resolveWorkspaceRole, w as workspaceRoleToCollaborationAccess, k as workspaceRoleToSandboxRole } from '../roles-BC1n4t37.js';
|
|
2
|
+
export { I as INVITATION_EXPIRY_DAYS, a as InvitationEmailBrand, b as InvitationEmailStatus, c as InvitationPermission, d as InvitationStatus, R as RenderInvitationEmailInput, e as RenderedInvitationEmail, g as generateInvitationToken, f as getInvitationExpiresAt, i as inviteUrlForToken, n as normalizeInvitationEmail, p as parseInvitationPermission, r as renderInvitationEmail } from '../invitations-CZ5G2tIn.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Pure invite-token helpers — generation, shape validation, and expiry math.
|
package/dist/teams/index.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INVITATION_EXPIRY_DAYS,
|
|
3
|
+
generateInvitationToken,
|
|
4
|
+
getInvitationExpiresAt,
|
|
5
|
+
inviteUrlForToken,
|
|
6
|
+
normalizeInvitationEmail,
|
|
7
|
+
parseInvitationPermission,
|
|
8
|
+
renderInvitationEmail
|
|
9
|
+
} from "../chunk-WEBBJBDH.js";
|
|
1
10
|
import {
|
|
2
11
|
generateInviteToken,
|
|
3
12
|
isInviteTokenShape,
|
|
@@ -20,17 +29,24 @@ import {
|
|
|
20
29
|
} from "../chunk-63CE7FEZ.js";
|
|
21
30
|
export {
|
|
22
31
|
ASSIGNABLE_WORKSPACE_ROLES,
|
|
32
|
+
INVITATION_EXPIRY_DAYS,
|
|
23
33
|
ORGANIZATION_ROLES,
|
|
24
34
|
ORGANIZATION_ROLE_RANK,
|
|
25
35
|
WORKSPACE_ROLES,
|
|
26
36
|
WORKSPACE_ROLE_RANK,
|
|
27
37
|
canManageWorkspaceMemberRole,
|
|
38
|
+
generateInvitationToken,
|
|
28
39
|
generateInviteToken,
|
|
40
|
+
getInvitationExpiresAt,
|
|
29
41
|
hasOrganizationRole,
|
|
30
42
|
hasWorkspaceRole,
|
|
43
|
+
inviteUrlForToken,
|
|
31
44
|
isAssignableWorkspaceRole,
|
|
32
45
|
isInviteTokenShape,
|
|
46
|
+
normalizeInvitationEmail,
|
|
33
47
|
organizationRoleGrantsWorkspaceOwner,
|
|
48
|
+
parseInvitationPermission,
|
|
49
|
+
renderInvitationEmail,
|
|
34
50
|
resolveWorkspaceRole,
|
|
35
51
|
validateInviteToken,
|
|
36
52
|
workspaceRoleToCollaborationAccess,
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { c as InvitationPermission, d as InvitationStatus, b as InvitationEmailStatus } from '../invitations-CZ5G2tIn.js';
|
|
2
|
+
import { EnforceSeatSeam, MemberSyncSeam } from './members-api.js';
|
|
3
|
+
export { SeatLimitError } from './members-api.js';
|
|
4
|
+
import { T as TeamDatabase, a as TeamTables, g as TeamParentTable, h as WorkspaceAccessApi } from '../access-BZ81Btt5.js';
|
|
5
|
+
import { a as WorkspaceInvitationTables } from '../invitations-schema-DaMO4hZE.js';
|
|
6
|
+
import '../roles-BC1n4t37.js';
|
|
7
|
+
import 'drizzle-orm/sqlite-core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Framework-neutral email-invitation lifecycle for the teams module: create /
|
|
11
|
+
* list / resend / revoke / preview / accept over the dedicated
|
|
12
|
+
* `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out
|
|
13
|
+
* of any one app's route file — each app binds this once to its own db/tables/
|
|
14
|
+
* access and mounts the handlers in its own routes with its own auth.
|
|
15
|
+
*
|
|
16
|
+
* Unlike `members-api` (whose "invite" is a pending workspaceMember row, no
|
|
17
|
+
* email), this models the rich lifecycle: a dedicated invitation row with status,
|
|
18
|
+
* 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the
|
|
19
|
+
* `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`
|
|
20
|
+
* still surfaces accepted members; pending invites live only here.
|
|
21
|
+
*
|
|
22
|
+
* Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the
|
|
23
|
+
* route adapter maps `{ status }` to its own framework's response. Three seams:
|
|
24
|
+
* - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a
|
|
25
|
+
* typed outcome; a failed send never blocks invitation creation (emailStatus
|
|
26
|
+
* is recorded as 'failed' and the invite link is still returned).
|
|
27
|
+
* - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time
|
|
28
|
+
* only when the invite would consume a NEW seat. Reused from members-api.
|
|
29
|
+
* - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new
|
|
30
|
+
* member to a sandbox/external system on accept. Fail-soft by contract.
|
|
31
|
+
*
|
|
32
|
+
* Imports `drizzle-orm`, so this is a subpath, never re-exported from root.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** The app's mail transport. Returns a typed outcome; never throws to the API. */
|
|
36
|
+
interface SendInvitationEmailInput {
|
|
37
|
+
to: string;
|
|
38
|
+
workspaceName: string;
|
|
39
|
+
inviterEmail: string;
|
|
40
|
+
permission: InvitationPermission;
|
|
41
|
+
inviteUrl: string;
|
|
42
|
+
expiresAt: Date;
|
|
43
|
+
}
|
|
44
|
+
type SendInvitationEmailResult = {
|
|
45
|
+
succeeded: true;
|
|
46
|
+
} | {
|
|
47
|
+
succeeded: false;
|
|
48
|
+
error: string;
|
|
49
|
+
};
|
|
50
|
+
interface SendInvitationEmailSeam {
|
|
51
|
+
(input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>;
|
|
52
|
+
}
|
|
53
|
+
/** The product's user table, narrowed to the columns the queries read. */
|
|
54
|
+
interface InvitationUserTable {
|
|
55
|
+
id: any;
|
|
56
|
+
email: any;
|
|
57
|
+
emailVerified: any;
|
|
58
|
+
}
|
|
59
|
+
/** The product's workspace table, narrowed to the columns the queries read. */
|
|
60
|
+
interface InvitationWorkspaceTable {
|
|
61
|
+
id: any;
|
|
62
|
+
name: any;
|
|
63
|
+
}
|
|
64
|
+
interface InvitationsApiOptions {
|
|
65
|
+
db: TeamDatabase;
|
|
66
|
+
tables: TeamTables;
|
|
67
|
+
/** The invitation table from `createWorkspaceInvitationTable`. */
|
|
68
|
+
invitationsTable: WorkspaceInvitationTables;
|
|
69
|
+
userTable: TeamParentTable & InvitationUserTable;
|
|
70
|
+
workspaceTable: TeamParentTable & InvitationWorkspaceTable;
|
|
71
|
+
access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>;
|
|
72
|
+
/** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */
|
|
73
|
+
sendInvitationEmail: SendInvitationEmailSeam;
|
|
74
|
+
/** OPTIONAL — seat-limit gate at create time; mirrors members-api. */
|
|
75
|
+
enforceSeat?: EnforceSeatSeam;
|
|
76
|
+
/** OPTIONAL — fire-and-forget member propagation on accept. */
|
|
77
|
+
memberSyncSeam?: Pick<MemberSyncSeam, 'add'>;
|
|
78
|
+
/** Inviter display fallback when the inviter's email is unknown. */
|
|
79
|
+
productDisplayName?: string;
|
|
80
|
+
}
|
|
81
|
+
interface WorkspaceInvitationView {
|
|
82
|
+
id: string;
|
|
83
|
+
workspaceId: string;
|
|
84
|
+
organizationId: string;
|
|
85
|
+
email: string;
|
|
86
|
+
invitedByUserId: string;
|
|
87
|
+
inviterEmail: string | null;
|
|
88
|
+
permissions: InvitationPermission;
|
|
89
|
+
token: string;
|
|
90
|
+
status: InvitationStatus;
|
|
91
|
+
emailStatus: InvitationEmailStatus;
|
|
92
|
+
expiresAt: Date;
|
|
93
|
+
createdAt: Date;
|
|
94
|
+
acceptedAt: Date | null;
|
|
95
|
+
revokedAt: Date | null;
|
|
96
|
+
lastSentAt: Date | null;
|
|
97
|
+
}
|
|
98
|
+
interface InvitationPreview {
|
|
99
|
+
workspaceId: string;
|
|
100
|
+
workspaceName: string;
|
|
101
|
+
email: string;
|
|
102
|
+
inviterEmail: string | null;
|
|
103
|
+
permissions: InvitationPermission;
|
|
104
|
+
status: InvitationStatus;
|
|
105
|
+
expiresAt: Date;
|
|
106
|
+
}
|
|
107
|
+
type InvitationOutcome<T> = {
|
|
108
|
+
succeeded: true;
|
|
109
|
+
value: T;
|
|
110
|
+
} | {
|
|
111
|
+
succeeded: false;
|
|
112
|
+
status: number;
|
|
113
|
+
error: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Build the invitations API bound to one product's db/tables/access/seams.
|
|
117
|
+
* Returns the six lifecycle handlers an app mounts in its routes.
|
|
118
|
+
*/
|
|
119
|
+
declare function createInvitationsApi(opts: InvitationsApiOptions): {
|
|
120
|
+
createInvitation: (input: {
|
|
121
|
+
workspaceId: string;
|
|
122
|
+
email: string;
|
|
123
|
+
permissions: string | undefined;
|
|
124
|
+
invitedByUserId: string;
|
|
125
|
+
origin: string;
|
|
126
|
+
now?: Date;
|
|
127
|
+
}) => Promise<InvitationOutcome<{
|
|
128
|
+
invitation: WorkspaceInvitationView;
|
|
129
|
+
inviteUrl: string;
|
|
130
|
+
emailError?: string;
|
|
131
|
+
}>>;
|
|
132
|
+
listInvitations: (input: {
|
|
133
|
+
workspaceId: string;
|
|
134
|
+
userId: string;
|
|
135
|
+
origin: string;
|
|
136
|
+
now?: Date;
|
|
137
|
+
}) => Promise<InvitationOutcome<{
|
|
138
|
+
invitations: Array<WorkspaceInvitationView & {
|
|
139
|
+
inviteUrl: string;
|
|
140
|
+
}>;
|
|
141
|
+
}>>;
|
|
142
|
+
resendInvitation: (input: {
|
|
143
|
+
invitationId: string;
|
|
144
|
+
userId: string;
|
|
145
|
+
origin: string;
|
|
146
|
+
now?: Date;
|
|
147
|
+
}) => Promise<InvitationOutcome<{
|
|
148
|
+
invitation: WorkspaceInvitationView;
|
|
149
|
+
inviteUrl: string;
|
|
150
|
+
emailError?: string;
|
|
151
|
+
}>>;
|
|
152
|
+
revokeInvitation: (input: {
|
|
153
|
+
invitationId: string;
|
|
154
|
+
userId: string;
|
|
155
|
+
now?: Date;
|
|
156
|
+
}) => Promise<InvitationOutcome<{
|
|
157
|
+
invitation: WorkspaceInvitationView;
|
|
158
|
+
}>>;
|
|
159
|
+
getPreview: (token: string, now?: Date) => Promise<InvitationOutcome<InvitationPreview>>;
|
|
160
|
+
acceptInvitation: (input: {
|
|
161
|
+
token: string;
|
|
162
|
+
userId: string;
|
|
163
|
+
now?: Date;
|
|
164
|
+
}) => Promise<InvitationOutcome<{
|
|
165
|
+
workspaceId: string;
|
|
166
|
+
}>>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export { EnforceSeatSeam, type InvitationOutcome, type InvitationPreview, type InvitationUserTable, type InvitationWorkspaceTable, type InvitationsApiOptions, MemberSyncSeam, type SendInvitationEmailInput, type SendInvitationEmailResult, type SendInvitationEmailSeam, type WorkspaceInvitationView, createInvitationsApi };
|