@objectstack/plugin-org-scoping 7.0.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/LICENSE +93 -0
- package/README.md +55 -0
- package/dist/index.d.mts +190 -0
- package/dist/index.d.ts +190 -0
- package/dist/index.js +626 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +592 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/claim-orphan-org-rows.ts","../src/clone-org-seed-data.ts","../src/ensure-default-organization.ts","../src/manifest.ts","../src/org-scoping-plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-org-scoping\n *\n * Row-level Organization isolation for ObjectStack:\n * - auto-stamps `organization_id` on insert from\n * `ExecutionContext.tenantId`,\n * - replays seed datasets (or clones from the donor org) on every\n * `sys_organization` insert,\n * - bootstraps a Default Organization for the first platform admin.\n *\n * Pair with `@objectstack/plugin-security` to get full multi-tenant\n * RBAC + RLS + Field-Level Security. Install standalone for\n * single-tenant deployments — plugin-security detects this plugin's\n * presence via `getService('org-scoping')` and adjusts wildcard\n * tenant policy handling accordingly.\n */\n\nexport { OrgScopingPlugin } from './org-scoping-plugin.js';\nexport type { OrgScopingPluginOptions } from './org-scoping-plugin.js';\nexport { claimOrphanOrgRows } from './claim-orphan-org-rows.js';\nexport { cloneOrgSeedData } from './clone-org-seed-data.js';\nexport {\n ensureDefaultOrganization,\n type EnsureDefaultOrganizationResult,\n} from './ensure-default-organization.js';\nexport {\n orgScopingObjects,\n orgScopingPluginManifestHeader,\n ORG_SCOPING_PLUGIN_ID,\n ORG_SCOPING_PLUGIN_VERSION,\n} from './manifest.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * claimOrphanOrgRows — assign seed-loaded records to the first organization.\n *\n * Seeds (`defineDataset`) are inserted by `SeedLoaderService` using\n * `{ context: { isSystem: true } }`, which intentionally bypasses\n * SecurityPlugin's `organization_id` auto-fill. As a result, in\n * multi-tenant mode every seed row lands with `organization_id = NULL`.\n *\n * That's correct for **cross-tenant metadata** — `sys_permission_set`\n * rows, default roles, etc. (objects whose schema has `managedBy` set)\n * — but for **business-domain seeds** (CRM `lead`, `account`, `contact`,\n * …) it means the rows are invisible to anyone bound to an organization\n * (the default `tenant_isolation` RLS policy\n * `organization_id = current_user.organization_id` filters them out).\n *\n * This helper runs **once**, on first-organization creation, and\n * back-fills `organization_id` on every orphaned (`organization_id IS\n * NULL`) seed row of every user-defined object that declares the\n * column. Result: out of the box, the freshly registered owner sees the\n * shipped demo data scoped to their first org — no manual claim step.\n *\n * Idempotent: a no-op once an organization-tagged row exists, and\n * `managedBy` schemas (`sys_*` better-auth/platform tables) are always\n * skipped so cross-tenant defaults stay cross-tenant.\n */\n\nimport type { ServiceObject } from '@objectstack/spec/data';\n\ninterface ClaimOptions {\n logger?: {\n info: (message: string, meta?: Record<string, any>) => void;\n warn: (message: string, meta?: Record<string, any>) => void;\n };\n}\n\nconst SYSTEM_CTX = { isSystem: true };\n\nfunction hasOrganizationField(schema: ServiceObject): boolean {\n const fields: any = (schema as any)?.fields;\n if (!fields) return false;\n if (Array.isArray(fields)) {\n return fields.some((f) => f?.name === 'organization_id');\n }\n return Object.prototype.hasOwnProperty.call(fields, 'organization_id');\n}\n\n/**\n * Assign every orphaned seed row to `organizationId`.\n *\n * Walks `ql.registry.getAllObjects()`, filters to schemas that\n * (a) are not `managedBy` (skip sys_/auth/platform tables),\n * (b) declare an `organization_id` field,\n * and runs an `update(where: { organization_id: null }, patch: {\n * organization_id: organizationId })` against each as `isSystem`.\n *\n * Returns a per-object summary `{ object, count }[]`.\n */\nexport async function claimOrphanOrgRows(\n ql: any,\n organizationId: string,\n options: ClaimOptions = {},\n): Promise<{ object: string; count: number }[]> {\n const logger = options.logger;\n if (!ql || typeof ql.update !== 'function' || typeof ql.find !== 'function') {\n return [];\n }\n const registry = (ql as any).registry;\n if (!registry || typeof registry.getAllObjects !== 'function') {\n logger?.warn?.('[org-scoping] claimOrphanOrgRows: registry unavailable');\n return [];\n }\n\n const schemas: ServiceObject[] = registry.getAllObjects();\n const results: { object: string; count: number }[] = [];\n\n for (const schema of schemas) {\n if (!schema?.name) continue;\n if ((schema as any).managedBy) continue;\n // Defense in depth: any platform-namespaced object (`sys_*`) is\n // off-limits for tenant claim regardless of `managedBy`. Platform\n // tables that should be tenant-scoped are inserted with an explicit\n // `organization_id` by the code that owns them, so they will never\n // be orphans here.\n if (schema.name.startsWith('sys_')) continue;\n if (!hasOrganizationField(schema)) continue;\n\n try {\n const orphans = await ql.find(\n schema.name,\n { where: { organization_id: null }, limit: 10_000, fields: ['id'] },\n { context: SYSTEM_CTX },\n );\n const list: any[] = Array.isArray(orphans)\n ? orphans\n : Array.isArray(orphans?.records)\n ? orphans.records\n : [];\n if (list.length === 0) continue;\n\n let updated = 0;\n for (const row of list) {\n if (!row?.id) continue;\n try {\n await ql.update(\n schema.name,\n { id: row.id, organization_id: organizationId },\n { context: SYSTEM_CTX },\n );\n updated += 1;\n } catch (e) {\n logger?.warn?.(`[org-scoping] claim failed for ${schema.name}:${row.id}`, {\n error: (e as Error).message,\n });\n }\n }\n if (updated > 0) {\n results.push({ object: schema.name, count: updated });\n }\n } catch (e) {\n logger?.warn?.(`[org-scoping] claim scan failed for ${schema.name}`, {\n error: (e as Error).message,\n });\n }\n }\n\n if (results.length > 0) {\n const total = results.reduce((s, r) => s + r.count, 0);\n logger?.info?.(`[org-scoping] claimed ${total} orphan seed row(s) for organization ${organizationId}`, {\n breakdown: results,\n });\n }\n return results;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * cloneOrgSeedData — give every newly-registered org its own copy of\n * the demo seed data.\n *\n * Multi-tenant deployments treat each `sys_organization` as a hard\n * isolation boundary. The platform-wide `claimOrphanOrgRows` hook\n * (see `claim-orphan-tenant-rows.ts`) only fires for the very first\n * org — every subsequent org (created explicitly by a user via\n * `createOrganization`, or by an admin from the console) starts\n * empty. For demo / trial-org UX (Salesforce-style \"you get a\n * fully populated sandbox on signup\"), we want every freshly minted\n * org to receive a private clone of the platform-first org's\n * user-defined data.\n *\n * Strategy:\n * 1. Pick the donor org — the very first `sys_organization`.\n * 2. Walk `ql.registry.getAllObjects()` once to collect schemas\n * that are user-defined (not `managedBy`, not `sys_*`) AND\n * declare an `organization_id` field.\n * 3. Pass A — for each donor object, find rows where\n * `organization_id = donorOrgId`, generate a new id, insert a\n * shallow copy under `targetOrgId`, recording an\n * `oldId → newId` map keyed by object name. Lookup field values\n * pointing at donor rows are left untouched in this pass; the\n * remap happens in pass B so we don't depend on topological\n * ordering of inserts.\n * 4. Pass B — for each cloned row, walk its lookup-shaped fields\n * and rewrite values that match the donor map for the field's\n * `reference` object.\n *\n * Idempotent: skipped if the target org already has rows in any\n * cloned object, or if no donor org exists, or if the target IS the\n * donor (claim hook handles the donor itself).\n *\n * Best-effort: per-object failures are logged at `warn` and don't\n * abort the rest of the clone. FK fields that reference an object\n * that wasn't cloned (e.g. the lookup target lives in `sys_*`, or\n * the remap key isn't present) are left as-is — broken refs are\n * preferable to losing whole rows.\n */\n\nimport type { ServiceObject } from '@objectstack/spec/data';\n\ninterface CloneOptions {\n logger?: {\n info: (message: string, meta?: Record<string, any>) => void;\n warn: (message: string, meta?: Record<string, any>) => void;\n };\n}\n\ninterface FieldDescriptor {\n name: string;\n type?: string;\n reference?: string;\n multiple?: boolean;\n unique?: boolean;\n}\n\nconst SYSTEM_CTX = { isSystem: true };\n\nconst SKIP_COPY_FIELDS = new Set<string>([\n 'id',\n 'created_at',\n 'updated_at',\n 'organization_id',\n]);\n\n// Computed / virtual / system-managed field types — these have no\n// physical column in the DB, so re-inserting them would fail with\n// \"table X has no column named Y\". `find()` returns them in the\n// projected row (formula evaluation, rollup summary), but they must\n// NEVER be sent back to `insert()`.\n//\n// NOTE: `autonumber` IS a real string column in the SQL driver — it\n// has no auto-generation in this codebase, the value comes from the\n// seed file itself. Cloning it preserves the demo's \"CTR-0001\" /\n// \"QTE-0001\" identifiers so users see meaningful titleFormats and\n// the `externalId` upsert key keeps working on subsequent re-seeds.\nconst SKIP_COPY_TYPES = new Set<string>(['formula', 'summary']);\n\nfunction fieldList(schema: ServiceObject): FieldDescriptor[] {\n const fields: any = (schema as any)?.fields;\n if (!fields) return [];\n if (Array.isArray(fields)) {\n return fields.map((f: any) => ({\n name: f?.name,\n type: f?.type,\n reference: f?.reference,\n multiple: f?.multiple,\n unique: f?.unique,\n }));\n }\n return Object.entries(fields as Record<string, any>).map(([name, f]) => ({\n name,\n type: f?.type,\n reference: f?.reference,\n multiple: f?.multiple,\n unique: f?.unique,\n }));\n}\n\nfunction isLookupField(f: FieldDescriptor): boolean {\n return (f.type === 'lookup' || f.type === 'master_detail' || f.type === 'tree') && !!f.reference;\n}\n\nfunction hasOrgField(schema: ServiceObject): boolean {\n return fieldList(schema).some((f) => f.name === 'organization_id');\n}\n\nfunction shortId(): string {\n // Mirror the format `nanoid(16)` used elsewhere in the codebase\n // without pulling a runtime dep here.\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';\n let out = '';\n for (let i = 0; i < 16; i++) {\n out += alphabet[Math.floor(Math.random() * alphabet.length)];\n }\n return out;\n}\n\nasync function findDonorOrgId(ql: any): Promise<string | null> {\n try {\n const res = await ql.find(\n 'sys_organization',\n { orderBy: { created_at: 'asc' }, limit: 1, fields: ['id'] },\n { context: SYSTEM_CTX },\n );\n const list: any[] = Array.isArray(res) ? res : Array.isArray(res?.records) ? res.records : [];\n return list[0]?.id ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function cloneOrgSeedData(\n ql: any,\n targetOrgId: string,\n options: CloneOptions = {},\n): Promise<{ object: string; count: number }[]> {\n const logger = options.logger;\n if (!ql || typeof ql.find !== 'function' || typeof ql.insert !== 'function') {\n return [];\n }\n const registry = (ql as any).registry;\n if (!registry || typeof registry.getAllObjects !== 'function') {\n logger?.warn?.('[org-scoping] cloneOrgSeedData: registry unavailable');\n return [];\n }\n\n const donorOrgId = await findDonorOrgId(ql);\n if (!donorOrgId) return [];\n if (donorOrgId === targetOrgId) return [];\n\n const schemas: ServiceObject[] = registry.getAllObjects().filter(\n (s: any) => s?.name && !s.managedBy && !s.name.startsWith('sys_') && hasOrgField(s),\n );\n\n // Pass A: clone rows shallowly, build per-object oldId → newId map.\n const remap: Record<string, Record<string, string>> = {};\n const summary: { object: string; count: number }[] = [];\n // Track inserted shadow records so pass B can rewrite their lookups\n // without re-fetching from the DB.\n const inserted: { object: string; newId: string; record: Record<string, unknown>; lookups: FieldDescriptor[] }[] = [];\n\n for (const schema of schemas) {\n const objectName = schema.name as string;\n try {\n // Idempotency: if target org already has any row in this object,\n // assume a previous clone (or manual data) and skip — never\n // double-clone.\n const existing = await ql.find(\n objectName,\n { where: { organization_id: targetOrgId }, limit: 1, fields: ['id'] },\n { context: SYSTEM_CTX },\n );\n const existingList: any[] = Array.isArray(existing)\n ? existing\n : Array.isArray(existing?.records)\n ? existing.records\n : [];\n if (existingList.length > 0) {\n continue;\n }\n\n const donorRows = await ql.find(\n objectName,\n { where: { organization_id: donorOrgId }, limit: 10_000 },\n { context: SYSTEM_CTX },\n );\n const rows: any[] = Array.isArray(donorRows)\n ? donorRows\n : Array.isArray(donorRows?.records)\n ? donorRows.records\n : [];\n if (rows.length === 0) continue;\n\n const fields = fieldList(schema);\n const lookups = fields.filter(isLookupField);\n const uniqueFields = fields.filter((f) => f.unique && !SKIP_COPY_FIELDS.has(f.name));\n const objectRemap: Record<string, string> = (remap[objectName] ??= {});\n let cloned = 0;\n for (const row of rows) {\n const newId = shortId();\n const data: Record<string, unknown> = { id: newId, organization_id: targetOrgId };\n for (const f of fields) {\n if (SKIP_COPY_FIELDS.has(f.name)) continue;\n if (f.type && SKIP_COPY_TYPES.has(f.type)) continue;\n if (row[f.name] === undefined) continue;\n data[f.name] = row[f.name];\n }\n // Disambiguate UNIQUE columns. Many seed schemas declare\n // single-column unique indexes (e.g. `lead.email`) without\n // tenant scoping — cloning the donor row verbatim would\n // collide. Append a per-tenant suffix so each org gets its\n // own copy.\n const suffix = `+${targetOrgId.slice(-6)}`;\n for (const uf of uniqueFields) {\n const v = data[uf.name];\n if (typeof v !== 'string' || !v) continue;\n if (uf.type === 'email' && v.includes('@')) {\n const [local, domain] = v.split('@');\n data[uf.name] = `clone-${targetOrgId.slice(-6)}-${local}@${domain}`;\n } else {\n data[uf.name] = `${v}${suffix}`;\n }\n }\n try {\n await ql.insert(objectName, data, { context: SYSTEM_CTX });\n objectRemap[row.id] = newId;\n inserted.push({ object: objectName, newId, record: data, lookups });\n cloned++;\n } catch (e) {\n logger?.warn?.('[org-scoping] cloneOrgSeedData: insert failed', {\n object: objectName,\n error: (e as Error).message,\n });\n }\n }\n if (cloned > 0) summary.push({ object: objectName, count: cloned });\n } catch (e) {\n logger?.warn?.('[org-scoping] cloneOrgSeedData: object failed', {\n object: objectName,\n error: (e as Error).message,\n });\n }\n }\n\n // Pass B: rewrite lookup field values using the per-object remap so\n // intra-clone relationships stay intact.\n //\n // Cross-tenant FK hygiene: when a donor row's lookup value DOESN'T\n // appear in `remap[reference]` (i.e. the donor itself had a stale\n // FK pointing at another tenant's record, or the referenced object\n // wasn't included in this clone), we NULL the field instead of\n // leaving the orphan string in place. Otherwise every subsequent\n // clone perpetuates the broken FK chain (donor → tenant A → tenant\n // B → ...) and renderers display raw IDs because `find()` for the\n // referenced ID returns no row in the current tenant.\n for (const item of inserted) {\n if (item.lookups.length === 0) continue;\n const patch: Record<string, unknown> = {};\n let dirty = false;\n for (const f of item.lookups) {\n const oldVal = item.record[f.name];\n if (oldVal == null) continue;\n const targetMap = remap[f.reference!];\n if (Array.isArray(oldVal)) {\n // For multi-value lookups: remap when possible, drop entries\n // that have no remap (rather than keep an orphan string).\n const next = oldVal\n .map((v: any) => (typeof v === 'string' && targetMap?.[v]) || null)\n .filter((v: any) => v != null);\n if (next.length !== oldVal.length || next.some((v, i) => v !== oldVal[i])) {\n patch[f.name] = next.length > 0 ? next : null;\n dirty = true;\n }\n } else if (typeof oldVal === 'string') {\n if (targetMap && targetMap[oldVal]) {\n patch[f.name] = targetMap[oldVal];\n dirty = true;\n } else {\n // Unresolvable cross-tenant reference — null it out so the\n // UI shows \"empty\" rather than a dangling ID.\n patch[f.name] = null;\n dirty = true;\n }\n }\n }\n if (!dirty) continue;\n try {\n await ql.update(item.object, { id: item.newId, ...patch }, { context: SYSTEM_CTX });\n } catch (e) {\n logger?.warn?.('[org-scoping] cloneOrgSeedData: lookup remap failed', {\n object: item.object,\n id: item.newId,\n error: (e as Error).message,\n });\n }\n }\n\n return summary;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ensureDefaultOrganization — multi-tenant bootstrap helper.\n *\n * In multi-tenant deployments the freshly-promoted platform admin\n * (`admin_full_access` granted with `organization_id IS NULL`) needs\n * at least one `sys_organization` to carry an `activeOrganizationId`\n * on their session. Without it, the default `tenant_isolation` RLS\n * policy filters everything to zero rows and the admin sees an empty\n * console even though they have full access.\n *\n * Strategy (idempotent, run on `kernel:ready` and after every\n * `sys_user_permission_set` insert):\n *\n * 1. Find the platform admin (oldest `sys_user_permission_set` row\n * with `permission_set_id = admin_full_access` and\n * `organization_id IS NULL`). If none, no-op.\n * 2. If that user already has any `sys_member` row, no-op (they\n * either created their own org or were invited into one — we\n * respect that and never auto-create a \"Default Organization\"\n * behind their back).\n * 3. Re-use a pre-existing `slug='default'` org if present;\n * otherwise create one. Stable slug keeps human-readable URLs\n * predictable across cold-boots.\n * 4. Insert a `sys_member { role: 'owner' }` linking the admin to\n * the default org.\n *\n * This is the ONLY framework-side auto-provisioning of an org.\n * Subsequent users must accept an invitation or explicitly create\n * their first organization — `claimOrphanOrgRows` / `cloneOrgSeedData`\n * handle the seed-data side for those flows.\n */\n\ninterface EnsureOptions {\n logger?: {\n info: (message: string, meta?: Record<string, any>) => void;\n warn: (message: string, meta?: Record<string, any>) => void;\n };\n}\n\nconst SYSTEM_CTX = { isSystem: true };\n\nasync function tryFind(ql: any, object: string, where: any, limit = 100): Promise<any[]> {\n try {\n const rows = await ql.find(object, { where, limit }, { context: SYSTEM_CTX });\n return Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];\n } catch {\n return [];\n }\n}\n\nasync function tryInsert(ql: any, object: string, data: any): Promise<any | null> {\n try {\n return await ql.insert(object, data, { context: SYSTEM_CTX });\n } catch {\n return null;\n }\n}\n\nfunction genId(prefix: string): string {\n const rand = Math.random().toString(36).slice(2, 10);\n const ts = Date.now().toString(36);\n return `${prefix}_${ts}${rand}`;\n}\n\nexport interface EnsureDefaultOrganizationResult {\n /** Whether a brand-new org row was inserted (vs. re-using slug=default). */\n defaultOrgCreated: boolean;\n /** Resolved (or freshly minted) default-org id; undefined when no admin exists yet. */\n defaultOrgId?: string;\n /** Whether a sys_member row was inserted binding the admin to the default org. */\n memberCreated: boolean;\n /** Human-readable reason when the helper short-circuited. */\n reason?: 'no_admin' | 'admin_already_in_org' | 'org_insert_failed' | 'member_insert_failed';\n}\n\n/**\n * Ensure the platform admin has a Default Organization to operate in.\n * Safe to call multiple times — idempotent on stable slug `default`\n * and on the presence of any existing `sys_member` row for the admin.\n */\nexport async function ensureDefaultOrganization(\n ql: any,\n options: EnsureOptions = {},\n): Promise<EnsureDefaultOrganizationResult> {\n const logger = options.logger;\n if (!ql || typeof ql.find !== 'function' || typeof ql.insert !== 'function') {\n return { defaultOrgCreated: false, memberCreated: false, reason: 'no_admin' };\n }\n\n // 1. Find the platform admin permission-set id.\n const adminPs = await tryFind(ql, 'sys_permission_set', { name: 'admin_full_access' }, 1);\n if (adminPs.length === 0 || !adminPs[0].id) {\n return { defaultOrgCreated: false, memberCreated: false, reason: 'no_admin' };\n }\n const adminPsId = adminPs[0].id;\n\n // 2. Find the platform admin user (oldest cross-tenant grant).\n const adminGrants = await tryFind(\n ql,\n 'sys_user_permission_set',\n { permission_set_id: adminPsId, organization_id: null },\n 50,\n );\n if (adminGrants.length === 0) {\n return { defaultOrgCreated: false, memberCreated: false, reason: 'no_admin' };\n }\n const sortedGrants = [...adminGrants].sort((a, b) => {\n const ta = a.created_at ? new Date(a.created_at).getTime() : 0;\n const tb = b.created_at ? new Date(b.created_at).getTime() : 0;\n return ta - tb;\n });\n const adminUserId: string | undefined = sortedGrants[0]?.user_id;\n if (!adminUserId) {\n return { defaultOrgCreated: false, memberCreated: false, reason: 'no_admin' };\n }\n\n // 3. Respect existing membership — never auto-create a default org\n // behind an admin who already belongs somewhere.\n const memberships = await tryFind(ql, 'sys_member', { user_id: adminUserId }, 1);\n if (memberships.length > 0) {\n return {\n defaultOrgCreated: false,\n memberCreated: false,\n reason: 'admin_already_in_org',\n };\n }\n\n // 4. Re-use or create the `default` org.\n let defaultOrgId: string | undefined;\n let defaultOrgCreated = false;\n const existingDefault = await tryFind(ql, 'sys_organization', { slug: 'default' }, 1);\n if (existingDefault.length > 0 && existingDefault[0].id) {\n defaultOrgId = String(existingDefault[0].id);\n } else {\n const newOrgId = genId('org');\n const orgRow = await tryInsert(ql, 'sys_organization', {\n id: newOrgId,\n name: 'Default Organization',\n slug: 'default',\n logo: null,\n metadata: null,\n });\n if (!orgRow) {\n logger?.warn?.('[org-scoping] failed to create default organization for platform admin');\n return { defaultOrgCreated: false, memberCreated: false, reason: 'org_insert_failed' };\n }\n defaultOrgId = orgRow?.id ?? newOrgId;\n defaultOrgCreated = true;\n }\n\n // 5. Bind the admin as owner.\n const memRow = await tryInsert(ql, 'sys_member', {\n id: genId('mem'),\n organization_id: defaultOrgId,\n user_id: adminUserId,\n role: 'owner',\n });\n if (!memRow) {\n logger?.warn?.('[org-scoping] failed to bind platform admin to default organization');\n return {\n defaultOrgCreated,\n defaultOrgId,\n memberCreated: false,\n reason: 'member_insert_failed',\n };\n }\n logger?.info?.(\n `[org-scoping] bound platform admin to default organization (${defaultOrgId})`,\n { userId: adminUserId, defaultOrgId },\n );\n return { defaultOrgCreated, defaultOrgId, memberCreated: true };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Canonical plugin-org-scoping manifest source.\n *\n * Imported by `objectstack.config.ts` (compile-time) and\n * `org-scoping-plugin.ts` (runtime `manifest.register`) so the two\n * registration paths cannot drift.\n */\n\nexport const ORG_SCOPING_PLUGIN_ID = 'com.objectstack.plugin-org-scoping';\nexport const ORG_SCOPING_PLUGIN_VERSION = '1.0.0';\n\n/** This plugin owns no `sys_*` objects — Organization itself lives in `@objectstack/platform-objects`. */\nexport const orgScopingObjects = [] as const;\n\n/** Manifest header shared by compile-time config and runtime registration. */\nexport const orgScopingPluginManifestHeader = {\n id: ORG_SCOPING_PLUGIN_ID,\n namespace: 'sys',\n version: ORG_SCOPING_PLUGIN_VERSION,\n type: 'plugin' as const,\n scope: 'system' as const,\n defaultDatasource: 'cloud',\n name: 'Organization Scoping Plugin',\n description:\n 'Row-level Organization isolation: auto-stamps `organization_id` on insert from ' +\n '`ExecutionContext.tenantId`, replays seed datasets per new org, and bootstraps a default ' +\n 'organization for the first platform admin.',\n};\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext } from '@objectstack/core';\nimport { claimOrphanOrgRows } from './claim-orphan-org-rows.js';\nimport { cloneOrgSeedData } from './clone-org-seed-data.js';\nimport { ensureDefaultOrganization } from './ensure-default-organization.js';\nimport {\n orgScopingObjects,\n orgScopingPluginManifestHeader,\n} from './manifest.js';\n\nexport interface OrgScopingPluginOptions {\n /**\n * Whether to auto-create a `Default Organization` (slug `default`)\n * and bind the first platform admin as `owner` when they have zero\n * memberships. Set to `false` for deployments that fully self-manage\n * org provisioning via invitation links or a custom onboarding flow.\n *\n * @default true\n */\n ensureDefaultOrganization?: boolean;\n}\n\n/**\n * OrgScopingPlugin\n *\n * Makes `sys_organization` a first-class row-level isolation boundary:\n *\n * 1. **insert auto-stamp** — on every authenticated `insert` whose\n * target object declares `organization_id`, fill the column from\n * `ExecutionContext.tenantId`. Without this, freshly-created\n * rows have `organization_id = NULL` and the default\n * `tenant_isolation` RLS policy hides them from the very user\n * who just created them.\n *\n * 2. **per-org seed replay** — after `sys_organization` insert, copy\n * the artifact's demo seed data into the new org. Three paths\n * (in order of preference):\n * a. replay registered `seed-datasets` via the kernel-level\n * `seed-replayer` callable (set by AppPlugin),\n * b. for the FIRST org, `claimOrphanOrgRows` adopts any\n * NULL-org rows a previous inline-seed may have inserted,\n * c. for SUBSEQUENT orgs, `cloneOrgSeedData` shallow-clones\n * rows from the very first org (donor-pattern).\n *\n * 3. **default-org bootstrap** — on `kernel:ready` and after every\n * `sys_user_permission_set` insert, ensure the platform admin has\n * a Default Organization to operate in (idempotent on slug\n * `default` + admin's existing memberships).\n *\n * Why split from plugin-security:\n * - plugin-security is a single-tenant-aware RBAC + RLS engine; it\n * should not know about Organization-specific seed flows.\n * - This plugin is purely opt-in: not installing it gives a\n * single-tenant deployment (no `organization_id` injection, no\n * per-org seed clone, no default-org bootstrap). plugin-security\n * detects its presence via `getService('org-scoping')` and adjusts\n * RLS policy stripping accordingly.\n *\n * Naming note: \"org-scoping\" deliberately avoids the word \"tenant\"\n * because in ObjectStack \"tenant\" already means *physical isolation*\n * (one Environment = one database, per ADR-0002 and driver-turso's\n * multi-tenant router). This plugin is about LOGICAL row-level\n * scoping inside a single database — orthogonal to physical tenancy.\n *\n * Dependencies:\n * - `objectql` (engine middleware host)\n */\nexport class OrgScopingPlugin implements Plugin {\n name = 'com.objectstack.org-scoping';\n type = 'standard';\n version = '1.0.0';\n dependencies = ['com.objectstack.engine.objectql'];\n\n /** Per-object field-name cache; same shape as SecurityPlugin's. */\n private readonly fieldNamesCache = new Map<string, Set<string> | null>();\n\n private readonly opts: Required<OrgScopingPluginOptions>;\n\n constructor(options: OrgScopingPluginOptions = {}) {\n this.opts = {\n ensureDefaultOrganization: options.ensureDefaultOrganization !== false,\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Initializing Org-Scoping Plugin...');\n // Service registration doubles as plugin-security's\n // \"multi-tenant mode is on\" probe: SecurityPlugin queries\n // `getService('org-scoping')` and keeps wildcard\n // `current_user.organization_id` RLS policies when this returns.\n ctx.registerService('org-scoping', this);\n\n ctx\n .getService<{ register(m: any): void }>('manifest')\n .register({\n ...orgScopingPluginManifestHeader,\n objects: orgScopingObjects,\n });\n ctx.logger.info('Org-Scoping Plugin initialized');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.logger.info('Starting Org-Scoping Plugin...');\n\n let ql: any;\n let metadata: any;\n try {\n ql = ctx.getService('objectql');\n try {\n metadata = ctx.getService('metadata');\n } catch {\n metadata = undefined;\n }\n } catch {\n ctx.logger.warn(\n 'ObjectQL service not available, org-scoping middleware not registered',\n );\n return;\n }\n if (!ql || typeof ql.registerMiddleware !== 'function') {\n ctx.logger.warn(\n 'ObjectQL engine does not support middleware, org-scoping middleware not registered',\n );\n return;\n }\n\n // ── Middleware A: auto-stamp `organization_id` on insert ──────────\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n if (opCtx.context?.isSystem) return next();\n if (\n opCtx.operation === 'insert' &&\n opCtx.data &&\n typeof opCtx.data === 'object' &&\n !Array.isArray(opCtx.data) &&\n opCtx.context?.tenantId\n ) {\n const fields = await this.getObjectFieldNames(metadata, opCtx.object, ql);\n if (fields && fields.has('organization_id')) {\n const data = opCtx.data as Record<string, unknown>;\n if (data.organization_id == null || data.organization_id === '') {\n data.organization_id = opCtx.context.tenantId;\n }\n }\n }\n await next();\n });\n\n // ── Middleware B: per-org seed pipeline on sys_organization insert ─\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n await next();\n if (\n opCtx?.object !== 'sys_organization' ||\n (opCtx?.operation !== 'create' && opCtx?.operation !== 'insert')\n ) {\n return;\n }\n const newOrgId = opCtx?.result?.id ?? opCtx?.data?.id;\n if (!newOrgId) return;\n\n const kernel: any = (ctx as any).kernel ?? ctx;\n let datasets: any[] | undefined;\n try {\n const raw = kernel?.getService?.('seed-datasets');\n if (Array.isArray(raw) && raw.length > 0) datasets = raw;\n } catch {\n /* service not registered */\n }\n\n // Count existing orgs to pick the right fallback path.\n let orgCount = 0;\n try {\n const allOrgs = await ql.find(\n 'sys_organization',\n { limit: 2, fields: ['id'] },\n { context: { isSystem: true } },\n );\n const list: any[] = Array.isArray(allOrgs)\n ? allOrgs\n : Array.isArray(allOrgs?.records)\n ? allOrgs.records\n : [];\n orgCount = list.length;\n } catch (e) {\n ctx.logger.warn('[org-scoping] failed to count organizations', {\n error: (e as Error).message,\n });\n }\n\n // Primary path: SeedLoader replay scoped to newOrgId.\n let replayed = false;\n try {\n const replayer: any = kernel?.getService?.('seed-replayer');\n if (typeof replayer === 'function') {\n const summary = await replayer(newOrgId);\n const total = (summary?.inserted ?? 0) + (summary?.updated ?? 0);\n ctx.logger.info(\n `[org-scoping] per-org seed replay for ${newOrgId}: +${summary?.inserted ?? 0} inserted, ${summary?.updated ?? 0} updated, ${summary?.errors?.length ?? 0} error(s)`,\n {\n organizationId: newOrgId,\n errors: summary?.errors?.slice?.(0, 5),\n },\n );\n if (total > 0) replayed = true;\n } else if (datasets) {\n ctx.logger.warn(\n '[org-scoping] per-org seed: datasets present but no replayer registered',\n { organizationId: newOrgId },\n );\n }\n } catch (e) {\n ctx.logger.warn(\n '[org-scoping] per-org seed replay failed, falling back',\n { organizationId: newOrgId, error: (e as Error).message },\n );\n }\n if (replayed) return;\n\n // Fallback A: legacy claim for first org.\n if (orgCount === 1) {\n try {\n const claims = await claimOrphanOrgRows(ql, newOrgId, { logger: ctx.logger });\n if (claims.length > 0) {\n const total = claims.reduce((s, c) => s + c.count, 0);\n ctx.logger.info(\n `[org-scoping] claimed ${total} orphan seed row(s) for first organization ${newOrgId}`,\n { breakdown: claims },\n );\n return;\n }\n } catch (e) {\n ctx.logger.warn('[org-scoping] claim-orphan-org-rows failed', {\n error: (e as Error).message,\n });\n }\n }\n\n // Fallback B: clone from donor org for subsequent orgs.\n if (orgCount > 1) {\n try {\n const summary = await cloneOrgSeedData(ql, newOrgId, { logger: ctx.logger });\n if (summary.length > 0) {\n const total = summary.reduce((s, c) => s + c.count, 0);\n ctx.logger.info(\n `[org-scoping] cloned ${total} seed row(s) for new organization ${newOrgId}`,\n { breakdown: summary },\n );\n }\n } catch (e) {\n ctx.logger.warn('[org-scoping] clone-org-seed-data failed', {\n organizationId: newOrgId,\n error: (e as Error).message,\n });\n }\n }\n });\n\n // ── Default-org bootstrap on kernel:ready + on admin grant ────────\n if (this.opts.ensureDefaultOrganization) {\n const runEnsure = async () => {\n try {\n const res = await ensureDefaultOrganization(ql, { logger: ctx.logger });\n if (res.defaultOrgCreated) {\n ctx.logger.info(\n `[org-scoping] created Default Organization ${res.defaultOrgId} for platform admin`,\n );\n }\n } catch (e) {\n ctx.logger.warn?.('[org-scoping] ensureDefaultOrganization failed', {\n error: (e as Error).message,\n });\n }\n };\n if (typeof (ctx as any).hook === 'function') {\n (ctx as any).hook('kernel:ready', runEnsure);\n } else {\n void runEnsure();\n }\n // Re-run after every admin grant — handles the \"first sign-up\n // promoted to platform admin\" case where the kernel:ready hook\n // fired before any user existed.\n ql.registerMiddleware(async (opCtx: any, next: () => Promise<void>) => {\n await next();\n if (\n opCtx?.object === 'sys_user_permission_set' &&\n (opCtx?.operation === 'insert' || opCtx?.operation === 'create')\n ) {\n await runEnsure();\n }\n });\n }\n\n ctx.logger.info('Org-Scoping middleware registered on ObjectQL engine');\n }\n\n async destroy(): Promise<void> {\n // No cleanup needed\n }\n\n /**\n * Resolve the column-name set for an object (mirrors SecurityPlugin's\n * loader so the two plugins behave consistently). Returns `null` if\n * the schema can't be loaded — caller skips injection.\n */\n private async getObjectFieldNames(\n metadata: any,\n objectName: string,\n ql?: any,\n ): Promise<Set<string> | null> {\n if (this.fieldNamesCache.has(objectName)) {\n return this.fieldNamesCache.get(objectName) ?? null;\n }\n const result = await this.loadObjectFieldNames(metadata, objectName, ql);\n if (result) this.fieldNamesCache.set(objectName, result);\n return result;\n }\n\n private async loadObjectFieldNames(\n metadata: any,\n objectName: string,\n ql?: any,\n ): Promise<Set<string> | null> {\n try {\n let obj: any =\n typeof ql?.getSchema === 'function' ? ql.getSchema(objectName) : null;\n if (!obj || !obj.fields) {\n obj = await metadata?.get?.('object', objectName);\n }\n if (!obj || !obj.fields) return null;\n const set = new Set<string>(['id']);\n if (Array.isArray(obj.fields)) {\n for (const f of obj.fields) {\n if (f?.name) set.add(String(f.name));\n }\n } else if (typeof obj.fields === 'object') {\n for (const key of Object.keys(obj.fields)) {\n set.add(key);\n const v = (obj.fields as Record<string, any>)[key];\n if (v && typeof v === 'object' && v.name) set.add(String(v.name));\n }\n } else {\n return null;\n }\n return set;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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","names":["SYSTEM_CTX","SYSTEM_CTX"]}
|