Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/dev-notes/2025-10-22/fix-type-errors/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# TypeScript 5.9 型推論の理解と実装

## 概要

TypeScript 5.9 の型の厳格化により、`prisma/seed.ts` のメソッド引数に型注釈が必要になった。関数パラメータに対して正しい型を推論し、適用した。

## Q&A 要約

### Q1: `(typeof users)[number]` とは?

**A:** 配列型 `users` から要素型を抽出する型操作。

- `typeof users` → 配列型そのもの
- `[number]` → 任意の数値インデックスでアクセスした時の型(全ての要素の共通型)

### Q2: `[number]` は配列のインデックスか?

**A:** 配列の「最初の要素」ではなく、「任意のインデックスでアクセスした結果の型」を意味する型操作。全インデックスで同じ型が返される。

### Q3: `ReturnType<typeof hoge>` とは?

**A:** 関数 `hoge` の戻り値の型を自動抽出する型操作。複雑なファクトリー関数の戻り値型を手動定義せずに取得できる。

## 実装結果

**すべての関数パラメータの型注釈を追加し、関数引数の型推論エラーを完全に解消した。**

```typescript
// addUser 関数
async function addUser(
user: (typeof users)[number],
password: string,
userFactory: ReturnType<typeof defineUserFactory>,
keyFactory: ReturnType<typeof defineKeyFactory>,
);

// addTask 関数
async function addTask(
task: (typeof tasks)[number],
taskFactory: ReturnType<typeof defineTaskFactory>,
);

// addWorkBook 関数
async function addWorkBook(
workbook: (typeof workbooks)[number],
workBookFactory: ReturnType<typeof defineWorkBookFactory>,
);

// addTag 関数
async function addTag(tag: (typeof tags)[number], tagFactory: ReturnType<typeof defineTagFactory>);

// addTaskTag 関数
async function addTaskTag(
task_tag: (typeof task_tags)[number],
taskTagFactory: ReturnType<typeof defineTaskTagFactory>,
);

// addSubmissionStatus 関数
async function addSubmissionStatus(
submission_status: (typeof submission_statuses)[number],
submissionStatusFactory: ReturnType<typeof defineSubmissionStatusFactory>,
);

// addAnswer 関数
async function addAnswer(
answer: (typeof answers)[number],
taskAnswerFactory: ReturnType<typeof defineTaskAnswerFactory>,
);
```

## 教訓

1. **型インデックスアクセス**: `Type[KeyType]` で型から値を抽出できる
2. **配列要素型の抽出**: `(typeof array)[number]` でシンプルに要素型を取得
3. **関数戻り値型の自動抽出**: `ReturnType<typeof fn>` で複雑な型定義を避けられる
4. **TypeScript 5.9 の型安全性**: 関数パラメータの暗黙的 `any` を禁止することで、デバッグ時の型ミスマッチを防げる
38 changes: 30 additions & 8 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
import PQueue from 'p-queue';
import { generateLuciaPasswordHash } from 'lucia/utils';

import { getTaskGrade } from '../src/lib/types/task';

import { classifyContest } from '../src/lib/utils/contest';

import { users, USER_PASSWORD_FOR_SEED } from './users';
Expand Down Expand Up @@ -104,7 +106,12 @@ async function addUsers() {

// See:
// https://lucia-auth.com/reference/lucia/modules/utils/#generateluciapasswordhash
async function addUser(user, password: string, userFactory, keyFactory) {
async function addUser(
user: (typeof users)[number],
password: string,
userFactory: ReturnType<typeof defineUserFactory>,
keyFactory: ReturnType<typeof defineKeyFactory>,
) {
const currentUser = await userFactory.createForConnect({
id: user.id,
username: user.name,
Expand Down Expand Up @@ -150,15 +157,18 @@ async function addTasks() {
console.log('Finished adding tasks.');
}

async function addTask(task, taskFactory) {
async function addTask(
task: (typeof tasks)[number],
taskFactory: ReturnType<typeof defineTaskFactory>,
) {
// Note: Task-Tag relationships are handled separately via TaskTag table
await taskFactory.create({
contest_type: classifyContest(task.contest_id),
contest_id: task.contest_id,
task_table_index: task.problem_index,
task_id: task.id,
title: task.title,
grade: task.grade,
grade: getTaskGrade(task.grade as string),
});
}

Expand Down Expand Up @@ -223,7 +233,10 @@ async function addWorkBooks() {
console.log('Finished adding workbooks.');
}

async function addWorkBook(workbook, workBookFactory) {
async function addWorkBook(
workbook: (typeof workbooks)[number],
workBookFactory: ReturnType<typeof defineWorkBookFactory>,
) {
const urlSlug = normalizeUrlSlug(workbook.urlSlug);

await workBookFactory.create({
Expand Down Expand Up @@ -293,7 +306,7 @@ async function addTags() {
console.log('Finished adding tags.');
}

async function addTag(tag, tagFactory) {
async function addTag(tag: (typeof tags)[number], tagFactory: ReturnType<typeof defineTagFactory>) {
// Note: Tags and Tasks are connected via the TaskTag relationship table
// which is handled separately in addTaskTags()
await tagFactory.create({
Expand Down Expand Up @@ -353,7 +366,10 @@ async function addTaskTags() {
await taskTagQueue.onIdle(); // Wait for all task tags to complete
console.log('Finished adding task tags.');
}
async function addTaskTag(task_tag, taskTagFactory) {
async function addTaskTag(
task_tag: (typeof task_tags)[number],
taskTagFactory: ReturnType<typeof defineTaskTagFactory>,
) {
await taskTagFactory.create({
id: task_tag.id,
priority: task_tag.priority,
Expand Down Expand Up @@ -398,7 +414,10 @@ async function addSubmissionStatuses() {
console.log('Finished adding submission statuses.');
}

async function addSubmissionStatus(submission_status, submissionStatusFactory) {
async function addSubmissionStatus(
submission_status: (typeof submission_statuses)[number],
submissionStatusFactory: ReturnType<typeof defineSubmissionStatusFactory>,
) {
await submissionStatusFactory.create({
id: submission_status.id,
status_name: submission_status.status_name,
Expand Down Expand Up @@ -460,7 +479,10 @@ async function addAnswers() {
console.log('Finished adding answers.');
}

async function addAnswer(answer, taskAnswerFactory) {
async function addAnswer(
answer: (typeof answers)[number],
taskAnswerFactory: ReturnType<typeof defineTaskAnswerFactory>,
) {
await taskAnswerFactory.create({
id: answer.id,
task: {
Expand Down
Loading