joist-codegen 2.3.0-next.118 → 2.3.0-next.119
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/build/EntityDbMetadata.cjs +5 -5
- package/build/EntityDbMetadata.cjs.map +1 -1
- package/build/EntityDbMetadata.d.cts +2 -2
- package/build/EntityDbMetadata.d.mts +2 -2
- package/build/EntityDbMetadata.js +2 -2
- package/build/EntityDbMetadata.js.map +1 -1
- package/build/generateEntitiesFile.cjs +0 -1
- package/build/generateEntitiesFile.cjs.map +1 -1
- package/build/generateEntitiesFile.d.cts +1 -1
- package/build/generateEntitiesFile.d.mts +1 -1
- package/build/generateEntitiesFile.js +0 -1
- package/build/generateEntitiesFile.js.map +1 -1
- package/build/index.cjs +5 -5
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts.map +1 -1
- package/build/index.js +6 -5
- package/build/index.js.map +1 -1
- package/build/loadMetadata.cjs +3 -3
- package/build/loadMetadata.cjs.map +1 -1
- package/build/loadMetadata.d.cts +1 -1
- package/build/loadMetadata.d.mts +1 -1
- package/build/loadMetadata.js +2 -2
- package/build/loadMetadata.js.map +1 -1
- package/build/pgMetadata.cjs +301 -0
- package/build/pgMetadata.cjs.map +1 -0
- package/build/pgMetadata.d.cts +122 -0
- package/build/pgMetadata.d.cts.map +1 -0
- package/build/pgMetadata.d.mts +122 -0
- package/build/pgMetadata.d.mts.map +1 -0
- package/build/pgMetadata.js +291 -0
- package/build/pgMetadata.js.map +1 -0
- package/build/utils.cjs +0 -1
- package/build/utils.cjs.map +1 -1
- package/build/utils.d.cts +1 -1
- package/build/utils.d.mts +1 -1
- package/build/utils.js +0 -1
- package/build/utils.js.map +1 -1
- package/package.json +12 -3
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport process from \"node:process\";\n\nimport { type ConnectionConfig, newPgConnectionConfig } from \"joist-utils\";\nimport { Client } from \"pg\";\nimport pgStructureModule from \"pg-structure\";\nimport { saveFiles } from \"ts-poet\";\n\nimport { assignTags } from \"./assignTags.ts\";\nimport { maybeRunTransforms } from \"./codemods/index.ts\";\nimport { type Config, loadConfig, stripStiPlaceholders, warnInvalidConfigEntries, writeConfig } from \"./config.ts\";\nimport {\n type DbMetadata,\n EntityDbMetadata,\n failIfOverlappingFieldNames,\n resolveNameConflicts,\n} from \"./EntityDbMetadata.ts\";\nimport { maybeSetForeignKeyOrdering } from \"./foreignKeyOrdering.ts\";\nimport { generateFiles } from \"./generate.ts\";\nimport { createFlushFunction } from \"./generateFlushFunction.ts\";\nimport { applyInheritanceUpdates } from \"./inheritance.ts\";\nimport { installSkills } from \"./installSkills.ts\";\nimport { loadEnumMetadata, loadPgEnumMetadata } from \"./loadMetadata.ts\";\nimport { LOG_LEVELS, loggerMaxWarningLevelHit } from \"./logger.ts\";\nimport { scanEntityFiles } from \"./scanEntityFiles.ts\";\nimport {\n isEntityTable,\n isEnumTable,\n isJoinTable,\n mapSimpleDbTypeToTypescriptType,\n shouldIncludeSchema,\n} from \"./utils.ts\";\n\nconst { default: pgStructure } = pgStructureModule;\n\nexport {\n type DbMetadata,\n type EnumField,\n makeEntity,\n type ManyToManyField,\n type ManyToOneField,\n type OneToManyField,\n type OneToOneField,\n type PolymorphicField,\n type PrimitiveField,\n type PrimitiveTypescriptType,\n} from \"./EntityDbMetadata.ts\";\nexport type { EnumMetadata, EnumRow, EnumTableData, PgEnumData, PgEnumMetadata } from \"./loadMetadata.ts\";\nexport { dateCode, plainDateCode, plainDateTimeCode, zonedDateTimeCode } from \"./utils.ts\";\nexport { type Config, EntityDbMetadata, mapSimpleDbTypeToTypescriptType };\n\nexport async function joistCodegen() {\n const config = await loadConfig();\n\n maybeSetDatabaseUrl(config);\n if (!process.env.DATABASE_URL && !process.env.DB_USER) {\n console.log(`Database connection information not found, please set either:`);\n console.log(` - the DATABASE_URL env variable (i.e. using .env and dotenv), or`);\n console.log(` - the databaseUrl key in joist-config.json`);\n return;\n }\n const pgConfig = newPgConnectionConfig();\n\n const client = new Client(pgConfig);\n await client.connect();\n\n const dbMetadata = await loadSchemaMetadata(config, client);\n const { entities, enums, totalTables } = dbMetadata;\n console.log(\n `Found ${totalTables} total tables, ${entities.length} entity tables, ${Object.entries(enums).length} enum tables`,\n );\n console.log(\"\");\n\n // Look for STI tables to synthesize separate metas\n applyInheritanceUpdates(config, dbMetadata);\n\n // Now that all entities (incl. STI subtypes) are known, fix up any colliding codegen'd type names\n resolveNameConflicts(config, dbMetadata);\n\n // Assign any new tags and write them back to the config file\n assignTags(config, dbMetadata);\n validateTagDelimiter(config, entities);\n\n // Scan `*.ts` files after we've expanded `Task` -> `TaskOld.ts`\n await scanEntityFiles(config, dbMetadata);\n\n // If we're not using deferred FKs, determine our DAG insert order\n await maybeSetForeignKeyOrdering(config, dbMetadata.entities);\n\n // Generate the flush function for tests\n await maybeGenerateFlushFunctions(config, client, pgConfig, dbMetadata);\n\n await client.end();\n\n // Apply any codemods to the user's codebase, if we have them\n await maybeRunTransforms(config);\n\n // Do some warnings\n for (const entity of entities) failIfOverlappingFieldNames(entity);\n warnInvalidConfigEntries(config, dbMetadata);\n\n // Finally actually generate the files (even if we found a fatal error)\n await generateAndSaveFiles(config, dbMetadata);\n\n // Install our bundled Agent Skills so coding agents can find them, unless explicitly disabled\n if (config.skills !== false) await installSkills();\n\n stripStiPlaceholders(config, entities);\n await writeConfig(config);\n}\n\n/** Uses entities and enums from the `db` schema and saves them into our entities directory. */\nexport async function generateAndSaveFiles(config: Config, dbMeta: DbMetadata): Promise<void> {\n const files = await generateFiles(config, dbMeta);\n const esmExt = config.esm ? (config.allowImportingTsExtensions ? \"ts\" : \"js\") : \"\";\n await saveFiles({\n toolName: \"joist-codegen\",\n directory: config.entitiesDirectory,\n files,\n toStringOpts: { importExtensions: esmExt || false },\n });\n}\n\nasync function maybeGenerateFlushFunctions(config: Config, client: Client, pgConfig: ConnectionConfig, db: DbMetadata) {\n // In graphql-service we have our own custom flush function, so allow skipping this\n if (config.createFlushFunction !== false) {\n // Look for multiple test databases\n if (Array.isArray(config.createFlushFunction)) {\n console.log(\"Creating flush_database functions\");\n await Promise.all(\n config.createFlushFunction.map(async (dbName) => {\n const client = new Client({ ...pgConfig, database: dbName });\n await client.connect();\n await createFlushFunction(client, db);\n await client.end();\n }),\n );\n } else {\n console.log(\"Creating flush_database function\");\n await createFlushFunction(client, db);\n }\n }\n}\n\nasync function loadSchemaMetadata(config: Config, client: Client): Promise<DbMetadata> {\n // Here we load all schemas, to avoid pg-structure failing on cross-schema foreign keys\n // like our cyanaudit triggers (https://github.com/ozum/pg-structure/issues/85), and then\n // later filter them non-public schema tables out.\n const db = await pgStructure(client);\n const enums = await loadEnumMetadata(db, client, config);\n const pgEnums = await loadPgEnumMetadata(db, client, config);\n const entities = db.tables\n .filter((t) => isEntityTable(config, t))\n .sortBy(\"name\")\n .map((table) => new EntityDbMetadata(config, table, enums));\n const totalTables = db.tables.length;\n const joinTables = db.tables.filter((t) => isJoinTable(config, t)).map((t) => t.name);\n const otherTables = db.tables\n .filter(\n (t) =>\n shouldIncludeSchema(config, t) &&\n !isEntityTable(config, t) &&\n !isEnumTable(config, t) &&\n !isJoinTable(config, t),\n )\n .map((t) => t.name);\n const entitiesByName = Object.fromEntries(entities.map((e) => [e.name, e]));\n return { entities, entitiesByName, enums, pgEnums, totalTables, joinTables, otherTables };\n}\n\nfunction maybeSetDatabaseUrl(config: Config): void {\n if (!process.env.DATABASE_URL && config.databaseUrl) {\n process.env.DATABASE_URL = config.databaseUrl;\n }\n}\n\nexport function maybeSetExitCode(): void {\n if (\n !process.argv.includes(\"--always-exit-code-zero\") &&\n // strict mode + warnings or greater\n ((process.argv.includes(\"--strict\") && loggerMaxWarningLevelHit >= LOG_LEVELS.warn) ||\n // otherwise errors or greater\n loggerMaxWarningLevelHit >= LOG_LEVELS.error)\n ) {\n process.exitCode = 1;\n }\n}\n\n/** Validates that tags can be parsed unambiguously with the configured delimiter. */\nfunction validateTagDelimiter(config: Config, entities: EntityDbMetadata[]): void {\n const tagDelimiter = config.tagDelimiter ?? \":\";\n for (const entity of entities) {\n if (tagDelimiter !== \"\" && `${entity.tagName}${tagDelimiter}`.indexOf(tagDelimiter) !== entity.tagName.length) {\n throw new Error(\n `Tagged id delimiter '${tagDelimiter}' cannot occur in or overlap tag '${entity.tagName}' for ${entity.name}`,\n );\n } else if (tagDelimiter === \"\" && !/^[a-z]+$/i.test(entity.tagName)) {\n throw new Error(`Delimiterless ids require an alphabetic tag, got '${entity.tagName}' for ${entity.name}`);\n } else if (\n tagDelimiter === \"\" &&\n entity.primaryKey.columnType !== \"int\" &&\n entity.primaryKey.columnType !== \"bigint\"\n ) {\n throw new Error(\n `Delimiterless ids require an int or bigint primary key, got '${entity.primaryKey.columnType}' for ${entity.name}`,\n );\n }\n }\n}\n\nif (typeof module !== \"undefined\" && require.main === module) {\n joistCodegen()\n .then(() => maybeSetExitCode())\n .catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync } from \"node:fs\";\nimport process from \"node:process\";\n\nimport { type ConnectionConfig, newPgConnectionConfig } from \"joist-utils\";\nimport { Client } from \"pg\";\nimport { saveFiles } from \"ts-poet\";\n\nimport { assignTags } from \"./assignTags.ts\";\nimport { maybeRunTransforms } from \"./codemods/index.ts\";\nimport { type Config, loadConfig, stripStiPlaceholders, warnInvalidConfigEntries, writeConfig } from \"./config.ts\";\nimport {\n type DbMetadata,\n EntityDbMetadata,\n failIfOverlappingFieldNames,\n resolveNameConflicts,\n} from \"./EntityDbMetadata.ts\";\nimport { maybeSetForeignKeyOrdering } from \"./foreignKeyOrdering.ts\";\nimport { generateFiles } from \"./generate.ts\";\nimport { createFlushFunction } from \"./generateFlushFunction.ts\";\nimport { applyInheritanceUpdates } from \"./inheritance.ts\";\nimport { installSkills } from \"./installSkills.ts\";\nimport { loadEnumMetadata, loadPgEnumMetadata } from \"./loadMetadata.ts\";\nimport { LOG_LEVELS, loggerMaxWarningLevelHit } from \"./logger.ts\";\nimport { loadPgMetadata } from \"./pgMetadata.ts\";\nimport { scanEntityFiles } from \"./scanEntityFiles.ts\";\nimport {\n isEntityTable,\n isEnumTable,\n isJoinTable,\n mapSimpleDbTypeToTypescriptType,\n shouldIncludeSchema,\n tableToEntityName,\n} from \"./utils.ts\";\n\nexport {\n type DbMetadata,\n type EnumField,\n makeEntity,\n type ManyToManyField,\n type ManyToOneField,\n type OneToManyField,\n type OneToOneField,\n type PolymorphicField,\n type PrimitiveField,\n type PrimitiveTypescriptType,\n} from \"./EntityDbMetadata.ts\";\nexport type { EnumMetadata, EnumRow, EnumTableData, PgEnumData, PgEnumMetadata } from \"./loadMetadata.ts\";\nexport { dateCode, plainDateCode, plainDateTimeCode, zonedDateTimeCode } from \"./utils.ts\";\nexport { type Config, EntityDbMetadata, mapSimpleDbTypeToTypescriptType };\n\nexport async function joistCodegen() {\n // pg-structure used to load .env on import; keep that behavior for fixtures and apps\n // that provide DATABASE_URL in a local .env without a separate dotenv bootstrap.\n if (existsSync(\".env\")) process.loadEnvFile();\n const config = await loadConfig();\n\n maybeSetDatabaseUrl(config);\n if (!process.env.DATABASE_URL && !process.env.DB_USER) {\n console.log(`Database connection information not found, please set either:`);\n console.log(` - the DATABASE_URL env variable (i.e. using .env and dotenv), or`);\n console.log(` - the databaseUrl key in joist-config.json`);\n return;\n }\n const pgConfig = newPgConnectionConfig();\n\n const client = new Client(pgConfig);\n await client.connect();\n\n const dbMetadata = await loadSchemaMetadata(config, client);\n const { entities, enums, totalTables } = dbMetadata;\n console.log(\n `Found ${totalTables} total tables, ${entities.length} entity tables, ${Object.entries(enums).length} enum tables`,\n );\n console.log(\"\");\n\n // Look for STI tables to synthesize separate metas\n applyInheritanceUpdates(config, dbMetadata);\n\n // Now that all entities (incl. STI subtypes) are known, fix up any colliding codegen'd type names\n resolveNameConflicts(config, dbMetadata);\n\n // Assign any new tags and write them back to the config file\n assignTags(config, dbMetadata);\n validateTagDelimiter(config, entities);\n\n // Scan `*.ts` files after we've expanded `Task` -> `TaskOld.ts`\n await scanEntityFiles(config, dbMetadata);\n\n // If we're not using deferred FKs, determine our DAG insert order\n await maybeSetForeignKeyOrdering(config, dbMetadata.entities);\n\n // Generate the flush function for tests\n await maybeGenerateFlushFunctions(config, client, pgConfig, dbMetadata);\n\n await client.end();\n\n // Apply any codemods to the user's codebase, if we have them\n await maybeRunTransforms(config);\n\n // Do some warnings\n for (const entity of entities) failIfOverlappingFieldNames(entity);\n warnInvalidConfigEntries(config, dbMetadata);\n\n // Finally actually generate the files (even if we found a fatal error)\n await generateAndSaveFiles(config, dbMetadata);\n\n // Install our bundled Agent Skills so coding agents can find them, unless explicitly disabled\n if (config.skills !== false) await installSkills();\n\n stripStiPlaceholders(config, entities);\n await writeConfig(config);\n}\n\n/** Uses entities and enums from the `db` schema and saves them into our entities directory. */\nexport async function generateAndSaveFiles(config: Config, dbMeta: DbMetadata): Promise<void> {\n const files = await generateFiles(config, dbMeta);\n const esmExt = config.esm ? (config.allowImportingTsExtensions ? \"ts\" : \"js\") : \"\";\n await saveFiles({\n toolName: \"joist-codegen\",\n directory: config.entitiesDirectory,\n files,\n toStringOpts: { importExtensions: esmExt || false },\n });\n}\n\nasync function maybeGenerateFlushFunctions(config: Config, client: Client, pgConfig: ConnectionConfig, db: DbMetadata) {\n // In graphql-service we have our own custom flush function, so allow skipping this\n if (config.createFlushFunction !== false) {\n // Look for multiple test databases\n if (Array.isArray(config.createFlushFunction)) {\n console.log(\"Creating flush_database functions\");\n await Promise.all(\n config.createFlushFunction.map(async (dbName) => {\n const client = new Client({ ...pgConfig, database: dbName });\n await client.connect();\n await createFlushFunction(client, db);\n await client.end();\n }),\n );\n } else {\n console.log(\"Creating flush_database function\");\n await createFlushFunction(client, db);\n }\n }\n}\n\nasync function loadSchemaMetadata(config: Config, client: Client): Promise<DbMetadata> {\n // Load all user schemas so cross-schema foreign keys can be resolved. Codegen filters\n // non-public tables below; trigger functions in other schemas do not affect entities.\n const db = await loadPgMetadata(client);\n const enums = await loadEnumMetadata(db, client, config);\n const pgEnums = await loadPgEnumMetadata(db, client, config);\n // The order also controls generated exports; sorting physical table names puts book_reviews\n // before books and changes initialization order for entity modules with circular imports.\n const entities = db.tables\n .filter((t) => isEntityTable(config, t))\n .sort((a, b) => tableToEntityName(config, a).localeCompare(tableToEntityName(config, b)))\n .map((table) => new EntityDbMetadata(config, table, enums));\n const totalTables = db.tables.length;\n const joinTables = db.tables.filter((t) => isJoinTable(config, t)).map((t) => t.name);\n const otherTables = db.tables\n .filter(\n (t) =>\n shouldIncludeSchema(config, t) &&\n !isEntityTable(config, t) &&\n !isEnumTable(config, t) &&\n !isJoinTable(config, t),\n )\n .map((t) => t.name);\n const entitiesByName = Object.fromEntries(entities.map((e) => [e.name, e]));\n return { entities, entitiesByName, enums, pgEnums, totalTables, joinTables, otherTables };\n}\n\nfunction maybeSetDatabaseUrl(config: Config): void {\n if (!process.env.DATABASE_URL && config.databaseUrl) {\n process.env.DATABASE_URL = config.databaseUrl;\n }\n}\n\nexport function maybeSetExitCode(): void {\n if (\n !process.argv.includes(\"--always-exit-code-zero\") &&\n // strict mode + warnings or greater\n ((process.argv.includes(\"--strict\") && loggerMaxWarningLevelHit >= LOG_LEVELS.warn) ||\n // otherwise errors or greater\n loggerMaxWarningLevelHit >= LOG_LEVELS.error)\n ) {\n process.exitCode = 1;\n }\n}\n\n/** Validates that tags can be parsed unambiguously with the configured delimiter. */\nfunction validateTagDelimiter(config: Config, entities: EntityDbMetadata[]): void {\n const tagDelimiter = config.tagDelimiter ?? \":\";\n for (const entity of entities) {\n if (tagDelimiter !== \"\" && `${entity.tagName}${tagDelimiter}`.indexOf(tagDelimiter) !== entity.tagName.length) {\n throw new Error(\n `Tagged id delimiter '${tagDelimiter}' cannot occur in or overlap tag '${entity.tagName}' for ${entity.name}`,\n );\n } else if (tagDelimiter === \"\" && !/^[a-z]+$/i.test(entity.tagName)) {\n throw new Error(`Delimiterless ids require an alphabetic tag, got '${entity.tagName}' for ${entity.name}`);\n } else if (\n tagDelimiter === \"\" &&\n entity.primaryKey.columnType !== \"int\" &&\n entity.primaryKey.columnType !== \"bigint\"\n ) {\n throw new Error(\n `Delimiterless ids require an int or bigint primary key, got '${entity.primaryKey.columnType}' for ${entity.name}`,\n );\n }\n }\n}\n\nif (typeof module !== \"undefined\" && require.main === module) {\n joistCodegen()\n .then(() => maybeSetExitCode())\n .catch((err) => {\n console.error(err);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoDA,eAAsB,eAAe;CAGnC,IAAI,WAAW,MAAM,GAAG,QAAQ,YAAY;CAC5C,MAAM,SAAS,MAAM,WAAW;CAEhC,oBAAoB,MAAM;CAC1B,IAAI,CAAC,QAAQ,IAAI,gBAAgB,CAAC,QAAQ,IAAI,SAAS;EACrD,QAAQ,IAAI,+DAA+D;EAC3E,QAAQ,IAAI,oEAAoE;EAChF,QAAQ,IAAI,8CAA8C;EAC1D;CACF;CACA,MAAM,WAAW,sBAAsB;CAEvC,MAAM,SAAS,IAAI,OAAO,QAAQ;CAClC,MAAM,OAAO,QAAQ;CAErB,MAAM,aAAa,MAAM,mBAAmB,QAAQ,MAAM;CAC1D,MAAM,EAAE,UAAU,OAAO,gBAAgB;CACzC,QAAQ,IACN,SAAS,YAAY,iBAAiB,SAAS,OAAO,kBAAkB,OAAO,QAAQ,KAAK,CAAC,CAAC,OAAO,aACvG;CACA,QAAQ,IAAI,EAAE;CAGd,wBAAwB,QAAQ,UAAU;CAG1C,qBAAqB,QAAQ,UAAU;CAGvC,WAAW,QAAQ,UAAU;CAC7B,qBAAqB,QAAQ,QAAQ;CAGrC,MAAM,gBAAgB,QAAQ,UAAU;CAGxC,MAAM,2BAA2B,QAAQ,WAAW,QAAQ;CAG5D,MAAM,4BAA4B,QAAQ,QAAQ,UAAU,UAAU;CAEtE,MAAM,OAAO,IAAI;CAGjB,MAAM,mBAAmB,MAAM;CAG/B,KAAK,MAAM,UAAU,UAAU,4BAA4B,MAAM;CACjE,yBAAyB,QAAQ,UAAU;CAG3C,MAAM,qBAAqB,QAAQ,UAAU;CAG7C,IAAI,OAAO,WAAW,OAAO,MAAM,cAAc;CAEjD,qBAAqB,QAAQ,QAAQ;CACrC,MAAM,YAAY,MAAM;AAC1B;;AAGA,eAAsB,qBAAqB,QAAgB,QAAmC;CAC5F,MAAM,QAAQ,MAAM,cAAc,QAAQ,MAAM;CAChD,MAAM,SAAS,OAAO,MAAO,OAAO,6BAA6B,OAAO,OAAQ;CAChF,MAAM,UAAU;EACd,UAAU;EACV,WAAW,OAAO;EAClB;EACA,cAAc,EAAE,kBAAkB,UAAU,MAAM;CACpD,CAAC;AACH;AAEA,eAAe,4BAA4B,QAAgB,QAAgB,UAA4B,IAAgB;CAErH,IAAI,OAAO,wBAAwB,OAAO;EAExC,IAAI,MAAM,QAAQ,OAAO,mBAAmB,GAAG;GAC7C,QAAQ,IAAI,mCAAmC;GAC/C,MAAM,QAAQ,IACZ,OAAO,oBAAoB,IAAI,OAAO,WAAW;IAC/C,MAAM,SAAS,IAAI,OAAO;KAAE,GAAG;KAAU,UAAU;IAAO,CAAC;IAC3D,MAAM,OAAO,QAAQ;IACrB,MAAM,oBAAoB,QAAQ,EAAE;IACpC,MAAM,OAAO,IAAI;GACnB,CAAC,CACH;EACF,OAAO;GACL,QAAQ,IAAI,kCAAkC;GAC9C,MAAM,oBAAoB,QAAQ,EAAE;EACtC;CACF;AACF;AAEA,eAAe,mBAAmB,QAAgB,QAAqC;CAGrF,MAAM,KAAK,MAAM,eAAe,MAAM;CACtC,MAAM,QAAQ,MAAM,iBAAiB,IAAI,QAAQ,MAAM;CACvD,MAAM,UAAU,MAAM,mBAAmB,IAAI,QAAQ,MAAM;CAG3D,MAAM,WAAW,GAAG,OACjB,QAAQ,MAAM,cAAc,QAAQ,CAAC,CAAC,CAAC,CACvC,MAAM,GAAG,MAAM,kBAAkB,QAAQ,CAAC,CAAC,CAAC,cAAc,kBAAkB,QAAQ,CAAC,CAAC,CAAC,CAAC,CACxF,KAAK,UAAU,IAAI,iBAAiB,QAAQ,OAAO,KAAK,CAAC;CAC5D,MAAM,cAAc,GAAG,OAAO;CAC9B,MAAM,aAAa,GAAG,OAAO,QAAQ,MAAM,YAAY,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI;CACpF,MAAM,cAAc,GAAG,OACpB,QACE,MACC,oBAAoB,QAAQ,CAAC,KAC7B,CAAC,cAAc,QAAQ,CAAC,KACxB,CAAC,YAAY,QAAQ,CAAC,KACtB,CAAC,YAAY,QAAQ,CAAC,CAC1B,CAAC,CACA,KAAK,MAAM,EAAE,IAAI;CAEpB,OAAO;EAAE;EAAU,gBADI,OAAO,YAAY,SAAS,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CACzC;EAAG;EAAO;EAAS;EAAa;EAAY;CAAY;AAC1F;AAEA,SAAS,oBAAoB,QAAsB;CACjD,IAAI,CAAC,QAAQ,IAAI,gBAAgB,OAAO,aACtC,QAAQ,IAAI,eAAe,OAAO;AAEtC;AAEA,SAAgB,mBAAyB;CACvC,IACE,CAAC,QAAQ,KAAK,SAAS,yBAAyB,MAE9C,QAAQ,KAAK,SAAS,UAAU,KAAK,4BAA4B,WAAW,QAE5E,4BAA4B,WAAW,QAEzC,QAAQ,WAAW;AAEvB;;AAGA,SAAS,qBAAqB,QAAgB,UAAoC;CAChF,MAAM,eAAe,OAAO,gBAAgB;CAC5C,KAAK,MAAM,UAAU,UACnB,IAAI,iBAAiB,MAAM,GAAG,OAAO,UAAU,eAAe,QAAQ,YAAY,MAAM,OAAO,QAAQ,QACrG,MAAM,IAAI,MACR,wBAAwB,aAAa,oCAAoC,OAAO,QAAQ,QAAQ,OAAO,MACzG;MACK,IAAI,iBAAiB,MAAM,CAAC,YAAY,KAAK,OAAO,OAAO,GAChE,MAAM,IAAI,MAAM,qDAAqD,OAAO,QAAQ,QAAQ,OAAO,MAAM;MACpG,IACL,iBAAiB,MACjB,OAAO,WAAW,eAAe,SACjC,OAAO,WAAW,eAAe,UAEjC,MAAM,IAAI,MACR,gEAAgE,OAAO,WAAW,WAAW,QAAQ,OAAO,MAC9G;AAGN;AAEA,IAAI,OAAO,WAAW,eAAA,UAAuB,SAAS,QACpD,aAAa,CAAC,CACX,WAAW,iBAAiB,CAAC,CAAC,CAC9B,OAAO,QAAQ;CACd,QAAQ,MAAM,GAAG;CACjB,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
package/build/loadMetadata.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_pgMetadata = require("./pgMetadata.cjs");
|
|
2
3
|
const require_utils = require("./utils.cjs");
|
|
3
4
|
require("./index.cjs");
|
|
4
5
|
const require_EntityDbMetadata = require("./EntityDbMetadata.cjs");
|
|
5
6
|
let change_case = require("change-case");
|
|
6
|
-
let pg_structure = require("pg-structure");
|
|
7
7
|
require("pg");
|
|
8
8
|
//#region src/loadMetadata.ts
|
|
9
9
|
async function loadEnumMetadata(db, client, config) {
|
|
10
|
-
const promises = db.tables.filter((t) => require_utils.isEnumTable(config, t)).
|
|
10
|
+
const promises = db.tables.filter((t) => require_utils.isEnumTable(config, t)).map(async (table) => {
|
|
11
11
|
const rows = (await client.query(`SELECT * FROM ${table.name} ORDER BY id`)).rows.map((row) => row);
|
|
12
12
|
const extraPrimitives = new require_EntityDbMetadata.EntityDbMetadata(config, table).primitives.filter((p) => !["code", "name"].includes(p.fieldName));
|
|
13
13
|
return [table.name, {
|
|
@@ -22,7 +22,7 @@ async function loadEnumMetadata(db, client, config) {
|
|
|
22
22
|
}
|
|
23
23
|
async function loadPgEnumMetadata(db, client, config) {
|
|
24
24
|
return db.types.reduce((all, type) => {
|
|
25
|
-
if (type instanceof
|
|
25
|
+
if (type instanceof require_pgMetadata.EnumType) return {
|
|
26
26
|
...all,
|
|
27
27
|
[type.name]: {
|
|
28
28
|
name: (0, change_case.pascalCase)(type.name),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadMetadata.cjs","names":["isEnumTable","EntityDbMetadata","pascalCase","EnumType"],"sources":["../src/loadMetadata.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { type Client } from \"pg\";\nimport { type
|
|
1
|
+
{"version":3,"file":"loadMetadata.cjs","names":["isEnumTable","EntityDbMetadata","pascalCase","EnumType"],"sources":["../src/loadMetadata.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { type Client } from \"pg\";\n\nimport { type Config, EntityDbMetadata, type PrimitiveField } from \"./index.ts\";\nimport { type Db, EnumType, type Table } from \"./pgMetadata.ts\";\nimport { isEnumTable } from \"./utils.ts\";\n\n/** A map from Enum table name to the rows currently in the table. */\nexport type EnumTableData = {\n table: Table;\n idType: \"integer\" | \"uuid\";\n // Pascal case version of table name\n name: string;\n rows: EnumRow[];\n extraPrimitives: PrimitiveField[];\n};\n\nexport type PgEnumData = {\n name: string;\n values: string[];\n};\n\nexport type EnumMetadata = Record<string, EnumTableData>;\nexport type PgEnumMetadata = Record<string, PgEnumData>;\n\nexport type EnumRow = { id: number; code: string; name: string; [key: string]: any };\n\nexport async function loadEnumMetadata(db: Db, client: Client, config: Config): Promise<EnumMetadata> {\n const promises = db.tables\n .filter((t) => isEnumTable(config, t))\n .map(async (table) => {\n const result = await client.query(`SELECT * FROM ${table.name} ORDER BY id`);\n const rows = result.rows.map((row) => row as EnumRow);\n // We're not really an entity, but appropriate EntityDbMetadata's `primitives` filtering\n const extraPrimitives = new EntityDbMetadata(config, table).primitives.filter(\n (p) => ![\"code\", \"name\"].includes(p.fieldName),\n );\n return [\n table.name,\n {\n table,\n idType: table.columns.get(\"id\")!.type.name === \"uuid\" ? \"uuid\" : \"integer\",\n name: pascalCase(table.name), // use tableToEntityName?\n rows,\n extraPrimitives,\n },\n ] satisfies [string, EnumTableData];\n });\n return Object.fromEntries(await Promise.all(promises));\n}\n\nexport async function loadPgEnumMetadata(db: Db, client: Client, config: Config): Promise<PgEnumMetadata> {\n return db.types.reduce((all, type) => {\n if (type instanceof EnumType) {\n return {\n ...all,\n [type.name]: {\n name: pascalCase(type.name),\n values: type.values,\n },\n };\n }\n return all;\n }, {});\n}\n"],"mappings":";;;;;;;;AA2BA,eAAsB,iBAAiB,IAAQ,QAAgB,QAAuC;CACpG,MAAM,WAAW,GAAG,OACjB,QAAQ,MAAMA,cAAAA,YAAY,QAAQ,CAAC,CAAC,CAAC,CACrC,IAAI,OAAO,UAAU;EAEpB,MAAM,QAAO,MADQ,OAAO,MAAM,iBAAiB,MAAM,KAAK,aAAa,EAAA,CACvD,KAAK,KAAK,QAAQ,GAAc;EAEpD,MAAM,kBAAkB,IAAIC,yBAAAA,iBAAiB,QAAQ,KAAK,CAAC,CAAC,WAAW,QACpE,MAAM,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,SAAS,EAAE,SAAS,CAC/C;EACA,OAAO,CACL,MAAM,MACN;GACE;GACA,QAAQ,MAAM,QAAQ,IAAI,IAAI,CAAC,CAAE,KAAK,SAAS,SAAS,SAAS;GACjE,OAAA,GAAMC,YAAAA,WAAAA,CAAW,MAAM,IAAI;GAC3B;GACA;EACF,CACF;CACF,CAAC;CACH,OAAO,OAAO,YAAY,MAAM,QAAQ,IAAI,QAAQ,CAAC;AACvD;AAEA,eAAsB,mBAAmB,IAAQ,QAAgB,QAAyC;CACxG,OAAO,GAAG,MAAM,QAAQ,KAAK,SAAS;EACpC,IAAI,gBAAgBC,mBAAAA,UAClB,OAAO;GACL,GAAG;IACF,KAAK,OAAO;IACX,OAAA,GAAMD,YAAAA,WAAAA,CAAW,KAAK,IAAI;IAC1B,QAAQ,KAAK;GACf;EACF;EAEF,OAAO;CACT,GAAG,CAAC,CAAC;AACP"}
|
package/build/loadMetadata.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Config } from "./config.cjs";
|
|
2
|
+
import { Db, Table } from "./pgMetadata.cjs";
|
|
2
3
|
import "./index.cjs";
|
|
3
4
|
import { PrimitiveField } from "./EntityDbMetadata.cjs";
|
|
4
|
-
import { Db, Table } from "pg-structure";
|
|
5
5
|
import { Client } from "pg";
|
|
6
6
|
//#region src/loadMetadata.d.ts
|
|
7
7
|
/** A map from Enum table name to the rows currently in the table. */
|
package/build/loadMetadata.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Config } from "./config.mjs";
|
|
2
|
+
import { Db, Table } from "./pgMetadata.mjs";
|
|
2
3
|
import "./index.mjs";
|
|
3
4
|
import { PrimitiveField } from "./EntityDbMetadata.mjs";
|
|
4
|
-
import { Db, Table } from "pg-structure";
|
|
5
5
|
import { Client } from "pg";
|
|
6
6
|
//#region src/loadMetadata.d.ts
|
|
7
7
|
/** A map from Enum table name to the rows currently in the table. */
|
package/build/loadMetadata.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { EnumType } from "./pgMetadata.js";
|
|
1
2
|
import { isEnumTable } from "./utils.js";
|
|
2
3
|
import "./index.js";
|
|
3
4
|
import { EntityDbMetadata } from "./EntityDbMetadata.js";
|
|
4
5
|
import { pascalCase } from "change-case";
|
|
5
|
-
import { EnumType } from "pg-structure";
|
|
6
6
|
import "pg";
|
|
7
7
|
//#region src/loadMetadata.ts
|
|
8
8
|
async function loadEnumMetadata(db, client, config) {
|
|
9
|
-
const promises = db.tables.filter((t) => isEnumTable(config, t)).
|
|
9
|
+
const promises = db.tables.filter((t) => isEnumTable(config, t)).map(async (table) => {
|
|
10
10
|
const rows = (await client.query(`SELECT * FROM ${table.name} ORDER BY id`)).rows.map((row) => row);
|
|
11
11
|
const extraPrimitives = new EntityDbMetadata(config, table).primitives.filter((p) => !["code", "name"].includes(p.fieldName));
|
|
12
12
|
return [table.name, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadMetadata.js","names":[],"sources":["../src/loadMetadata.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { type Client } from \"pg\";\nimport { type
|
|
1
|
+
{"version":3,"file":"loadMetadata.js","names":[],"sources":["../src/loadMetadata.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { type Client } from \"pg\";\n\nimport { type Config, EntityDbMetadata, type PrimitiveField } from \"./index.ts\";\nimport { type Db, EnumType, type Table } from \"./pgMetadata.ts\";\nimport { isEnumTable } from \"./utils.ts\";\n\n/** A map from Enum table name to the rows currently in the table. */\nexport type EnumTableData = {\n table: Table;\n idType: \"integer\" | \"uuid\";\n // Pascal case version of table name\n name: string;\n rows: EnumRow[];\n extraPrimitives: PrimitiveField[];\n};\n\nexport type PgEnumData = {\n name: string;\n values: string[];\n};\n\nexport type EnumMetadata = Record<string, EnumTableData>;\nexport type PgEnumMetadata = Record<string, PgEnumData>;\n\nexport type EnumRow = { id: number; code: string; name: string; [key: string]: any };\n\nexport async function loadEnumMetadata(db: Db, client: Client, config: Config): Promise<EnumMetadata> {\n const promises = db.tables\n .filter((t) => isEnumTable(config, t))\n .map(async (table) => {\n const result = await client.query(`SELECT * FROM ${table.name} ORDER BY id`);\n const rows = result.rows.map((row) => row as EnumRow);\n // We're not really an entity, but appropriate EntityDbMetadata's `primitives` filtering\n const extraPrimitives = new EntityDbMetadata(config, table).primitives.filter(\n (p) => ![\"code\", \"name\"].includes(p.fieldName),\n );\n return [\n table.name,\n {\n table,\n idType: table.columns.get(\"id\")!.type.name === \"uuid\" ? \"uuid\" : \"integer\",\n name: pascalCase(table.name), // use tableToEntityName?\n rows,\n extraPrimitives,\n },\n ] satisfies [string, EnumTableData];\n });\n return Object.fromEntries(await Promise.all(promises));\n}\n\nexport async function loadPgEnumMetadata(db: Db, client: Client, config: Config): Promise<PgEnumMetadata> {\n return db.types.reduce((all, type) => {\n if (type instanceof EnumType) {\n return {\n ...all,\n [type.name]: {\n name: pascalCase(type.name),\n values: type.values,\n },\n };\n }\n return all;\n }, {});\n}\n"],"mappings":";;;;;;;AA2BA,eAAsB,iBAAiB,IAAQ,QAAgB,QAAuC;CACpG,MAAM,WAAW,GAAG,OACjB,QAAQ,MAAM,YAAY,QAAQ,CAAC,CAAC,CAAC,CACrC,IAAI,OAAO,UAAU;EAEpB,MAAM,QAAO,MADQ,OAAO,MAAM,iBAAiB,MAAM,KAAK,aAAa,EAAA,CACvD,KAAK,KAAK,QAAQ,GAAc;EAEpD,MAAM,kBAAkB,IAAI,iBAAiB,QAAQ,KAAK,CAAC,CAAC,WAAW,QACpE,MAAM,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,SAAS,EAAE,SAAS,CAC/C;EACA,OAAO,CACL,MAAM,MACN;GACE;GACA,QAAQ,MAAM,QAAQ,IAAI,IAAI,CAAC,CAAE,KAAK,SAAS,SAAS,SAAS;GACjE,MAAM,WAAW,MAAM,IAAI;GAC3B;GACA;EACF,CACF;CACF,CAAC;CACH,OAAO,OAAO,YAAY,MAAM,QAAQ,IAAI,QAAQ,CAAC;AACvD;AAEA,eAAsB,mBAAmB,IAAQ,QAAgB,QAAyC;CACxG,OAAO,GAAG,MAAM,QAAQ,KAAK,SAAS;EACpC,IAAI,gBAAgB,UAClB,OAAO;GACL,GAAG;IACF,KAAK,OAAO;IACX,MAAM,WAAW,KAAK,IAAI;IAC1B,QAAQ,KAAK;GACf;EACF;EAEF,OAAO;CACT,GAAG,CAAC,CAAC;AACP"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/pgMetadata.ts
|
|
3
|
+
/** The small part of PostgreSQL's catalog model used by Joist codegen. */
|
|
4
|
+
var Items = class extends Array {
|
|
5
|
+
byName;
|
|
6
|
+
/** Looks up a column by name without scanning the table on every call. */
|
|
7
|
+
get(name) {
|
|
8
|
+
this.byName ??= new Map(this.map((item) => [item.name, item]));
|
|
9
|
+
const item = this.byName.get(name);
|
|
10
|
+
if (!item) throw new Error(`No item named ${name}`);
|
|
11
|
+
return item;
|
|
12
|
+
}
|
|
13
|
+
/** Keep the name index current if more catalog rows arrive after a lookup. */
|
|
14
|
+
push(...items) {
|
|
15
|
+
const length = super.push(...items);
|
|
16
|
+
if (this.byName) for (const item of items) this.byName.set(item.name, item);
|
|
17
|
+
return length;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var EnumType = class {
|
|
21
|
+
name;
|
|
22
|
+
values;
|
|
23
|
+
shortName;
|
|
24
|
+
constructor(name, values) {
|
|
25
|
+
this.name = name;
|
|
26
|
+
this.values = values;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var Column = class {
|
|
30
|
+
name;
|
|
31
|
+
type;
|
|
32
|
+
notNull;
|
|
33
|
+
defaultValue;
|
|
34
|
+
isGenerated;
|
|
35
|
+
arrayDimension;
|
|
36
|
+
comment;
|
|
37
|
+
foreignKeys = [];
|
|
38
|
+
uniqueIndexes = [];
|
|
39
|
+
isPrimaryKey = false;
|
|
40
|
+
constructor(name, type, notNull, defaultValue, isGenerated, arrayDimension, comment) {
|
|
41
|
+
this.name = name;
|
|
42
|
+
this.type = type;
|
|
43
|
+
this.notNull = notNull;
|
|
44
|
+
this.defaultValue = defaultValue;
|
|
45
|
+
this.isGenerated = isGenerated;
|
|
46
|
+
this.arrayDimension = arrayDimension;
|
|
47
|
+
this.comment = comment;
|
|
48
|
+
}
|
|
49
|
+
get isForeignKey() {
|
|
50
|
+
return this.foreignKeys.length > 0;
|
|
51
|
+
}
|
|
52
|
+
get default() {
|
|
53
|
+
return this.defaultValue?.replace(/^('.*?')::.+$/, "$1") ?? null;
|
|
54
|
+
}
|
|
55
|
+
get commentData() {
|
|
56
|
+
const data = this.comment?.match(/\[pg-structure\]([\s\S]*?)\[\/pg-structure\]/)?.[1];
|
|
57
|
+
return data ? JSON.parse(data) : void 0;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var Index = class {
|
|
61
|
+
name;
|
|
62
|
+
columns;
|
|
63
|
+
isUnique;
|
|
64
|
+
isPartial;
|
|
65
|
+
constructor(name, columns, isUnique, isPartial) {
|
|
66
|
+
this.name = name;
|
|
67
|
+
this.columns = columns;
|
|
68
|
+
this.isUnique = isUnique;
|
|
69
|
+
this.isPartial = isPartial;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var ForeignKey = class {
|
|
73
|
+
name;
|
|
74
|
+
table;
|
|
75
|
+
referencedTable;
|
|
76
|
+
columns;
|
|
77
|
+
isDeferred;
|
|
78
|
+
isDeferrable;
|
|
79
|
+
onDelete;
|
|
80
|
+
constructor(name, table, referencedTable, columns, isDeferred, isDeferrable, onDelete) {
|
|
81
|
+
this.name = name;
|
|
82
|
+
this.table = table;
|
|
83
|
+
this.referencedTable = referencedTable;
|
|
84
|
+
this.columns = columns;
|
|
85
|
+
this.isDeferred = isDeferred;
|
|
86
|
+
this.isDeferrable = isDeferrable;
|
|
87
|
+
this.onDelete = onDelete;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var M2ORelation = class {
|
|
91
|
+
foreignKey;
|
|
92
|
+
type = "m2o";
|
|
93
|
+
constructor(foreignKey) {
|
|
94
|
+
this.foreignKey = foreignKey;
|
|
95
|
+
}
|
|
96
|
+
get sourceTable() {
|
|
97
|
+
return this.foreignKey.table;
|
|
98
|
+
}
|
|
99
|
+
get targetTable() {
|
|
100
|
+
return this.foreignKey.referencedTable;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var O2MRelation = class {
|
|
104
|
+
foreignKey;
|
|
105
|
+
type = "o2m";
|
|
106
|
+
constructor(foreignKey) {
|
|
107
|
+
this.foreignKey = foreignKey;
|
|
108
|
+
}
|
|
109
|
+
get sourceTable() {
|
|
110
|
+
return this.foreignKey.referencedTable;
|
|
111
|
+
}
|
|
112
|
+
get targetTable() {
|
|
113
|
+
return this.foreignKey.table;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var M2MRelation = class {
|
|
117
|
+
foreignKey;
|
|
118
|
+
targetForeignKey;
|
|
119
|
+
type = "m2m";
|
|
120
|
+
constructor(foreignKey, targetForeignKey) {
|
|
121
|
+
this.foreignKey = foreignKey;
|
|
122
|
+
this.targetForeignKey = targetForeignKey;
|
|
123
|
+
}
|
|
124
|
+
get sourceTable() {
|
|
125
|
+
return this.foreignKey.referencedTable;
|
|
126
|
+
}
|
|
127
|
+
get targetTable() {
|
|
128
|
+
return this.targetForeignKey.referencedTable;
|
|
129
|
+
}
|
|
130
|
+
get joinTable() {
|
|
131
|
+
return this.foreignKey.table;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var Table = class {
|
|
135
|
+
name;
|
|
136
|
+
schema;
|
|
137
|
+
columns = new Items();
|
|
138
|
+
indexes = new Items();
|
|
139
|
+
foreignKeys = [];
|
|
140
|
+
foreignKeysToThis = [];
|
|
141
|
+
uniqueConstraints = [];
|
|
142
|
+
primaryKey;
|
|
143
|
+
constructor(name, schema) {
|
|
144
|
+
this.name = name;
|
|
145
|
+
this.schema = schema;
|
|
146
|
+
}
|
|
147
|
+
get m2oRelations() {
|
|
148
|
+
return this.foreignKeys.map((fk) => new M2ORelation(fk));
|
|
149
|
+
}
|
|
150
|
+
get o2mRelations() {
|
|
151
|
+
return this.foreignKeysToThis.map((fk) => new O2MRelation(fk));
|
|
152
|
+
}
|
|
153
|
+
get m2mRelations() {
|
|
154
|
+
return this.foreignKeysToThis.flatMap((fk) => fk.table.foreignKeys.filter((other) => other !== fk).map((other) => new M2MRelation(fk, other)));
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Reads only the PostgreSQL catalog data used by codegen.
|
|
159
|
+
*
|
|
160
|
+
* Load tables and columns before constraints so each foreign key can link the same Table and Column
|
|
161
|
+
* objects used by entity fields. For example, books.author_id and authors.id are joined through
|
|
162
|
+
* the foreign key, while a trigger calling cyanaudit in another schema is irrelevant to codegen.
|
|
163
|
+
*/
|
|
164
|
+
async function loadPgMetadata(client) {
|
|
165
|
+
const tables = new Items();
|
|
166
|
+
const byOid = /* @__PURE__ */ new Map();
|
|
167
|
+
const columns = /* @__PURE__ */ new Map();
|
|
168
|
+
const tableRows = await client.query(`
|
|
169
|
+
SELECT c.oid, c.relname AS name, n.nspname AS schema
|
|
170
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
171
|
+
WHERE c.relkind IN ('r', 'p') AND n.nspname <> 'information_schema'
|
|
172
|
+
AND n.nspname NOT LIKE 'pg_%' AND NOT pg_is_other_temp_schema(n.oid)
|
|
173
|
+
ORDER BY n.nspname, c.relname
|
|
174
|
+
`);
|
|
175
|
+
for (const row of tableRows.rows) {
|
|
176
|
+
const table = new Table(row.name, { name: row.schema });
|
|
177
|
+
tables.push(table);
|
|
178
|
+
byOid.set(row.oid, table);
|
|
179
|
+
columns.set(row.oid, /* @__PURE__ */ new Map());
|
|
180
|
+
}
|
|
181
|
+
const enumRows = await client.query(`
|
|
182
|
+
SELECT t.oid, t.typname AS name, e.enumlabel AS value
|
|
183
|
+
FROM pg_type t JOIN pg_enum e ON e.enumtypid = t.oid
|
|
184
|
+
JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
185
|
+
WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg_%'
|
|
186
|
+
ORDER BY t.oid, e.enumsortorder
|
|
187
|
+
`);
|
|
188
|
+
const enums = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const row of enumRows.rows) {
|
|
190
|
+
if (!enums.has(row.oid)) enums.set(row.oid, new EnumType(row.name, []));
|
|
191
|
+
enums.get(row.oid).values.push(row.value);
|
|
192
|
+
}
|
|
193
|
+
const columnRows = await client.query(`
|
|
194
|
+
SELECT a.attrelid AS "tableOid", a.attnum AS position, a.attname AS name,
|
|
195
|
+
COALESCE(NULLIF(t.typelem, 0), t.oid) AS "typeOid",
|
|
196
|
+
base.typname AS "typeName", a.attnotnull AS "notNull",
|
|
197
|
+
pg_get_expr(d.adbin, d.adrelid) AS "defaultValue", a.attgenerated AS generated,
|
|
198
|
+
a.attndims AS "arrayDimension", col_description(a.attrelid, a.attnum) AS comment
|
|
199
|
+
FROM pg_attribute a JOIN pg_type t ON t.oid = a.atttypid
|
|
200
|
+
JOIN pg_type base ON base.oid = COALESCE(NULLIF(t.typelem, 0), t.oid)
|
|
201
|
+
LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
|
202
|
+
WHERE a.attrelid = ANY($1::oid[]) AND a.attnum > 0 AND NOT a.attisdropped
|
|
203
|
+
ORDER BY a.attrelid, a.attnum
|
|
204
|
+
`, [Array.from(byOid.keys())]);
|
|
205
|
+
for (const row of columnRows.rows) {
|
|
206
|
+
const type = enums.get(row.typeOid) ?? sqlType(row.typeName);
|
|
207
|
+
const column = new Column(row.name, type, row.notNull, row.defaultValue, row.generated !== "", row.arrayDimension, row.comment);
|
|
208
|
+
byOid.get(row.tableOid).columns.push(column);
|
|
209
|
+
columns.get(row.tableOid).set(row.position, column);
|
|
210
|
+
}
|
|
211
|
+
const indexRows = await client.query(`
|
|
212
|
+
SELECT i.indrelid AS "tableOid", i.indexrelid AS "indexOid", c.relname AS name,
|
|
213
|
+
i.indkey::int2[] AS positions, i.indisunique AS unique, i.indpred IS NOT NULL AS partial
|
|
214
|
+
FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
|
|
215
|
+
WHERE i.indrelid = ANY($1::oid[])
|
|
216
|
+
`, [Array.from(byOid.keys())]);
|
|
217
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const row of indexRows.rows) {
|
|
219
|
+
const table = byOid.get(row.tableOid);
|
|
220
|
+
const index = new Index(row.name, row.positions.flatMap((pos) => columns.get(row.tableOid).get(pos) ?? []), row.unique, row.partial);
|
|
221
|
+
table.indexes.push(index);
|
|
222
|
+
indexes.set(row.indexOid, index);
|
|
223
|
+
if (row.unique) for (const column of index.columns) column.uniqueIndexes.push(index);
|
|
224
|
+
}
|
|
225
|
+
const constraintRows = await client.query(`
|
|
226
|
+
SELECT conrelid AS "tableOid", confrelid AS "targetOid", conindid AS "indexOid",
|
|
227
|
+
conname AS name, contype AS kind, conkey AS positions,
|
|
228
|
+
condeferred AS deferred, condeferrable AS deferrable, confdeltype AS "onDelete"
|
|
229
|
+
FROM pg_constraint WHERE conrelid = ANY($1::oid[]) AND contype IN ('p', 'u', 'f')
|
|
230
|
+
ORDER BY conrelid, conname
|
|
231
|
+
`, [Array.from(byOid.keys())]);
|
|
232
|
+
for (const row of constraintRows.rows) {
|
|
233
|
+
const table = byOid.get(row.tableOid);
|
|
234
|
+
const keys = (row.positions ?? []).map((pos) => columns.get(row.tableOid).get(pos));
|
|
235
|
+
if (row.kind === "p") {
|
|
236
|
+
table.primaryKey = { columns: keys };
|
|
237
|
+
for (const column of keys) column.isPrimaryKey = true;
|
|
238
|
+
} else if (row.kind === "u") table.uniqueConstraints.push({
|
|
239
|
+
columns: keys,
|
|
240
|
+
index: indexes.get(row.indexOid)
|
|
241
|
+
});
|
|
242
|
+
else {
|
|
243
|
+
const target = byOid.get(row.targetOid);
|
|
244
|
+
if (!target) continue;
|
|
245
|
+
const fk = new ForeignKey(row.name, table, target, keys, row.deferred, row.deferrable, deleteAction(row.onDelete));
|
|
246
|
+
table.foreignKeys.push(fk);
|
|
247
|
+
target.foreignKeysToThis.push(fk);
|
|
248
|
+
for (const column of keys) column.foreignKeys.push(fk);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
tables,
|
|
253
|
+
types: Array.from(enums.values())
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** Matches the type names codegen expects from PostgreSQL's builtin aliases. */
|
|
257
|
+
function sqlType(name) {
|
|
258
|
+
switch (name) {
|
|
259
|
+
case "int2": return { name: "smallint" };
|
|
260
|
+
case "int4": return {
|
|
261
|
+
name: "integer",
|
|
262
|
+
shortName: "int"
|
|
263
|
+
};
|
|
264
|
+
case "int8": return { name: "bigint" };
|
|
265
|
+
case "bool": return { name: "boolean" };
|
|
266
|
+
case "float4": return { name: "real" };
|
|
267
|
+
case "float8": return { name: "double precision" };
|
|
268
|
+
case "varchar": return { name: "character varying" };
|
|
269
|
+
case "timestamptz": return { name: "timestamp with time zone" };
|
|
270
|
+
case "timestamp": return { name: "timestamp without time zone" };
|
|
271
|
+
case "time": return { name: "time without time zone" };
|
|
272
|
+
default: return {
|
|
273
|
+
name,
|
|
274
|
+
shortName: name
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Decodes the pg_constraint foreign-key delete action. */
|
|
279
|
+
function deleteAction(code) {
|
|
280
|
+
switch (code) {
|
|
281
|
+
case "a": return "NO ACTION";
|
|
282
|
+
case "r": return "RESTRICT";
|
|
283
|
+
case "c": return "CASCADE";
|
|
284
|
+
case "n": return "SET NULL";
|
|
285
|
+
case "d": return "SET DEFAULT";
|
|
286
|
+
default: throw new Error(`Unknown PostgreSQL delete action: ${code}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
exports.Column = Column;
|
|
291
|
+
exports.EnumType = EnumType;
|
|
292
|
+
exports.ForeignKey = ForeignKey;
|
|
293
|
+
exports.Index = Index;
|
|
294
|
+
exports.Items = Items;
|
|
295
|
+
exports.M2MRelation = M2MRelation;
|
|
296
|
+
exports.M2ORelation = M2ORelation;
|
|
297
|
+
exports.O2MRelation = O2MRelation;
|
|
298
|
+
exports.Table = Table;
|
|
299
|
+
exports.loadPgMetadata = loadPgMetadata;
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=pgMetadata.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pgMetadata.cjs","names":[],"sources":["../src/pgMetadata.ts"],"sourcesContent":["import type { Client } from \"pg\";\n\n/** The small part of PostgreSQL's catalog model used by Joist codegen. */\nexport class Items<T extends { name: string }> extends Array<T> {\n private byName?: Map<string, T>;\n\n /** Looks up a column by name without scanning the table on every call. */\n get(name: string): T {\n // Catalog rows are appended before codegen reads them. Build the index on first lookup\n // so derived arrays from filter/map can still be indexed without special handling.\n this.byName ??= new Map(this.map((item) => [item.name, item]));\n const item = this.byName.get(name);\n if (!item) throw new Error(`No item named ${name}`);\n return item;\n }\n\n /** Keep the name index current if more catalog rows arrive after a lookup. */\n push(...items: T[]): number {\n const length = super.push(...items);\n if (this.byName) for (const item of items) this.byName.set(item.name, item);\n return length;\n }\n}\n\nexport type Action = \"NO ACTION\" | \"RESTRICT\" | \"CASCADE\" | \"SET NULL\" | \"SET DEFAULT\";\nexport type JSONData = string | number | boolean | null | JSONData[] | { [key: string]: JSONData };\n\nexport class EnumType {\n readonly shortName?: string;\n constructor(\n public name: string,\n public values: string[],\n ) {}\n}\n\nexport class Column {\n readonly foreignKeys: ForeignKey[] = [];\n readonly uniqueIndexes: Index[] = [];\n isPrimaryKey = false;\n\n constructor(\n public name: string,\n public type: { name: string; shortName?: string } | EnumType,\n public notNull: boolean,\n public defaultValue: string | null,\n public isGenerated: boolean,\n public arrayDimension: number,\n public comment: string | null,\n ) {}\n\n get isForeignKey(): boolean {\n return this.foreignKeys.length > 0;\n }\n\n get default(): string | null {\n return this.defaultValue?.replace(/^('.*?')::.+$/, \"$1\") ?? null;\n }\n\n get commentData(): JSONData | undefined {\n const data = this.comment?.match(/\\[pg-structure\\]([\\s\\S]*?)\\[\\/pg-structure\\]/)?.[1];\n return data ? (JSON.parse(data) as JSONData) : undefined;\n }\n}\n\nexport class Index {\n constructor(\n public name: string,\n public columns: Column[],\n public isUnique: boolean,\n public isPartial: boolean,\n ) {}\n}\n\nexport class ForeignKey {\n constructor(\n public name: string,\n public table: Table,\n public referencedTable: Table,\n public columns: Column[],\n public isDeferred: boolean,\n public isDeferrable: boolean,\n public onDelete: Action,\n ) {}\n}\n\nexport class M2ORelation {\n readonly type = \"m2o\";\n\n constructor(public foreignKey: ForeignKey) {}\n\n get sourceTable(): Table {\n return this.foreignKey.table;\n }\n\n get targetTable(): Table {\n return this.foreignKey.referencedTable;\n }\n}\n\nexport class O2MRelation {\n readonly type = \"o2m\";\n\n constructor(public foreignKey: ForeignKey) {}\n\n get sourceTable(): Table {\n return this.foreignKey.referencedTable;\n }\n\n get targetTable(): Table {\n return this.foreignKey.table;\n }\n}\n\nexport class M2MRelation {\n readonly type = \"m2m\";\n\n constructor(\n public foreignKey: ForeignKey,\n public targetForeignKey: ForeignKey,\n ) {}\n\n get sourceTable(): Table {\n return this.foreignKey.referencedTable;\n }\n\n get targetTable(): Table {\n return this.targetForeignKey.referencedTable;\n }\n\n get joinTable(): Table {\n return this.foreignKey.table;\n }\n}\n\nexport class Table {\n readonly columns = new Items<Column>();\n readonly indexes = new Items<Index>();\n readonly foreignKeys: ForeignKey[] = [];\n readonly foreignKeysToThis: ForeignKey[] = [];\n readonly uniqueConstraints: { columns: Column[]; index: Index }[] = [];\n primaryKey?: { columns: Column[] };\n\n constructor(\n public name: string,\n public schema: { name: string },\n ) {}\n\n get m2oRelations(): M2ORelation[] {\n return this.foreignKeys.map((fk) => new M2ORelation(fk));\n }\n\n get o2mRelations(): O2MRelation[] {\n return this.foreignKeysToThis.map((fk) => new O2MRelation(fk));\n }\n\n get m2mRelations(): M2MRelation[] {\n return this.foreignKeysToThis.flatMap((fk) =>\n fk.table.foreignKeys.filter((other) => other !== fk).map((other) => new M2MRelation(fk, other)),\n );\n }\n}\n\nexport type Db = { tables: Items<Table>; types: EnumType[] };\n\n/**\n * Reads only the PostgreSQL catalog data used by codegen.\n *\n * Load tables and columns before constraints so each foreign key can link the same Table and Column\n * objects used by entity fields. For example, books.author_id and authors.id are joined through\n * the foreign key, while a trigger calling cyanaudit in another schema is irrelevant to codegen.\n */\nexport async function loadPgMetadata(client: Client): Promise<Db> {\n const tables = new Items<Table>();\n const byOid = new Map<number, Table>();\n const columns = new Map<number, Map<number, Column>>();\n\n const tableRows = await client.query<{ oid: number; name: string; schema: string }>(`\n SELECT c.oid, c.relname AS name, n.nspname AS schema\n FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.relkind IN ('r', 'p') AND n.nspname <> 'information_schema'\n AND n.nspname NOT LIKE 'pg_%' AND NOT pg_is_other_temp_schema(n.oid)\n ORDER BY n.nspname, c.relname\n `);\n for (const row of tableRows.rows) {\n const table = new Table(row.name, { name: row.schema });\n tables.push(table);\n byOid.set(row.oid, table);\n columns.set(row.oid, new Map());\n }\n\n const enumRows = await client.query<{ oid: number; name: string; value: string }>(`\n SELECT t.oid, t.typname AS name, e.enumlabel AS value\n FROM pg_type t JOIN pg_enum e ON e.enumtypid = t.oid\n JOIN pg_namespace n ON n.oid = t.typnamespace\n WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg_%'\n ORDER BY t.oid, e.enumsortorder\n `);\n const enums = new Map<number, EnumType>();\n for (const row of enumRows.rows) {\n if (!enums.has(row.oid)) enums.set(row.oid, new EnumType(row.name, []));\n enums.get(row.oid)!.values.push(row.value);\n }\n\n const columnRows = await client.query<{\n tableOid: number;\n position: number;\n name: string;\n typeOid: number;\n typeName: string;\n notNull: boolean;\n defaultValue: string | null;\n generated: string;\n arrayDimension: number;\n comment: string | null;\n }>(\n `\n SELECT a.attrelid AS \"tableOid\", a.attnum AS position, a.attname AS name,\n COALESCE(NULLIF(t.typelem, 0), t.oid) AS \"typeOid\",\n base.typname AS \"typeName\", a.attnotnull AS \"notNull\",\n pg_get_expr(d.adbin, d.adrelid) AS \"defaultValue\", a.attgenerated AS generated,\n a.attndims AS \"arrayDimension\", col_description(a.attrelid, a.attnum) AS comment\n FROM pg_attribute a JOIN pg_type t ON t.oid = a.atttypid\n JOIN pg_type base ON base.oid = COALESCE(NULLIF(t.typelem, 0), t.oid)\n LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum\n WHERE a.attrelid = ANY($1::oid[]) AND a.attnum > 0 AND NOT a.attisdropped\n ORDER BY a.attrelid, a.attnum\n `,\n [Array.from(byOid.keys())],\n );\n for (const row of columnRows.rows) {\n const type = enums.get(row.typeOid) ?? sqlType(row.typeName);\n const column = new Column(\n row.name,\n type,\n row.notNull,\n row.defaultValue,\n row.generated !== \"\",\n row.arrayDimension,\n row.comment,\n );\n byOid.get(row.tableOid)!.columns.push(column);\n columns.get(row.tableOid)!.set(row.position, column);\n }\n\n const indexRows = await client.query<{\n tableOid: number;\n indexOid: number;\n name: string;\n positions: number[];\n unique: boolean;\n partial: boolean;\n }>(\n `\n SELECT i.indrelid AS \"tableOid\", i.indexrelid AS \"indexOid\", c.relname AS name,\n i.indkey::int2[] AS positions, i.indisunique AS unique, i.indpred IS NOT NULL AS partial\n FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid\n WHERE i.indrelid = ANY($1::oid[])\n `,\n [Array.from(byOid.keys())],\n );\n const indexes = new Map<number, Index>();\n for (const row of indexRows.rows) {\n const table = byOid.get(row.tableOid)!;\n const index = new Index(\n row.name,\n row.positions.flatMap((pos) => columns.get(row.tableOid)!.get(pos) ?? []),\n row.unique,\n row.partial,\n );\n table.indexes.push(index);\n indexes.set(row.indexOid, index);\n if (row.unique) for (const column of index.columns) column.uniqueIndexes.push(index);\n }\n\n const constraintRows = await client.query<{\n tableOid: number;\n targetOid: number;\n indexOid: number;\n name: string;\n kind: string;\n positions: number[] | null;\n deferred: boolean;\n deferrable: boolean;\n onDelete: string;\n }>(\n `\n SELECT conrelid AS \"tableOid\", confrelid AS \"targetOid\", conindid AS \"indexOid\",\n conname AS name, contype AS kind, conkey AS positions,\n condeferred AS deferred, condeferrable AS deferrable, confdeltype AS \"onDelete\"\n FROM pg_constraint WHERE conrelid = ANY($1::oid[]) AND contype IN ('p', 'u', 'f')\n ORDER BY conrelid, conname\n `,\n [Array.from(byOid.keys())],\n );\n for (const row of constraintRows.rows) {\n const table = byOid.get(row.tableOid)!;\n const keys = (row.positions ?? []).map((pos) => columns.get(row.tableOid)!.get(pos)!);\n if (row.kind === \"p\") {\n table.primaryKey = { columns: keys };\n for (const column of keys) column.isPrimaryKey = true;\n } else if (row.kind === \"u\") {\n table.uniqueConstraints.push({ columns: keys, index: indexes.get(row.indexOid)! });\n } else {\n const target = byOid.get(row.targetOid);\n if (!target) continue;\n const fk = new ForeignKey(\n row.name,\n table,\n target,\n keys,\n row.deferred,\n row.deferrable,\n deleteAction(row.onDelete),\n );\n table.foreignKeys.push(fk);\n target.foreignKeysToThis.push(fk);\n for (const column of keys) column.foreignKeys.push(fk);\n }\n }\n\n return { tables, types: Array.from(enums.values()) };\n}\n\n/** Matches the type names codegen expects from PostgreSQL's builtin aliases. */\nfunction sqlType(name: string): { name: string; shortName?: string } {\n switch (name) {\n case \"int2\":\n return { name: \"smallint\" };\n case \"int4\":\n return { name: \"integer\", shortName: \"int\" };\n case \"int8\":\n return { name: \"bigint\" };\n case \"bool\":\n return { name: \"boolean\" };\n case \"float4\":\n return { name: \"real\" };\n case \"float8\":\n return { name: \"double precision\" };\n case \"varchar\":\n return { name: \"character varying\" };\n case \"timestamptz\":\n return { name: \"timestamp with time zone\" };\n case \"timestamp\":\n return { name: \"timestamp without time zone\" };\n case \"time\":\n return { name: \"time without time zone\" };\n default:\n // Foreign-key serdes need shortName even for types with no PostgreSQL alias, such as uuid.\n return { name, shortName: name };\n }\n}\n\n/** Decodes the pg_constraint foreign-key delete action. */\nfunction deleteAction(code: string): Action {\n switch (code) {\n case \"a\":\n return \"NO ACTION\";\n case \"r\":\n return \"RESTRICT\";\n case \"c\":\n return \"CASCADE\";\n case \"n\":\n return \"SET NULL\";\n case \"d\":\n return \"SET DEFAULT\";\n default:\n throw new Error(`Unknown PostgreSQL delete action: ${code}`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Client } from "pg";
|
|
2
|
+
//#region src/pgMetadata.d.ts
|
|
3
|
+
/** The small part of PostgreSQL's catalog model used by Joist codegen. */
|
|
4
|
+
declare class Items<T extends {
|
|
5
|
+
name: string;
|
|
6
|
+
}> extends Array<T> {
|
|
7
|
+
private byName?;
|
|
8
|
+
/** Looks up a column by name without scanning the table on every call. */
|
|
9
|
+
get(name: string): T;
|
|
10
|
+
/** Keep the name index current if more catalog rows arrive after a lookup. */
|
|
11
|
+
push(...items: T[]): number;
|
|
12
|
+
}
|
|
13
|
+
type Action = "NO ACTION" | "RESTRICT" | "CASCADE" | "SET NULL" | "SET DEFAULT";
|
|
14
|
+
type JSONData = string | number | boolean | null | JSONData[] | {
|
|
15
|
+
[key: string]: JSONData;
|
|
16
|
+
};
|
|
17
|
+
declare class EnumType {
|
|
18
|
+
name: string;
|
|
19
|
+
values: string[];
|
|
20
|
+
readonly shortName?: string;
|
|
21
|
+
constructor(name: string, values: string[]);
|
|
22
|
+
}
|
|
23
|
+
declare class Column {
|
|
24
|
+
name: string;
|
|
25
|
+
type: {
|
|
26
|
+
name: string;
|
|
27
|
+
shortName?: string;
|
|
28
|
+
} | EnumType;
|
|
29
|
+
notNull: boolean;
|
|
30
|
+
defaultValue: string | null;
|
|
31
|
+
isGenerated: boolean;
|
|
32
|
+
arrayDimension: number;
|
|
33
|
+
comment: string | null;
|
|
34
|
+
readonly foreignKeys: ForeignKey[];
|
|
35
|
+
readonly uniqueIndexes: Index[];
|
|
36
|
+
isPrimaryKey: boolean;
|
|
37
|
+
constructor(name: string, type: {
|
|
38
|
+
name: string;
|
|
39
|
+
shortName?: string;
|
|
40
|
+
} | EnumType, notNull: boolean, defaultValue: string | null, isGenerated: boolean, arrayDimension: number, comment: string | null);
|
|
41
|
+
get isForeignKey(): boolean;
|
|
42
|
+
get default(): string | null;
|
|
43
|
+
get commentData(): JSONData | undefined;
|
|
44
|
+
}
|
|
45
|
+
declare class Index {
|
|
46
|
+
name: string;
|
|
47
|
+
columns: Column[];
|
|
48
|
+
isUnique: boolean;
|
|
49
|
+
isPartial: boolean;
|
|
50
|
+
constructor(name: string, columns: Column[], isUnique: boolean, isPartial: boolean);
|
|
51
|
+
}
|
|
52
|
+
declare class ForeignKey {
|
|
53
|
+
name: string;
|
|
54
|
+
table: Table;
|
|
55
|
+
referencedTable: Table;
|
|
56
|
+
columns: Column[];
|
|
57
|
+
isDeferred: boolean;
|
|
58
|
+
isDeferrable: boolean;
|
|
59
|
+
onDelete: Action;
|
|
60
|
+
constructor(name: string, table: Table, referencedTable: Table, columns: Column[], isDeferred: boolean, isDeferrable: boolean, onDelete: Action);
|
|
61
|
+
}
|
|
62
|
+
declare class M2ORelation {
|
|
63
|
+
foreignKey: ForeignKey;
|
|
64
|
+
readonly type = "m2o";
|
|
65
|
+
constructor(foreignKey: ForeignKey);
|
|
66
|
+
get sourceTable(): Table;
|
|
67
|
+
get targetTable(): Table;
|
|
68
|
+
}
|
|
69
|
+
declare class O2MRelation {
|
|
70
|
+
foreignKey: ForeignKey;
|
|
71
|
+
readonly type = "o2m";
|
|
72
|
+
constructor(foreignKey: ForeignKey);
|
|
73
|
+
get sourceTable(): Table;
|
|
74
|
+
get targetTable(): Table;
|
|
75
|
+
}
|
|
76
|
+
declare class M2MRelation {
|
|
77
|
+
foreignKey: ForeignKey;
|
|
78
|
+
targetForeignKey: ForeignKey;
|
|
79
|
+
readonly type = "m2m";
|
|
80
|
+
constructor(foreignKey: ForeignKey, targetForeignKey: ForeignKey);
|
|
81
|
+
get sourceTable(): Table;
|
|
82
|
+
get targetTable(): Table;
|
|
83
|
+
get joinTable(): Table;
|
|
84
|
+
}
|
|
85
|
+
declare class Table {
|
|
86
|
+
name: string;
|
|
87
|
+
schema: {
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
readonly columns: Items<Column>;
|
|
91
|
+
readonly indexes: Items<Index>;
|
|
92
|
+
readonly foreignKeys: ForeignKey[];
|
|
93
|
+
readonly foreignKeysToThis: ForeignKey[];
|
|
94
|
+
readonly uniqueConstraints: {
|
|
95
|
+
columns: Column[];
|
|
96
|
+
index: Index;
|
|
97
|
+
}[];
|
|
98
|
+
primaryKey?: {
|
|
99
|
+
columns: Column[];
|
|
100
|
+
};
|
|
101
|
+
constructor(name: string, schema: {
|
|
102
|
+
name: string;
|
|
103
|
+
});
|
|
104
|
+
get m2oRelations(): M2ORelation[];
|
|
105
|
+
get o2mRelations(): O2MRelation[];
|
|
106
|
+
get m2mRelations(): M2MRelation[];
|
|
107
|
+
}
|
|
108
|
+
type Db = {
|
|
109
|
+
tables: Items<Table>;
|
|
110
|
+
types: EnumType[];
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Reads only the PostgreSQL catalog data used by codegen.
|
|
114
|
+
*
|
|
115
|
+
* Load tables and columns before constraints so each foreign key can link the same Table and Column
|
|
116
|
+
* objects used by entity fields. For example, books.author_id and authors.id are joined through
|
|
117
|
+
* the foreign key, while a trigger calling cyanaudit in another schema is irrelevant to codegen.
|
|
118
|
+
*/
|
|
119
|
+
declare function loadPgMetadata(client: Client): Promise<Db>;
|
|
120
|
+
//#endregion
|
|
121
|
+
export { Action, Column, Db, EnumType, ForeignKey, Index, Items, JSONData, M2MRelation, M2ORelation, O2MRelation, Table, loadPgMetadata };
|
|
122
|
+
//# sourceMappingURL=pgMetadata.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pgMetadata.d.cts","names":[],"sources":["../src/pgMetadata.ts"],"mappings":";;;cAGa,MAAM;EAAY;WAAwB,MAAM;UACnD;;EAGR,IAAI,eAAe;;EAUnB,QAAQ,OAAO;;KAOL;KACA,8CAA8C;GAAgB,cAAc;;cAE3E;EAGF;EACA;WAHA;EACT,YACS,cACA;;cAIE;EAMF;EACA;IAAQ;IAAc;MAAuB;EAC7C;EACA;EACA;EACA;EACA;WAXA,aAAa;WACb,eAAe;EACxB;EAEA,YACS,cACA;IAAQ;IAAc;MAAuB,UAC7C,kBACA,6BACA,sBACA,wBACA;MAGL;MAIA;MAIA,eAAe;;cAMR;EAEF;EACA,SAAS;EACT;EACA;EAJT,YACS,cACA,SAAS,UACT,mBACA;;cAIE;EAEF;EACA,OAAO;EACP,iBAAiB;EACjB,SAAS;EACT;EACA;EACA,UAAU;EAPnB,YACS,cACA,OAAO,OACP,iBAAiB,OACjB,SAAS,UACT,qBACA,uBACA,UAAU;;cAIR;EAGQ,YAAY;WAFtB;EAET,YAAmB,YAAY;MAE3B,eAAe;MAIf,eAAe;;cAKR;EAGQ,YAAY;WAFtB;EAET,YAAmB,YAAY;MAE3B,eAAe;MAIf,eAAe;;cAKR;EAIF,YAAY;EACZ,kBAAkB;WAJlB;EAET,YACS,YAAY,YACZ,kBAAkB;MAGvB,eAAe;MAIf,eAAe;MAIf,aAAa;;cAKN;EASF;EACA;IAAU;;WATV,SAAO,MAAA;WACP,SAAO,MAAA;WACP,aAAa;WACb,mBAAmB;WACnB;IAAqB,SAAS;IAAU,OAAO;;EACxD;IAAe,SAAS;;EAExB,YACS,cACA;IAAU;;MAGf,gBAAgB;MAIhB,gBAAgB;MAIhB,gBAAgB;;KAOV;EAAO,QAAQ,MAAM;EAAQ,OAAO;;;;;;;;;iBAS1B,eAAe,QAAQ,SAAS,QAAQ"}
|