@happyvertical/smrt-core 0.45.3 → 0.46.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/AGENTS.md +1 -0
- package/agents/generators.md +0 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +20 -1
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/custom-action.d.ts +71 -39
- package/dist/generators/custom-action.d.ts.map +1 -1
- package/dist/generators/custom-action.js +71 -39
- package/dist/generators/custom-action.js.map +1 -1
- package/dist/generators/index.d.ts +0 -2
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -3
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +1 -1
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/tenant-gate.d.ts +33 -9
- package/dist/generators/tenant-gate.d.ts.map +1 -1
- package/dist/generators/tenant-gate.js +2 -1
- package/dist/generators/tenant-gate.js.map +1 -1
- package/dist/generators.js +2 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/knowledge.js +28 -16
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/framework-base-tables.d.ts +148 -0
- package/dist/migrations/framework-base-tables.d.ts.map +1 -0
- package/dist/migrations/framework-base-tables.js +419 -0
- package/dist/migrations/framework-base-tables.js.map +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -1
- package/dist/migrations.js +2 -1
- package/dist/postgres-permissions.d.ts +40 -0
- package/dist/postgres-permissions.d.ts.map +1 -0
- package/dist/postgres-permissions.js +353 -0
- package/dist/postgres-permissions.js.map +1 -0
- package/dist/registry/framework-base-classes.d.ts.map +1 -1
- package/dist/registry/framework-base-classes.js +4 -1
- package/dist/registry/framework-base-classes.js.map +1 -1
- package/dist/scripts/create-wrappers.js +0 -5
- package/dist/smrt-knowledge.json +7 -8
- package/dist/testing/database.d.ts +6 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +9 -1
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
- package/dist/vite-plugin/api-client-entries.js +16 -14
- package/dist/vite-plugin/api-client-entries.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +2 -166
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +3 -2
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +20 -24
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/templates/default-ui.ts +22 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +4 -9
- package/dist/generators/cli.d.ts +0 -155
- package/dist/generators/cli.d.ts.map +0 -1
- package/dist/generators/cli.js +0 -473
- package/dist/generators/cli.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-permissions.js","names":[],"sources":["../src/postgres-permissions.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\n/** PostgreSQL ACLs only; this is not application authorization or RLS. */\nexport interface PostgresPermissionContract {\n schema: string;\n /** Defaults affect every future object created by migrationOwner in schema. */\n schemaExclusive: true;\n migrationOwner: string;\n runtimeRole: string;\n managedTables: string[];\n monitor?: { role: string; tables: Record<string, string[]> };\n}\n\nexport interface PostgresPermissionDiagnostic {\n code: string;\n severity: 'missing' | 'excessive' | 'unsupported';\n role?: string;\n resource: string;\n message: string;\n}\n\nexport interface PostgresPermissionPlan {\n contract: PostgresPermissionContract;\n diagnostics: PostgresPermissionDiagnostic[];\n statements: string[];\n canApply: boolean;\n fingerprint: string;\n limitations: string[];\n}\n\ntype Executor = Pick<DatabaseInterface, 'query'>;\ninterface Row {\n [key: string]: unknown;\n oid: string;\n name: string;\n schema: string;\n owner: string;\n acl: Acl[];\n kind: string;\n rolname: string;\n member: string;\n parent: string;\n relation: string;\n version: string;\n executor: string;\n superuser: boolean;\n rls: boolean;\n}\ntype Acl = {\n grantee: string;\n privilege: string;\n grantable: boolean;\n grantor?: string;\n};\nconst bookkeeping = new Set([\n '_smrt_migrations',\n '_smrt_schema_migrations',\n '_smrt_backfills',\n]);\nconst identifier = (value: string) => `\"${value.replaceAll('\"', '\"\"')}\"`;\nconst literal = (value: string) =>\n `E'${value.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"''\")}'`;\nconst qualified = (schema: string, name: string) =>\n `${identifier(schema)}.${identifier(name)}`;\n\nfunction object(\n value: unknown,\n keys: string[],\n label: string,\n): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`${label} must be an object.`);\n }\n for (const key of Object.keys(value)) {\n if (!keys.includes(key))\n throw new Error(`Unknown ${label} property: ${key}.`);\n }\n return value as Record<string, unknown>;\n}\nfunction name(value: unknown, label: string): string {\n if (\n typeof value !== 'string' ||\n !value.length ||\n value.includes('\\0') ||\n Buffer.byteLength(value) > 63\n ) {\n throw new Error(\n `${label} must be a nonempty PostgreSQL identifier (at most 63 bytes).`,\n );\n }\n return value;\n}\n/** Validate configuration before opening a connection; unknown options fail closed. */\nexport function validatePostgresPermissionContract(\n value: unknown,\n): PostgresPermissionContract {\n const input = object(\n value,\n [\n 'schema',\n 'schemaExclusive',\n 'migrationOwner',\n 'runtimeRole',\n 'managedTables',\n 'monitor',\n ],\n 'postgresPermissions',\n );\n if (input.schemaExclusive !== true)\n throw new Error(\n 'postgresPermissions.schemaExclusive must explicitly be true.',\n );\n const schema = name(input.schema, 'schema');\n if (schema === 'information_schema' || schema.startsWith('pg_'))\n throw new Error('A dedicated application schema is required.');\n const migrationOwner = name(input.migrationOwner, 'migrationOwner');\n const runtimeRole = name(input.runtimeRole, 'runtimeRole');\n if (!Array.isArray(input.managedTables))\n throw new Error('managedTables must be an array.');\n const managedTables = [\n ...new Set(\n input.managedTables.map((table) => name(table, 'managedTables entry')),\n ),\n ].sort();\n let monitor: PostgresPermissionContract['monitor'];\n if (input.monitor !== undefined) {\n const entry = object(input.monitor, ['role', 'tables'], 'monitor');\n const role = name(entry.role, 'monitor.role');\n if (\n !entry.tables ||\n typeof entry.tables !== 'object' ||\n Array.isArray(entry.tables)\n )\n throw new Error('monitor.tables must map tables to column arrays.');\n const tables: Record<string, string[]> = Object.create(null);\n for (const [table, columns] of Object.entries(entry.tables).sort(\n ([a], [b]) => a.localeCompare(b),\n )) {\n name(table, 'monitor table');\n if (!Array.isArray(columns) || !columns.length)\n throw new Error(\n `monitor table ${table} requires a nonempty column array.`,\n );\n tables[table] = [\n ...new Set(columns.map((column) => name(column, 'monitor column'))),\n ].sort();\n }\n monitor = { role, tables };\n }\n const roles = [\n migrationOwner,\n runtimeRole,\n ...(monitor ? [monitor.role] : []),\n ];\n if (\n new Set(roles).size !== roles.length ||\n roles.some(\n (role) => role.toUpperCase() === 'PUBLIC' || role.startsWith('pg_'),\n )\n )\n throw new Error(\n 'Migration, runtime and monitor roles must be distinct non-system roles.',\n );\n return {\n schema,\n schemaExclusive: true,\n migrationOwner,\n runtimeRole,\n managedTables,\n ...(monitor ? { monitor } : {}),\n };\n}\n\nconst acl = (expression: string) =>\n `COALESCE((SELECT json_agg(json_build_object('grantee', CASE WHEN a.grantee = 0 THEN 'PUBLIC' ELSE pg_get_userbyid(a.grantee) END, 'grantor', pg_get_userbyid(a.grantor), 'privilege', a.privilege_type, 'grantable', a.is_grantable) ORDER BY a.grantee, a.privilege_type, a.grantor) FROM aclexplode(${expression}) a), '[]'::json)`;\n\nasync function snapshot(\n db: Executor,\n contract: PostgresPermissionContract,\n): Promise<Record<string, Row[]>> {\n const roleNames = [\n contract.migrationOwner,\n contract.runtimeRole,\n ...(contract.monitor ? [contract.monitor.role] : []),\n ]\n .map(literal)\n .join(',');\n const restrictedRoleNames = [\n contract.runtimeRole,\n ...(contract.monitor ? [contract.monitor.role] : []),\n ]\n .map(literal)\n .join(',');\n // information_schema.sql runs after initial ACL recording. Trust only its\n // explicitly PUBLIC-readable stock relations, never internal metadata views.\n // Normal-operation OIDs start at 16384:\n // https://www.postgresql.org/docs/16/system-catalog-initial-data.html\n // Source: postgres/postgres REL_14_STABLE..REL_18_STABLE,\n // GRANT SELECT [ON TABLE] ... TO PUBLIC in:\n // https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/catalog/information_schema.sql\n // https://github.com/postgres/postgres/blob/REL_18_STABLE/src/backend/catalog/information_schema.sql\n const informationSchemaPublicSelect =\n \"'administrable_role_authorizations','applicable_roles','attributes','character_sets','check_constraint_routine_usage','check_constraints','collation_character_set_applicability','collations','column_column_usage','column_domain_usage','column_options','column_privileges','column_udt_usage','columns','constraint_column_usage','constraint_table_usage','data_type_privileges','domain_constraints','domain_udt_usage','domains','element_types','enabled_roles','foreign_data_wrapper_options','foreign_data_wrappers','foreign_server_options','foreign_servers','foreign_table_options','foreign_tables','information_schema_catalog_name','key_column_usage','parameters','referential_constraints','role_column_grants','role_routine_grants','role_table_grants','role_udt_grants','role_usage_grants','routine_column_usage','routine_privileges','routine_routine_usage','routine_sequence_usage','routine_table_usage','routines','schemata','sequences','sql_features','sql_implementation_info','sql_sizing','table_constraints','table_privileges','tables','triggered_update_columns','triggers','udt_privileges','usage_privileges','user_defined_types','user_mapping_options','user_mappings','view_column_usage','view_routine_usage','view_table_usage','views'\";\n // pg_init_privs records non-default initial ACLs. Missing entries use the\n // PostgreSQL object-kind default; extension-provided ACLs are not stock trust.\n // https://www.postgresql.org/docs/current/catalog-pg-init-privs.html\n const initialAcl = (fallback: string) =>\n `CASE WHEN i.privtype='i' THEN i.initprivs WHEN i.privtype='e' THEN NULL::aclitem[] ELSE ${fallback} END`;\n // Only a type referenced by another type's typarray is its generated array.\n // Nonzero typelem also describes non-array types (point); domains over arrays\n // remain independent privilege targets and must not be filtered out.\n const queries: Record<string, string> = {\n server: `SELECT current_setting('server_version_num') AS version, current_database() AS database, current_user AS executor, (SELECT rolsuper FROM pg_roles WHERE rolname=current_user) AS superuser`,\n roles: `SELECT oid::text, rolname, rolsuper, rolcreaterole, rolcreatedb, rolreplication, rolbypassrls FROM pg_roles WHERE rolname IN (${roleNames}) ORDER BY rolname`,\n memberships: `SELECT pg_get_userbyid(member) AS member, pg_get_userbyid(roleid) AS parent FROM pg_auth_members WHERE member IN (SELECT oid FROM pg_roles WHERE rolname IN (${roleNames})) OR roleid IN (SELECT oid FROM pg_roles WHERE rolname IN (${roleNames})) ORDER BY member, roleid`,\n database: `SELECT datname AS name, pg_get_userbyid(datdba) AS owner, ${acl(\"COALESCE(datacl, acldefault('d',datdba))\")} AS acl FROM pg_database WHERE datname=current_database()`,\n schemas: `SELECT nspname AS name, pg_get_userbyid(nspowner) AS owner, ${acl(\"COALESCE(nspacl, acldefault('n',nspowner))\")} AS acl FROM pg_namespace WHERE left(nspname,3) <> 'pg_' AND nspname <> 'information_schema' ORDER BY nspname`,\n relations: `SELECT c.oid::text, n.nspname AS schema, c.relname AS name, c.relkind AS kind, pg_get_userbyid(c.relowner) AS owner, c.relrowsecurity AS rls, ${acl(\"COALESCE(c.relacl, acldefault(CASE WHEN c.relkind='S' THEN 's'::\\\"char\\\" ELSE 'r'::\\\"char\\\" END,c.relowner))\")} AS acl FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE left(n.nspname,3) <> 'pg_' AND n.nspname <> 'information_schema' AND c.relkind IN ('r','p','v','m','f','S') ORDER BY n.nspname,c.relname`,\n columns: `SELECT c.oid::text AS relation, a.attname AS name, ${acl('a.attacl')} AS acl FROM pg_attribute a JOIN pg_class c ON c.oid=a.attrelid JOIN pg_namespace n ON n.oid=c.relnamespace WHERE left(n.nspname,3) <> 'pg_' AND n.nspname <> 'information_schema' AND c.relkind IN ('r','p','v','m','f') AND a.attnum>0 AND NOT a.attisdropped ORDER BY c.oid,a.attnum`,\n routines: `SELECT n.nspname AS schema, p.oid::text, p.proname AS name, pg_get_userbyid(p.proowner) AS owner, ${acl(\"COALESCE(p.proacl, acldefault('f',p.proowner))\")} AS acl FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace WHERE left(n.nspname,3) <> 'pg_' AND n.nspname <> 'information_schema' ORDER BY n.nspname,p.oid`,\n types: `SELECT n.nspname AS schema, t.typname AS name, pg_get_userbyid(t.typowner) AS owner, ${acl(\"COALESCE(t.typacl, acldefault('T',t.typowner))\")} AS acl FROM pg_type t JOIN pg_namespace n ON n.oid=t.typnamespace LEFT JOIN pg_class c ON c.oid=t.typrelid WHERE left(n.nspname,3) <> 'pg_' AND n.nspname <> 'information_schema' AND NOT EXISTS (SELECT 1 FROM pg_type element WHERE element.typarray=t.oid) AND (t.typrelid=0 OR c.relkind='c') ORDER BY n.nspname,t.typname`,\n systemSchemas: `SELECT nspname AS name, pg_get_userbyid(nspowner) AS owner, ${acl(\"COALESCE(nspacl, acldefault('n',nspowner))\")} AS acl FROM pg_namespace WHERE left(nspname,3)='pg_' OR nspname='information_schema' ORDER BY nspname`,\n systemPrivileges: `WITH resources AS (\n SELECT n.nspname AS schema, c.relname AS name, c.oid::text AS oid, CASE WHEN c.relkind='S' THEN 'sequence' ELSE 'relation' END AS kind, pg_get_userbyid(c.relowner) AS owner,\n COALESCE(c.relacl,acldefault(CASE WHEN c.relkind='S' THEN 's'::\"char\" ELSE 'r'::\"char\" END,c.relowner)) AS current_acl,\n CASE WHEN i.objoid IS NULL AND n.nspname='information_schema' AND c.oid<16384 AND c.relname IN (${informationSchemaPublicSelect}) THEN ARRAY[makeaclitem(0,c.relowner,'SELECT',false)] ELSE ${initialAcl(\"acldefault(CASE WHEN c.relkind='S' THEN 's'::\\\"char\\\" ELSE 'r'::\\\"char\\\" END,c.relowner)\")} END AS initial_acl\n FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace\n LEFT JOIN pg_init_privs i ON i.objoid=c.oid AND i.classoid='pg_class'::regclass AND i.objsubid=0\n WHERE (left(n.nspname,3)='pg_' OR n.nspname='information_schema') AND c.relkind IN ('r','p','v','m','f','S')\n UNION ALL\n SELECT n.nspname, c.relname || '.' || a.attname, c.oid::text || ':' || a.attnum::text, 'column', pg_get_userbyid(c.relowner),\n a.attacl, ${initialAcl('NULL::aclitem[]')}\n FROM pg_attribute a JOIN pg_class c ON c.oid=a.attrelid JOIN pg_namespace n ON n.oid=c.relnamespace\n LEFT JOIN pg_init_privs i ON i.objoid=c.oid AND i.classoid='pg_class'::regclass AND i.objsubid=a.attnum\n WHERE (left(n.nspname,3)='pg_' OR n.nspname='information_schema') AND a.attnum>0 AND NOT a.attisdropped\n UNION ALL\n SELECT n.nspname, p.proname, p.oid::text, 'routine', pg_get_userbyid(p.proowner), COALESCE(p.proacl,acldefault('f',p.proowner)), ${initialAcl(\"CASE WHEN p.oid<16384 THEN acldefault('f',p.proowner) ELSE NULL::aclitem[] END\")}\n FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace\n LEFT JOIN pg_init_privs i ON i.objoid=p.oid AND i.classoid='pg_proc'::regclass AND i.objsubid=0\n WHERE left(n.nspname,3)='pg_' OR n.nspname='information_schema'\n UNION ALL\n SELECT n.nspname, t.typname, t.oid::text, 'type', pg_get_userbyid(t.typowner), COALESCE(t.typacl,acldefault('T',t.typowner)), ${initialAcl(\"CASE WHEN t.oid<16384 THEN acldefault('T',t.typowner) ELSE NULL::aclitem[] END\")}\n FROM pg_type t JOIN pg_namespace n ON n.oid=t.typnamespace LEFT JOIN pg_class c ON c.oid=t.typrelid\n LEFT JOIN pg_init_privs i ON i.objoid=t.oid AND i.classoid='pg_type'::regclass AND i.objsubid=0\n WHERE (left(n.nspname,3)='pg_' OR n.nspname='information_schema') AND NOT EXISTS (SELECT 1 FROM pg_type element WHERE element.typarray=t.oid) AND (t.typrelid=0 OR c.relkind='c')\n ) SELECT r.schema,r.name,r.oid,r.kind,r.owner,COALESCE(d.acl,'[]'::json) AS acl FROM resources r\n CROSS JOIN LATERAL (\n SELECT json_agg(json_build_object('grantee', CASE WHEN a.grantee=0 THEN 'PUBLIC' ELSE pg_get_userbyid(a.grantee) END,\n 'grantor',pg_get_userbyid(a.grantor),'privilege',a.privilege_type,'grantable',a.is_grantable)\n ORDER BY a.grantee,a.privilege_type,a.grantor) AS acl\n FROM aclexplode(r.current_acl) a\n WHERE a.grantee IN (SELECT oid FROM pg_roles WHERE rolname IN (${restrictedRoleNames}))\n OR (a.grantee=0 AND NOT EXISTS (SELECT 1 FROM aclexplode(r.initial_acl) b WHERE b.grantee=0 AND b.privilege_type=a.privilege_type AND (b.is_grantable OR NOT a.is_grantable)))\n ) d WHERE d.acl IS NOT NULL OR (r.kind<>'column' AND r.owner IN (${restrictedRoleNames})) ORDER BY r.schema,r.kind,r.name,r.oid`,\n largeObjects: `SELECT oid::text, pg_get_userbyid(lomowner) AS owner, ${acl(\"COALESCE(lomacl, acldefault('L',lomowner))\")} AS acl FROM pg_largeobject_metadata ORDER BY oid`,\n largeObjectSettings: `SELECT setting,reset_val,source FROM pg_settings WHERE name='lo_compat_privileges'`,\n largeObjectRoleSettings: `SELECT s.setdatabase::text AS database, CASE WHEN s.setrole=0 THEN 'ALL ROLES' ELSE pg_get_userbyid(s.setrole) END AS role, split_part(config.value,'=',2) AS setting FROM pg_db_role_setting s CROSS JOIN LATERAL unnest(s.setconfig) config(value) WHERE s.setdatabase IN (0,(SELECT oid FROM pg_database WHERE datname=current_database())) AND (s.setrole=0 OR s.setrole IN (SELECT oid FROM pg_roles WHERE rolname IN (${restrictedRoleNames}))) AND split_part(config.value,'=',1)='lo_compat_privileges' ORDER BY s.setdatabase,s.setrole,config.value`,\n parameters: `SELECT parname AS name, ${acl('paracl')} AS acl FROM pg_parameter_acl ORDER BY parname`,\n defaults: `SELECT pg_get_userbyid(d.defaclrole) AS owner, COALESCE(n.nspname,'') AS schema, d.defaclobjtype AS kind, ${acl('d.defaclacl')} AS acl FROM pg_default_acl d LEFT JOIN pg_namespace n ON n.oid=d.defaclnamespace ORDER BY d.defaclrole,d.defaclnamespace,d.defaclobjtype`,\n };\n const result: Record<string, Row[]> = {};\n // A transaction executor must never have concurrent statements dispatched to it.\n for (const [key, query] of Object.entries(queries)) {\n // Parameter ACLs were introduced in PostgreSQL 15.\n if (key === 'parameters' && Number(result.server[0].version) < 150000) {\n result[key] = [];\n continue;\n }\n result[key] = (await db.query(query)).rows as Row[];\n }\n return result;\n}\n\nasync function plan(\n db: Executor,\n input: unknown,\n): Promise<PostgresPermissionPlan> {\n const contract = validatePostgresPermissionContract(input);\n const state = await snapshot(db, contract);\n const diagnostics: PostgresPermissionDiagnostic[] = [];\n const statements: string[] = [];\n const roles = [\n contract.runtimeRole,\n ...(contract.monitor ? [contract.monitor.role] : []),\n ];\n const add = (\n severity: PostgresPermissionDiagnostic['severity'],\n code: string,\n resource: string,\n message: string,\n role?: string,\n ) =>\n diagnostics.push({\n severity,\n code,\n resource,\n message,\n ...(role ? { role } : {}),\n });\n const unsupported = (\n code: string,\n resource: string,\n message: string,\n role?: string,\n ) => add('unsupported', code, resource, message, role);\n if (Number(state.server[0].version) < 140000)\n unsupported(\n 'server-version',\n 'database',\n 'PostgreSQL 14 or later is required.',\n );\n for (const role of [contract.migrationOwner, ...roles]) {\n const row = state.roles.find((entry) => entry.rolname === role);\n if (!row) {\n unsupported(\n 'missing-role',\n role,\n 'Infrastructure must create this role.',\n role,\n );\n continue;\n }\n if (\n roles.includes(role) &&\n [\n 'rolsuper',\n 'rolcreaterole',\n 'rolcreatedb',\n 'rolreplication',\n 'rolbypassrls',\n ].some((key) => row[key])\n )\n unsupported(\n 'privileged-role',\n role,\n 'Remove SUPERUSER, CREATEROLE, CREATEDB, REPLICATION and BYPASSRLS authority using infrastructure role management.',\n role,\n );\n }\n for (const membership of state.memberships)\n unsupported(\n 'role-membership',\n membership.member,\n `Role ${membership.member} is a member of ${membership.parent}; inbound and outbound membership, inherited privileges, and SET ROLE authority require infrastructure review. Memberships are never modified.`,\n membership.member,\n );\n const schema = state.schemas.find((entry) => entry.name === contract.schema);\n if (!schema)\n unsupported(\n 'missing-schema',\n contract.schema,\n 'Create the dedicated schema as the migration owner before reconciliation.',\n );\n else if (schema.owner !== contract.migrationOwner)\n unsupported(\n 'schema-owner',\n contract.schema,\n `Schema must be owned by ${contract.migrationOwner}; ownership is never changed.`,\n );\n const database = state.database[0];\n if (roles.includes(database.owner))\n unsupported(\n 'database-owner',\n database.name,\n 'Runtime and monitor must not own the database.',\n database.owner,\n );\n\n const relevant = (entries: Acl[], role: string) =>\n entries.filter(\n (entry) => entry.grantee === role || entry.grantee === 'PUBLIC',\n );\n function reconcile(\n kind: string,\n resource: string,\n entries: Acl[],\n role: string,\n desired: string[],\n suffix = '',\n ) {\n for (const entry of entries)\n if (entry.grantor === role && entry.grantee !== role)\n unsupported(\n 'dependent-grant',\n resource,\n `Configured role granted ${entry.privilege} to ${entry.grantee}; grant chains require infrastructure review to preserve other roles.`,\n role,\n );\n for (const entry of entries) {\n if (\n entry.grantee === 'PUBLIC' &&\n !(\n kind === 'DATABASE' &&\n entry.privilege === 'CONNECT' &&\n !entry.grantable\n )\n ) {\n unsupported(\n 'public-privilege',\n resource,\n `PUBLIC ${entry.privilege} is outside the explicit role contract; infrastructure must review it without changing unrelated access.`,\n role,\n );\n }\n }\n const effective = relevant(entries, role);\n const excess = effective.filter(\n (entry) => !desired.includes(entry.privilege) || entry.grantable,\n );\n const missing = desired.filter(\n (privilege) =>\n !entries.some(\n (entry) => entry.grantee === role && entry.privilege === privilege,\n ),\n );\n for (const entry of excess) {\n add(\n 'excessive',\n 'excessive-privilege',\n resource,\n `${entry.privilege}${entry.grantable ? ' WITH GRANT OPTION' : ''} is not declared (source: ${entry.grantee}).`,\n role,\n );\n if (entry.grantee === 'PUBLIC')\n unsupported(\n 'public-privilege',\n resource,\n `PUBLIC ${entry.privilege} requires infrastructure review; revoking it would affect other roles.`,\n role,\n );\n }\n for (const privilege of missing)\n add(\n 'missing',\n 'missing-privilege',\n resource,\n `${privilege} is required.`,\n role,\n );\n if (!excess.length && !missing.length) return;\n statements.push(\n `REVOKE ALL PRIVILEGES${suffix} ON ${kind} ${resource} FROM ${identifier(role)}`,\n );\n if (desired.length)\n statements.push(\n `GRANT ${desired.join(', ')}${suffix} ON ${kind} ${resource} TO ${identifier(role)}`,\n );\n }\n for (const role of roles) {\n reconcile('DATABASE', identifier(database.name), database.acl, role, [\n 'CONNECT',\n ]);\n for (const entry of state.schemas) {\n if (entry.owner === role)\n unsupported(\n 'resource-owner',\n entry.name,\n 'Configured role owns a schema and has DDL authority.',\n role,\n );\n if (entry.name === contract.schema)\n reconcile('SCHEMA', identifier(entry.name), entry.acl, role, ['USAGE']);\n else if (\n relevant(entry.acl, role).some((grant) => grant.privilege === 'CREATE')\n )\n unsupported(\n 'outside-schema-create',\n entry.name,\n 'Effective CREATE outside the dedicated schema requires infrastructure repair.',\n role,\n );\n }\n }\n for (const schema of state.systemSchemas) {\n for (const role of roles) {\n if (schema.owner === role) {\n unsupported(\n 'system-schema-owner',\n schema.name,\n 'Configured roles must not own system schemas; infrastructure must restore separate ownership. System schema ownership is never modified.',\n role,\n );\n }\n for (const grant of relevant(schema.acl, role)) {\n if (grant.privilege === 'CREATE') {\n unsupported(\n 'system-schema-authority',\n schema.name,\n `Effective CREATE on a system schema (source: ${grant.grantee}) gives DDL authority and requires infrastructure repair. System schema ACLs are never modified.`,\n role,\n );\n }\n }\n }\n }\n for (const resource of state.systemPrivileges) {\n if (resource.kind !== 'column' && roles.includes(resource.owner)) {\n unsupported(\n 'system-object-owner',\n qualified(resource.schema, resource.name),\n `Configured role owns a system ${resource.kind}; ownership retains implicit alteration and grant authority even when its ordinary ACLs are revoked. Infrastructure must restore ownership; SMRT never changes it.`,\n resource.owner,\n );\n }\n for (const grant of resource.acl) {\n unsupported(\n 'system-privilege-delta',\n qualified(resource.schema, resource.name),\n `${grant.grantee} has exceptional ${grant.privilege}${grant.grantable ? ' WITH GRANT OPTION' : ''} on a system ${resource.kind}; review infrastructure grants against PostgreSQL initial ACLs. System ACLs are never modified.`,\n grant.grantee === 'PUBLIC' ? undefined : grant.grantee,\n );\n }\n }\n for (const resource of state.largeObjects) {\n if (roles.includes(resource.owner)) {\n unsupported(\n 'large-object-owner',\n `large object ${resource.oid}`,\n 'Configured role owns a large object outside the declared table/column contract. Infrastructure must review ownership; SMRT never changes it.',\n resource.owner,\n );\n }\n for (const grant of resource.acl) {\n if (roles.includes(grant.grantee) || grant.grantee === 'PUBLIC') {\n unsupported(\n 'large-object-privilege',\n `large object ${resource.oid}`,\n `${grant.grantee} has ${grant.privilege}${grant.grantable ? ' WITH GRANT OPTION' : ''} outside the declared table/column contract. Infrastructure must review this ACL; SMRT neither reads payloads nor changes large-object permissions.`,\n grant.grantee === 'PUBLIC' ? undefined : grant.grantee,\n );\n }\n }\n }\n const explicitlyOff = (value: unknown) =>\n ['off', 'false', 'no', '0'].includes(String(value).toLowerCase());\n if (state.largeObjectSettings.length !== 1) {\n unsupported(\n 'large-object-compatibility',\n 'lo_compat_privileges',\n 'The large-object ACL compatibility setting could not be established; qualification requires an observable disabled setting.',\n );\n }\n for (const settings of state.largeObjectSettings) {\n if (\n !explicitlyOff(settings.setting) ||\n !explicitlyOff(settings.reset_val) ||\n !['default', 'configuration file', 'command line', 'database'].includes(\n String(settings.source),\n )\n ) {\n unsupported(\n 'large-object-compatibility',\n 'lo_compat_privileges',\n 'Large-object ACL checks must be enabled for every configured role. An enabled or operator-specific lo_compat_privileges override cannot establish that baseline; review deployment settings and reconnect without an operator-specific override.',\n );\n }\n }\n for (const settings of state.largeObjectRoleSettings) {\n if (!explicitlyOff(settings.setting)) {\n unsupported(\n 'large-object-compatibility',\n 'lo_compat_privileges',\n `Role/database defaults enable or do not establish large-object ACL checks (${String(settings.role)}, database OID ${String(settings.database)}). Infrastructure must disable compatibility mode before qualification.`,\n settings.role === 'ALL ROLES' ? undefined : String(settings.role),\n );\n }\n }\n for (const parameter of state.parameters) {\n for (const role of roles) {\n for (const grant of relevant(parameter.acl, role)) {\n unsupported(\n 'parameter-privilege',\n parameter.name,\n `Effective ${grant.privilege} on a PostgreSQL configuration parameter (source: ${grant.grantee}) requires infrastructure review; parameter ACLs are never modified.`,\n role,\n );\n }\n }\n }\n const tables = state.relations.filter(\n (entry) => entry.schema === contract.schema && entry.kind !== 'S',\n );\n for (const table of bookkeeping) {\n if (!tables.some((entry) => entry.name === table)) {\n unsupported(\n 'missing-bookkeeping-table',\n qualified(contract.schema, table),\n 'Bootstrap all framework bookkeeping tables as the migration owner before permission setup. Stop runtime and monitoring access during migrations or restores, then reconcile before reactivation.',\n );\n }\n }\n const declared = new Set(contract.managedTables);\n // Framework feature tables are owned by SMRT even when absent from manifests.\n for (const table of tables)\n if (table.name.startsWith('_smrt_')) declared.add(table.name);\n for (const table of declared)\n if (!tables.some((entry) => entry.name === table))\n unsupported(\n 'missing-table',\n qualified(contract.schema, table),\n 'Run supported schema migrations before reconciling permissions.',\n );\n for (const [table, columns] of Object.entries(\n contract.monitor?.tables ?? {},\n )) {\n const relation = tables.find((entry) => entry.name === table);\n if (!declared.has(table) || !relation)\n unsupported(\n 'monitor-table',\n table,\n 'Monitor tables must exist and belong to managedTables or framework tables.',\n );\n for (const column of columns)\n if (\n !relation ||\n !state.columns.some(\n (entry) => entry.relation === relation.oid && entry.name === column,\n )\n )\n unsupported(\n 'monitor-column',\n `${table}.${column}`,\n 'The declared monitoring column does not exist.',\n );\n }\n for (const relation of state.relations) {\n const resource = qualified(relation.schema, relation.name);\n const columns = state.columns.filter(\n (entry) => entry.relation === relation.oid,\n );\n if (relation.schema !== contract.schema) {\n for (const role of roles)\n if (\n relation.owner === role ||\n relevant(relation.acl, role).length ||\n columns.some((column) => relevant(column.acl, role).length)\n )\n unsupported(\n 'outside-resource-access',\n resource,\n 'Effective access to an unrelated relation requires infrastructure review; unrelated ACLs are preserved.',\n role,\n );\n continue;\n }\n if (relation.owner !== contract.migrationOwner)\n unsupported(\n 'resource-owner',\n resource,\n `Managed resources must be owned by ${contract.migrationOwner}; ownership is never changed.`,\n );\n if (relation.kind !== 'S' && !declared.has(relation.name))\n unsupported(\n 'undeclared-table',\n resource,\n 'The dedicated schema contains an undeclared table; defaults cannot safely cover a mixed schema.',\n );\n if (!['r', 'p', 'S'].includes(relation.kind))\n unsupported(\n 'unsupported-relation',\n resource,\n `Relation kind ${relation.kind} requires a separate security review.`,\n );\n if (relation.rls)\n unsupported(\n 'row-level-security',\n resource,\n 'RLS policies require a separate application authorization review; ACL verification cannot establish allowed row operations.',\n );\n for (const role of roles) {\n const isRuntime = role === contract.runtimeRole;\n const desired = isRuntime\n ? relation.kind === 'S'\n ? ['USAGE']\n : bookkeeping.has(relation.name)\n ? ['SELECT']\n : ['SELECT', 'INSERT', 'UPDATE', 'DELETE']\n : [];\n reconcile(\n relation.kind === 'S' ? 'SEQUENCE' : 'TABLE',\n resource,\n relation.acl,\n role,\n desired,\n );\n for (const column of columns) {\n const wanted =\n !isRuntime &&\n (contract.monitor?.tables[relation.name] ?? []).includes(column.name)\n ? ['SELECT']\n : [];\n reconcile(\n 'TABLE',\n resource,\n column.acl,\n role,\n wanted,\n ` (${identifier(column.name)})`,\n );\n }\n }\n }\n for (const category of ['routines', 'types'] as const)\n for (const resource of state[category]) {\n for (const role of roles)\n if (\n resource.schema === contract.schema ||\n resource.owner === role ||\n relevant(resource.acl, role).length\n )\n unsupported(\n `unsupported-${category}`,\n qualified(resource.schema, resource.name),\n `User-defined ${category} are outside the supported table/sequence contract; review their effective privileges explicitly.`,\n role,\n );\n }\n // Global grants combine with schema grants: a schema REVOKE cannot cancel them.\n for (const defaults of state.defaults) {\n const resource = `DEFAULT ${defaults.kind} (${defaults.schema || 'global'})`;\n if (\n defaults.owner !== contract.migrationOwner &&\n defaults.acl.some((entry: Acl) => roles.includes(entry.grantee))\n )\n unsupported(\n 'other-creator-access',\n resource,\n `Creator ${defaults.owner} grants configured roles future access outside this contract.`,\n );\n if (\n defaults.schema !== contract.schema &&\n defaults.schema !== '' &&\n defaults.acl.some(\n (entry: Acl) =>\n entry.grantee === 'PUBLIC' || roles.includes(entry.grantee),\n )\n )\n unsupported(\n 'outside-default-privilege',\n resource,\n 'Defaults outside the managed schema grant future access and require infrastructure review.',\n );\n if (\n defaults.schema === '' &&\n defaults.acl.some(\n (entry: Acl) =>\n roles.includes(entry.grantee) ||\n (entry.grantee === 'PUBLIC' &&\n ['r', 'S', 'n'].includes(defaults.kind)),\n )\n )\n unsupported(\n 'global-default-privilege',\n resource,\n 'Global creator defaults grant configured roles or PUBLIC access and cannot be repaired within this schema.',\n );\n if (\n defaults.schema === contract.schema &&\n defaults.owner !== contract.migrationOwner\n )\n unsupported(\n 'other-creator-default',\n resource,\n `Defaults for creator ${defaults.owner} require separate ownership review.`,\n );\n if (\n defaults.schema === contract.schema &&\n !['r', 'S'].includes(defaults.kind) &&\n defaults.acl.some(\n (entry: Acl) =>\n roles.includes(entry.grantee) || entry.grantee === 'PUBLIC',\n )\n )\n unsupported(\n 'unsupported-default',\n resource,\n 'Only table and sequence schema defaults are supported.',\n );\n }\n for (const [kind, sqlKind, runtime] of [\n ['r', 'TABLES', ['SELECT', 'INSERT', 'UPDATE', 'DELETE']],\n ['S', 'SEQUENCES', ['USAGE']],\n ] as const) {\n const entry = state.defaults.find(\n (item) =>\n item.schema === contract.schema &&\n item.owner === contract.migrationOwner &&\n item.kind === kind,\n );\n if (entry?.acl.some((grant) => grant.grantee === 'PUBLIC')) {\n unsupported(\n 'public-default',\n `${contract.schema} ${sqlKind}`,\n 'PUBLIC creator defaults are outside the explicit role contract and require infrastructure review.',\n );\n }\n for (const role of roles) {\n const desired: readonly string[] =\n role === contract.runtimeRole ? runtime : [];\n const effective = relevant(entry?.acl ?? [], role);\n const extra = effective.filter(\n (grant) => !desired.includes(grant.privilege) || grant.grantable,\n );\n const missing = desired.filter(\n (privilege) =>\n !(entry?.acl ?? []).some(\n (grant) => grant.grantee === role && grant.privilege === privilege,\n ),\n );\n if (!extra.length && !missing.length) continue;\n for (const grant of extra) {\n add(\n 'excessive',\n 'default-excessive',\n `${contract.schema} ${sqlKind}`,\n `${grant.privilege} default from ${grant.grantee} is not declared.`,\n role,\n );\n if (grant.grantee === 'PUBLIC')\n unsupported(\n 'public-default',\n contract.schema,\n 'PUBLIC defaults affect other roles and require infrastructure repair.',\n );\n }\n for (const privilege of missing)\n add(\n 'missing',\n 'default-missing',\n `${contract.schema} ${sqlKind}`,\n `Creator ${contract.migrationOwner} must default ${privilege}.`,\n role,\n );\n const prefix = `ALTER DEFAULT PRIVILEGES FOR ROLE ${identifier(contract.migrationOwner)} IN SCHEMA ${identifier(contract.schema)}`;\n statements.push(\n `${prefix} REVOKE ALL PRIVILEGES ON ${sqlKind} FROM ${identifier(role)}`,\n );\n if (desired.length)\n statements.push(\n `${prefix} GRANT ${desired.join(', ')} ON ${sqlKind} TO ${identifier(role)}`,\n );\n }\n }\n const executor = state.server[0];\n if (\n statements.length &&\n !executor.superuser &&\n executor.executor !== contract.migrationOwner\n )\n unsupported(\n 'execution-authority',\n 'database',\n 'Apply requires the migration owner or a superuser; plan and apply must use the same actor.',\n );\n if (\n statements.some((sql) => sql.includes(' ON DATABASE ')) &&\n !executor.superuser &&\n database.owner !== executor.executor\n )\n unsupported(\n 'database-authority',\n database.name,\n 'Database ACL remediation requires its owner or a superuser.',\n );\n return {\n contract,\n diagnostics,\n limitations: [\n 'Only PostgreSQL table, column, sequence, schema and database ACLs are qualified; application authorization and RLS are separate. Large-object ownership/access is unsupported, and lo_compat_privileges must remain off for deployed roles.',\n 'Future user-defined routines and types are unsupported. PostgreSQL implicit global defaults grant PUBLIC EXECUTE/USAGE; rerun diagnostics after every migration before activating runtime roles.',\n 'Pause migrations and external ACL/role writers during planning and apply. The advisory lock coordinates SMRT permission writers only.',\n 'All framework bookkeeping tables must exist before setup. Stop runtime and monitor access throughout migrations, restores and repair: recreated bookkeeping tables temporarily receive creator CRUD defaults and require explicit reconciliation before either role is reactivated.',\n ],\n statements: [...new Set(statements)],\n canApply: !diagnostics.some((entry) => entry.severity === 'unsupported'),\n fingerprint: createHash('sha256')\n .update(JSON.stringify({ contract, state }))\n .digest('hex'),\n };\n}\n\n/** Read-only catalog inspection. No application records or credentials are read. */\nexport async function planPostgresPermissions(\n db: Executor,\n contract: PostgresPermissionContract,\n): Promise<PostgresPermissionPlan> {\n return plan(db, contract);\n}\n\n/** Apply the reviewed contract atomically, rejecting stale catalog snapshots. */\nexport async function applyPostgresPermissions(\n db: DatabaseInterface,\n contract: PostgresPermissionContract,\n options: { expectedFingerprint: string },\n): Promise<PostgresPermissionPlan> {\n validatePostgresPermissionContract(contract);\n if (!options || !/^[a-f0-9]{64}$/.test(options.expectedFingerprint))\n throw new Error(\n 'An expectedFingerprint from a reviewed permission plan is required.',\n );\n if (!db.transaction)\n throw new Error(\n 'PostgreSQL permissions require a transaction-capable database.',\n );\n return db.transaction(async (tx) => {\n await tx.query(\"SET LOCAL lock_timeout = '5s'\");\n await tx.query(\"SET LOCAL statement_timeout = '30s'\");\n // Cooperating permission writers serialize; operators must quiesce migrations.\n await tx.query(\n `SELECT pg_advisory_xact_lock(hashtext(${literal(`smrt-permissions:${contract.schema}`)}))`,\n );\n const before = await plan(tx, contract);\n if (before.fingerprint !== options.expectedFingerprint)\n throw new Error(\n 'Permission plan is stale; generate and review a new plan.',\n );\n if (!before.canApply)\n throw new Error(\n 'Permission plan has unsupported privilege sources or missing prerequisites; resolve its diagnostics first.',\n );\n const executor = (\n await tx.query(\n 'SELECT current_user AS name, rolsuper FROM pg_roles WHERE rolname=current_user',\n )\n ).rows[0];\n if (!executor.rolsuper && executor.name !== contract.migrationOwner)\n throw new Error(\n 'Apply must execute as the migration owner or a PostgreSQL superuser.',\n );\n for (const statement of before.statements) await tx.query(statement);\n const after = await plan(tx, contract);\n if (!after.canApply || after.diagnostics.length)\n throw new Error(\n 'Permission verification failed; all permission changes have been rolled back.',\n );\n return after;\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"framework-base-classes.d.ts","sourceRoot":"","sources":["../../src/registry/framework-base-classes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"framework-base-classes.d.ts","sourceRoot":"","sources":["../../src/registry/framework-base-classes.ts"],"names":[],"mappings":"AAgFA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAMT"}
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
* - `./schema-builder.ts` (skips table/DDL generation for these classes)
|
|
21
21
|
* - `../knowledge.ts` (the `.smrt/smrt-knowledge.json` surface projection)
|
|
22
22
|
* - `../generators/mcp.ts` (`MCPGenerator.generateTools()`)
|
|
23
|
-
* - `../generators/cli.ts` (`CLIGenerator.listCommands()`)
|
|
24
23
|
* - `../generators/rest.ts` (`APIGenerator.findClassByCollectionSegment()`)
|
|
25
24
|
* - `../vite-plugin/sveltekit-generator.ts` (REST route generation)
|
|
26
25
|
* - `../vite-plugin/sync-apply-route.ts` (`collectSyncApplyTargets()`)
|
|
@@ -28,6 +27,10 @@
|
|
|
28
27
|
* collection and WebMCP tool selectors)
|
|
29
28
|
* - `@happyvertical/smrt-cli`'s `cli-generator.ts` (the live local `smrt`
|
|
30
29
|
* binary's object command generation), via core's public export
|
|
30
|
+
* - `../testing/database.ts` (`getTestDatabase()` skips table creation for
|
|
31
|
+
* these classes, but only for an implicit `classes` list — an explicit
|
|
32
|
+
* `classes: [...]` caller naming a framework base class is still honored,
|
|
33
|
+
* unlike every unconditional filter above) (#2645)
|
|
31
34
|
*
|
|
32
35
|
* Do not add another hardcoded copy of this list at a new call site — import
|
|
33
36
|
* from here instead.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"framework-base-classes.js","names":[],"sources":["../../src/registry/framework-base-classes.ts"],"sourcesContent":["/**\n * The framework's own abstract base classes, keyed by simple class name to\n * their declaring package.\n *\n * These are scaffolding every application model descends from — they carry\n * no `@smrt()` decorator and have no independent existence as a resource.\n * A foundation package like `@happyvertical/smrt-core` declares them as real\n * local classes (there is no other package for them to live in), so\n * `loadAllManifests()` registers them exactly like any genuine domain class\n * when a consumer installs `@happyvertical/smrt-core` — with no decoration\n * or framework-base filter of its own (#2642). The registration criterion\n * this framework was designed around is descent from `SmrtObject`, not the\n * presence of a decorator — undecorated domain classes (e.g. a hand-written\n * `SmrtCollection` subclass) keep their surfaces; only the framework's own\n * bases are excluded here, checked by class identity rather than decoration.\n *\n * This is the SINGLE shared source of truth for that identity check within\n * `@happyvertical/smrt-core` and its consumers — imported by:\n * - `./schema-builder.ts` (skips table/DDL generation for these classes)\n * - `../knowledge.ts` (the `.smrt/smrt-knowledge.json` surface projection)\n * - `../generators/mcp.ts` (`MCPGenerator.generateTools()`)\n * - `../generators/
|
|
1
|
+
{"version":3,"file":"framework-base-classes.js","names":[],"sources":["../../src/registry/framework-base-classes.ts"],"sourcesContent":["/**\n * The framework's own abstract base classes, keyed by simple class name to\n * their declaring package.\n *\n * These are scaffolding every application model descends from — they carry\n * no `@smrt()` decorator and have no independent existence as a resource.\n * A foundation package like `@happyvertical/smrt-core` declares them as real\n * local classes (there is no other package for them to live in), so\n * `loadAllManifests()` registers them exactly like any genuine domain class\n * when a consumer installs `@happyvertical/smrt-core` — with no decoration\n * or framework-base filter of its own (#2642). The registration criterion\n * this framework was designed around is descent from `SmrtObject`, not the\n * presence of a decorator — undecorated domain classes (e.g. a hand-written\n * `SmrtCollection` subclass) keep their surfaces; only the framework's own\n * bases are excluded here, checked by class identity rather than decoration.\n *\n * This is the SINGLE shared source of truth for that identity check within\n * `@happyvertical/smrt-core` and its consumers — imported by:\n * - `./schema-builder.ts` (skips table/DDL generation for these classes)\n * - `../knowledge.ts` (the `.smrt/smrt-knowledge.json` surface projection)\n * - `../generators/mcp.ts` (`MCPGenerator.generateTools()`)\n * - `../generators/rest.ts` (`APIGenerator.findClassByCollectionSegment()`)\n * - `../vite-plugin/sveltekit-generator.ts` (REST route generation)\n * - `../vite-plugin/sync-apply-route.ts` (`collectSyncApplyTargets()`)\n * - `../vite-plugin/web-collections.ts` (the `smrt-virt-web` client's\n * collection and WebMCP tool selectors)\n * - `@happyvertical/smrt-cli`'s `cli-generator.ts` (the live local `smrt`\n * binary's object command generation), via core's public export\n * - `../testing/database.ts` (`getTestDatabase()` skips table creation for\n * these classes, but only for an implicit `classes` list — an explicit\n * `classes: [...]` caller naming a framework base class is still honored,\n * unlike every unconditional filter above) (#2645)\n *\n * Do not add another hardcoded copy of this list at a new call site — import\n * from here instead.\n *\n * `packages/scanner/src/inheritance-resolver.ts` keeps its own separate\n * `FRAMEWORK_BASE_CLASSES` set: that package is a lower-level AST layer this\n * one has no reason to otherwise depend on, and that set controls a\n * different concern (scanner-level extends-chain termination and stub\n * resolution for packages that don't declare these classes locally), not\n * resource exposure.\n *\n * `scanner/manifest-generator.ts`'s `FRAMEWORK_METHOD_BASE_NAMES` is a\n * DIFFERENT, narrower (3-name: `SmrtObject`/`SmrtClass`/`SmrtCollection`) set\n * answering a different question — not \"is this class a resource?\" but\n * \"should this ancestor's METHODS merge into a subclass that doesn't declare\n * them?\" `SmrtReportCollection` and `SmrtJunction` are deliberately in this\n * 8-name resource set but NOT in that 3-name method-merge set: their declared\n * methods (`list`/`get` overrides, `attach`/`detach`/`byLeft`/`byRight`/\n * `setLinks`) are real, subclass-inherited API, not generic object-lifecycle\n * plumbing. Unifying the two sets — applying this one to method merging too —\n * strips those methods from every subclass; that regression actually\n * happened during #2624 and was caught only by a\n * `packages/template-sveltekit` snapshot test. Do not merge these lists.\n *\n * Most of these live in `@happyvertical/smrt-core` itself, but\n * `SmrtReport`/`SmrtReportCollection` are declared in\n * `@happyvertical/smrt-reports` — the owning package is per-name, not a\n * single blanket package check. A new framework base must be added here AND\n * to the scanner's `FRAMEWORK_BASE_CLASSES`.\n *\n * Deliberately NOT exported: only `isFrameworkBaseClass()` below needs to be\n * public. A `Map` survives TypeScript's `ReadonlyMap` typing only at the\n * type-check layer — a plain-JS or `as Map` caller could still `.set()`/\n * `.clear()` an exported instance at runtime and change what the framework\n * considers a base class. Keeping the map itself module-private closes that\n * off entirely rather than relying on callers not to do that.\n */\nconst FRAMEWORK_BASE_CLASS_PACKAGES: ReadonlyMap<string, string> = new Map([\n ['SmrtObject', '@happyvertical/smrt-core'],\n ['SmrtClass', '@happyvertical/smrt-core'],\n ['SmrtCollection', '@happyvertical/smrt-core'],\n ['SmrtJunction', '@happyvertical/smrt-core'],\n ['SmrtHierarchical', '@happyvertical/smrt-core'],\n ['SmrtPolymorphicAssociation', '@happyvertical/smrt-core'],\n ['SmrtReport', '@happyvertical/smrt-reports'],\n ['SmrtReportCollection', '@happyvertical/smrt-reports'],\n]);\n\n/**\n * True when `className`/`packageName` identify one of the framework's own\n * abstract base classes rather than a genuine resource.\n *\n * Both a class name AND its declaring package must match: a consuming\n * package is free to declare its own local class named e.g. `SmrtReport`\n * without colliding with the framework base of the same name.\n */\nexport function isFrameworkBaseClass(\n className: string | undefined,\n packageName: string | undefined,\n): boolean {\n return (\n className !== undefined &&\n packageName !== undefined &&\n FRAMEWORK_BASE_CLASS_PACKAGES.get(className) === packageName\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqEA,IAAM,gDAA6D,IAAI,IAAI;CACzE,CAAC,cAAc,0BAA0B;CACzC,CAAC,aAAa,0BAA0B;CACxC,CAAC,kBAAkB,0BAA0B;CAC7C,CAAC,gBAAgB,0BAA0B;CAC3C,CAAC,oBAAoB,0BAA0B;CAC/C,CAAC,8BAA8B,0BAA0B;CACzD,CAAC,cAAc,6BAA6B;CAC5C,CAAC,wBAAwB,6BAA6B;AACxD,CAAC;;;;;;;;;AAUD,SAAgB,qBACd,WACA,aACS;CACT,OACE,cAAc,KAAA,KACd,gBAAgB,KAAA,KAChB,8BAA8B,IAAI,SAAS,MAAM;AAErD"}
|
|
@@ -36,11 +36,6 @@ const wrappers = [
|
|
|
36
36
|
path: 'fields/index.js',
|
|
37
37
|
exports: '*', // Re-export everything from fields
|
|
38
38
|
},
|
|
39
|
-
{
|
|
40
|
-
path: 'generators/cli.js',
|
|
41
|
-
exports: ['main'], // Import and run main for CLI
|
|
42
|
-
isCLI: true,
|
|
43
|
-
},
|
|
44
39
|
{
|
|
45
40
|
path: 'prebuild/cli.js',
|
|
46
41
|
exports: '*', // Re-export everything for prebuild CLI
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-core",
|
|
6
|
-
"packageVersion": "0.
|
|
6
|
+
"packageVersion": "0.46.0",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "039518d9faeb724c10d84e225cbc1377f36925fe92b4b87f0c9573ff1f9f899e",
|
|
11
|
+
"packageJson": "19168420d23ce19b5ffadd397655d39347c2741601b072de6da8ed12ba2e0522",
|
|
12
|
+
"agents": "c47ed940e0cb37471a3acf61f992a73c43008f127141b8f498009b82b9760c0d",
|
|
13
13
|
"moduleDoc:agents/object-runtime.md": "4b6cc7930b071cf3310732f382fa06492d5eec3720fec24092ae21c207d91bfb",
|
|
14
14
|
"moduleDoc:agents/revision-guard.md": "aa6b1ddb5b6b49fa27ebbe575ec7fcd6d5ceb35ee25acc702f877a226eb9a99a",
|
|
15
15
|
"moduleDoc:agents/collection-reads.md": "4ce06e8b70b9ce9b77b47b3e2ed266c899bb7962ca015d4714f07a4aca10a865",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"moduleDoc:agents/schema-paths.md": "72f56201086ca687b03a44df968605b6990391df65ebfff7e461596d3f53dba6",
|
|
19
19
|
"moduleDoc:agents/change-feed.md": "c5921f2536c5f092690cc9ce5dcdf906376a34413d3c10613be6287c11f87f86",
|
|
20
20
|
"moduleDoc:agents/change-signals.md": "d9cb6a5541728ffea46607a6b1d4fa61d4621849f2b4ea86a0645fbb0af892e9",
|
|
21
|
-
"moduleDoc:agents/generators.md": "
|
|
21
|
+
"moduleDoc:agents/generators.md": "aa65cadf76994b190f4379f5d3889833be8ea6fe34114d84cf677640a534f150",
|
|
22
22
|
"moduleDoc:agents/build-knowledge.md": "5e67560801f22ccb1413a512452bcc5d29fafed9b4411561b8c8d9095eba50f3",
|
|
23
23
|
"moduleDoc:agents/memory.md": "49334471e7a64199bb936756f3a2690ecdd9cff45f677557ea04e7c4b7c78559"
|
|
24
24
|
},
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"./consumer-plugin",
|
|
28
28
|
"./filesystem",
|
|
29
29
|
"./generators",
|
|
30
|
-
"./generators/cli",
|
|
31
30
|
"./generators/mcp",
|
|
32
31
|
"./generators/rest",
|
|
33
32
|
"./generators/swagger",
|
|
@@ -981,7 +980,7 @@
|
|
|
981
980
|
"polymorphicAssociations": 1,
|
|
982
981
|
"uuidColumns": 3
|
|
983
982
|
},
|
|
984
|
-
"agentDoc": "# @happyvertical/smrt-core\n\nFoundation ORM, registry, schema/code generation, AI integration, and DispatchBus.\nRead the module for the subsystem being edited; root AGENTS covers shared model\nand repository rules.\n\n## Modules\n\n| Source | Scope | Module doc |\n|---|---|---|\n| `src/object.ts`, `src/collection.ts`, `src/child-accessors.ts` | Lifecycle, hydration, operators, STI, child accessors, dispatch | [agents/object-runtime.md](agents/object-runtime.md) |\n| `src/revision-guard.ts` | Guarded writes and PostgreSQL revision precision | [agents/revision-guard.md](agents/revision-guard.md) |\n| `src/collection.ts` | Projections, latest-related, facets, counts, read plans | [agents/collection-reads.md](agents/collection-reads.md) |\n| `src/collection.ts` | Limits, sort whitelist, generated list order | [agents/query-bounds.md](agents/query-bounds.md) |\n| `src/data-query.ts` | Transport-neutral bounded query normalization | [agents/data-query.md](agents/data-query.md) |\n| `src/schema/`, `src/migrations/`, `src/cascade.ts`, `src/system/` | DDL parity, indexes, migrations, delete integrity, retention | [agents/schema-paths.md](agents/schema-paths.md) |\n| `src/change-feed.ts` | Durable changes, cursors, table versions, retention | [agents/change-feed.md](agents/change-feed.md) |\n| `src/change-signals.ts` | Signal bus, replica fan-out, SSE | [agents/change-signals.md](agents/change-signals.md) |\n| `src/generators/`, `src/vite-plugin/web-collections.ts` | REST/CLI/MCP generation, manifest hashes, ETags | [agents/generators.md](agents/generators.md) |\n| `src/vite-plugin/`, `src/consumer-plugin/`, `src/knowledge.ts` | Decorator UI hints, knowledge projection, generation snapshots | [agents/build-knowledge.md](agents/build-knowledge.md) |\n| `src/object.ts`, `src/collection.ts`, `src/learning/memory.ts` | Context memory and semantic search | [agents/memory.md](agents/memory.md) |\n\n## Cross-module invariants\n\n- `ObjectRegistry` is a `globalThis` singleton so registration survives HMR.\n\n- Production DDL uses manifest generators; `getTestDatabase()` uses registry\n generators. Keep columns, indexes, FK actions, and runtime conflict targets in\n parity (`src/schema/schema-path-parity.test.ts`). Every new query predicate\n needs its index or an explicit reason none is needed.\n- Tenant scoping covers every read and every unique/conflict key. Explicit\n conflict columns are not rewritten; their author must include tenant scope.\n- Persisted saves use `id` and loaded `updated_at`; new saves use natural keys.\n Preserve revision compare-and-swap ordering through public `save()`,\n `claimRevision()`, and transaction APIs. Embedded saves, deletes, and complete\n `withTransaction()` callbacks share a process-local write queue.\n- Collection model hydration is serial in result order: initialization may query\n the same transaction-bound PostgreSQL client. Use projections for plain rows.\n- Native DuckDB UUIDs must be cast coherently on read before identity reuse;\n custom embedded revision paths use `getCanonicalPersistedRow()`. Never replay\n a mutation to discover result types.\n- `withDatabase(db, callback)` restores only database bindings (including public\n `options.db`); `withTransaction(callback)` also restores identity/revision\n metadata after rollback. Do not use a bound instance concurrently.\n- `ensureSystemTables(db, typeHint?)` provisions framework tables idempotently;\n call it on a base PostgreSQL connection before caller-owned transactions.\n Bootstrap uses an advisory lock. Application tables still require migrations;\n runtime table verification checks existence only.\n- Manifest generation fails closed on scanner errors, including unresolved\n decorator spreads. Never emit partial, default-open registration.\n- Generated registration repairs bundled class identity using the exact imported\n constructor, explicit package, and isolated one-object manifest. Never infer\n ownership from paths, simple names, or table names; packages can share names.\n Consumer regression gate: `packages/bundle-gate/src/__tests__/registry-identity.spec.ts`.\n\n## Gotchas\n\n- Optional filesystem support stays lazy: use `createFilesystemAdapter()` in\n `src/filesystem-loader.ts`, not a static files-SDK import. Fully bundled apps\n import `@happyvertical/smrt-core/filesystem` at startup. Use\n `importOptionalDependency()` for similarly heavy optional dependencies.\n- Database retries are transient-only, four attempts total for `get`/`upsert`.\n Use `src/db-errors.ts` classifiers through the cause chain, never message\n matching (SDK driver text can live in `context.originalError`). Constraints,\n bad input, missing tables, and aborted PostgreSQL transactions fail immediately.\n Unique/PK violations become `VALIDATION_UNIQUE_CONSTRAINT`, NOT NULL becomes\n `VALIDATION_REQUIRED_FIELD`; other failures keep the driver error as `cause`.\n- Property initializers precede option values; options win. Arrays/objects are\n shallow-cloned. Collection creation caches fields; table verification caches\n by DB URL and table. Preserve these scopes when changing initialization.\n- The Vite plugin loads scanner/schema code from `dist/` when present. Rebuild\n core after editing those sources before testing consumer manifest generation.\n Vite 8 requires `oxc.decorator: { legacy: true, emitDecoratorMetadata: true }`.\n\n## Validation\n\nRun focused tests first, then applicable package checks:\n\n```bash\npnpm --filter @happyvertical/smrt-core test\npnpm --filter @happyvertical/smrt-core typecheck\npnpm --filter @happyvertical/smrt-core build\npnpm --filter @happyvertical/smrt-core test:postgres\npnpm check:agents-chain\npnpm smrt dev:knowledge-check\n```\n\nThe PostgreSQL lane is required for numeric types, UUID casts, conflict targets,\ntimestamps, and migrations. Tests generate their manifest before Vitest; restart\nwatch mode after adding decorated classes. Documentation-only changes need\ninstruction-chain and knowledge freshness checks, not the runtime test suite.\n",
|
|
983
|
+
"agentDoc": "# @happyvertical/smrt-core\n\nFoundation ORM, registry, schema/code generation, AI integration, and DispatchBus.\nRead the module for the subsystem being edited; root AGENTS covers shared model\nand repository rules.\n\n## Modules\n\n| Source | Scope | Module doc |\n|---|---|---|\n| `src/object.ts`, `src/collection.ts`, `src/child-accessors.ts` | Lifecycle, hydration, operators, STI, child accessors, dispatch | [agents/object-runtime.md](agents/object-runtime.md) |\n| `src/revision-guard.ts` | Guarded writes and PostgreSQL revision precision | [agents/revision-guard.md](agents/revision-guard.md) |\n| `src/collection.ts` | Projections, latest-related, facets, counts, read plans | [agents/collection-reads.md](agents/collection-reads.md) |\n| `src/collection.ts` | Limits, sort whitelist, generated list order | [agents/query-bounds.md](agents/query-bounds.md) |\n| `src/data-query.ts` | Transport-neutral bounded query normalization | [agents/data-query.md](agents/data-query.md) |\n| `src/schema/`, `src/migrations/`, `src/cascade.ts`, `src/system/` | DDL parity, indexes, migrations, delete integrity, retention | [agents/schema-paths.md](agents/schema-paths.md) |\n| `src/postgres-permissions.ts` | Explicit PostgreSQL ACL plans and atomic reconciliation | [deployment permission contract](../../docs/content/postgres-permissions.md) |\n| `src/change-feed.ts` | Durable changes, cursors, table versions, retention | [agents/change-feed.md](agents/change-feed.md) |\n| `src/change-signals.ts` | Signal bus, replica fan-out, SSE | [agents/change-signals.md](agents/change-signals.md) |\n| `src/generators/`, `src/vite-plugin/web-collections.ts` | REST/CLI/MCP generation, manifest hashes, ETags | [agents/generators.md](agents/generators.md) |\n| `src/vite-plugin/`, `src/consumer-plugin/`, `src/knowledge.ts` | Decorator UI hints, knowledge projection, generation snapshots | [agents/build-knowledge.md](agents/build-knowledge.md) |\n| `src/object.ts`, `src/collection.ts`, `src/learning/memory.ts` | Context memory and semantic search | [agents/memory.md](agents/memory.md) |\n\n## Cross-module invariants\n\n- `ObjectRegistry` is a `globalThis` singleton so registration survives HMR.\n\n- Production DDL uses manifest generators; `getTestDatabase()` uses registry\n generators. Keep columns, indexes, FK actions, and runtime conflict targets in\n parity (`src/schema/schema-path-parity.test.ts`). Every new query predicate\n needs its index or an explicit reason none is needed.\n- Tenant scoping covers every read and every unique/conflict key. Explicit\n conflict columns are not rewritten; their author must include tenant scope.\n- Persisted saves use `id` and loaded `updated_at`; new saves use natural keys.\n Preserve revision compare-and-swap ordering through public `save()`,\n `claimRevision()`, and transaction APIs. Embedded saves, deletes, and complete\n `withTransaction()` callbacks share a process-local write queue.\n- Collection model hydration is serial in result order: initialization may query\n the same transaction-bound PostgreSQL client. Use projections for plain rows.\n- Native DuckDB UUIDs must be cast coherently on read before identity reuse;\n custom embedded revision paths use `getCanonicalPersistedRow()`. Never replay\n a mutation to discover result types.\n- `withDatabase(db, callback)` restores only database bindings (including public\n `options.db`); `withTransaction(callback)` also restores identity/revision\n metadata after rollback. Do not use a bound instance concurrently.\n- `ensureSystemTables(db, typeHint?)` provisions framework tables idempotently;\n call it on a base PostgreSQL connection before caller-owned transactions.\n Bootstrap uses an advisory lock. Application tables still require migrations;\n runtime table verification checks existence only.\n- Manifest generation fails closed on scanner errors, including unresolved\n decorator spreads. Never emit partial, default-open registration.\n- Generated registration repairs bundled class identity using the exact imported\n constructor, explicit package, and isolated one-object manifest. Never infer\n ownership from paths, simple names, or table names; packages can share names.\n Consumer regression gate: `packages/bundle-gate/src/__tests__/registry-identity.spec.ts`.\n\n## Gotchas\n\n- Optional filesystem support stays lazy: use `createFilesystemAdapter()` in\n `src/filesystem-loader.ts`, not a static files-SDK import. Fully bundled apps\n import `@happyvertical/smrt-core/filesystem` at startup. Use\n `importOptionalDependency()` for similarly heavy optional dependencies.\n- Database retries are transient-only, four attempts total for `get`/`upsert`.\n Use `src/db-errors.ts` classifiers through the cause chain, never message\n matching (SDK driver text can live in `context.originalError`). Constraints,\n bad input, missing tables, and aborted PostgreSQL transactions fail immediately.\n Unique/PK violations become `VALIDATION_UNIQUE_CONSTRAINT`, NOT NULL becomes\n `VALIDATION_REQUIRED_FIELD`; other failures keep the driver error as `cause`.\n- Property initializers precede option values; options win. Arrays/objects are\n shallow-cloned. Collection creation caches fields; table verification caches\n by DB URL and table. Preserve these scopes when changing initialization.\n- The Vite plugin loads scanner/schema code from `dist/` when present. Rebuild\n core after editing those sources before testing consumer manifest generation.\n Vite 8 requires `oxc.decorator: { legacy: true, emitDecoratorMetadata: true }`.\n\n## Validation\n\nRun focused tests first, then applicable package checks:\n\n```bash\npnpm --filter @happyvertical/smrt-core test\npnpm --filter @happyvertical/smrt-core typecheck\npnpm --filter @happyvertical/smrt-core build\npnpm --filter @happyvertical/smrt-core test:postgres\npnpm check:agents-chain\npnpm smrt dev:knowledge-check\n```\n\nThe PostgreSQL lane is required for numeric types, UUID casts, conflict targets,\ntimestamps, and migrations. Tests generate their manifest before Vitest; restart\nwatch mode after adding decorated classes. Documentation-only changes need\ninstruction-chain and knowledge freshness checks, not the runtime test suite.\n",
|
|
985
984
|
"moduleDocs": [
|
|
986
985
|
{
|
|
987
986
|
"path": "agents/object-runtime.md",
|
|
@@ -1026,7 +1025,7 @@
|
|
|
1026
1025
|
{
|
|
1027
1026
|
"path": "agents/generators.md",
|
|
1028
1027
|
"module": "generators",
|
|
1029
|
-
"content": "# smrt-core/code generators\n\nModule semantics for `src/generators/` + `src/vite-plugin/`. Package orientation, the cross-module\ninvariants, and the traps that apply before editing anything live in\n[../AGENTS.md](../AGENTS.md) — read that first.\n\n## Code Generators\n\n| Generator | Location | Output |\n|-----------|----------|--------|\n| REST API | `src/generators/rest.ts` | OpenAPI-compliant CRUD endpoints |\n| CLI | `src/generators/cli.ts` | `objectname:action` admin commands — writable allowlist, exhaustive-include, `--from-file`, fail-closed tenant context |\n| MCP Server | `src/generators/mcp.ts` | Model Context Protocol tools |\n| Web collections | `src/vite-plugin/web-collections.ts` (selectors) + `generateWebModule` | `@happyvertical/smrt-virt-web` — one typed collection definition per API-exposed REST collection (#1761), consumed by `@happyvertical/smrt-web` |\n\nThe same web virtual module exports `webMcpToolDefinitions` (#2518), a\ncanonical per-tool array selected independently of list materialization. Every\nnon-empty canonical API action set contributes tools, so get-only and\ncustom-action-only models are discoverable; custom actions declared on a\n`SmrtCollection` merge into the owning row collection. Each definition carries\ncomplete route and invalidation metadata. `collectionDefinitions` and its\nembedded descriptor copy remain unchanged for existing cache-backed consumers.\n\nGenerated API clients share `selectApiClientEntries()` across the runtime Vite\nmodule, its ambient declaration, and physical prebuild declarations. When a\ncollection class and its populated model share an endpoint, the model owns the\ncanonical collection key and row payload schema; the collection class remains\navailable under a deterministic class-derived secondary key. Selection and\ncollision suffixes must not depend on manifest insertion order (#2027).\nFor aggregated manifests, inheritance and item-type references resolve exact\nqualified names first, then package-local simple names, then a stable identity\nfallback so duplicate class names across packages cannot reintroduce ordering.\n\nThe web module also emits a build-time **`manifestHash`** constant (#1764): `computeWebManifestHash(manifest)` is a deterministic, replica-stable digest of the emitted web-collection SHAPE (name/className/endpoint/idField/actions/fields/relationships), canonicalized (recursive key sort) before `sha256 → base64url`, truncated to 16 chars — so the same schema always hashes the same, and a field add/remove/type-change/edge-change changes it. A change means old persisted client rows may mis-hydrate, so smrt-web keys its durable persistence namespace on it and its `updateAvailable` contract signal compares against it. Four co-managed emission sites must not drift: the runtime value (`generateWebModule`), the `@happyvertical/smrt-virt-web` ambient d.ts (`vite-plugin/index.ts`), the physical `@smrt/web` d.ts (`prebuild/index.ts`), and the hand-written type mirror in `@happyvertical/smrt-web` (`packages/smrt-web/src/index.ts` — dependency-free, so textual sync only).\n\n`webMcpToolDefinitions` is deliberately outside that digest: tool-only route,\nidentifier, or annotation changes cannot alter persisted row hydration.\n\nPer-field web emission (#2046): `buildWebFieldDefinitions` carries `description` (from `@field({ description })`) and sanitized `ui` hints (from `@field({ ui: { basic, group, order, locked } })`, read off the manifest `_meta.ui` bag through per-key type guards) into each emitted field definition, and `buildWebToolDescriptors` threads the same `description` into browser MCP tool schemas. `sensitive`/`transient` fields are excluded from emission entirely, so their descriptions never ship. Both keys are conditional, so hint-less schemas emit byte-identical definitions (and hashes) as before; adding a description/ui hint changes the manifest hash — deliberate over-invalidation, harmless per the #1764 contract.\n\n## Generated MCP server output language\n\n`MCPGenerator` builds every file as TypeScript, so the requested `outputPath`\nextension decides what is written (#2279). `.ts`/`.mts` targets keep the source\nverbatim for `tsx` or Node type stripping — which is why the generated source\nmust stay erasable-syntax-only (no parameter properties, enums, or namespaces).\nEvery other target (`.smrt/mcp-server/index.js` by default) is transpiled to\nJavaScript with lazily loaded `oxc-transform` before writing, because the\nprinted run script and the generated `claude-config.example.json` both invoke\nit with plain `node`. Ordinary core imports and `.ts`/`.mts` output therefore\ndo not load OXC's native bindings.\nThis keeps `typescript` dev-only in `@happyvertical/smrt-core`; generated MCP\nsource must remain erasable-syntax-only. A `.cjs`/`.cts` target is rejected\noutright: generated servers are ES modules. `src/generators/mcp-emit.ts` owns\nthose decisions — do not reintroduce a bare `writeFile` of generated source.\n\nModular output writes `config`, `tools/index`, and `handlers/index` with the\nentry point's own extension, and emits the entry's relative import specifiers\nwith that same extension, so the files it imports both exist and load with the\nsame module semantics — an `.mjs` entry gets `.mjs` siblings, not `.js` ones a\nCommonJS package would then parse as CommonJS. The entry is written at the\nrequested path rather than a hardcoded `index.js`.\nGenerated code also has to be valid in an ES module: `arguments` is not a legal\nbinding name there, however convenient it reads.\n\n## Browser-plane playbook preflight route (#2590)\n\n`GET {basePath}/_preflight?key=<playbook key>` (`src/generators/preflight-route.ts`)\nis an advisory, read-effect, idempotent report of what a caller's playbook would\nbe allowed to do — capability *selection*, never authorization. Resolution and\nverdict shaping live in `@happyvertical/smrt-playbooks`, which depends on this\npackage, so core takes the evaluator as the `APIConfig.playbookPreflight` seam and\nthe dependency stays one-way. Without a provider the route 404s.\n\n**`authMiddleware` is never invoked by preflight**, and that is enforced\nstructurally rather than by discipline: `PlaybookPreflightRouteOptions` has no\nauth member of any kind, and `rest.ts` passes the boolean `appAuthConfigured`\ninstead — so there is no handle in the module to invoke by mistake. A synthetic-\n`Request` dry run is explicitly not an option: the middleware is request-bound,\nreturns a `Response` rather than a boolean, and may consult session stores,\nrate-limit, or audit. The app-auth layer therefore reports `unknown`, which is the\nhonest answer, and a future `authPredicate` seam can fill it in without changing\nthe contract.\n\nThe static layers preflight predicts against are exported from the same module —\n`isApiActionEnabledForObject`, `isRestActionRoutable`, `isRestRoutePublic`,\n`restFieldReadPermissions`, `restMethodForApiAction`,\n`resolveRegisteredObjectName` — and `APIGenerator`'s own\n`isApiActionEnabled` / `isRoutePublic` now delegate to them, so the route and the\nprediction of the route cannot drift. Exposure and existence are separate\nquestions: `include`/`exclude` gate a route, they do not conjure one, so\n`isRestActionRoutable` additionally requires a custom action to be declared in\n`api.routes` — the only map `dispatchCustomCollectionAction` iterates. A custom\naction is predicted against the verb its own route config declares, so a\n`public: 'read'` opt-out neither silently covers a `POST` action nor falsely\ndenies a declared `GET` one. Every unresolvable key returns the provider's single uniform\n\"unavailable\" body with an unconditional 200: unknown and unauthorized keys are\nindistinguishable at the HTTP layer too.\n\n## Emitted agent surface (#2591)\n\nGenerated model tools have always been build-time artifacts — virtual module,\nmanifest, knowledge graph. View intents (#2588) and playbooks (#2589) existed\nonly once something mounted, so \"what can an agent do in this app\" had no answer\nshort of enumerating every route. This closes that.\n\nThe same OXC scan that builds the manifest also runs the scanner's\nagent-surface matcher (`ScanResults.agentSurface`). `smrtPlugin()` captures it\nin `scanWithOxc`, projects it with `toKnowledgeAgentSurface`, and passes it to\n`buildDomainKnowledgeManifest` as `agentSurface`. Note that declaration\ndiscovery is NOT bound to the plugin's `include` glob — an app that scans\n`src/lib/objects/**` for models still has its `src/lib/agent/*.intents.ts`\nsidecars found (see `packages/scanner/AGENTS.md`). Two more consequences worth\nholding onto:\n\n- **It never touches `manifest.json`.** The runtime manifest stays\n runtime-focused; the agent-addressable surface is an agent/developer contract,\n so it lands in `.smrt/smrt-knowledge.json` and `dist/smrt-knowledge.json`\n only, under `agentSurface: { intents, playbooks, diagnostics }`.\n- **It is passed in, not scanned in `knowledge.ts`.** The scanner carries a\n native parser binary and `smrt-core`'s main entry is browser-reachable, so\n core's sync knowledge builder must not import it. The Vite plugin already\n imports the scanner lazily on the Node side and is the only caller that writes\n this artifact.\n\nThe field is **omitted entirely** when a package declares nothing, which is what\nmakes it additive in practice rather than only on paper: every existing\npackage's checked-in artifact stays byte-identical.\n\nEach declaring module gets a `sourceHashes` entry under the\n`agentSurface:<package-relative path>` prefix (`AGENT_SURFACE_HASH_PREFIX`), so\nEDITING an intent sidecar marks the artifact stale exactly like editing\n`AGENTS.md` does (`stale-domain-knowledge`).\n\nHashes alone cannot see an **added** declaration, though: a brand-new sidecar\nhas no recorded hash to mismatch, the runtime manifest never carries intents,\nand `AGENTS.md` is untouched — so every other signal stays green while the\nartifact omits a real operation. `dev:knowledge-check` therefore also re-derives\nthe declaration SET from source and compares it to the artifact by identity,\nreporting either direction as `stale-agent-surface`. The scan is bounded like\nthe numeric-precision lint: `src` only, behind the scanner's token pre-filter.\n\nThat re-derivation must model what the EMITTER sees, not merely what is on\ndisk, or it reports drift no rebuild can clear. Which files count is decided by\nthe scanner's exported `isAgentSurfaceSourcePath` — the same predicate the\nemitter itself uses, never a list copied into the checker — and the per-file\nresults run through `mergeAgentSurfaces` before comparing, because the merge is\nwhere a duplicate identity and a derived tool-name collision are resolved and\nthe artifact is the merged result.\nDiagnostics are compared alongside identities: a sidecar containing only a\ncomputed declaration adds no identity and has no prior hash, so without that,\n\"a diagnostic, never silence\" would quietly become \"a diagnostic, until the\nartifact goes stale\". The walk covers `<pkg>/src` while the emitter globs the\nwhole project root, so an emitted entry from outside `src` is not reported as\nmissing — this check did not look there, and claiming otherwise would be an\nerror nothing could clear.\n\nBoth `stale-*` codes are warnings by default and errors under `--strict`, which\nis what CI runs. Alongside them: `agent-surface-missing-identity`,\n`agent-surface-duplicate-identity`, and `agent-surface-empty-playbook` are\nerrors, and `agent-surface-not-static` is a warning. A cross-file duplicate\narrives as a *diagnostic* rather than two entries — the scanner's merge already\ndropped the loser — so that diagnostic maps to the duplicate error rather than\nthe not-static warning; otherwise the error would be unreachable for the case it\nexists to catch.\n\n`smrt doctor` prints the whole surface — model tools, intents, playbooks — from\nthese artifacts alone, with no application running.\n\n## Custom-action contract\n\n`resolveCustomActionMetadata()` is the common discovery and invocation contract\nfor generated REST routes and API clients, MCP, CLI, WebMCP, and simple\nREST-resource discovery. Receiver scope comes from the executable method, never a\nconfiguration-only `api.routes[name].scope` override: instance model methods\nare item-scoped and require `id`; static model methods and recognized\n`SmrtCollection` methods are collection-scoped and do not accept `id`. Route\nconfiguration may still choose its path and HTTP verb, but it cannot turn an\ninstance call into `ClassRef.action` or vice versa.\n\nWhen scanner method metadata exists, discovery projects each named parameter\nand its JSON-schema type, and invokers pass the values positionally in declared\norder. The legacy single `options` bag remains compatible when metadata is\nabsent (or the declared method takes `options`). Do not infer this from runtime\nfunction arity. An omitted typed `options` parameter remains `undefined`, so a\nmethod's JavaScript default initializer continues to apply; an explicit `null`\nremains `null`. Flat tool and CLI inputs reserve `id` for receiver parsing. If\nan action declares an `id` parameter, its flat MCP/WebMCP field is `actionId`\n(and CLI uses `--action-id`); REST keeps its independent path/body\nnamespaces. Typed CLI actions may use standard flag names such as `limit`,\n`offset`, `where`, and `format` without those values being stripped as CRUD\nflags.\n\nCustom actions may return an explicit, domain-neutral failure object with\n`ok: false`, `code`, and `message` plus optional `status`, `details`,\n`retryable`, and `correlationId`. `normalizeCustomActionFailure()` redacts it;\ngenerated REST returns `{ error: failure }` with the non-2xx status, while MCP\nreturns `isError: true` and `_meta['io.happyvertical/smrt']`. Opaque successful\nobjects (including `{ code, message }`) remain untouched; thrown exceptions are\nnot reclassified as domain failures.\n\nCustom route metadata also classifies browser-tool effects. Set `effect` to\n`read`, `write`, or `destructive`, with truthful `idempotent` and `openWorld`\nflags. CRUD classification is fixed: list/get are read, create/update are write,\nand delete is destructive. An undeclared custom action deliberately defaults to\ndestructive, non-idempotent, and open-world so a browser capability policy never\nfails open.\n\nGenerated reads (`list`/`get`) on the REST and SvelteKit generators support conditional GET (helpers in `src/generators/conditional-get.ts`). ETag v2 (#1765): the validator is the table's change-feed version (`getTableVersion`) keyed by the request representation, so a **concrete** `If-None-Match` short-circuits into a 304 with an empty body **before** the collection query runs — an unchanged table revalidates with zero table scan. A wildcard `If-None-Match: *` is deferred until the payload builds (existence confirmed), so a missing item still returns 404, not a false 304. Tenant-scoped reads fold the active tenant into the representation (`resolveTenantEtagDiscriminator`) so one tenant's cached validator never satisfies another's read of the same URL. Routes whose GET renders via a **custom serializer** (which can load related tables the base-table version can't observe) keep the v1 body-hash ETag (`#1757`, query-first but correct); the default `toPublicJSON` path — all REST reads and non-serializer SvelteKit reads — uses v2. v2 is weakly consistent by design (the cost of not reading the data): a revalidation in the sub-statement window between a committed write and its feed append can return a stale 304 that self-heals on the next revalidation. The other v2 window — a deploy that changes the response shape WITHOUT a table write — is closed by the **#1764 ETag salt**: `computeTableVersionEtag(version, representation, manifestHash?)` folds the build's web-collection shape digest into the digest, so a shape-only redeploy busts every read validator (`undefined` reproduces the pre-#1764 unsalted value byte-for-byte for direct helper callers). The generated SvelteKit route bakes the digest in as a `MANIFEST_HASH` constant (via `generateConditionalGetRouteHelper`'s `manifestHash` option, sourced from `computeWebManifestHash(manifest)`) — automatic for the SvelteKit transport. The runtime `APIGenerator` auto-populates the same salt from the runtime registry with `computeRuntimeWebManifestHash()` when `APIConfig.manifestHash` is omitted; explicit `APIConfig.manifestHash` still wins for custom setups. The digest scope is get-OR-list (`selectWebEtagSaltEntries`), so **get-only** routes are salted too. Strong consistency still requires the v1 body-hash path. Cache-Control policy (unchanged from #1757): `private, no-cache` by default; public models may opt into shared caching via `@smrt({ api: { public: true | 'read', cache: { sMaxage } } })` → `public, max-age=0, s-maxage=<n>`; non-public models never emit shared-cache headers. Tenant-scoped models (any mode) never emit them either — bodies vary with session-cookie tenant context that URL-keyed shared caches cannot see; `sMaxage` is neutralized to `private, no-cache` with a one-time warning.\n"
|
|
1028
|
+
"content": "# smrt-core/code generators\n\nModule semantics for `src/generators/` + `src/vite-plugin/`. Package orientation, the cross-module\ninvariants, and the traps that apply before editing anything live in\n[../AGENTS.md](../AGENTS.md) — read that first.\n\n## Code Generators\n\n| Generator | Location | Output |\n|-----------|----------|--------|\n| REST API | `src/generators/rest.ts` | OpenAPI-compliant CRUD endpoints |\n| MCP Server | `src/generators/mcp.ts` | Model Context Protocol tools |\n| Web collections | `src/vite-plugin/web-collections.ts` (selectors) + `generateWebModule` | `@happyvertical/smrt-virt-web` — one typed collection definition per API-exposed REST collection (#1761), consumed by `@happyvertical/smrt-web` |\n\nThe same web virtual module exports `webMcpToolDefinitions` (#2518), a\ncanonical per-tool array selected independently of list materialization. Every\nnon-empty canonical API action set contributes tools, so get-only and\ncustom-action-only models are discoverable; custom actions declared on a\n`SmrtCollection` merge into the owning row collection. Each definition carries\ncomplete route and invalidation metadata. `collectionDefinitions` and its\nembedded descriptor copy remain unchanged for existing cache-backed consumers.\n\nGenerated API clients share `selectApiClientEntries()` across the runtime Vite\nmodule, its ambient declaration, and physical prebuild declarations. When a\ncollection class and its populated model share an endpoint, the model owns the\ncanonical collection key and row payload schema; the collection class remains\navailable under a deterministic class-derived secondary key. Selection and\ncollision suffixes must not depend on manifest insertion order (#2027).\nFor aggregated manifests, inheritance and item-type references resolve exact\nqualified names first, then package-local simple names, then a stable identity\nfallback so duplicate class names across packages cannot reintroduce ordering.\n\nThe web module also emits a build-time **`manifestHash`** constant (#1764): `computeWebManifestHash(manifest)` is a deterministic, replica-stable digest of the emitted web-collection SHAPE (name/className/endpoint/idField/actions/fields/relationships), canonicalized (recursive key sort) before `sha256 → base64url`, truncated to 16 chars — so the same schema always hashes the same, and a field add/remove/type-change/edge-change changes it. A change means old persisted client rows may mis-hydrate, so smrt-web keys its durable persistence namespace on it and its `updateAvailable` contract signal compares against it. Four co-managed emission sites must not drift: the runtime value (`generateWebModule`), the `@happyvertical/smrt-virt-web` ambient d.ts (`vite-plugin/index.ts`), the physical `@smrt/web` d.ts (`prebuild/index.ts`), and the hand-written type mirror in `@happyvertical/smrt-web` (`packages/smrt-web/src/index.ts` — dependency-free, so textual sync only).\n\n`webMcpToolDefinitions` is deliberately outside that digest: tool-only route,\nidentifier, or annotation changes cannot alter persisted row hydration.\n\nPer-field web emission (#2046): `buildWebFieldDefinitions` carries `description` (from `@field({ description })`) and sanitized `ui` hints (from `@field({ ui: { basic, group, order, locked } })`, read off the manifest `_meta.ui` bag through per-key type guards) into each emitted field definition, and `buildWebToolDescriptors` threads the same `description` into browser MCP tool schemas. `sensitive`/`transient` fields are excluded from emission entirely, so their descriptions never ship. Both keys are conditional, so hint-less schemas emit byte-identical definitions (and hashes) as before; adding a description/ui hint changes the manifest hash — deliberate over-invalidation, harmless per the #1764 contract.\n\n## Generated MCP server output language\n\n`MCPGenerator` builds every file as TypeScript, so the requested `outputPath`\nextension decides what is written (#2279). `.ts`/`.mts` targets keep the source\nverbatim for `tsx` or Node type stripping — which is why the generated source\nmust stay erasable-syntax-only (no parameter properties, enums, or namespaces).\nEvery other target (`.smrt/mcp-server/index.js` by default) is transpiled to\nJavaScript with lazily loaded `oxc-transform` before writing, because the\nprinted run script and the generated `claude-config.example.json` both invoke\nit with plain `node`. Ordinary core imports and `.ts`/`.mts` output therefore\ndo not load OXC's native bindings.\nThis keeps `typescript` dev-only in `@happyvertical/smrt-core`; generated MCP\nsource must remain erasable-syntax-only. A `.cjs`/`.cts` target is rejected\noutright: generated servers are ES modules. `src/generators/mcp-emit.ts` owns\nthose decisions — do not reintroduce a bare `writeFile` of generated source.\n\nModular output writes `config`, `tools/index`, and `handlers/index` with the\nentry point's own extension, and emits the entry's relative import specifiers\nwith that same extension, so the files it imports both exist and load with the\nsame module semantics — an `.mjs` entry gets `.mjs` siblings, not `.js` ones a\nCommonJS package would then parse as CommonJS. The entry is written at the\nrequested path rather than a hardcoded `index.js`.\nGenerated code also has to be valid in an ES module: `arguments` is not a legal\nbinding name there, however convenient it reads.\n\n## Browser-plane playbook preflight route (#2590)\n\n`GET {basePath}/_preflight?key=<playbook key>` (`src/generators/preflight-route.ts`)\nis an advisory, read-effect, idempotent report of what a caller's playbook would\nbe allowed to do — capability *selection*, never authorization. Resolution and\nverdict shaping live in `@happyvertical/smrt-playbooks`, which depends on this\npackage, so core takes the evaluator as the `APIConfig.playbookPreflight` seam and\nthe dependency stays one-way. Without a provider the route 404s.\n\n**`authMiddleware` is never invoked by preflight**, and that is enforced\nstructurally rather than by discipline: `PlaybookPreflightRouteOptions` has no\nauth member of any kind, and `rest.ts` passes the boolean `appAuthConfigured`\ninstead — so there is no handle in the module to invoke by mistake. A synthetic-\n`Request` dry run is explicitly not an option: the middleware is request-bound,\nreturns a `Response` rather than a boolean, and may consult session stores,\nrate-limit, or audit. The app-auth layer therefore reports `unknown`, which is the\nhonest answer, and a future `authPredicate` seam can fill it in without changing\nthe contract.\n\nThe static layers preflight predicts against are exported from the same module —\n`isApiActionEnabledForObject`, `isRestActionRoutable`, `isRestRoutePublic`,\n`restFieldReadPermissions`, `restMethodForApiAction`,\n`resolveRegisteredObjectName` — and `APIGenerator`'s own\n`isApiActionEnabled` / `isRoutePublic` now delegate to them, so the route and the\nprediction of the route cannot drift. Exposure and existence are separate\nquestions: `include`/`exclude` gate a route, they do not conjure one, so\n`isRestActionRoutable` additionally requires a custom action to be declared in\n`api.routes` — the only map `dispatchCustomCollectionAction` iterates. A custom\naction is predicted against the verb its own route config declares, so a\n`public: 'read'` opt-out neither silently covers a `POST` action nor falsely\ndenies a declared `GET` one. Every unresolvable key returns the provider's single uniform\n\"unavailable\" body with an unconditional 200: unknown and unauthorized keys are\nindistinguishable at the HTTP layer too.\n\n## Emitted agent surface (#2591)\n\nGenerated model tools have always been build-time artifacts — virtual module,\nmanifest, knowledge graph. View intents (#2588) and playbooks (#2589) existed\nonly once something mounted, so \"what can an agent do in this app\" had no answer\nshort of enumerating every route. This closes that.\n\nThe same OXC scan that builds the manifest also runs the scanner's\nagent-surface matcher (`ScanResults.agentSurface`). `smrtPlugin()` captures it\nin `scanWithOxc`, projects it with `toKnowledgeAgentSurface`, and passes it to\n`buildDomainKnowledgeManifest` as `agentSurface`. Note that declaration\ndiscovery is NOT bound to the plugin's `include` glob — an app that scans\n`src/lib/objects/**` for models still has its `src/lib/agent/*.intents.ts`\nsidecars found (see `packages/scanner/AGENTS.md`). Two more consequences worth\nholding onto:\n\n- **It never touches `manifest.json`.** The runtime manifest stays\n runtime-focused; the agent-addressable surface is an agent/developer contract,\n so it lands in `.smrt/smrt-knowledge.json` and `dist/smrt-knowledge.json`\n only, under `agentSurface: { intents, playbooks, diagnostics }`.\n- **It is passed in, not scanned in `knowledge.ts`.** The scanner carries a\n native parser binary and `smrt-core`'s main entry is browser-reachable, so\n core's sync knowledge builder must not import it. The Vite plugin already\n imports the scanner lazily on the Node side and is the only caller that writes\n this artifact.\n\nThe field is **omitted entirely** when a package declares nothing, which is what\nmakes it additive in practice rather than only on paper: every existing\npackage's checked-in artifact stays byte-identical.\n\nEach declaring module gets a `sourceHashes` entry under the\n`agentSurface:<package-relative path>` prefix (`AGENT_SURFACE_HASH_PREFIX`), so\nEDITING an intent sidecar marks the artifact stale exactly like editing\n`AGENTS.md` does (`stale-domain-knowledge`).\n\nHashes alone cannot see an **added** declaration, though: a brand-new sidecar\nhas no recorded hash to mismatch, the runtime manifest never carries intents,\nand `AGENTS.md` is untouched — so every other signal stays green while the\nartifact omits a real operation. `dev:knowledge-check` therefore also re-derives\nthe declaration SET from source and compares it to the artifact by identity,\nreporting either direction as `stale-agent-surface`. The scan is bounded like\nthe numeric-precision lint: `src` only, behind the scanner's token pre-filter.\n\nThat re-derivation must model what the EMITTER sees, not merely what is on\ndisk, or it reports drift no rebuild can clear. Which files count is decided by\nthe scanner's exported `isAgentSurfaceSourcePath` — the same predicate the\nemitter itself uses, never a list copied into the checker — and the per-file\nresults run through `mergeAgentSurfaces` before comparing, because the merge is\nwhere a duplicate identity and a derived tool-name collision are resolved and\nthe artifact is the merged result.\nDiagnostics are compared alongside identities: a sidecar containing only a\ncomputed declaration adds no identity and has no prior hash, so without that,\n\"a diagnostic, never silence\" would quietly become \"a diagnostic, until the\nartifact goes stale\". The walk covers `<pkg>/src` while the emitter globs the\nwhole project root, so an emitted entry from outside `src` is not reported as\nmissing — this check did not look there, and claiming otherwise would be an\nerror nothing could clear.\n\nBoth `stale-*` codes are warnings by default and errors under `--strict`, which\nis what CI runs. Alongside them: `agent-surface-missing-identity`,\n`agent-surface-duplicate-identity`, and `agent-surface-empty-playbook` are\nerrors, and `agent-surface-not-static` is a warning. A cross-file duplicate\narrives as a *diagnostic* rather than two entries — the scanner's merge already\ndropped the loser — so that diagnostic maps to the duplicate error rather than\nthe not-static warning; otherwise the error would be unreachable for the case it\nexists to catch.\n\n`smrt doctor` prints the whole surface — model tools, intents, playbooks — from\nthese artifacts alone, with no application running.\n\n## Custom-action contract\n\n`resolveCustomActionMetadata()` is the common discovery and invocation contract\nfor generated REST routes and API clients, MCP, CLI, WebMCP, and simple\nREST-resource discovery. Receiver scope comes from the executable method, never a\nconfiguration-only `api.routes[name].scope` override: instance model methods\nare item-scoped and require `id`; static model methods and recognized\n`SmrtCollection` methods are collection-scoped and do not accept `id`. Route\nconfiguration may still choose its path and HTTP verb, but it cannot turn an\ninstance call into `ClassRef.action` or vice versa.\n\nWhen scanner method metadata exists, discovery projects each named parameter\nand its JSON-schema type, and invokers pass the values positionally in declared\norder. The legacy single `options` bag remains compatible when metadata is\nabsent (or the declared method takes `options`). Do not infer this from runtime\nfunction arity. An omitted typed `options` parameter remains `undefined`, so a\nmethod's JavaScript default initializer continues to apply; an explicit `null`\nremains `null`. Flat tool and CLI inputs reserve `id` for receiver parsing. If\nan action declares an `id` parameter, its flat MCP/WebMCP field is `actionId`\n(and CLI uses `--action-id`); REST keeps its independent path/body\nnamespaces. Typed CLI actions may use standard flag names such as `limit`,\n`offset`, `where`, and `format` without those values being stripped as CRUD\nflags.\n\nCustom actions may return an explicit, domain-neutral failure object with\n`ok: false`, `code`, and `message` plus optional `status`, `details`,\n`retryable`, and `correlationId`. `normalizeCustomActionFailure()` redacts it;\ngenerated REST returns `{ error: failure }` with the non-2xx status, while MCP\nreturns `isError: true` and `_meta['io.happyvertical/smrt']`. Opaque successful\nobjects (including `{ code, message }`) remain untouched; thrown exceptions are\nnot reclassified as domain failures.\n\nCustom route metadata also classifies browser-tool effects. Set `effect` to\n`read`, `write`, or `destructive`, with truthful `idempotent` and `openWorld`\nflags. CRUD classification is fixed: list/get are read, create/update are write,\nand delete is destructive. An undeclared custom action deliberately defaults to\ndestructive, non-idempotent, and open-world so a browser capability policy never\nfails open.\n\nGenerated reads (`list`/`get`) on the REST and SvelteKit generators support conditional GET (helpers in `src/generators/conditional-get.ts`). ETag v2 (#1765): the validator is the table's change-feed version (`getTableVersion`) keyed by the request representation, so a **concrete** `If-None-Match` short-circuits into a 304 with an empty body **before** the collection query runs — an unchanged table revalidates with zero table scan. A wildcard `If-None-Match: *` is deferred until the payload builds (existence confirmed), so a missing item still returns 404, not a false 304. Tenant-scoped reads fold the active tenant into the representation (`resolveTenantEtagDiscriminator`) so one tenant's cached validator never satisfies another's read of the same URL. Routes whose GET renders via a **custom serializer** (which can load related tables the base-table version can't observe) keep the v1 body-hash ETag (`#1757`, query-first but correct); the default `toPublicJSON` path — all REST reads and non-serializer SvelteKit reads — uses v2. v2 is weakly consistent by design (the cost of not reading the data): a revalidation in the sub-statement window between a committed write and its feed append can return a stale 304 that self-heals on the next revalidation. The other v2 window — a deploy that changes the response shape WITHOUT a table write — is closed by the **#1764 ETag salt**: `computeTableVersionEtag(version, representation, manifestHash?)` folds the build's web-collection shape digest into the digest, so a shape-only redeploy busts every read validator (`undefined` reproduces the pre-#1764 unsalted value byte-for-byte for direct helper callers). The generated SvelteKit route bakes the digest in as a `MANIFEST_HASH` constant (via `generateConditionalGetRouteHelper`'s `manifestHash` option, sourced from `computeWebManifestHash(manifest)`) — automatic for the SvelteKit transport. The runtime `APIGenerator` auto-populates the same salt from the runtime registry with `computeRuntimeWebManifestHash()` when `APIConfig.manifestHash` is omitted; explicit `APIConfig.manifestHash` still wins for custom setups. The digest scope is get-OR-list (`selectWebEtagSaltEntries`), so **get-only** routes are salted too. Strong consistency still requires the v1 body-hash path. Cache-Control policy (unchanged from #1757): `private, no-cache` by default; public models may opt into shared caching via `@smrt({ api: { public: true | 'read', cache: { sMaxage } } })` → `public, max-age=0, s-maxage=<n>`; non-public models never emit shared-cache headers. Tenant-scoped models (any mode) never emit them either — bodies vary with session-cookie tenant context that URL-keyed shared caches cannot see; `sMaxage` is neutralized to `private, no-cache` with a one-time warning.\n"
|
|
1030
1029
|
},
|
|
1031
1030
|
{
|
|
1032
1031
|
"path": "agents/build-knowledge.md",
|
|
@@ -53,7 +53,12 @@ export declare function resolveTestDatabaseDDLEngine(type: TestDatabaseOptions['
|
|
|
53
53
|
* - Uses `SchemaGenerator.generateSQL()` - the single source of truth for DDL
|
|
54
54
|
* - Handles STI (Single Table Inheritance) correctly
|
|
55
55
|
* - Creates system tables for framework functionality
|
|
56
|
-
* - Safe for parallel test execution
|
|
56
|
+
* - Safe for parallel test execution: each call creates an isolated
|
|
57
|
+
* in-memory instance with its own embedded write-queue identity, so
|
|
58
|
+
* unrelated `:memory:` databases never serialize writes against each
|
|
59
|
+
* other (#2707). A `cache=shared` URL is the deliberate exception: it
|
|
60
|
+
* asks SQLite/DuckDB to genuinely share the underlying database, so it
|
|
61
|
+
* keeps sharing one write-queue identity too.
|
|
57
62
|
*
|
|
58
63
|
* @param options - Configuration options
|
|
59
64
|
* @returns Promise resolving to configured DatabaseInterface
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/testing/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/testing/database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAyJ5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IAEjD;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,mBAAmB,CAAC,MAAM,CAAC,EACjC,EAAE,EAAE,iBAAiB,EACrB,iBAAiB,UAAQ,GACxB,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAwC3C;AA4DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAiO5B"}
|
package/dist/testing/database.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ensurePostgresChangeFeedAppendFunction } from "../change-feed.js";
|
|
|
5
5
|
import { ensureLegacySystemTableCompatibility } from "../system/compatibility.js";
|
|
6
6
|
import { SchemaGenerator } from "../schema/generator.js";
|
|
7
7
|
import { isCollectionRegistration, resolveCollectionItemClassName, resolveRelatedRegistration } from "../registry/collection-resolution.js";
|
|
8
|
+
import { isFrameworkBaseClass } from "../registry/framework-base-classes.js";
|
|
8
9
|
import { ObjectRegistry } from "../registry.js";
|
|
9
10
|
import { planForeignKeyCreation } from "../schema/foreign-key-planner.js";
|
|
10
11
|
import { getDatabase } from "@happyvertical/sql";
|
|
@@ -110,7 +111,12 @@ function resolveRequestedSchemaClassNames(classNames) {
|
|
|
110
111
|
* - Uses `SchemaGenerator.generateSQL()` - the single source of truth for DDL
|
|
111
112
|
* - Handles STI (Single Table Inheritance) correctly
|
|
112
113
|
* - Creates system tables for framework functionality
|
|
113
|
-
* - Safe for parallel test execution
|
|
114
|
+
* - Safe for parallel test execution: each call creates an isolated
|
|
115
|
+
* in-memory instance with its own embedded write-queue identity, so
|
|
116
|
+
* unrelated `:memory:` databases never serialize writes against each
|
|
117
|
+
* other (#2707). A `cache=shared` URL is the deliberate exception: it
|
|
118
|
+
* asks SQLite/DuckDB to genuinely share the underlying database, so it
|
|
119
|
+
* keeps sharing one write-queue identity too.
|
|
114
120
|
*
|
|
115
121
|
* @param options - Configuration options
|
|
116
122
|
* @returns Promise resolving to configured DatabaseInterface
|
|
@@ -148,6 +154,7 @@ async function getTestDatabase(options = {}) {
|
|
|
148
154
|
__smrtSkipVitestSchemaPreparation: true
|
|
149
155
|
});
|
|
150
156
|
if (includeSystemTables) await initializeSystemTables(db);
|
|
157
|
+
const isImplicitClassList = classes === void 0;
|
|
151
158
|
const classNames = resolveRequestedSchemaClassNames(classes ?? ObjectRegistry.getQualifiedClassNames());
|
|
152
159
|
if (classNames.length === 0) return db;
|
|
153
160
|
const schemaGenerator = new SchemaGenerator();
|
|
@@ -157,6 +164,7 @@ async function getTestDatabase(options = {}) {
|
|
|
157
164
|
for (const className of classNames) {
|
|
158
165
|
const registered = ObjectRegistry.getClass(className);
|
|
159
166
|
if (isSTIChild(className)) continue;
|
|
167
|
+
if (isImplicitClassList && isFrameworkBaseClass(registered?.name, registered?.packageName)) continue;
|
|
160
168
|
const tableName = ObjectRegistry.getTableName(className);
|
|
161
169
|
if (!tableName || schemas.has(tableName)) continue;
|
|
162
170
|
const fields = await ObjectRegistry.getAllFields(className);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","names":[],"sources":["../../src/testing/database.ts"],"sourcesContent":["/**\n * Test database utilities for SMRT framework\n *\n * Provides `getTestDatabase()` which creates an in-memory database with all\n * registered SMRT object schemas. Uses the same schema generation logic as\n * the migration system to ensure consistency between test and production.\n *\n * @example\n * ```typescript\n * import { getTestDatabase } from '@happyvertical/smrt-core/testing';\n *\n * beforeEach(async () => {\n * const db = await getTestDatabase();\n * collection = await MyCollection.create({ db });\n * });\n * ```\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport { ensurePostgresChangeFeedAppendFunction } from '../change-feed.js';\nimport {\n type CollectionRegistrationLookup,\n isCollectionRegistration,\n resolveCollectionItemClassName,\n resolveRelatedRegistration,\n} from '../registry/collection-resolution.js';\nimport { ObjectRegistry } from '../registry.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport {\n schemaForeignKeys,\n schemaForeignKeysForEngine,\n} from '../schema/foreign-key-ddl.js';\nimport { planForeignKeyCreation } from '../schema/foreign-key-planner.js';\nimport { SchemaGenerator } from '../schema/generator.js';\nimport type { SchemaDefinition } from '../schema/types.js';\nimport { ensureLegacySystemTableCompatibility } from '../system/compatibility.js';\nimport { getSystemTableDDL } from '../system/schema.js';\n\ntype TestDatabaseConnectionOptions = Parameters<typeof getDatabase>[0] & {\n __smrtSkipVitestSchemaPreparation?: boolean;\n};\n\nfunction resolveSTIBaseRegistration(className: string, stiBaseName: string) {\n const registered = ObjectRegistry.getClass(className);\n\n if (stiBaseName.includes(':')) {\n return ObjectRegistry.getClass(stiBaseName);\n }\n\n if (registered?.packageName) {\n const samePackageBase = ObjectRegistry.getClassInPackage(\n registered.packageName,\n stiBaseName,\n );\n if (samePackageBase) {\n return samePackageBase;\n }\n }\n\n return ObjectRegistry.getClass(stiBaseName);\n}\n\nfunction resolveSTIBaseLookupName(\n className: string,\n stiBaseName: string,\n): string {\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n return stiBase?.qualifiedName || stiBase?.name || stiBaseName;\n}\n\nfunction isSTIChild(className: string): boolean {\n const stiBaseName = ObjectRegistry.getSTIBase(className);\n if (!stiBaseName) {\n return false;\n }\n\n const registered = ObjectRegistry.getClass(className);\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n\n if (registered && stiBase) {\n return registered !== stiBase;\n }\n\n return stiBaseName !== className;\n}\n\ntype RegisteredSchemaClass = NonNullable<\n ReturnType<typeof ObjectRegistry.getClass>\n>;\n\nconst collectionRegistrationLookup: CollectionRegistrationLookup = {\n findClass: (className) => ObjectRegistry.getClass(className),\n findClassInPackage: (packageName, className) =>\n ObjectRegistry.getClassInPackage(packageName, className),\n getInheritanceChain: (className) =>\n ObjectRegistry.getInheritanceChain(className),\n};\n\nfunction resolveCollectionSchemaClassName(\n className: string,\n registered: RegisteredSchemaClass,\n): string {\n const itemClassName = resolveCollectionItemClassName(\n className,\n registered,\n collectionRegistrationLookup,\n );\n if (itemClassName) {\n const itemRegistration = resolveRelatedRegistration(\n itemClassName,\n registered,\n collectionRegistrationLookup,\n );\n const itemLookupName =\n itemRegistration?.qualifiedName ||\n itemRegistration?.name ||\n itemClassName;\n const stiBase = ObjectRegistry.getSTIBase(itemLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(itemLookupName, stiBase)\n : itemLookupName;\n }\n\n const tableName =\n registered.schema?.tableName ||\n registered.config.tableName ||\n ObjectRegistry.getTableName(className);\n if (!tableName) {\n return className;\n }\n\n const collectionPackage = registered.packageName;\n\n for (const candidate of ObjectRegistry.getAllClasses().values()) {\n if (\n candidate === registered ||\n isCollectionRegistration(\n candidate.qualifiedName || candidate.name,\n candidate,\n collectionRegistrationLookup,\n )\n ) {\n continue;\n }\n\n const candidateTableName =\n candidate.schema?.tableName || candidate.config.tableName;\n if (candidateTableName !== tableName) {\n continue;\n }\n\n if (\n collectionPackage &&\n candidate.packageName &&\n candidate.packageName !== collectionPackage\n ) {\n continue;\n }\n\n const candidateLookupName = candidate.qualifiedName || candidate.name;\n const stiBase = ObjectRegistry.getSTIBase(candidateLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(candidateLookupName, stiBase)\n : candidateLookupName;\n }\n\n return className;\n}\n\n/**\n * Options for creating a test database\n */\nexport interface TestDatabaseOptions {\n /**\n * Database type (default: 'sqlite')\n * - 'sqlite': SQLite database\n * - 'json': JSON adapter (stores data as JSON files with DuckDB for querying)\n * - 'duckdb': Native DuckDB database\n * - 'postgres': PostgreSQL database (normally supplied through `db`)\n */\n type?: 'sqlite' | 'json' | 'duckdb' | 'postgres';\n\n /**\n * Database URL (default: ':memory:')\n * Use ':memory:' for in-memory databases (fastest for tests)\n * Or provide a file path for persistent test databases\n */\n url?: string;\n\n /**\n * Pre-existing database to initialize schemas in.\n * If provided, `type` and `url` are ignored.\n */\n db?: DatabaseInterface;\n\n /**\n * Specific classes to setup schemas for.\n * If not provided, sets up schemas for all registered classes.\n */\n classes?: string[];\n\n /**\n * Whether to include system tables (default: true)\n * System tables include _smrt_contexts, _smrt_migrations, etc.\n */\n includeSystemTables?: boolean;\n\n /**\n * Explicitly omit physical FK constraints for adapter/query tests whose\n * subject is unrelated to referential enforcement. Defaults to `false`.\n * This is required (and deliberately noisy at the call site) when a DuckDB\n * fixture uses generated `ON UPDATE CASCADE`, which DuckDB cannot enforce.\n */\n omitForeignKeyConstraints?: boolean;\n}\n\n/** Resolve the DDL dialect used when preparing an existing test database. */\nexport function resolveTestDatabaseDDLEngine(\n type: TestDatabaseOptions['type'],\n db: DatabaseInterface,\n inferFromDatabase = false,\n): 'sqlite' | 'json' | 'duckdb' | 'postgres' {\n // When this helper created the adapter, the requested type is authoritative.\n // Native DuckDB and JSON-on-DuckDB both expose exportTable(), so capability\n // inference must not override an explicit native DuckDB request.\n if (!inferFromDatabase) {\n return type ?? 'sqlite';\n }\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n exportTable?: unknown;\n inferSchemaFromJSON?: unknown;\n getTableLoadErrors?: unknown;\n };\n\n if (\n typeof configuredDb.inferSchemaFromJSON === 'function' ||\n typeof configuredDb.getTableLoadErrors === 'function'\n ) {\n return 'json';\n }\n\n if (typeof configuredDb.exportTable === 'function') {\n // The native DuckDB adapter exposes schema evolution capabilities that the\n // JSON adapter does not. Keep exportTable-only test doubles compatible with\n // the historical JSON structural marker.\n if (\n typeof configuredDb.getTableSchema === 'function' &&\n typeof configuredDb.alterTable?.addColumn === 'function'\n ) {\n return 'duckdb';\n }\n return 'json';\n }\n\n return detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n}\n\nfunction omitTestForeignKeyConstraints(\n schema: SchemaDefinition,\n): SchemaDefinition {\n const foreignKeyTargets = new Set(\n schemaForeignKeys(schema).map((foreignKey) => foreignKey.referencesTable),\n );\n return {\n ...schema,\n columns: Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => [\n name,\n column.foreignKey ? { ...column, foreignKey: undefined } : column,\n ]),\n ),\n foreignKeys: [],\n dependencies: schema.dependencies.filter(\n (dependency) => !foreignKeyTargets.has(dependency),\n ),\n };\n}\n\nfunction resolveRequestedSchemaClassName(className: string): string {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n return className;\n }\n\n if (\n !isCollectionRegistration(\n className,\n registered,\n collectionRegistrationLookup,\n )\n ) {\n const stiBase = ObjectRegistry.getSTIBase(className);\n return stiBase ? resolveSTIBaseLookupName(className, stiBase) : className;\n }\n\n return resolveCollectionSchemaClassName(className, registered);\n}\n\nfunction resolveRequestedSchemaClassNames(classNames: string[]): string[] {\n const resolved: string[] = [];\n const seen = new Set<string>();\n\n for (const className of classNames) {\n const schemaClassName = resolveRequestedSchemaClassName(className);\n if (seen.has(schemaClassName)) {\n continue;\n }\n\n seen.add(schemaClassName);\n resolved.push(schemaClassName);\n }\n\n return resolved;\n}\n\n/**\n * Creates an in-memory test database with schemas pre-created\n *\n * This utility is designed for testing. It creates an in-memory database\n * and initializes all registered SMRT object schemas using the **same\n * schema generation logic** as the production migration system.\n *\n * **Key features:**\n * - Uses `SchemaGenerator.generateSQL()` - the single source of truth for DDL\n * - Handles STI (Single Table Inheritance) correctly\n * - Creates system tables for framework functionality\n * - Safe for parallel test execution (each call creates isolated instance)\n *\n * @param options - Configuration options\n * @returns Promise resolving to configured DatabaseInterface\n *\n * @example\n * ```typescript\n * // Basic usage - all registered schemas\n * const db = await getTestDatabase();\n *\n * // Specific classes only\n * const db = await getTestDatabase({ classes: ['Council', 'Meeting'] });\n *\n * // JSON adapter instead of SQLite\n * const db = await getTestDatabase({ type: 'json' });\n *\n * // Native DuckDB adapter\n * const db = await getTestDatabase({ type: 'duckdb', url: ':memory:' });\n *\n * // File-based database for persistent tests\n * const db = await getTestDatabase({ type: 'sqlite', url: '/tmp/test.db' });\n *\n * // Initialize schemas in an existing database\n * const myDb = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * await getTestDatabase({ db: myDb });\n *\n * // Skip system tables (rare use case)\n * const db = await getTestDatabase({ includeSystemTables: false });\n * ```\n */\nexport async function getTestDatabase(\n options: TestDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const {\n type,\n url = ':memory:',\n db: existingDb,\n classes,\n includeSystemTables = true,\n omitForeignKeyConstraints = false,\n } = options;\n\n // Use existing database or create new one\n const db =\n existingDb ??\n (await getDatabase({\n type: type ?? 'sqlite',\n url,\n __smrtSkipVitestSchemaPreparation: true,\n } as TestDatabaseConnectionOptions));\n\n // Initialize system tables (same as production)\n if (includeSystemTables) {\n await initializeSystemTables(db);\n }\n\n // Get class names to setup\n const classNames = resolveRequestedSchemaClassNames(\n classes ?? ObjectRegistry.getQualifiedClassNames(),\n );\n\n // Skip if no classes registered\n if (classNames.length === 0) {\n return db;\n }\n\n // Use the same schema generation as production\n const schemaGenerator = new SchemaGenerator();\n const ddlEngine = resolveTestDatabaseDDLEngine(\n type,\n db,\n existingDb !== undefined,\n );\n\n // Collect every table before executing DDL so dependency ordering and cycle\n // handling are identical to production migration/schema paths (#2413).\n const schemas = new Map<\n string,\n { schema: SchemaDefinition; className: string }\n >();\n const authoritativeSchemas = ObjectRegistry.getAllSchemasAsDefinitions();\n\n for (const className of classNames) {\n // R11: the registration carries idType (native uuid vs text); read it\n // below when building runtimeSchemaConfig.\n const registered = ObjectRegistry.getClass(className);\n // Skip STI children - their schema is part of the base class table.\n // main (#1324): isSTIChild() compares RegisteredClass *identity* rather\n // than raw strings, so it stays correct under R5-canon (getSTIBase returns\n // qualified names) while also handling collection/override registrations.\n if (isSTIChild(className)) {\n continue;\n }\n\n const tableName = ObjectRegistry.getTableName(className);\n if (!tableName || schemas.has(tableName)) {\n continue;\n }\n\n const fields = await ObjectRegistry.getAllFields(className);\n const strategy = ObjectRegistry.getTableStrategy(className);\n // Mirrors `schema/utils.ts`: this bag is rebuilt by hand, so every `@smrt()`\n // option the generator reads has to be listed or tests silently diverge\n // from the production schema. `indexes` (#2357) was missing from both.\n const runtimeSchemaConfig = {\n conflictColumns: ObjectRegistry.getConflictColumns(className),\n idType: registered?.config.idType,\n indexes: registered?.config.indexes,\n registry: ObjectRegistry,\n };\n\n // Generate schema through the registry paths. Production (`manifest.json`\n // and therefore `smrt db:migrate`) uses the manifest paths\n // (`generateSTISchemaFromManifest` / `generateCTISchemaFromManifest`);\n // the two families are held to the same column and index sets by\n // `src/schema/schema-path-parity.test.ts` (#2359) — do not assume they\n // agree by construction.\n const schema =\n strategy === 'sti'\n ? await schemaGenerator.generateSTISchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n )\n : schemaGenerator.generateSchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n );\n\n // Manifest registrations can carry explicit FK actions that differ from\n // decorator defaults. The deterministic merged table schema preserves\n // those authoritative actions while still backfilling runtime-only fields.\n const authoritativeSchema = authoritativeSchemas[tableName];\n const effectiveColumns = authoritativeSchema\n ? Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => {\n const manifestColumn = authoritativeSchema.columns[name];\n return [\n name,\n manifestColumn\n ? { ...column, foreignKey: manifestColumn.foreignKey }\n : column,\n ];\n }),\n )\n : schema.columns;\n const effectiveForeignKeys = schemaForeignKeys({\n columns: effectiveColumns,\n foreignKeys: [],\n });\n const effectiveSchema: SchemaDefinition = {\n ...schema,\n columns: effectiveColumns,\n foreignKeys: effectiveForeignKeys,\n dependencies: effectiveForeignKeys\n .map((foreignKey) => foreignKey.referencesTable)\n .filter((dependency) => dependency !== tableName),\n };\n\n schemas.set(tableName, {\n schema: omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(effectiveSchema)\n : effectiveSchema,\n className,\n });\n }\n\n // Explicitly requested test classes still need their registered parents.\n // Add the authoritative dependency closure so a child-only request cannot\n // emit a physical reference to a table this helper never creates.\n const addDependencies = (schema: SchemaDefinition): void => {\n for (const foreignKey of schemaForeignKeysForEngine(schema, ddlEngine)) {\n if (schemas.has(foreignKey.referencesTable)) continue;\n const dependency = authoritativeSchemas[foreignKey.referencesTable];\n if (!dependency) continue;\n const effectiveDependency = omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(dependency)\n : dependency;\n schemas.set(dependency.tableName, {\n schema: effectiveDependency,\n className: dependency.tableName,\n });\n addDependencies(effectiveDependency);\n }\n };\n for (const { schema } of [...schemas.values()]) addDependencies(schema);\n\n const tablePlan = planForeignKeyCreation(\n [...schemas.values()].map(({ schema }) => schema),\n ddlEngine,\n );\n const ddlStrategy = (await import('../schema/ddl/index.js')).getDDLStrategy(\n ddlEngine,\n );\n\n for (const schema of tablePlan.schemas) {\n const className = schemas.get(schema.tableName)?.className ?? 'unknown';\n const ddl = schemaGenerator.generateSQL(schema, ddlEngine);\n\n try {\n await db.query(ddl);\n\n // Create indexes (use DDL strategy so jsonPath / where / etc. render)\n const indexStatements = ddlStrategy.generateIndexes(schema);\n for (const indexSQL of indexStatements) {\n try {\n await db.query(indexSQL);\n } catch (indexError) {\n // Log but don't fail on index creation errors\n // Some indexes may fail if columns don't exist (STI meta fields)\n console.warn(\n `[getTestDatabase] Warning: Failed to create index: ${indexError instanceof Error ? indexError.message : String(indexError)} (SQL: ${indexSQL})`,\n );\n }\n }\n } catch (error) {\n // Provide helpful error message for table creation failures\n throw new Error(\n `[getTestDatabase] Failed to create table '${schema.tableName}' for class '${className}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n for (const statement of tablePlan.deferredStatements) {\n try {\n await db.query(statement);\n } catch (error) {\n throw new Error(\n `[getTestDatabase] Failed to add deferred foreign-key constraint: ${error instanceof Error ? error.message : String(error)} (SQL: ${statement})`,\n { cause: error },\n );\n }\n }\n\n return db;\n}\n\n/**\n * Initialize SMRT system tables in a database\n *\n * System tables use _smrt_ prefix and store framework metadata.\n * All statements use `IF NOT EXISTS` for idempotency.\n *\n * @param db - Database interface to initialize\n */\nasync function initializeSystemTables(db: DatabaseInterface): Promise<void> {\n await ensureLegacySystemTableCompatibility(db);\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n };\n const engine = detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n\n // Split multi-statement SQL into individual statements\n const allStatements: string[] = [];\n for (const multiStatementSQL of getSystemTableDDL(engine)) {\n const statements = multiStatementSQL\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n allStatements.push(...statements);\n }\n\n // Use db.query() — system tables use CREATE TABLE/INDEX IF NOT EXISTS\n // which databases handle natively without per-column existence checks.\n for (const statement of allStatements) {\n await db.query(statement);\n }\n\n // PostgreSQL keeps the best-effort append boundary in a function that must\n // be executed whole rather than included in semicolon-split portable DDL.\n await ensurePostgresChangeFeedAppendFunction(db);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"database.js","names":[],"sources":["../../src/testing/database.ts"],"sourcesContent":["/**\n * Test database utilities for SMRT framework\n *\n * Provides `getTestDatabase()` which creates an in-memory database with all\n * registered SMRT object schemas. Uses the same schema generation logic as\n * the migration system to ensure consistency between test and production.\n *\n * @example\n * ```typescript\n * import { getTestDatabase } from '@happyvertical/smrt-core/testing';\n *\n * beforeEach(async () => {\n * const db = await getTestDatabase();\n * collection = await MyCollection.create({ db });\n * });\n * ```\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { getDatabase } from '@happyvertical/sql';\nimport { ensurePostgresChangeFeedAppendFunction } from '../change-feed.js';\nimport {\n type CollectionRegistrationLookup,\n isCollectionRegistration,\n resolveCollectionItemClassName,\n resolveRelatedRegistration,\n} from '../registry/collection-resolution.js';\nimport { isFrameworkBaseClass } from '../registry/framework-base-classes.js';\nimport { ObjectRegistry } from '../registry.js';\nimport { detectEngine } from '../schema/ddl/index.js';\nimport {\n schemaForeignKeys,\n schemaForeignKeysForEngine,\n} from '../schema/foreign-key-ddl.js';\nimport { planForeignKeyCreation } from '../schema/foreign-key-planner.js';\nimport { SchemaGenerator } from '../schema/generator.js';\nimport type { SchemaDefinition } from '../schema/types.js';\nimport { ensureLegacySystemTableCompatibility } from '../system/compatibility.js';\nimport { getSystemTableDDL } from '../system/schema.js';\n\ntype TestDatabaseConnectionOptions = Parameters<typeof getDatabase>[0] & {\n __smrtSkipVitestSchemaPreparation?: boolean;\n};\n\nfunction resolveSTIBaseRegistration(className: string, stiBaseName: string) {\n const registered = ObjectRegistry.getClass(className);\n\n if (stiBaseName.includes(':')) {\n return ObjectRegistry.getClass(stiBaseName);\n }\n\n if (registered?.packageName) {\n const samePackageBase = ObjectRegistry.getClassInPackage(\n registered.packageName,\n stiBaseName,\n );\n if (samePackageBase) {\n return samePackageBase;\n }\n }\n\n return ObjectRegistry.getClass(stiBaseName);\n}\n\nfunction resolveSTIBaseLookupName(\n className: string,\n stiBaseName: string,\n): string {\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n return stiBase?.qualifiedName || stiBase?.name || stiBaseName;\n}\n\nfunction isSTIChild(className: string): boolean {\n const stiBaseName = ObjectRegistry.getSTIBase(className);\n if (!stiBaseName) {\n return false;\n }\n\n const registered = ObjectRegistry.getClass(className);\n const stiBase = resolveSTIBaseRegistration(className, stiBaseName);\n\n if (registered && stiBase) {\n return registered !== stiBase;\n }\n\n return stiBaseName !== className;\n}\n\ntype RegisteredSchemaClass = NonNullable<\n ReturnType<typeof ObjectRegistry.getClass>\n>;\n\nconst collectionRegistrationLookup: CollectionRegistrationLookup = {\n findClass: (className) => ObjectRegistry.getClass(className),\n findClassInPackage: (packageName, className) =>\n ObjectRegistry.getClassInPackage(packageName, className),\n getInheritanceChain: (className) =>\n ObjectRegistry.getInheritanceChain(className),\n};\n\nfunction resolveCollectionSchemaClassName(\n className: string,\n registered: RegisteredSchemaClass,\n): string {\n const itemClassName = resolveCollectionItemClassName(\n className,\n registered,\n collectionRegistrationLookup,\n );\n if (itemClassName) {\n const itemRegistration = resolveRelatedRegistration(\n itemClassName,\n registered,\n collectionRegistrationLookup,\n );\n const itemLookupName =\n itemRegistration?.qualifiedName ||\n itemRegistration?.name ||\n itemClassName;\n const stiBase = ObjectRegistry.getSTIBase(itemLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(itemLookupName, stiBase)\n : itemLookupName;\n }\n\n const tableName =\n registered.schema?.tableName ||\n registered.config.tableName ||\n ObjectRegistry.getTableName(className);\n if (!tableName) {\n return className;\n }\n\n const collectionPackage = registered.packageName;\n\n for (const candidate of ObjectRegistry.getAllClasses().values()) {\n if (\n candidate === registered ||\n isCollectionRegistration(\n candidate.qualifiedName || candidate.name,\n candidate,\n collectionRegistrationLookup,\n )\n ) {\n continue;\n }\n\n const candidateTableName =\n candidate.schema?.tableName || candidate.config.tableName;\n if (candidateTableName !== tableName) {\n continue;\n }\n\n if (\n collectionPackage &&\n candidate.packageName &&\n candidate.packageName !== collectionPackage\n ) {\n continue;\n }\n\n const candidateLookupName = candidate.qualifiedName || candidate.name;\n const stiBase = ObjectRegistry.getSTIBase(candidateLookupName);\n return stiBase\n ? resolveSTIBaseLookupName(candidateLookupName, stiBase)\n : candidateLookupName;\n }\n\n return className;\n}\n\n/**\n * Options for creating a test database\n */\nexport interface TestDatabaseOptions {\n /**\n * Database type (default: 'sqlite')\n * - 'sqlite': SQLite database\n * - 'json': JSON adapter (stores data as JSON files with DuckDB for querying)\n * - 'duckdb': Native DuckDB database\n * - 'postgres': PostgreSQL database (normally supplied through `db`)\n */\n type?: 'sqlite' | 'json' | 'duckdb' | 'postgres';\n\n /**\n * Database URL (default: ':memory:')\n * Use ':memory:' for in-memory databases (fastest for tests)\n * Or provide a file path for persistent test databases\n */\n url?: string;\n\n /**\n * Pre-existing database to initialize schemas in.\n * If provided, `type` and `url` are ignored.\n */\n db?: DatabaseInterface;\n\n /**\n * Specific classes to setup schemas for.\n * If not provided, sets up schemas for all registered classes.\n */\n classes?: string[];\n\n /**\n * Whether to include system tables (default: true)\n * System tables include _smrt_contexts, _smrt_migrations, etc.\n */\n includeSystemTables?: boolean;\n\n /**\n * Explicitly omit physical FK constraints for adapter/query tests whose\n * subject is unrelated to referential enforcement. Defaults to `false`.\n * This is required (and deliberately noisy at the call site) when a DuckDB\n * fixture uses generated `ON UPDATE CASCADE`, which DuckDB cannot enforce.\n */\n omitForeignKeyConstraints?: boolean;\n}\n\n/** Resolve the DDL dialect used when preparing an existing test database. */\nexport function resolveTestDatabaseDDLEngine(\n type: TestDatabaseOptions['type'],\n db: DatabaseInterface,\n inferFromDatabase = false,\n): 'sqlite' | 'json' | 'duckdb' | 'postgres' {\n // When this helper created the adapter, the requested type is authoritative.\n // Native DuckDB and JSON-on-DuckDB both expose exportTable(), so capability\n // inference must not override an explicit native DuckDB request.\n if (!inferFromDatabase) {\n return type ?? 'sqlite';\n }\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n exportTable?: unknown;\n inferSchemaFromJSON?: unknown;\n getTableLoadErrors?: unknown;\n };\n\n if (\n typeof configuredDb.inferSchemaFromJSON === 'function' ||\n typeof configuredDb.getTableLoadErrors === 'function'\n ) {\n return 'json';\n }\n\n if (typeof configuredDb.exportTable === 'function') {\n // The native DuckDB adapter exposes schema evolution capabilities that the\n // JSON adapter does not. Keep exportTable-only test doubles compatible with\n // the historical JSON structural marker.\n if (\n typeof configuredDb.getTableSchema === 'function' &&\n typeof configuredDb.alterTable?.addColumn === 'function'\n ) {\n return 'duckdb';\n }\n return 'json';\n }\n\n return detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n}\n\nfunction omitTestForeignKeyConstraints(\n schema: SchemaDefinition,\n): SchemaDefinition {\n const foreignKeyTargets = new Set(\n schemaForeignKeys(schema).map((foreignKey) => foreignKey.referencesTable),\n );\n return {\n ...schema,\n columns: Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => [\n name,\n column.foreignKey ? { ...column, foreignKey: undefined } : column,\n ]),\n ),\n foreignKeys: [],\n dependencies: schema.dependencies.filter(\n (dependency) => !foreignKeyTargets.has(dependency),\n ),\n };\n}\n\nfunction resolveRequestedSchemaClassName(className: string): string {\n const registered = ObjectRegistry.getClass(className);\n if (!registered) {\n return className;\n }\n\n if (\n !isCollectionRegistration(\n className,\n registered,\n collectionRegistrationLookup,\n )\n ) {\n const stiBase = ObjectRegistry.getSTIBase(className);\n return stiBase ? resolveSTIBaseLookupName(className, stiBase) : className;\n }\n\n return resolveCollectionSchemaClassName(className, registered);\n}\n\nfunction resolveRequestedSchemaClassNames(classNames: string[]): string[] {\n const resolved: string[] = [];\n const seen = new Set<string>();\n\n for (const className of classNames) {\n const schemaClassName = resolveRequestedSchemaClassName(className);\n if (seen.has(schemaClassName)) {\n continue;\n }\n\n seen.add(schemaClassName);\n resolved.push(schemaClassName);\n }\n\n return resolved;\n}\n\n/**\n * Creates an in-memory test database with schemas pre-created\n *\n * This utility is designed for testing. It creates an in-memory database\n * and initializes all registered SMRT object schemas using the **same\n * schema generation logic** as the production migration system.\n *\n * **Key features:**\n * - Uses `SchemaGenerator.generateSQL()` - the single source of truth for DDL\n * - Handles STI (Single Table Inheritance) correctly\n * - Creates system tables for framework functionality\n * - Safe for parallel test execution: each call creates an isolated\n * in-memory instance with its own embedded write-queue identity, so\n * unrelated `:memory:` databases never serialize writes against each\n * other (#2707). A `cache=shared` URL is the deliberate exception: it\n * asks SQLite/DuckDB to genuinely share the underlying database, so it\n * keeps sharing one write-queue identity too.\n *\n * @param options - Configuration options\n * @returns Promise resolving to configured DatabaseInterface\n *\n * @example\n * ```typescript\n * // Basic usage - all registered schemas\n * const db = await getTestDatabase();\n *\n * // Specific classes only\n * const db = await getTestDatabase({ classes: ['Council', 'Meeting'] });\n *\n * // JSON adapter instead of SQLite\n * const db = await getTestDatabase({ type: 'json' });\n *\n * // Native DuckDB adapter\n * const db = await getTestDatabase({ type: 'duckdb', url: ':memory:' });\n *\n * // File-based database for persistent tests\n * const db = await getTestDatabase({ type: 'sqlite', url: '/tmp/test.db' });\n *\n * // Initialize schemas in an existing database\n * const myDb = await getDatabase({ type: 'sqlite', url: ':memory:' });\n * await getTestDatabase({ db: myDb });\n *\n * // Skip system tables (rare use case)\n * const db = await getTestDatabase({ includeSystemTables: false });\n * ```\n */\nexport async function getTestDatabase(\n options: TestDatabaseOptions = {},\n): Promise<DatabaseInterface> {\n const {\n type,\n url = ':memory:',\n db: existingDb,\n classes,\n includeSystemTables = true,\n omitForeignKeyConstraints = false,\n } = options;\n\n // Use existing database or create new one\n const db =\n existingDb ??\n (await getDatabase({\n type: type ?? 'sqlite',\n url,\n __smrtSkipVitestSchemaPreparation: true,\n } as TestDatabaseConnectionOptions));\n\n // Initialize system tables (same as production)\n if (includeSystemTables) {\n await initializeSystemTables(db);\n }\n\n // Get class names to setup. The framework-base filter below only applies\n // when the caller left `classes` implicit: an explicit list naming a\n // framework base class must still be honored (#2645).\n const isImplicitClassList = classes === undefined;\n const classNames = resolveRequestedSchemaClassNames(\n classes ?? ObjectRegistry.getQualifiedClassNames(),\n );\n\n // Skip if no classes registered\n if (classNames.length === 0) {\n return db;\n }\n\n // Use the same schema generation as production\n const schemaGenerator = new SchemaGenerator();\n const ddlEngine = resolveTestDatabaseDDLEngine(\n type,\n db,\n existingDb !== undefined,\n );\n\n // Collect every table before executing DDL so dependency ordering and cycle\n // handling are identical to production migration/schema paths (#2413).\n const schemas = new Map<\n string,\n { schema: SchemaDefinition; className: string }\n >();\n const authoritativeSchemas = ObjectRegistry.getAllSchemasAsDefinitions();\n\n for (const className of classNames) {\n // R11: the registration carries idType (native uuid vs text); read it\n // below when building runtimeSchemaConfig.\n const registered = ObjectRegistry.getClass(className);\n // Skip STI children - their schema is part of the base class table.\n // main (#1324): isSTIChild() compares RegisteredClass *identity* rather\n // than raw strings, so it stays correct under R5-canon (getSTIBase returns\n // qualified names) while also handling collection/override registrations.\n if (isSTIChild(className)) {\n continue;\n }\n\n // Framework abstract base classes (SmrtObject, SmrtClass, ...) are\n // scaffolding, not resources, and produce phantom smrt_objects/\n // smrt_classes/smrt_collections/smrt_hierarchicals/\n // smrt_polymorphic_associations tables when the default registry\n // snapshot happens to include them (#2645). Mirrors the production\n // `buildMergedTableSchemas()` filter in `registry/schema-builder.ts`, but\n // only for the implicit class list — an explicit `classes: [...]` caller\n // that names a framework base class is still honored.\n if (\n isImplicitClassList &&\n isFrameworkBaseClass(registered?.name, registered?.packageName)\n ) {\n continue;\n }\n\n const tableName = ObjectRegistry.getTableName(className);\n if (!tableName || schemas.has(tableName)) {\n continue;\n }\n\n const fields = await ObjectRegistry.getAllFields(className);\n const strategy = ObjectRegistry.getTableStrategy(className);\n // Mirrors `schema/utils.ts`: this bag is rebuilt by hand, so every `@smrt()`\n // option the generator reads has to be listed or tests silently diverge\n // from the production schema. `indexes` (#2357) was missing from both.\n const runtimeSchemaConfig = {\n conflictColumns: ObjectRegistry.getConflictColumns(className),\n idType: registered?.config.idType,\n indexes: registered?.config.indexes,\n registry: ObjectRegistry,\n };\n\n // Generate schema through the registry paths. Production (`manifest.json`\n // and therefore `smrt db:migrate`) uses the manifest paths\n // (`generateSTISchemaFromManifest` / `generateCTISchemaFromManifest`);\n // the two families are held to the same column and index sets by\n // `src/schema/schema-path-parity.test.ts` (#2359) — do not assume they\n // agree by construction.\n const schema =\n strategy === 'sti'\n ? await schemaGenerator.generateSTISchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n )\n : schemaGenerator.generateSchemaFromRegistry(\n className,\n tableName,\n fields,\n runtimeSchemaConfig,\n );\n\n // Manifest registrations can carry explicit FK actions that differ from\n // decorator defaults. The deterministic merged table schema preserves\n // those authoritative actions while still backfilling runtime-only fields.\n const authoritativeSchema = authoritativeSchemas[tableName];\n const effectiveColumns = authoritativeSchema\n ? Object.fromEntries(\n Object.entries(schema.columns).map(([name, column]) => {\n const manifestColumn = authoritativeSchema.columns[name];\n return [\n name,\n manifestColumn\n ? { ...column, foreignKey: manifestColumn.foreignKey }\n : column,\n ];\n }),\n )\n : schema.columns;\n const effectiveForeignKeys = schemaForeignKeys({\n columns: effectiveColumns,\n foreignKeys: [],\n });\n const effectiveSchema: SchemaDefinition = {\n ...schema,\n columns: effectiveColumns,\n foreignKeys: effectiveForeignKeys,\n dependencies: effectiveForeignKeys\n .map((foreignKey) => foreignKey.referencesTable)\n .filter((dependency) => dependency !== tableName),\n };\n\n schemas.set(tableName, {\n schema: omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(effectiveSchema)\n : effectiveSchema,\n className,\n });\n }\n\n // Explicitly requested test classes still need their registered parents.\n // Add the authoritative dependency closure so a child-only request cannot\n // emit a physical reference to a table this helper never creates.\n const addDependencies = (schema: SchemaDefinition): void => {\n for (const foreignKey of schemaForeignKeysForEngine(schema, ddlEngine)) {\n if (schemas.has(foreignKey.referencesTable)) continue;\n const dependency = authoritativeSchemas[foreignKey.referencesTable];\n if (!dependency) continue;\n const effectiveDependency = omitForeignKeyConstraints\n ? omitTestForeignKeyConstraints(dependency)\n : dependency;\n schemas.set(dependency.tableName, {\n schema: effectiveDependency,\n className: dependency.tableName,\n });\n addDependencies(effectiveDependency);\n }\n };\n for (const { schema } of [...schemas.values()]) addDependencies(schema);\n\n const tablePlan = planForeignKeyCreation(\n [...schemas.values()].map(({ schema }) => schema),\n ddlEngine,\n );\n const ddlStrategy = (await import('../schema/ddl/index.js')).getDDLStrategy(\n ddlEngine,\n );\n\n for (const schema of tablePlan.schemas) {\n const className = schemas.get(schema.tableName)?.className ?? 'unknown';\n const ddl = schemaGenerator.generateSQL(schema, ddlEngine);\n\n try {\n await db.query(ddl);\n\n // Create indexes (use DDL strategy so jsonPath / where / etc. render)\n const indexStatements = ddlStrategy.generateIndexes(schema);\n for (const indexSQL of indexStatements) {\n try {\n await db.query(indexSQL);\n } catch (indexError) {\n // Log but don't fail on index creation errors\n // Some indexes may fail if columns don't exist (STI meta fields)\n console.warn(\n `[getTestDatabase] Warning: Failed to create index: ${indexError instanceof Error ? indexError.message : String(indexError)} (SQL: ${indexSQL})`,\n );\n }\n }\n } catch (error) {\n // Provide helpful error message for table creation failures\n throw new Error(\n `[getTestDatabase] Failed to create table '${schema.tableName}' for class '${className}': ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n for (const statement of tablePlan.deferredStatements) {\n try {\n await db.query(statement);\n } catch (error) {\n throw new Error(\n `[getTestDatabase] Failed to add deferred foreign-key constraint: ${error instanceof Error ? error.message : String(error)} (SQL: ${statement})`,\n { cause: error },\n );\n }\n }\n\n return db;\n}\n\n/**\n * Initialize SMRT system tables in a database\n *\n * System tables use _smrt_ prefix and store framework metadata.\n * All statements use `IF NOT EXISTS` for idempotency.\n *\n * @param db - Database interface to initialize\n */\nasync function initializeSystemTables(db: DatabaseInterface): Promise<void> {\n await ensureLegacySystemTableCompatibility(db);\n\n const configuredDb = db as DatabaseInterface & {\n config?: { type?: string; url?: string };\n type?: string;\n };\n const engine = detectEngine(\n db.url || configuredDb.config?.url || '',\n configuredDb.type || configuredDb.config?.type,\n );\n\n // Split multi-statement SQL into individual statements\n const allStatements: string[] = [];\n for (const multiStatementSQL of getSystemTableDDL(engine)) {\n const statements = multiStatementSQL\n .split(';')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n allStatements.push(...statements);\n }\n\n // Use db.query() — system tables use CREATE TABLE/INDEX IF NOT EXISTS\n // which databases handle natively without per-column existence checks.\n for (const statement of allStatements) {\n await db.query(statement);\n }\n\n // PostgreSQL keeps the best-effort append boundary in a function that must\n // be executed whole rather than included in semicolon-split portable DDL.\n await ensurePostgresChangeFeedAppendFunction(db);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client-entries.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/api-client-entries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"api-client-entries.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/api-client-entries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAQ7B,MAAM,MAAM,mBAAmB,GAC3B,MAAM,GACN,KAAK,GACL,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,GAAG,EAAE,qBAAqB,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,sEAAsE;IACtE,aAAa,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC;QAC7B,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,UAAU,EAAE,KAAK,CAAC;YAChB,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,CAAC,EAAE,OAAO,CAAC;YACnB,OAAO,CAAC,EAAE,OAAO,CAAC;SACnB,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED,wBAAgB,uBAAuB,CACrC,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,mBAAmB,EAAE,EAClC,iBAAiB,GAAE,MAAM,EAAO,GAC/B,MAAM,GAAG,SAAS,CAepB;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,EAC/C,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAChC,MAAM,CAyCR;AAoSD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,mBAAmB,GAC5B,cAAc,EAAE,CAwElB"}
|