Last active
July 20, 2023 12:00
-
-
Save virtuallyunknown/8168802b352897a263b55b12287d4a25 to your computer and use it in GitHub Desktop.
Kanel + Kysely
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { join, relative, sep } from "node:path"; | |
import { recase } from '@kristiandupont/recase'; | |
import kanel from 'kanel'; | |
import kanelKysely from 'kanel-kysely'; | |
const toCamelCase = recase('snake', 'camel'); | |
const toPascalCase = recase('snake', 'pascal'); | |
export function trimWhitespaceHook(path, lines, instantiatedConfig) { | |
return lines.filter((line, index, array) => { | |
if (line === '' && array[index - 1].startsWith(' ')) { | |
return; | |
} | |
return line === '' ? '\n' : line; | |
}); | |
} | |
export function convertESMPathsHook(path, lines, instantiatedConfig) { | |
return lines.map(line => line.replace(/^import\stype\s.*'(.*)';$/, (match, p1) => { | |
return /\sfrom\s'kysely';$/.test(match) | |
? match | |
: match.replace(p1, `${p1}.js`) | |
})) | |
} | |
function getKyselyItemMetadata(details, selectorName, canInitialize, canMutate) { | |
const typeNames = generateTypeNames({ name: details.name, type: 'table' }) | |
return ({ | |
tableInterfaceName: typeNames.tableInterfaceName, | |
selectableName: typeNames.selectableName, | |
insertableName: canInitialize ? typeNames.insertableName : undefined, | |
updatableName: canMutate ? typeNames.updatableName : undefined, | |
}) | |
} | |
function generateTypeNames({ name, type = null } = {}) { | |
const pascalName = toPascalCase(name); | |
if (type === 'table') { | |
return { | |
tableInterfaceName: `DB${pascalName}`, | |
selectableName: `DB${pascalName}Selectable`, | |
insertableName: `DB${pascalName}Insertable`, | |
updatableName: `DB${pascalName}Updateable` | |
} | |
} | |
else if (type === 'identifier') { | |
return { | |
identifierName: `DB${pascalName}` | |
} | |
} | |
} | |
function generateIndexHook(outputAcc, instantiatedConfig) { | |
const allEntities = Object.values(instantiatedConfig.schemas).reduce((acc, elem) => { | |
const entitiesInSchema = Object.values(elem) | |
.filter(Array.isArray) | |
.reduce((acc2, elem2) => [...acc2, ...elem2], []); | |
return [...acc, ...entitiesInSchema]; | |
}, []); | |
const lines = allEntities.map((details) => { | |
let result; | |
const { path } = instantiatedConfig.getMetadata(details, "selector", instantiatedConfig); | |
let importPath = relative(instantiatedConfig.outputPath, path); | |
if (sep === "\\") { | |
importPath = importPath.replace(/\\/g, "/"); | |
} | |
if (details.kind === "table") { | |
const { tableInterfaceName, selectableName, insertableName, updatableName } = generateTypeNames({ name: details.name, type: 'table' }); | |
const additionalImports = [selectableName, insertableName, updatableName]; | |
if (instantiatedConfig.generateIdentifierType) { | |
const identifierColumns = details.columns.filter((column) => column.isPrimaryKey && !column.reference); | |
identifierColumns.forEach((column) => { | |
const { identifierName } = generateTypeNames({ name: `${details.name}_${column.name}`, type: 'identifier' }); | |
additionalImports.push(identifierName); | |
}); | |
} | |
result = `export type { default as ${tableInterfaceName}, ${additionalImports.join(", ")} } from './${importPath}.js';`; | |
} | |
else if (details.kind === 'view' || details.kind === 'materializedView') { | |
const { identifierName } = generateTypeNames({ name: details.name, type: 'identifier' }); | |
result = `export type { default as ${identifierName} } from './${importPath}.js';`; | |
} | |
else if (details.kind === "enum") { | |
const { identifierName } = generateTypeNames({ name: details.name, type: 'identifier' }); | |
const prefix = instantiatedConfig.enumStyle === "type" ? "type " : ""; | |
result = `export ${prefix}{ default as ${identifierName} } from './${importPath}.js';`; | |
} | |
else { | |
console.log(details.kind); | |
const { name } = instantiatedConfig.getMetadata(details, "selector", instantiatedConfig); | |
result = `export type { default as ${name} } from './${importPath}.js';`; | |
} | |
return result; | |
}); | |
const indexFile = { | |
declarations: [ | |
{ | |
declarationType: "generic", | |
lines, | |
}, | |
], | |
}; | |
const path = join(instantiatedConfig.outputPath, "db-types"); | |
return { | |
...outputAcc, | |
[path]: indexFile, | |
}; | |
} | |
async function processDatabase() { | |
await kanel.processDatabase({ | |
connection: { | |
// credentials | |
}, | |
outputPath: 'src/types', | |
resolveViews: true, | |
preDeleteOutputFolder: true, | |
enumStyle: 'type', | |
preRenderHooks: [kanelKysely.makeKyselyHook(({ databaseFilename: 'db', getKyselyItemMetadata })), generateIndexHook], | |
postRenderHooks: [trimWhitespaceHook, convertESMPathsHook], | |
getMetadata: (details, generateFor, instantiatedConfig) => { | |
const suffix = ['selector', 'initializer', 'mutator'].includes(generateFor) && details.kind !== 'enum' | |
? `_${generateFor}` | |
: ''; | |
return { | |
name: toPascalCase(`${details.name}${suffix}`), | |
comment: [`generateFor: ${generateFor} | details.name: ${details.name} | details.kind: ${details.kind}`], | |
path: join(instantiatedConfig.outputPath, 'db', toPascalCase(details.name)), | |
}; | |
}, | |
getPropertyMetadata: (property, details, generateFor) => { | |
return { | |
name: toCamelCase(property.name), | |
} | |
}, | |
generateIdentifierType: (column, details, config) => { | |
const { identifierName } = generateTypeNames({ name: `${details.name}_${column.name}`, type: 'identifier' }) | |
const innerType = kanel.resolveType(column, details, { | |
...config, | |
generateIdentifierType: undefined, | |
}); | |
return { | |
declarationType: 'typeDeclaration', | |
name: identifierName, | |
exportAs: 'named', | |
typeDefinition: [innerType], | |
} | |
} | |
}); | |
} | |
await processDatabase(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment