a homebrewed DnD campaign based in the Honkai: Star Rail universe
hsr honkaistarrail dnd
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

db: +`InferUpdateModel<T>` type, +public ID column to some tables

+393 -91
+4 -3
app/src/lib/server/background/background-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, ··· 22 23 export type BackgroundTable = typeof background 23 24 export type Background = InferSelectModel<BackgroundTable> 24 25 export type NewBackground = InferInsertModel<BackgroundTable> 25 - export type UpdateBackground = Partial<Omit<NewBackground, 'id'>> 26 + export type UpdateBackground = InferUpdateModel<BackgroundTable, 'publicId'> 26 27 export const zBackgroundSchema = zDrizzleSchema(background) 27 28 28 29 export type BackgroundAbilityTable = typeof backgroundAbility 29 30 export type BackgroundAbility = InferSelectModel<BackgroundAbilityTable> 30 31 export type NewBackgroundAbility = InferInsertModel<BackgroundAbilityTable> 31 - export type UpdateBackgroundAbility = Partial<Omit<NewBackgroundAbility, 'id'>> 32 + export type UpdateBackgroundAbility = InferUpdateModel<BackgroundAbilityTable> 32 33 export const zBackgroundAbilitySchema = zDrizzleSchema(backgroundAbility) 33 34 34 35 export type BackgroundSkillProficiencyTable = typeof backgroundSkillProficiency 35 36 export type BackgroundSkillProficiency = InferSelectModel<BackgroundSkillProficiencyTable> 36 37 export type NewBackgroundSkillProficiency = InferInsertModel<BackgroundSkillProficiencyTable> 37 - export type UpdateBackgroundSkillProficiency = Partial<NewBackgroundSkillProficiency> 38 + export type UpdateBackgroundSkillProficiency = InferUpdateModel<BackgroundSkillProficiencyTable> 38 39 export const zBackgroundSkillProficiencySchema = zDrizzleSchema(backgroundSkillProficiency) 39 40 40 41 export class BackgroundRepository implements RepositoryCountable {
+2
app/src/lib/server/background/db-schema.ts
··· 1 1 import { index, integer, pgTable, primaryKey, text } from 'drizzle-orm/pg-core' 2 + import { publicId } from '$lib/unique-id' 2 3 import { lower } from '$server/db/expressions' 3 4 import { equipment, tool } from '$server/equipment/db-schema' 4 5 import { feat } from '$server/feat/db-schema' ··· 9 10 'background', 10 11 { 11 12 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 13 + publicId: publicId(), 12 14 name: text('name').notNull(), 13 15 description: text('description').notNull(), 14 16 sourcebookId: integer('sourcebook_id')
+3 -3
app/src/lib/server/background/equipment-package-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource } from '$server/db/types' 2 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 3 3 import { background, equipmentPackage, equipmentPackageItem } from '$server/background/db-schema' 4 4 import { querySingle } from '$server/db/expressions' 5 5 import { zDrizzleSchema } from '$server/db/standard-schema' ··· 7 7 export type EquipmentPackageTable = typeof equipmentPackage 8 8 export type EquipmentPackage = InferSelectModel<EquipmentPackageTable> 9 9 export type NewEquipmentPackage = InferInsertModel<EquipmentPackageTable> 10 - export type UpdateEquipmentPackage = Partial<NewEquipmentPackage> 10 + export type UpdateEquipmentPackage = InferUpdateModel<EquipmentPackageTable> 11 11 export const zEquipmentPackageSchema = zDrizzleSchema(equipmentPackage) 12 12 13 13 export type EquipmentPackageItemTable = typeof equipmentPackageItem 14 14 export type EquipmentPackageItem = InferSelectModel<EquipmentPackageItemTable> 15 15 export type NewEquipmentPackageItem = InferInsertModel<EquipmentPackageItemTable> 16 - export type UpdateEquipmentPackageItem = Partial<NewEquipmentPackageItem> 16 + export type UpdateEquipmentPackageItem = InferUpdateModel<EquipmentPackageItemTable> 17 17 export const zEquipmentPackageItemSchema = zDrizzleSchema(equipmentPackageItem) 18 18 19 19 export class EquipmentPackageRepository {
+3 -3
app/src/lib/server/campaign/campaign-member-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { UserId } from '$server/auth/auth-repo' 3 3 import type { Campaign } from '$server/campaign/campaign-repo' 4 - import type { DatabaseSource } from '$server/db/types' 4 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 5 5 import { user } from '$server/auth/db-schema' 6 6 import { campaign, campaignMember } from '$server/campaign/db-schema' 7 7 import { zDrizzleSchema } from '$server/db/standard-schema' ··· 9 9 export type CampaignMemberTable = typeof campaignMember 10 10 export type CampaignMember = InferSelectModel<CampaignMemberTable> 11 11 export type NewCampaignMember = InferInsertModel<CampaignMemberTable> 12 - export type UpdateCampaignMember = Partial<Omit<NewCampaignMember, 'id' | 'invitedAt'>> 12 + export type UpdateCampaignMember = InferUpdateModel<CampaignMemberTable, 'invitedAt'> 13 13 export const zCampaignMemberSchema = zDrizzleSchema(campaignMember) 14 14 15 15 export class CampaignMemberRepository { ··· 19 19 } 20 20 21 21 public async addCampaignMember(model: NewCampaignMember) { 22 - return await this.db.insert(campaignMember).values(model) 22 + return await this.db.insert(campaignMember).values(model).onConflictDoNothing() 23 23 } 24 24 25 25 public async addManyCampaignMembers(options: {
+42 -27
app/src/lib/server/campaign/campaign-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, TableColumnNames, UpdateQueryParams } from '$server/db/types' 2 + import type { 3 + DatabaseSource, 4 + InferUpdateModel, 5 + TableColumnNames, 6 + UpdateQueryParams, 7 + } from '$server/db/types' 3 8 import { user } from '$server/auth/db-schema' 4 9 import { campaign, campaignSession, campaignSourceMaterial } from '$server/campaign/db-schema' 5 10 import { querySingle } from '$server/db/expressions' 6 11 import { selectColumns } from '$server/db/queries' 7 12 import { zDrizzleSchema } from '$server/db/standard-schema' 8 - import { sourcebook } from '$server/sourcebook/db-schema' 13 + import type { NewCampaignSourceMaterial } from './campaign-source-material-repo' 14 + import { CampaignMemberRepository, type CampaignMember } from './campaign-member-repo' 9 15 10 16 export type CampaignTable = typeof campaign 11 17 export type Campaign = InferSelectModel<CampaignTable> 12 18 export type NewCampaign = InferInsertModel<CampaignTable> 13 - export type UpdateCampaign = Partial<Omit<NewCampaign, 'id' | 'createdAt'>> 19 + export type UpdateCampaign = InferUpdateModel<CampaignTable, 'createdAt' | 'publicId'> 14 20 export const zCampaignSchema = zDrizzleSchema(campaign) 15 21 16 22 export type CampaignSessionTable = typeof campaignSession 17 23 export type CampaignSession = InferSelectModel<CampaignSessionTable> 18 24 export type NewCampaignSession = InferInsertModel<CampaignSessionTable> 19 - export type UpdateCampaignSession = Partial<Omit<NewCampaignSession, 'id' | 'createdAt'>> 25 + export type UpdateCampaignSession = InferUpdateModel<CampaignSessionTable, 'createdAt'> 20 26 export const zCampaignSessionSchema = zDrizzleSchema(campaignSession) 21 27 22 28 export class CampaignRepository { ··· 25 31 this.db = db 26 32 } 27 33 28 - public async createCampaign(model: NewCampaign) { 29 - return await this.db.insert(campaign).values(model) 34 + public async createCampaign(params: { 35 + model: NewCampaign 36 + sourceMaterials: Omit<NewCampaignSourceMaterial, 'campaignId'>[] 37 + members: Omit<CampaignMember, 'campaignId'>[] 38 + }) { 39 + return await this.db.transaction(async (tx) => { 40 + const { model, sourceMaterials, members } = params 41 + const newCampaign = await tx 42 + .insert(campaign) 43 + .values(model) 44 + .returning({ id: campaign.id }) 45 + .then(querySingle) 46 + 47 + if (newCampaign === null) { 48 + tx.rollback() 49 + return 50 + } 51 + 52 + const campaignId = newCampaign.id 53 + await tx.insert(campaignSourceMaterial).values( 54 + sourceMaterials.map((source) => ({ 55 + campaignId, 56 + ...source, 57 + })), 58 + ) 59 + 60 + const memberRepo = new CampaignMemberRepository(tx) 61 + await memberRepo.addManyCampaignMembers({ 62 + campaignId, 63 + models: members, 64 + }) 65 + }) 30 66 } 31 67 32 68 public async deleteCampaignById(id: Campaign['id']) { ··· 59 95 .where(eq(campaign.ownerId, ownerId)) 60 96 } 61 97 62 - public async getCampaignOwner() { 63 - return await this.db 64 - .select({ 65 - owner: user, 66 - }) 67 - .from(campaign) 68 - .leftJoin(user, eq(user.id, campaign.ownerId)) 69 - .then(querySingle) 70 - } 71 - 72 98 public async getCampaignsBy<C extends TableColumnNames<CampaignTable>>( 73 99 columns?: Record<C, true>, 74 100 ) { ··· 78 104 public async updateCampaign(params: UpdateQueryParams<Campaign['id'], UpdateCampaign>) { 79 105 const { id, model } = params 80 106 return await this.db.update(campaign).set(model).where(eq(campaign.id, id)) 81 - } 82 - 83 - /* campaign source material table */ 84 - public async getCampaignSourceMaterials(campaignId: Campaign['id']) { 85 - return await this.db 86 - .select({ 87 - sourceMaterial: campaignSourceMaterial, 88 - }) 89 - .from(campaignSourceMaterial) 90 - .leftJoin(sourcebook, eq(sourcebook.id, campaignSourceMaterial.sourcebookId)) 91 - .where(eq(campaign.id, campaignId)) 92 107 } 93 108 }
+29
app/src/lib/server/campaign/campaign-source-material-repo.ts
··· 1 + import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 3 + import { campaign, campaignSourceMaterial } from '$server/campaign/db-schema' 4 + import { zDrizzleSchema } from '$server/db/standard-schema' 5 + import { sourcebook } from '$server/sourcebook/db-schema' 6 + import type { Campaign } from './campaign-repo' 7 + 8 + export type CampaignSourceMaterialTable = typeof campaignSourceMaterial 9 + export type CampaignSourceMaterial = InferSelectModel<CampaignSourceMaterialTable> 10 + export type NewCampaignSourceMaterial = InferInsertModel<CampaignSourceMaterialTable> 11 + export type UpdateCampaignSourceMaterial = InferUpdateModel<CampaignSourceMaterialTable> 12 + export const zCampaignSourceMaterialSchema = zDrizzleSchema(campaignSourceMaterial) 13 + 14 + export class CampaignSourceMaterialRepository { 15 + private db: DatabaseSource 16 + public constructor(db: DatabaseSource) { 17 + this.db = db 18 + } 19 + 20 + public async getCampaignSourceMaterials(campaignId: Campaign['id']) { 21 + return await this.db 22 + .select({ 23 + sourceMaterial: campaignSourceMaterial, 24 + }) 25 + .from(campaignSourceMaterial) 26 + .leftJoin(sourcebook, eq(sourcebook.id, campaignSourceMaterial.sourcebookId)) 27 + .where(eq(campaign.id, campaignId)) 28 + } 29 + }
+2
app/src/lib/server/campaign/db-schema.ts
··· 1 1 import { boolean, index, integer, pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core' 2 + import { publicId } from '$lib/unique-id' 2 3 import { user } from '$server/auth/db-schema' 3 4 import { lower } from '$server/db/expressions' 4 5 import { sourcebook } from '$server/sourcebook/db-schema' ··· 7 8 'campaign', 8 9 { 9 10 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 11 + publicId: publicId(), 10 12 ownerId: text('owner_id') 11 13 .notNull() 12 14 .references(() => user.id),
+5 -5
app/src/lib/server/character/character-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { UserId } from '$server/auth/auth-repo' 3 - import type { DatabaseSource, UpdateQueryParams } from '$server/db/types' 3 + import type { DatabaseSource, InferUpdateModel, UpdateQueryParams } from '$server/db/types' 4 4 import { user } from '$server/auth/db-schema' 5 5 import { background } from '$server/background/db-schema' 6 6 import { ··· 19 19 export type CharacterTable = typeof character 20 20 export type Character = InferSelectModel<CharacterTable> 21 21 export type NewCharacter = InferInsertModel<CharacterTable> 22 - export type UpdateCharacter = Partial<Omit<NewCharacter, 'id' | 'createdAt'>> 22 + export type UpdateCharacter = InferUpdateModel<CharacterTable, 'publicId' | 'createdAt'> 23 23 export const zCharacterSchema = zDrizzleSchema(character) 24 24 25 25 export type CharacterInventoryTable = typeof characterInventory 26 26 export type CharacterInventory = InferSelectModel<CharacterInventoryTable> 27 27 export type NewCharacterInventory = InferInsertModel<CharacterInventoryTable> 28 - export type UpdateCharacterInventory = Partial<NewCharacterInventory> 28 + export type UpdateCharacterInventory = InferUpdateModel<CharacterInventoryTable> 29 29 export const zCharacterInventorySchema = zDrizzleSchema(characterInventory) 30 30 31 31 export type CharacterLanguageTable = typeof characterLanguage 32 32 export type CharacterLanguage = InferSelectModel<CharacterLanguageTable> 33 33 export type NewCharacterLanguage = InferInsertModel<CharacterLanguageTable> 34 - export type UpdateCharacterLanguage = Partial<NewCharacterLanguage> 34 + export type UpdateCharacterLanguage = InferUpdateModel<CharacterLanguageTable> 35 35 export const zCharacterLanguageSchema = zDrizzleSchema(characterLanguage) 36 36 37 37 export type CharacterSpellTable = typeof characterSpell 38 38 export type CharacterSpell = InferSelectModel<CharacterSpellTable> 39 39 export type NewCharacterSpell = InferInsertModel<CharacterSpellTable> 40 - export type UpdateCharacterSpell = Partial<NewCharacterSpell> 40 + export type UpdateCharacterSpell = InferUpdateModel<CharacterSpellTable> 41 41 export const zCharacterSpellSchema = zDrizzleSchema(characterSpell) 42 42 43 43 // query results
+6 -6
app/src/lib/server/character/character-status-repo.ts
··· 1 1 import { and, eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { Character } from '$server/character/character-repo' 3 - import type { DatabaseSource } from '$server/db/types' 3 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 4 4 import { 5 5 characterCondition, 6 6 characterConditionImmunity, ··· 14 14 export type CharacterConditionTable = typeof characterCondition 15 15 export type CharacterCondition = InferSelectModel<CharacterConditionTable> 16 16 export type NewCharacterCondition = InferInsertModel<CharacterConditionTable> 17 - export type UpdateCharacterCondition = Partial<Omit<NewCharacterCondition, 'id'>> 17 + export type UpdateCharacterCondition = InferUpdateModel<CharacterConditionTable> 18 18 export const zCharacterConditionSchema = zDrizzleSchema(characterCondition) 19 19 20 20 export type CharacterConditionImmunityTable = typeof characterConditionImmunity 21 21 export type CharacterConditionImmunity = InferSelectModel<CharacterConditionImmunityTable> 22 22 export type NewCharacterConditionImmunity = InferInsertModel<CharacterConditionImmunityTable> 23 - export type UpdateCharacterConditionImmunity = Partial<NewCharacterConditionImmunity> 23 + export type UpdateCharacterConditionImmunity = InferUpdateModel<CharacterConditionImmunityTable> 24 24 export const zCharacterConditionImmunitySchema = zDrizzleSchema(characterConditionImmunity) 25 25 26 26 export type CharacterResistanceTable = typeof characterDmgResistance 27 27 export type CharacterResistance = InferSelectModel<CharacterResistanceTable> 28 28 export type NewCharacterResistance = InferInsertModel<CharacterResistanceTable> 29 - export type UpdateCharacterResistance = Partial<NewCharacterResistance> 29 + export type UpdateCharacterResistance = InferUpdateModel<CharacterResistanceTable> 30 30 export const zCharacterResistanceSchema = zDrizzleSchema(characterDmgResistance) 31 31 32 32 export type CharacterVulnerabilityTable = typeof characterDmgVulnerability 33 33 export type CharacterVulnerability = InferSelectModel<CharacterVulnerabilityTable> 34 34 export type NewCharacterVulnerability = InferInsertModel<CharacterVulnerabilityTable> 35 - export type UpdateCharacterVulnerability = Partial<NewCharacterVulnerability> 35 + export type UpdateCharacterVulnerability = InferUpdateModel<CharacterVulnerabilityTable> 36 36 export const zCharacterVulnerabilitySchema = zDrizzleSchema(characterDmgVulnerability) 37 37 38 38 export type CharacterImmunityTable = typeof characterDmgImmunity 39 39 export type CharacterImmunity = InferSelectModel<CharacterImmunityTable> 40 40 export type NewCharacterImmunity = InferInsertModel<CharacterImmunityTable> 41 - export type UpdateCharacterImmunity = Partial<NewCharacterImmunity> 41 + export type UpdateCharacterImmunity = InferUpdateModel<CharacterImmunityTable> 42 42 export const zCharacterImmunitySchema = zDrizzleSchema(characterDmgImmunity) 43 43 44 44 export class CharacterStatusRepository {
+2
app/src/lib/server/character/db-schema.ts
··· 10 10 timestamp, 11 11 varchar, 12 12 } from 'drizzle-orm/pg-core' 13 + import { publicId } from '$lib/unique-id' 13 14 import { user } from '$server/auth/db-schema' 14 15 import { background } from '$server/background/db-schema' 15 16 import { campaign } from '$server/campaign/db-schema' ··· 25 26 'character', 26 27 { 27 28 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 29 + publicId: publicId(), 28 30 ownerId: varchar('owner_id', { length: 36 }) 29 31 .notNull() 30 32 .references(() => user.id),
+3 -3
app/src/lib/server/class/class-feature-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, RepositoryCountable } from '$server/db/types' 2 + import type { DatabaseSource, InferUpdateModel, RepositoryCountable } from '$server/db/types' 3 3 import { 4 4 characterClass, 5 5 characterClassFeature, ··· 11 11 export type ClassFeatureTable = typeof characterClassFeature 12 12 export type ClassFeature = InferSelectModel<ClassFeatureTable> 13 13 export type NewClassFeature = InferInsertModel<ClassFeatureTable> 14 - export type UpdateClassFeature = Partial<NewClassFeature> 14 + export type UpdateClassFeature = InferUpdateModel<ClassFeatureModTable> 15 15 export const zClassFeatureSchema = zDrizzleSchema(characterClassFeature) 16 16 17 17 export type ClassFeatureModTable = typeof characterClassFeatureMod 18 18 export type ClassFeatureMod = InferSelectModel<ClassFeatureModTable> 19 19 export type NewClassFeatureMod = InferInsertModel<ClassFeatureModTable> 20 - export type UpdateClassFeatureMod = Partial<NewClassFeatureMod> 20 + export type UpdateClassFeatureMod = InferUpdateModel<ClassFeatureModTable> 21 21 export const zClassFeatureModSchema = zDrizzleSchema(characterClassFeatureMod) 22 22 23 23 export class ClassFeatureRepository implements RepositoryCountable {
+5 -4
app/src/lib/server/class/class-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, 7 8 } from '$server/db/types' 8 9 import { characterClass } from '$server/class/db-schema' 9 - import { querySingle } from '$server/db/expressions' 10 + import { asArray, querySingle } from '$server/db/expressions' 10 11 import { selectColumns } from '$server/db/queries' 11 12 import { zDrizzleSchema } from '$server/db/standard-schema' 12 13 import { sourcebook } from '$server/sourcebook/db-schema' ··· 14 15 export type ClassTable = typeof characterClass 15 16 export type Class = InferSelectModel<ClassTable> 16 17 export type NewClass = InferInsertModel<ClassTable> 17 - export type UpdateClass = Partial<NewClass> 18 + export type UpdateClass = InferUpdateModel<ClassTable, 'publicId' | 'createdAt'> 18 19 export const zClassSchema = zDrizzleSchema(characterClass) 19 20 20 21 export class ClassRepository implements RepositoryCountable { ··· 23 24 this.db = db 24 25 } 25 26 26 - public async createClass(model: NewClass) { 27 - return await this.db.insert(characterClass).values(model).onConflictDoNothing() 27 + public async createClass(model: NewClass | NewClass[]) { 28 + return await this.db.insert(characterClass).values(asArray(model)).onConflictDoNothing() 28 29 } 29 30 30 31 public async deleteClassById(id: Class['id']) {
+2
app/src/lib/server/class/db-schema.ts
··· 9 9 text, 10 10 timestamp, 11 11 } from 'drizzle-orm/pg-core' 12 + import { publicId } from '$lib/unique-id' 12 13 import { lower } from '$server/db/expressions' 13 14 import { sourcebook } from '$server/sourcebook/db-schema' 14 15 ··· 16 17 'character_class', 17 18 { 18 19 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 20 + publicId: publicId(), 19 21 sourcebookId: integer('sourcebook_id') 20 22 .notNull() 21 23 .references(() => sourcebook.id, { onDelete: 'cascade' }),
+4
app/src/lib/server/db/expressions.ts
··· 24 24 export function querySingle<T>(results: T[]): T | null { 25 25 return results.at(0) ?? null 26 26 } 27 + 28 + export function asArray<T>(items: T | T[]): T[] { 29 + return Array.isArray(items) ? items : [items] 30 + }
+9
app/src/lib/server/db/types.ts
··· 17 17 */ 18 18 export type DatabaseSource = DatabaseConn | DatabaseTransaction 19 19 20 + /** 21 + * Infer the updatable-type of a column, synonymous to Drizzle's 22 + * built-in `InferSelectModel<T>` and `InferInsertModel<T>` types 23 + */ 24 + export type InferUpdateModel< 25 + TTable extends Table, 26 + TOmitColumns extends keyof InferInsertModel<TTable> = never, 27 + > = Partial<Omit<InferInsertModel<TTable>, TOmitColumns>> 28 + 20 29 export type UpdateQueryParams<Id, Model> = { 21 30 id: Id 22 31 model: Model
+7 -2
app/src/lib/server/equipment/armor-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, RepositoryCountable, UpdateQueryParams } from '$server/db/types' 2 + import type { 3 + DatabaseSource, 4 + InferUpdateModel, 5 + RepositoryCountable, 6 + UpdateQueryParams, 7 + } from '$server/db/types' 3 8 import { querySingle } from '$server/db/expressions' 4 9 import { armor, equipment } from '$server/equipment/db-schema' 5 10 import { EquipmentRepository, type NewEquipment } from '$server/equipment/equipment-repo' ··· 7 12 export type ArmorTable = typeof armor 8 13 export type Armor = InferSelectModel<ArmorTable> 9 14 export type NewArmor = InferInsertModel<ArmorTable> 10 - export type UpdateArmor = Partial<NewArmor> 15 + export type UpdateArmor = InferUpdateModel<ArmorTable> 11 16 12 17 export class ArmorRepository implements RepositoryCountable { 13 18 private db: DatabaseSource
+2
app/src/lib/server/equipment/db-schema.ts
··· 11 11 text, 12 12 timestamp, 13 13 } from 'drizzle-orm/pg-core' 14 + import { publicId } from '$lib/unique-id' 14 15 import { lower } from '$server/db/expressions' 15 16 import { ability } from '$server/mechanic/db-schema' 16 17 import { sourcebook } from '$server/sourcebook/db-schema' ··· 19 20 'equipment', 20 21 { 21 22 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 23 + publicId: publicId(), 22 24 sourcebookId: integer('sourcebook_id') 23 25 .notNull() 24 26 .references(() => sourcebook.id),
+2 -1
app/src/lib/server/equipment/equipment-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, ··· 14 15 export type EquipmentTable = typeof equipment 15 16 export type Equipment = InferSelectModel<EquipmentTable> 16 17 export type NewEquipment = InferInsertModel<EquipmentTable> 17 - export type UpdateEquipment = Partial<Omit<NewEquipment, 'createdAt'>> 18 + export type UpdateEquipment = InferUpdateModel<EquipmentTable, 'publicId' | 'createdAt'> 18 19 export const zEquipmentSchema = zDrizzleSchema(equipment) 19 20 20 21 export class EquipmentRepository implements RepositoryCountable {
+7 -2
app/src/lib/server/equipment/tool-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, RepositoryCountable, UpdateQueryParams } from '$server/db/types' 2 + import type { 3 + DatabaseSource, 4 + InferUpdateModel, 5 + RepositoryCountable, 6 + UpdateQueryParams, 7 + } from '$server/db/types' 3 8 import { querySingle } from '$server/db/expressions' 4 9 import { zDrizzleSchema } from '$server/db/standard-schema' 5 10 import { equipment, tool } from '$server/equipment/db-schema' ··· 9 14 export type ToolTable = typeof tool 10 15 export type Tool = InferSelectModel<ToolTable> 11 16 export type NewTool = InferInsertModel<ToolTable> 12 - export type UpdateTool = Partial<NewTool> 17 + export type UpdateTool = InferUpdateModel<ToolTable> 13 18 export const zToolSchema = zDrizzleSchema(tool) 14 19 15 20 export class ToolRepository implements RepositoryCountable {
+2
app/src/lib/server/faction/db-schema.ts
··· 9 9 text, 10 10 timestamp, 11 11 } from 'drizzle-orm/pg-core' 12 + import { publicId } from '$lib/unique-id' 12 13 import { sourcebook } from '$server/sourcebook/db-schema' 13 14 14 15 export const faction = pgTable( 15 16 'faction', 16 17 { 17 18 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 19 + publicId: publicId(), 18 20 parentFactionId: integer('parent_faction_id'), 19 21 sourcebookId: integer('sourcebook_id') 20 22 .notNull()
+3 -2
app/src/lib/server/faction/faction-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, ··· 14 15 export type FactionTable = typeof faction 15 16 export type Faction = InferSelectModel<FactionTable> 16 17 export type NewFaction = InferInsertModel<FactionTable> 17 - export type UpdateFaction = Partial<NewFaction> 18 + export type UpdateFaction = InferUpdateModel<FactionTable, 'publicId'> 18 19 export const zFactionSchema = zDrizzleSchema(faction) 19 20 20 21 export class FactionRepository implements RepositoryCountable { ··· 24 25 } 25 26 26 27 public async createFaction(model: NewFaction) { 27 - return await this.db.insert(faction).values(model) 28 + return await this.db.insert(faction).values(model).onConflictDoNothing() 28 29 } 29 30 30 31 public async deleteFactionById(id: Faction['id']) {
+2
app/src/lib/server/feat/db-schema.ts
··· 1 1 import { boolean, index, integer, pgTable, text } from 'drizzle-orm/pg-core' 2 + import { publicId } from '$lib/unique-id' 2 3 import { lower } from '$server/db/expressions' 3 4 4 5 export const feat = pgTable( 5 6 'feat', 6 7 { 7 8 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 9 + publicId: publicId(), 8 10 name: text('name').notNull(), 9 11 description: text('description').notNull(), 10 12 isRepeatable: boolean('is_repeatable').default(false).notNull(),
+2 -1
app/src/lib/server/feat/feat-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, ··· 13 14 export type FeatTable = typeof feat 14 15 export type Feat = InferSelectModel<FeatTable> 15 16 export type NewFeat = InferInsertModel<FeatTable> 16 - export type UpdateFeat = Partial<NewFeat> 17 + export type UpdateFeat = InferUpdateModel<FeatTable, 'publicId'> 17 18 export const zFeatSchema = zDrizzleSchema(feat) 18 19 19 20 export class FeatRepository implements RepositoryCountable {
+2 -1
app/src/lib/server/language/language-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 RepositoryCountableWithSourceBook, 6 7 UpdateQueryParams, ··· 14 15 export type LanguageTable = typeof language 15 16 export type Language = InferSelectModel<LanguageTable> 16 17 export type NewLanguage = InferInsertModel<LanguageTable> 17 - export type UpdateLanguage = Partial<NewLanguage> 18 + export type UpdateLanguage = InferUpdateModel<LanguageTable> 18 19 export const zLanguageSchema = zDrizzleSchema(language) 19 20 20 21 export class LanguageRepository implements RepositoryCountable, RepositoryCountableWithSourceBook {
+89
app/src/lib/server/mechanic/ability-skill-repo.ts
··· 1 + import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 3 + import { querySingle } from '$server/db/expressions' 4 + import { ability, skill } from './db-schema' 5 + 6 + export type AbilityTable = typeof ability 7 + export type Ability = InferSelectModel<AbilityTable> 8 + export type NewAbility = InferInsertModel<AbilityTable> 9 + export type UpdateAbility = InferUpdateModel<AbilityTable> 10 + 11 + export type SkillTable = typeof skill 12 + export type Skill = InferSelectModel<SkillTable> 13 + export type NewSkill = InferInsertModel<SkillTable> 14 + export type UpdateSkill = InferUpdateModel<SkillTable> 15 + 16 + export type CreateAbilityParams = { 17 + model: NewAbility 18 + skills: Omit<NewSkill, 'abilityId'>[] 19 + } 20 + export type CreateAbilityResult = { 21 + ability: Ability 22 + skills: Skill[] 23 + } | null 24 + 25 + export class AbilityRepository { 26 + private db: DatabaseSource 27 + public constructor(db: DatabaseSource) { 28 + this.db = db 29 + } 30 + 31 + public async deleteAbilityById(id: Ability['id']) { 32 + return await this.db.delete(ability).where(eq(ability.id, id)) 33 + } 34 + 35 + public async createAbility(params: CreateAbilityParams): Promise<CreateAbilityResult | null> { 36 + return await this.db.transaction( 37 + async (tx) => { 38 + const { model, skills } = params 39 + const newAbility = await tx.insert(ability).values(model).returning().then(querySingle) 40 + 41 + if (newAbility === null) { 42 + tx.rollback() 43 + return null 44 + } 45 + 46 + const abilityId = newAbility.id 47 + const newSkills = await tx 48 + .insert(skill) 49 + .values( 50 + skills.map((s) => ({ 51 + abilityId, 52 + ...s, 53 + })), 54 + ) 55 + .returning() 56 + 57 + return { 58 + ability: newAbility, 59 + skills: newSkills, 60 + } 61 + }, 62 + { 63 + accessMode: 'read write', 64 + deferrable: true, 65 + }, 66 + ) 67 + } 68 + 69 + public async getAbilityById(id: Ability['id']): Promise<Ability | null> { 70 + return await this.db.select().from(ability).where(eq(ability.id, id)).then(querySingle) 71 + } 72 + 73 + public async deleteSkillById(id: Skill['id']) { 74 + return await this.db.delete(skill).where(eq(skill.id, id)) 75 + } 76 + 77 + public async getSkillById(id: Skill['id']): Promise<{ 78 + ability: Ability | null 79 + skill: Skill 80 + } | null> { 81 + return await this.db 82 + .select() 83 + .from(skill) 84 + .leftJoin(ability, eq(ability.id, skill.abilityId)) 85 + .where(eq(skill.id, id)) 86 + .limit(1) 87 + .then(querySingle) 88 + } 89 + }
+8 -3
app/src/lib/server/mechanic/condition-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, RepositoryCountable, UpdateQueryParams } from '$server/db/types' 2 + import type { 3 + DatabaseSource, 4 + InferUpdateModel, 5 + RepositoryCountable, 6 + UpdateQueryParams, 7 + } from '$server/db/types' 3 8 import { querySingle } from '$server/db/expressions' 4 9 import { zDrizzleSchema } from '$server/db/standard-schema' 5 10 import { condition } from '$server/mechanic/db-schema' ··· 8 13 export type ConditionTable = typeof condition 9 14 export type Condition = InferSelectModel<ConditionTable> 10 15 export type NewCondition = InferInsertModel<ConditionTable> 11 - export type UpdateCondition = Partial<NewCondition> 16 + export type UpdateCondition = InferUpdateModel<ConditionTable> 12 17 export const zConditionSchema = zDrizzleSchema(condition) 13 18 14 19 export class ConditionRepository implements RepositoryCountable { ··· 18 23 } 19 24 20 25 public async createCondition(model: NewCondition) { 21 - return await this.db.insert(condition).values(model) 26 + return await this.db.insert(condition).values(model).onConflictDoNothing() 22 27 } 23 28 24 29 public async deleteConditionById(id: Condition['id']) {
+8 -3
app/src/lib/server/mechanic/damage-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, RepositoryCountable, UpdateQueryParams } from '$server/db/types' 2 + import type { 3 + DatabaseSource, 4 + InferUpdateModel, 5 + RepositoryCountable, 6 + UpdateQueryParams, 7 + } from '$server/db/types' 3 8 import { querySingle } from '$server/db/expressions' 4 9 import { zDrizzleSchema } from '$server/db/standard-schema' 5 10 import { damage } from '$server/mechanic/db-schema' ··· 8 13 export type DamageTable = typeof damage 9 14 export type Damage = InferSelectModel<DamageTable> 10 15 export type NewDamage = InferInsertModel<DamageTable> 11 - export type UpdateDamage = Partial<NewDamage> 16 + export type UpdateDamage = InferUpdateModel<DamageTable> 12 17 export const zDamageTable = zDrizzleSchema(damage) 13 18 14 19 export class DamageRepository implements RepositoryCountable { ··· 18 23 } 19 24 20 25 public async createDamage(model: Damage) { 21 - return await this.db.insert(damage).values(model) 26 + return await this.db.insert(damage).values(model).onConflictDoNothing() 22 27 } 23 28 24 29 public async deleteDamageById(id: Damage['id']) {
+2
app/src/lib/server/monster/db-schema.ts
··· 9 9 smallint, 10 10 text, 11 11 } from 'drizzle-orm/pg-core' 12 + import { publicId } from '$lib/unique-id' 12 13 import { campaignSession } from '$server/campaign/db-schema' 13 14 import { lower } from '$server/db/expressions' 14 15 import { damage } from '$server/mechanic/db-schema' ··· 18 19 'monster', 19 20 { 20 21 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 22 + publicId: publicId(), 21 23 sourcebookId: integer('sourcebook_id') 22 24 .notNull() 23 25 .references(() => sourcebook.id),
+7 -7
app/src/lib/server/monster/monster-defense-repo.ts
··· 1 1 import type { InferSelectModel, InferInsertModel } from 'drizzle-orm' 2 - import type { DatabaseSource } from '$server/db/types' 2 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 3 3 import type { Monster } from '$server/monster/monster-repo' 4 4 import { zDrizzleSchema } from '$server/db/standard-schema' 5 5 import { monsterDmgImmunity, monsterDmgResistance, monsterDmgVulnerability } from './db-schema' ··· 7 7 export type MonsterResistanceTable = typeof monsterDmgResistance 8 8 export type MonsterResistance = InferSelectModel<MonsterResistanceTable> 9 9 export type NewMonsterResistance = InferInsertModel<MonsterResistanceTable> 10 - export type UpdateMonsterResistance = Partial<NewMonsterResistance> 10 + export type UpdateMonsterResistance = InferUpdateModel<MonsterResistanceTable> 11 11 export const zMonsterResistanceSchema = zDrizzleSchema(monsterDmgResistance) 12 12 13 13 export type MonsterImmunityTable = typeof monsterDmgImmunity 14 14 export type MonsterImmunity = InferSelectModel<MonsterImmunityTable> 15 15 export type NewMonsterImmunity = InferInsertModel<MonsterImmunityTable> 16 - export type UpdateMonsterImmunity = Partial<NewMonsterImmunity> 16 + export type UpdateMonsterImmunity = InferUpdateModel<MonsterImmunityTable> 17 17 export const zMonsterImmunitySchema = zDrizzleSchema(monsterDmgImmunity) 18 18 19 - export type monsterVulnerabilityTable = typeof monsterDmgVulnerability 20 - export type monsterVulnerability = InferSelectModel<monsterVulnerabilityTable> 21 - export type NewMonsterVulnerability = InferInsertModel<monsterVulnerabilityTable> 22 - export type UpdateMonsterVulnerability = Partial<NewMonsterVulnerability> 19 + export type MonsterVulnerabilityTable = typeof monsterDmgVulnerability 20 + export type MonsterVulnerability = InferSelectModel<MonsterVulnerabilityTable> 21 + export type NewMonsterVulnerability = InferInsertModel<MonsterVulnerabilityTable> 22 + export type UpdateMonsterVulnerability = InferUpdateModel<MonsterVulnerabilityTable> 23 23 export const zMonsterVulnerabilitySchema = zDrizzleSchema(monsterDmgVulnerability) 24 24 25 25 export class MonsterDefenseRepository {
+3 -3
app/src/lib/server/monster/monster-instance-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, UpdateQueryParams } from '$server/db/types' 2 + import type { DatabaseSource, InferUpdateModel, UpdateQueryParams } from '$server/db/types' 3 3 import { querySingle } from '$server/db/expressions' 4 4 import { zDrizzleSchema } from '$server/db/standard-schema' 5 5 import { monster, monsterInstance } from '$server/monster/db-schema' ··· 7 7 export type MonsterInstanceTable = typeof monsterInstance 8 8 export type MonsterInstance = InferSelectModel<MonsterInstanceTable> 9 9 export type NewMonsterInstance = InferInsertModel<MonsterInstanceTable> 10 - export type UpdateMonsterInstance = Partial<NewMonsterInstance> 10 + export type UpdateMonsterInstance = InferUpdateModel<MonsterInstanceTable> 11 11 export const zMonsterInstanceSchema = zDrizzleSchema(monsterInstance) 12 12 13 13 export class MonsterInstanceRepository { ··· 17 17 } 18 18 19 19 public async createMonsterInstance(model: NewMonsterInstance) { 20 - return await this.db.insert(monsterInstance).values(model) 20 + return await this.db.insert(monsterInstance).values(model).onConflictDoNothing() 21 21 } 22 22 23 23 public async deleteMonsterInstanceById(id: MonsterInstance['id']) {
+3 -2
app/src/lib/server/monster/monster-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 RepositoryCountableWithSourceBook, 6 7 TableColumnNames, ··· 27 28 export type MonsterTable = typeof monster 28 29 export type Monster = InferSelectModel<MonsterTable> 29 30 export type NewMonster = InferInsertModel<MonsterTable> 30 - export type UpdateMonster = Partial<NewMonster> 31 + export type UpdateMonster = InferUpdateModel<MonsterTable> 31 32 export const zMonsterSchema = zDrizzleSchema(monster) 32 33 33 34 export type MonsterAbilityTable = typeof monsterAbility 34 35 export type MonsterAbility = InferSelectModel<MonsterAbilityTable> 35 36 export type NewMonsterAbility = InferInsertModel<MonsterAbilityTable> 36 - export type UpdateMonsterAbility = Partial<NewMonsterAbility> 37 + export type UpdateMonsterAbility = InferUpdateModel<MonsterAbilityTable> 37 38 export const zMonsterAbilitySchema = zDrizzleSchema(monsterAbility) 38 39 39 40 export class MonsterRepository implements RepositoryCountable, RepositoryCountableWithSourceBook {
+2
app/src/lib/server/readable/db-schema.ts
··· 1 1 import { index, integer, pgTable, text } from 'drizzle-orm/pg-core' 2 + import { publicId } from '$lib/unique-id' 2 3 import { lower } from '$server/db/expressions' 3 4 import { faction } from '$server/faction/db-schema' 4 5 import { sourcebook } from '$server/sourcebook/db-schema' ··· 7 8 'readable', 8 9 { 9 10 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 11 + publicId: publicId(), 10 12 sourcebookId: integer('sourcebook_id') 11 13 .notNull() 12 14 .references(() => sourcebook.id),
+8 -3
app/src/lib/server/readable/readable-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 - import type { DatabaseSource, RepositoryCountable, UpdateQueryParams } from '$server/db/types' 2 + import type { 3 + DatabaseSource, 4 + InferUpdateModel, 5 + RepositoryCountable, 6 + UpdateQueryParams, 7 + } from '$server/db/types' 3 8 import { querySingle } from '$server/db/expressions' 4 9 import { zDrizzleSchema } from '$server/db/standard-schema' 5 10 import { faction } from '$server/faction/db-schema' ··· 9 14 export type ReadableTable = typeof readable 10 15 export type Readable = InferSelectModel<ReadableTable> 11 16 export type NewReadable = InferInsertModel<ReadableTable> 12 - export type UpdateReadable = Partial<NewReadable> 17 + export type UpdateReadable = InferUpdateModel<ReadableTable, 'publicId'> 13 18 export const zReadableSchema = zDrizzleSchema(readable) 14 19 15 20 export class ReadableRepository implements RepositoryCountable { ··· 23 28 } 24 29 25 30 public async createReadable(model: NewReadable) { 26 - return await this.db.insert(readable).values(model) 31 + return await this.db.insert(readable).values(model).onConflictDoNothing() 27 32 } 28 33 29 34 public async getReadableById(id: Readable['id']) {
+3 -1
app/src/lib/server/sourcebook/db-schema.ts
··· 1 1 import { index, integer, pgTable, timestamp, text, primaryKey } from 'drizzle-orm/pg-core' 2 + import { publicId } from '$lib/unique-id' 2 3 import { lower } from '$server/db/expressions' 3 4 4 5 export const sourcebook = pgTable( 5 6 'sourcebook', 6 7 { 7 8 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 9 + publicId: publicId(), 8 10 name: text('name').unique().notNull(), 9 11 description: text('description').notNull(), 10 12 createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), ··· 15 17 16 18 export const genre = pgTable('genre', { 17 19 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 18 - name: text('name').notNull(), 20 + name: text('name').unique().notNull(), 19 21 description: text('description').notNull(), 20 22 }) 21 23
+3 -2
app/src/lib/server/sourcebook/sourcebook-repo.ts
··· 1 1 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, ··· 13 14 export type SourcebookTable = typeof sourcebook 14 15 export type Sourcebook = InferSelectModel<SourcebookTable> 15 16 export type NewSourcebook = InferInsertModel<SourcebookTable> 16 - export type UpdateSourcebook = Partial<Omit<NewSourcebook, 'createdAt'>> 17 + export type UpdateSourcebook = InferUpdateModel<SourcebookTable, 'publicId' | 'createdAt'> 17 18 export const zSourcebookSchema = zDrizzleSchema(sourcebook) 18 19 19 20 export class SourcebookRepository implements RepositoryCountable { ··· 23 24 } 24 25 25 26 public async createSourcebook(model: NewSourcebook) { 26 - return await this.db.insert(sourcebook).values(model) 27 + return await this.db.insert(sourcebook).values(model).onConflictDoNothing() 27 28 } 28 29 29 30 public async deleteSourcebookById(id: Sourcebook['id']) {
+2
app/src/lib/server/species/db-schema.ts
··· 10 10 text, 11 11 check, 12 12 } from 'drizzle-orm/pg-core' 13 + import { publicId } from '$lib/unique-id' 13 14 import { lower } from '$server/db/expressions' 14 15 import { sourcebook } from '$server/sourcebook/db-schema' 15 16 ··· 17 18 'species', 18 19 { 19 20 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 21 + publicId: publicId(), 20 22 sourcebookId: integer('sourcebook_id') 21 23 .notNull() 22 24 .references(() => sourcebook.id, { onDelete: 'cascade' }),
+54
app/src/lib/server/species/species-feature-repo.ts
··· 1 + import type { InferInsertModel, InferSelectModel } from 'drizzle-orm' 2 + import type { DatabaseSource, InferUpdateModel } from '$server/db/types' 3 + import { querySingle } from '$server/db/expressions' 4 + import { speciesFeature, speciesFeatureMod } from './db-schema' 5 + 6 + export type SpeciesFeatureTable = typeof speciesFeature 7 + export type SpeciesFeature = InferSelectModel<SpeciesFeatureTable> 8 + export type NewSpeciesFeature = InferInsertModel<SpeciesFeatureTable> 9 + export type UpdateSpeciesFeature = InferUpdateModel<SpeciesFeatureTable> 10 + 11 + export type SpeciesFeatureModTable = typeof speciesFeatureMod 12 + export type SpeciesFeatureMod = InferSelectModel<SpeciesFeatureModTable> 13 + export type NewSpeciesFeatureMod = InferInsertModel<SpeciesFeatureModTable> 14 + export type UpdateSpeciesFeatureMod = InferUpdateModel<SpeciesFeatureModTable> 15 + 16 + export class SpeciesFeatureRepository { 17 + private db: DatabaseSource 18 + public constructor(db: DatabaseSource) { 19 + this.db = db 20 + } 21 + 22 + public async createSpeciesFeature(params: { 23 + model: SpeciesFeature 24 + mods: Omit<NewSpeciesFeatureMod, 'speciesFeatureId'>[] 25 + }) { 26 + return await this.db.transaction( 27 + async (tx) => { 28 + const { model, mods } = params 29 + const newFeature = await tx 30 + .insert(speciesFeature) 31 + .values(model) 32 + .returning({ id: speciesFeature.id }) 33 + .then(querySingle) 34 + 35 + if (newFeature === null) { 36 + tx.rollback() 37 + return 38 + } 39 + 40 + const speciesFeatureId = newFeature.id 41 + await tx.insert(speciesFeatureMod).values( 42 + mods.map((mod) => ({ 43 + speciesFeatureId, 44 + ...mod, 45 + })), 46 + ) 47 + }, 48 + { 49 + accessMode: 'read write', 50 + deferrable: true, 51 + }, 52 + ) 53 + } 54 + }
+3 -2
app/src/lib/server/species/species-repo.ts
··· 1 1 import { eq, type InferSelectModel } from 'drizzle-orm' 2 2 import type { 3 3 DatabaseSource, 4 + InferUpdateModel, 4 5 RepositoryCountable, 5 6 TableColumnNames, 6 7 UpdateQueryParams, ··· 14 15 export type SpeciesTable = typeof species 15 16 export type Species = InferSelectModel<SpeciesTable> 16 17 export type NewSpecies = InferSelectModel<SpeciesTable> 17 - export type UpdateSpecies = Partial<Omit<NewSpecies, 'id' | 'createdAt'>> 18 + export type UpdateSpecies = InferUpdateModel<SpeciesTable, 'publicId' | 'createdAt'> 18 19 export const zSpeciesSchema = zDrizzleSchema(species) 19 20 20 21 export class SpeciesRepository implements RepositoryCountable { ··· 24 25 } 25 26 26 27 public async createSpecies(model: NewSpecies) { 27 - return await this.db.insert(species).values(model) 28 + return await this.db.insert(species).values(model).onConflictDoNothing() 28 29 } 29 30 30 31 public async deleteSpeciesById(id: Species['id']) {
+2
app/src/lib/server/spell/db-schema.ts
··· 11 11 check, 12 12 pgEnum, 13 13 } from 'drizzle-orm/pg-core' 14 + import { publicId } from '$lib/unique-id' 14 15 import { lower } from '$server/db/expressions' 15 16 import { damage } from '$server/mechanic/db-schema' 16 17 import { sourcebook } from '$server/sourcebook/db-schema' ··· 30 31 'spell', 31 32 { 32 33 id: integer('id').primaryKey().generatedAlwaysAsIdentity(), 34 + publicId: publicId(), 33 35 sourcebookId: integer('sourcebook_id') 34 36 .notNull() 35 37 .references(() => sourcebook.id),
+3 -2
app/src/lib/server/spell/spell-repo.ts
··· 2 2 import { eq, type InferInsertModel, type InferSelectModel } from 'drizzle-orm' 3 3 import type { 4 4 DatabaseSource, 5 + InferUpdateModel, 5 6 RepositoryCountable, 6 7 TableColumnNames, 7 8 UpdateQueryParams, ··· 20 21 export type NewSpell = Omit<InferInsertModel<SpellTable>, 'duration'> & { 21 22 duration: SpellDuration 22 23 } 23 - export type UpdateSpell = Partial<Omit<NewSpell, 'createdAt' | 'duration'>> & { 24 + export type UpdateSpell = InferUpdateModel<SpellTable, 'createdAt'> & { 24 25 duration?: SpellDuration 25 26 } 26 27 export const zSpellSchema = zDrizzleSchema(spell) ··· 32 33 } 33 34 34 35 public async createSpell(values: NewSpell) { 35 - return await this.db.insert(spell).values(values).returning() 36 + return await this.db.insert(spell).values(values).onConflictDoNothing() 36 37 } 37 38 38 39 public async deleteSpellById(id: Spell['id']) {
+40
app/src/lib/unique-id.ts
··· 1 + import { text } from 'drizzle-orm/pg-core' 2 + import { customAlphabet } from 'nanoid' 3 + import { z } from 'zod/v4' 4 + import type { BrandedType } from './utils' 5 + 6 + const publicIdAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz' 7 + const publicIdLength = 10 8 + const nanoid = customAlphabet(publicIdAlphabet, publicIdLength) 9 + 10 + /** 11 + * A zod schema for verifying a unique, public ID 12 + * (via `nanoid` with some customizations, hence not using 13 + * zod's built-in `z.nanoid()` API) 14 + */ 15 + export const zPublicId = z.string().regex(new RegExp(`^[${publicIdAlphabet}]{${publicId}}$`)) 16 + 17 + export type BrandedInternalId<TBrand extends string> = BrandedType<number, TBrand> 18 + export type BrandedPublicId = BrandedType<z.infer<typeof zPublicId>, 'publicId'> 19 + 20 + /** 21 + * A Postgres column with a unique, non-null `text` data type. 22 + * Intended as public-facing and readable IDs in URLs. 23 + * @see https://planetscale.com/blog/why-we-chose-nanoids-for-planetscales-api 24 + */ 25 + export function publicId(name: string = 'public_id') { 26 + return text(name) 27 + .$defaultFn(() => nanoid()) 28 + .unique() 29 + .notNull() 30 + } 31 + 32 + /** 33 + * A human-readable ID in a URL, following the pattern 34 + * of either `[id]` or `[id]-[string]`. 35 + */ 36 + export function resolvePublicId(urlId: string): string { 37 + const hyphenIdx = urlId.indexOf('-') 38 + const normalizedId = hyphenIdx === -1 ? urlId : urlId.slice(0, hyphenIdx) 39 + return normalizedId 40 + }
+3
app/src/lib/utils.ts
··· 20 20 return twMerge(clsx(inputs)) 21 21 } 22 22 23 + declare const __brand: unique symbol 24 + export type BrandedType<T, TBrand extends string> = T & { readonly [__brand]: TBrand } 25 + 23 26 // eslint-disable-next-line @typescript-eslint/no-explicit-any 24 27 export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T 25 28 // eslint-disable-next-line @typescript-eslint/no-explicit-any