﻿Geminiと始めるTypeScriptプログラミング


掲載リスト




リスト1-1
________________


デスクトップに「gemini_project」というフォルダーを作り、その中に移動して。ここで作業を行います。
________________




リスト1-2
________________


TypeScriptで、hello.tsファイルに「Hello TypeScript!」と表示するプログラムを作成して。
________________




リスト1-3
________________


1から100までを合計するコード「calc.ts」を作成して。 
________________


リスト1-4
________________


# メモ帳アプリを作る
Webブラウザで動くシンプルなメモ帳のアプリを作成します。
## 機能
メモ帳に用意される機能は以下のとおりです。
- テキストの自動保存
- 標準テキストの記述（スタイルは不要）
- テキストファイルへの出力
## UI
UIは、画面中央にテキストを編集するエリアがあり、下部に操作のボタンが並ぶ形とします。UI作成の注意点は以下のとおりです。
- ボタン類は立体感のある表示で、濃いブルーを基調とする。
- 入力フィールドは12ptのGenevaフォントを使う。
## その他
作成するプログラムに関する補足情報は以下のとおりです。
- コードはTypeScriptを使う。
- フロントエンドはReactを利用する。
- CSSフレームワークには、Tailwind CSSを使う。
________________




リスト1-5
________________


# 目的
[ここに、最終的に何を作りたいのかのゴールを短く書く]


# 役割
あなたは信頼性の高いコードを書くTypeScriptのプロフェッショナルです。


# 開発環境
- 言語: TypeScript
- 実行環境: Node.js (直接実行)


# 制約条件
- [条件1: 初心者にも分かりやすいように、複雑な構文は使わないでください]
- [条件2: 重要な処理の行には、必ず日本語でコメントを入れてください]
- [条件3: 変数や関数の名前は、意味が伝わる分かりやすい英語にしてください]


# 出力フォーマット
- 完成したコードのみを出力してください（または、コードと簡単な実行方法だけを出力してください）。
________________






リスト1-6
________________


【目的】
[ここに、最終的に何を作りたいのかのゴールを短く書く]


【役割】
あなたは信頼性の高いコードを書くTypeScriptのプロフェッショナルです。


【開発環境】
・言語: TypeScript
・実行環境: Node.js (直接実行)


【制約条件】
・[条件1: 初心者にも分かりやすいように、複雑な構文は使わないでください]
・[条件2: 重要な処理の行には、必ず日本語でコメントを入れてください]
・[条件3: 変数や関数の名前は、意味が伝わる分かりやすい英語にしてください]


【出力フォーマット】
・完成したコードのみを出力してください（または、コードと簡単な実行方法だけを出力してください）。
________________






リスト1-7
________________


# 目的
商品の本体価格から、10%の消費税を含んだ税込価格を計算して表示するプログラムの作成。


# 役割
あなたは親切で丁寧なTypeScriptのシニアエンジニアです。


# 開発環境
- 言語: TypeScript
- 実行環境: Node.js


# 制約条件
- 本体価格を入れる変数と、税込価格を計算する関数を分けて作成してください。
- 今回は本体価格を「1200」としてプログラム内で設定してください。
- 各行が何をしているか、初心者向けに日本語でコメントを添えてください。
- ファイル名は「tax-calculator.ts」としてください。


# 出力フォーマット
コードブロックの中に、完成したTypeScriptコードを記述してください。
________________






リスト1-8
// 本体価格を1200として変数に代入します
const basePrice: number = 1200;


/**
 * 本体価格を受け取り、10%の消費税を加えた税込価格を計算する関数です
 * @param price 本体価格
 * @returns 税込価格（小数点以下切り捨て）
 */
function calculateTaxIncludedPrice(price: number): number {
  // 消費税率を10%（0.1）として計算します
  const taxRate: number = 0.1;
  // 本体価格に消費税分を加え、Math.floorで小数点以下を切り捨てて整数にします
  return Math.floor(price * (1 + taxRate));
}


// 作成した関数を使って税込価格を計算し、変数に保存します
const totalPrice: number = calculateTaxIncludedPrice(basePrice);


// 計算結果をコンソールに表示します
console.log("本体価格: " + basePrice + "円");
console.log("税込価格（10%）: " + totalPrice + "円");



リスト1-9
const greetingWord: string = "Hello Compile!";
const targetYear: number = 2026;


function displayMessage(text: string, year: number): void {
    console.log(text + " - Welcome to " + year);
}


displayMessage(greetingWord, targetYear);



リスト1-10
var greetingWord = "Hello Compile!";
var targetYear = 2026;
function displayMessage(text, year) {
    console.log(text + " - Welcome to " + year);
}
displayMessage(greetingWord, targetYear);



リスト2-1
const userName: string = "アリス";
console.log("こんにちは、" + userName + "さん！");



リスト2-2
________________


example.tsというファイルを作成し、以下のコードを記述してください。


[ ここにリスト2-1のコードをペーストする]
________________






リスト2-3
let message: string = "エラーを体験しよう";
message = 12345; // ここでエラーになる



リスト2-4
const applePrice: number = 150;
const quantity: number = 3;
const totalPrice: number = applePrice * quantity;
console.log("合計金額は " + totalPrice + " 円です。");



リスト2-5
________________


example.tsの内容を以下に書き換えてください。


[ リスト2-4をペースト ]
________________






リスト2-6
const taxRate: number = 1.1;
const price: number = 12500;
const tax: number = price * taxRate;
console.log("税込金額は " + tax + " 円です。");



リスト2-7
console.log("税込金額は " + tax.toFixed(2) + " 円です。");



リスト2-8
const isLoggedIn: boolean = true;
console.log("ユーザーのログイン状態: " + isLoggedIn);



リスト2-9
const userAge: number = 18;
const isAdult: boolean = userAge >= 20;
console.log("このユーザーは成人ですか？ " + isAdult);



リスト2-10
const members: string[] = ["アリス", "ボブ", "チャーリー"];
console.log("1番目のメンバー: " + members[0]);
console.log("メンバーの総数: " + members.length);



リスト2-11
const scores: number[] = [80, 95, 70];
scores.push(90);
console.log("スコア一覧: " + scores);



リスト2-12
const score: number = 85;


if (score >= 80) {
    console.log("素晴らしい！合格です。");
} else {
    console.log("残念、不合格です。もう一度頑張りましょう。");
}



リスト2-13
// 1から5までカウントアップする
for (let i: number = 0; i < 5; i++) {
    console.log(`${i + 1}回目の処理です。`);
}



リスト2-14
const tools: string[] = ["VS Code", "Gemini", "Node.js"];


for (const tool of tools) {
    console.log(`便利ツール: ${tool}`);
}



リスト2-15
const userScore = { alice: 90, bob: 75 };


for (const name in userScore) {
    // name には "alice", "bob" というキー（文字列）が入る
    console.log(`${name}さんのスコア`); 
}



リスト2-16
// JavaScriptの場合（実行するまでエラーに気づけない）
const prices = [100, 200, 300];
for (const price of prices) {
    // 数値型（number）には文字数を数える length プロパティはないが、
    // JavaScriptは実行時に undefined を返すだけで作成時は教えてくれない
    console.log(price.length); 
}



リスト2-17
// TypeScriptの場合（コードを書いている最中にブロック！）
const prices: number[] = [100, 200, 300];
for (const price of prices) {
    // エラー: プロパティ 'length' は型 'number' に存在しません。
    console.log(price.length); 
}



リスト2-18
const systemConfig = {
    env: "production",
    version: 1.2
};


for (const key in systemConfig) {
    // TypeScriptではここでエラーが発生することがある！
    console.log(systemConfig[key]); 
}



リスト2-19
const systemConfig = {
    env: "production",
    version: 1.2
};


for (const key in systemConfig) {
    // keyof演算子を使用して型安全にする
    console.log(systemConfig[key as keyof typeof systemConfig]); 
}



リスト2-20
let energy: number = 5;


// energyが0より大きい間、ループを繰り返す
while (energy > 0) {
    console.log(`行動中！残り: ${energy}`);
    energy--; // これを忘れると「無限ループ」になるので注意！
}



リスト2-22
// 信号の色を設定
const signal: string = "red";


switch (signal) {
    case "green":
        console.log("進んでも良いです。");
        break; // 処理を抜け出すために必須！
    case "yellow":
        console.log("注意して停止してください。");
        break;
    case "red":
        console.log("止まってください。");
        break;
    default:
        console.log("信号のデータが壊れています。");
        break;
}



リスト2-23
let userCount = 0;


// JavaScriptでは、数値の「0」は「Falsy（偽）」と判定される！
if (userCount) {
    // 0人のときは、ここを通らない（意図しない挙動になりやすい）
    console.log("ユーザーが存在します");
}



リスト2-24
let userCount: number = 0;


// TypeScriptでは、booleanになる比較式を推奨
if (userCount > 0) {
    console.log("ユーザーが存在します");
}



リスト2-25
function makeGreeting(name: string): string {
    return "こんにちは、" + name + "さん！";
}


const resultMessage: string = makeGreeting("ボブ");
console.log(resultMessage);



リスト2-26
// 【元の形】通常の関数（function構文）
function double(x: number): number {
    return x * 2;
}


// 関数を呼び出す
const result = double(10);



リスト2-27
// 【変換後】アロー関数の基本形
const double = (x: number): number => {
    return x * 2;
};


// 関数を呼び出す
const result = double(10);



リスト2-28
________________


TypeScriptの「アロー関数」と「値としての関数（コールバック関数）」のメリットが初心者に最もよく伝わる、実践的なサンプルコードを作成してください。
テーマは、商品の価格配列を加工する「価格計算システム」とします。


【要件】
1. まず、関数そのものの型（関数型）を型定義（type）を使って「PriceCalculator」として定義してください（引数に number を受け取り、戻り値として number を返す関数の型）。
2. 「PriceCalculator」型に適合するアロー関数を2つ作成してください。
   - 1つ目：消費税10%を計算して加算するアロー関数（taxCalculator）
   - 2つ目：一律10%引きのセール価格を計算するアロー関数（saleCalculator）
3. 【値としての関数の活用】
   価格の配列（number[]）と、上記で定義した「PriceCalculator型の関数」を引数として受け取り、配列内のすべての価格にその計算を適用して新しい配列を返す関数「processPrices」を作成してください。
4. クラスや難しい構文は使わず、純粋に関数の強力さがわかる実行コードを末尾に含めてください。
5. コードは「price_calc_system.ts」というファイルを作成し、この中に記述してください。


コードが出力されたら、「アロー関数の省略記法のルール」と「関数を値として使い回す（コールバック関数）ことの圧倒的なメリット」を初心者向けに丁寧に解説してください。
________________






リスト2-29
// ==========================================
// 1. 関数そのものの「型」を定義する
// ==========================================
// 「数値を1つ受け取り、数値を1つ返す関数」という規格を type で定義
type PriceCalculator = (price: number) => number;


// ==========================================
// 2. 規格に沿った「アロー関数」を定義する
// ==========================================


// 消費税10%を加算するアロー関数（最も省略した綺麗な書き方）
const addTax: PriceCalculator = (price) => price * 1.1;


// 10%オフのセール価格にするアロー関数
const applySale: PriceCalculator = (price) => price * 0.9;


// ==========================================
// 3. 「関数を引数として受け取る」関数を作成する
// ==========================================
// 第2引数で「PriceCalculator型」の関数をそのまま丸ごと受け取るのがポイント！
const processPrices = (prices: number[], calculator: PriceCalculator): number[] => {
    const result: number[] = [];
    
    for (const price of prices) {
        // 引数として回ってきた関数（内部では calculator と命名）を実行する
        const calculatedPrice = calculator(price);
        result.push(Math.floor(calculatedPrice)); // 小数点以下は切り捨て
    }
    
    return result;
};


// ==========================================
// 4. テスト用の実行コード
// ==========================================
console.log("=== モダン関数システムのテスト開始 ===");


const rawPrices = [1000, 2000, 5000];
console.log(`元の価格: [ ${rawPrices.join(", ")} ] 円`);


// ① 税込計算をする関数（addTax）を「値」としてそのまま引き渡す！
const taxedPrices = processPrices(rawPrices, addTax);
console.log(`税込価格: [ ${taxedPrices.join(", ")} ] 円`);


// ② セール計算をする関数（applySale）を「値」としてそのまま引き渡す！
const salePrices = processPrices(rawPrices, applySale);
console.log(`セール価格: [ ${salePrices.join(", ")} ] 円`);


// ③ 応用：その場で「2倍にするアロー関数」を作って直接放り込むことも可能！
const doublePrices = processPrices(rawPrices, (price) => price * 2);
console.log(`倍額価格: [ ${doublePrices.join(", ")} ] 円`);



リスト2-30
function greet(name: string) {
  console.log("こんにちは、" + name);
  
if (name === "Taro") {
  console.log("特別メッセージ");
}


greet(100);



リスト2-31
________________
現在開いているファイルで構文エラーが出ています。
原因を特定して、最小限の修正で直してください。
修正したコードの内容と、どこが原因だったかを短く説明してください。
________________






リスト2-32
function greet(name: string) {
  console.log("こんにちは、" + name);
  
  if (name === "Taro") {
    console.log("特別メッセージ");
  }
}


greet("Taro");



リスト2-33
________________


example.tsで構文エラーが出ています。
原因を特定して、最小限の修正で直してください。
修正したコードの内容と、どこが原因だったかを短く説明してください。
________________






リスト2-34
function showFirstChar(text: any) {
  console.log(text[0]);
}


let message = null;
showFirstChar(message);





リスト2-35
________________


現在開いているファイルで実行時エラーが発生しています。
コンパイルは通りますが、実行時に失敗します。


エラーメッセージ:
TypeError: Cannot read properties of null (reading '0')


再現手順:
node example.tsを実行すると直後に発生


要件:
- 実行時エラーの原因を特定してください
- 最小限の修正で直してください
- 元の処理の意図はなるべく変えないでください
- 修正後のコードを示し、ファイルを更新してください
- なぜそのエラーが起きたのかを簡潔に説明してください
________________






リスト2-36
function showFirstChar(text: any) {
  if (text != null) { // textがnullまたはundefinedでないことを確認
    console.log(text[0]);
  }
}


let message = null;
showFirstChar(message);



リスト2-37
________________
根本原因は、引数がanyになっていることです。
これを明示的な型に修正する方向で考えてください。
________________






リスト2-38
function showFirstChar(text: string[]) {
  console.log(text[0]);
}


let message = ["TypeScript"];
showFirstChar(message);



リスト2-39
function judge(score: number) {
  if (score > 60) {
    console.log("合格");
  } else {
    console.log("不合格");
  }
}


judge(60);



リスト2-40
________________


現在開いているファイルには、構文エラーや実行時エラーは出ませんが、期待した通りに動かない論理エラーがあります。


【期待する動作】
60以上では合格、60未満では不合格と表示する


【実際の動作】
60でも不合格となる


【要件】
- 論理エラーの原因を特定してください
- 最小限の修正で直してください
- 元の処理の流れはなるべく変えないでください
- 修正後のコードを示してください
- どこが間違っていたのかを簡潔に説明してください
________________






リスト2-41
function judge(score: number) {
  if (score >= 60) {
    console.log("合格");
  } else {
    console.log("不合格");
  }
}


judge(60);



リスト3-1
class Monster {
    // 今は中身が完全に空っぽです
}


const myMonster = new Monster();
console.log(myMonster);



リスト3-2
class Monster {
    name: string = "名無しのモンスター";
    hp: number = 100;
}


const myMonster = new Monster();
console.log("モンスターの名前: " + myMonster.name);
console.log("モンスターの初期HP: " + myMonster.hp);



リスト3-3
class Monster {
    name: string = "スライム";
    hp: number = 100;


    attack(): void {
        console.log(this.name + " の攻撃！相手に 10 ダメージを与えた！");
    }
}


const myMonster = new Monster();
myMonster.attack();



リスト3-4
class Monster {
    name: string;
    hp: number;


    constructor(mName: string, mHp: number) {
        this.name = mName;
        this.hp = mHp;
    }


    attack(): void {
        console.log(this.name + " の攻撃！相手に 10 ダメージを与えた！");
    }
}


const monster1 = new Monster("スライム", 80);
const monster2 = new Monster("ドラゴン", 2000);


console.log(monster1.name + " の HP は " + monster1.hp);
console.log(monster2.name + " の HP は " + monster2.hp);


monster1.attack();
monster2.attack();



リスト3-5
class UserAccount {
    public nickname: string;    // どこからでも触れる
    private _secretId: string;  // クラスの内部専用


    constructor(name: string, id: string) {
        this.nickname = name;
        this._secretId = id;
    }


    public showProfile(): void {
        // クラスの「内側」なので _secretId にアクセスできる
        console.log(`名前: ${this.nickname} (ID: ${this._secretId})`);
    }
}


const user = new UserAccount("たかし", "PASS-1234");
user.nickname = "ケン"; // OK: public なので書き換え可能
// user._secretId = "HACK-555"; // 外部からprivateにはアクセスできない


user.showProfile();



リスト3-6
class Config {
    public readonly apiEndpoint: string = "https://api.example.com/v1";
}


const config = new Config();
console.log("接続先: " + config.apiEndpoint); // 読み込みは自由にできる
// config.apiEndpoint = "https://bad-url.com"; // 変更しようとするとエラー



リスト3-7
class Person {
    private _age: number = 0;


    // 1. ゲッター（値を読み出す窓口）
    get age(): number {
        return this._age;
    }


    // 2. セッター（値を書き換える窓口）
    set age(value: number) {
        if (value < 0) {
            console.log("❌ エラー: 年齢にマイナスの値は設定できません！");
            return; // 処理を中断して、_age を書き換えない
        }
        this._age = value;
    }
}


const me = new Person();
me.age = 25;       // 裏で set age(25) が動いている！
console.log(me.age); // 裏で get age() が動いている！


me.age = -10;      // 不正なデータを入れようとする
console.log(me.age); // 年齢は 25 のまま守られている



リスト3-8
________________


TypeScriptで、オブジェクト指向の「継承（Inheritance）」の基本を説明するためのサンプルコードを作成してください。以下の要件をすべて満たすコードにしてください。なお、コードはexample.tsを上書きして作成してください。


【要件】
1. 親クラスとして `Animal` クラスを作成する
   - プロパティ: `name: string` (public)
   - コンストラクタ: `name` を受け取って初期化する
   - メソッド: `move(): void`。実行するとコンソールに「`${this.name} はトコトコ移動した。`」と出力する


2. 子クラスとして `Dog` クラスを作成する
   - `Animal` を継承する
   - メソッド: `bark(): void`。実行するとコンソールに「ワンワン！」と出力する


3. 子クラスとして `Cat` クラスを作成する
   - `Animal` を継承する
   - メソッド: `meow(): void`。実行するとコンソールに「ニャー！」と出力する


4. インスタンス化と実行例
   - `Dog` のインスタンス `myDog` を名前「ポチ」で生成し、`move()` と `bark()` を呼び出す
   - `Cat` のインスタンス `myCat` を名前「たま」で生成し、`move()` と `meow()` を呼び出す


5. コード内には、どこが親クラスでどこが子クラスなのか、また「親から引き継いだメソッドを実行できる」といった初心者に分かりやすいコメントを適宜入れてください。
________________






リスト3-9
// 親クラス：一般的な動物
class Animal {
    public name: string;


    constructor(name: string) {
        this.name = name;
    }


    public move(): void {
        console.log(`${this.name} はトコトコ移動した。`);
    }
}


// 子クラス：犬（Animal の設計図を 100% 引き継ぐ）
class Dog extends Animal {
    public bark(): void {
        console.log("ワンワン！");
    }
}


// 子クラス：猫（Animal の設計図を引き継ぐ）
class Cat extends Animal {
    public meow(): void {
        console.log("ニャー！");
    }
}


const myDog = new Dog("ポチ");
myDog.move(); // 親から引き継いだメソッドを実行できる
myDog.bark(); // 自分が新しく追加したメソッドを実行できる


const myCat = new Cat("たま");
myCat.move(); // 親から引き継いだメソッドを実行できる
myCat.meow(); // 自分が新しく追加したメソッドを実行できる



リスト3-10
class Bird extends Animal {
    public wingSpan: number;


    constructor(name: string, wing: number) {
        super(name); // 1. 親のコンストラクタを最初に呼び出す
        this.wingSpan = wing;
    }


    // 2. 親の move() メソッドをオーバーライドする
    public move(): void {
        console.log(`${this.name} は大空をパタパタと飛んだ！（翼長: ${this.wingSpan}cm）`);
    }
}


const eagle = new Bird("ワシ", 150);
eagle.move(); // 上書きされた新しい move() が実行される



リスト3-11
class Parent {
    protected familySecret: string = "代々伝わる秘伝のタレのレシピ";
}


class Child extends Parent {
    public revealSecret(): void {
        // 継承した「子クラス」の内部なので、protected なプロパティに触れる！
        console.log(`親から受け継いだ秘密: ${this.familySecret}`);
    }
}


const childInstance = new Child();
childInstance.revealSecret();
// console.log(childInstance.familySecret); // 外部からのアクセスはエラー



リスト3-12
// 抽象クラス（未完成の親型紙）
abstract class Character {
    protected name: string;
    
    constructor(name: string) {
        this.name = name;
    }


    // 抽象メソッド（名前と型だけの約束。中身の {} は書かない！）
    abstract performAction(): void;
}


// 子クラス：戦士（親の未完成な部分をしっかりと具現化する）
class Warrior extends Character {
    public performAction(): void {
        console.log(`⚔️ ${this.name} の大剣が唸る！ 激しい一撃！`);
    }
}


// const hero = new Character("勇者"); // インスタンスは作成できない
const hero = new Warrior("アグニ");
hero.performAction();



リスト3-13
________________


TypeScriptで、インターフェースの実装（implements）の基本を説明するためのサンプルコードを作成してください。コードはexample.tsを上書きして作成してください。
コードの要件は以下の通りです。


【要件】
1. インターフェース `Loggable` を作成する
   - プロパティ: `logId: string`
   - メソッド: `printLog(): void`


2. クラス `OrderSystem` を作成し、`Loggable` インターフェースを実装（implements）する
   - `logId` の初期値として `"SYS-ORD-01"` を代入する（アクセス修飾子は public）
   - `printLog(): void` メソッドを実装し、実行するとコンソールに「`ログ出力中... [管理ID: ${this.logId}] システムは正常です。`」と出力する


3. 実行例
   - `OrderSystem` のインスタンス `system` を生成する
   - `system.printLog()` を呼び出す


4. コード内には、インターフェースの定義部分、クラスへの実装部分にそれぞれ初心者向けの分かりやすいコメント（例：「ログ出力機能のインターフェース」「クラスにインターフェースを実装する（implements）」など）を適宜入れてください。
________________






リスト3-14
// ログ出力機能のインターフェース
interface Loggable {
    logId: string;
    printLog(): void;
}


// クラスにインターフェースを実装する（implements）
class OrderSystem implements Loggable {
    public logId: string = "SYS-ORD-01"; // プロパティの実装


    // メソッドの実装
    public printLog(): void {
        console.log(`ログ出力中... [管理ID: ${this.logId}] システムは正常です。`);
    }
}


const system = new OrderSystem();
system.printLog();



リスト3-15
interface UserProfile {
    nickname: string;
    age: number;
    memo?: string; // あってもなくても良いオプション項目
}


const profileA: UserProfile = {
    nickname: "さくら",
    age: 22,
    memo: "ゲームが趣味です"
};


const profileB: UserProfile = {
    nickname: "けんじ",
    age: 30
    // memoがなくてもエラーにならない！
};


console.log(profileA.nickname + "のメモ: " + profileA.memo);
console.log(profileB.nickname + "のメモ: " + profileB.memo);



リスト3-16
________________


TypeScriptの「インターフェース」のメリットが初心者に最もよく伝わる、実践的なサンプルコードを作成してください。
テーマは、ユーザーにメッセージを送る「通知システム」にしてください。


【要件】
1. まず、通知を行うための共通規格として「Notifier」というインターフェースを定義してください。ここには、宛先（receiver）と本文（message）を受け取り、戻り値のない「send()」メソッドのルールだけを定義します。
2. 「Notifier」インターフェースを実装（implements）した、メール送信用の「EmailNotifier」クラスと、SMS送信用の「SMSNotifier」クラスの2つを作成してください。それぞれのsend()メソッド内では、コンソールに異なる形式のログ（「[メール送信] 〇〇宛...」「[SMS送信] 〇〇宛...」など）を出力させてください。
3. インターフェースの最大の強みである「異なるクラスを同じ型として一挙に扱う（多態性）」を示すため、「Notifier」型の配列を受け取り、ループ処理で一斉に通知を送る関数、または実行コードを作成してください。
4. 生成されたコードは、「information_system.ts」ファイルとして保存してください。


コードが出力されたら、「なぜ直接クラスを作らずに、わざわざインターフェースを挟む必要があるのか」のメリットを、プログラミング初心者に向けて解説してください。
________________






リスト3-17
// 1. 共通の「規格（契約）」を定義するインターフェース
interface Notifier {
    send(receiver: string, message: string): void;
}


// 2. インターフェース（規格）に従って作られた「メール送信」クラス
class EmailNotifier implements Notifier {
    public send(receiver: string, message: string): void {
        console.log(`✉️ [メール送信] ${receiver} アドレスへ送信中...`);
        console.log(`   本文: ${message}`);
        console.log("   => ステータス: 送信完了\n");
    }
}


// 3. インターフェース（規格）に従って作られた「SMS送信」クラス
class SMSNotifier implements Notifier {
    public send(receiver: string, message: string): void {
        console.log(`📱 [SMS送信] ${receiver} 携帯番号へ暗号化送信中...`);
        console.log(`   本文: [要約] ${message}`);
        console.log("   => ステータス: 送信完了\n");
    }
}


// === ここからインターフェースの凄さがわかる実行コード ===


// 異なるクラスから作られたインスタンスを「Notifier型」配列にまとめる！
const notificationChannels: Notifier[] = [
    new EmailNotifier(),
    new SMSNotifier()
];


const alertMessage = "システムメンテナンスが本日22時より開始されます。";
const userEmail = "tanaka@example.com";
const userPhone = "090-1234-5678";


console.log("--- 共通規格を使って一斉に通知を送信します --- \n");


// 配列の中身が「メール」か「SMS」かを気にせず「send()」を呼べる！
notificationChannels.forEach((channel) => {
    // 1回目のループでは EmailNotifier の send() が動き、
    // 2回目のループでは SMSNotifier の send() が自動的に動く！
    if (channel instanceof EmailNotifier) {
        channel.send(userEmail, alertMessage);
    } else {
        channel.send(userPhone, alertMessage);
    }
});



リスト3-18
type Employee = {
    id: number;
    name: string;
};


const staff: Employee = {
    id: 1001,
    name: "山田太郎"
};


console.log("社員ID: " + staff.id + ", 名前: " + staff.name);



リスト3-19
type Signal = "red" | "yellow" | "green";


function action(signal: Signal): string {
  switch (signal) {
    case "red":
      return "止まる";
    case "yellow":
      return "注意する";
    case "green":
      return "進む";
    default:
      return "不明";
  }
}


console.log(action("red"));    // 止まる
console.log(action("green"));  // 進む



リスト3-20
________________


TypeScriptで、関数の型定義（type）と高階関数（関数を引数に取る関数）の基本を説明するためのサンプルコードを作成してください。コードは、example.tsを上書きして作成してください。
以下の1〜5の要件と流れをすべて満たすコードにしてください。


【要件】
1. 関数の型 `TextTransformer` を定義する
   - 引数: `text: string`、戻り値: `string`


2. `TextTransformer` 型に沿ったアロー関数を2つ作成する
   - `convertToShout`: 受け取った文字列の語尾に「！！」を付与して返す
   - `convertToSecret`: 受け取った文字列の先頭に「【極秘】」を付与して返す


3. 高階関数の型 `AlertFunction` を定義する
   - 引数: `message: string`, `transformer: TextTransformer`、戻り値: `void`


4. `AlertFunction` 型を実装した高階関数 `displayAlert` を作成する
   - 処理内容: 内部で `transformer(message)` を実行して文字列を加工し、コンソールに「`[通知] ${加工後の文字列}`」と出力する


5. 実行テストのコードを含める
   - コンソールに「`--- 通知システムのテスト開始 ---`」と出力する
   - `displayAlert("システムエラーが発生しました", convertToShout)` を実行する
   - `displayAlert("バックアップが完了しました", convertToSecret)` を実行する


6. 各ステップ（1. 関数の型定義、2. 加工ルールの作成、3. 高階関数の型定義、4. 高階関数の作成、5. 実行テスト）に対応する分かりやすい日本語コメントを必ず含めてください。
________________






リスト3-21
// 1. 関数の規格（型）をあらかじめ定義する
type TextTransformer = (text: string) => string;


// 2. 規格に沿った加工ルール（アロー関数）を2つ用意する
const convertToShout: TextTransformer = (text) => `${text}！！`;
const convertToSecret: TextTransformer = (text) => `【極秘】${text}`;


// 3. 関数を使った型を定義する
type AlertFunction = (message: string, transformer: TextTransformer) => void;


// 4. 関数を引数（値）として受け取る「高階関数」を作成する
const displayAlert: AlertFunction = (message: string, transformer: TextTransformer): void => {
    // 預かった加工ルール（関数）をここで実行する
    const transformedMessage = transformer(message);
    console.log(`[通知] ${transformedMessage}`);
};


// 5. 実行テスト（関数を「値」として投げ込む）
console.log("--- 通知システムのテスト開始 ---");


// ① 語尾を強める関数（convertToShout）を引き渡す
displayAlert("システムエラーが発生しました", convertToShout);


// ② 極秘ラベルをつける関数（convertToSecret）を引き渡す
displayAlert("バックアップが完了しました", convertToSecret);



リスト3-22
________________


TypeScriptの「型定義（type）」「インターフェース（interface）」「クラス（class）」の3つをすべて組み合わせた、初心者の目標となるような美しい総合サンプルコードを作成してください。
テーマは「ECサイトの注文管理システム」とします。コードは「ec_system.ts」というファイルを作成し、ここに保存してください。


【設計の要件】


1. 【型定義の活用】
   - 注文の状態を表す「OrderStatus」型を、"pending"（未決済） | "processing"（処理中） | "shipped"（発送済み） | "cancelled"（キャンセル） のリテラル型のユニオン型として定義してください。
   - 商品のデータを表す「Product」型を、オブジェクト型として定義してください（プロパティ：id, name, price）。priceはマイナスの値が入らないように考慮します。
2. 【インターフェースの活用】
   - 将来的に様々な決済方法（クレカ、コンビニ払いなど）に対応できるよう、共通規格として「PaymentProcessor」インターフェースを定義してください。金額（amount）を受け取り、成否（boolean）を返す「processPayment()」メソッドのルールを定めます。
   - このインターフェースを実装したクレジットカード決済用の「CreditCardProcessor」クラスを1つ作成してください。
3. 【クラスの活用】
   - 注文そのものを管理する「Order」クラスを作成してください。
   - 注文ステータス（_status）と、購入する商品の配列（_products）は、外部から直接書き換えられないよう「private」にします。
   - コンストラクタで、注文ID（orderId）と、使用する決済プロセッサ（PaymentProcessorインターフェース型）を受け取るようにしてください。
   - メソッドとして、商品を追加する「addProduct()」、合計金額を計算する「getTotalPrice()」、そして決済を実行してステータスを進める「checkout()」を実装してください。


コードの末尾には、実際にインスタンスを作って商品を追加し、チェックアウトするまでのテスト用実行コードも含めてください。
出力後、これら3つの機能（型・インターフェース・クラス）がどのように連携し、なぜ安全なシステムが構築できているのかを初心者向けに優しく徹底解説してください。
________________






リスト3-23
// ==========================================
// 1. 型定義（Type Alias）のセクション
// ==========================================


// 注文ステータスを特定の4つの文字列だけに制限する（タイポを完全に防ぐ）
type OrderStatus = "pending" | "processing" | "shipped" | "cancelled";


// 商品データの構造をシンプルに定義する
type Product = {
    readonly id: string; // 後からIDを書き換えられないようにreadonlyにする
    name: string;
    price: number;
};


// ==========================================
// 2. インターフェース（Interface）のセクション
// ==========================================


// あらゆる決済方法が守るべき「共通の取扱説明書」
interface PaymentProcessor {
    processPayment(amount: number): boolean;
}


// 共通規格（インターフェース）を実装した、具体的なクレジットカード決済クラス
class CreditCardProcessor implements PaymentProcessor {
    public processPayment(amount: number): boolean {
        console.log(`💳 [クレジットカード決済窓口] ${amount}円の決済を承認中...`);
        // 本来はここに通信処理などが入ります。今回は確定で成功（true）とします
        console.log("   => 決済が正常に承認されました。");
        return true;
    }
}


// ==========================================
// 3. クラス（Class）のセクション
// ==========================================


// 注文全体のデータを守り、ビジネスルール（ロジック）を実行する主役
class Order {
    // 外部から勝手に書き換えられたら困るデータはprivateで隠す（カプセル化）
    private _status: OrderStatus = "pending"; 
    private _products: Product[] = [];
    public readonly orderId: string;
    private _paymentProcessor: PaymentProcessor;


    /**
     * コンストラクタ
     * strip-only modeに対応するため、プロパティの代入を明示的に行います。
     */
    constructor(orderId: string, paymentProcessor: PaymentProcessor) {
        this.orderId = orderId;
        this._paymentProcessor = paymentProcessor;
    }


    // 注文に商品を追加するメソッド（窓口）
    public addProduct(product: Product): void {
        if (product.price <= 0) {
            console.log(`❌ エラー: ${product.name} の価格が不正です。0円以下の商品は追加できません。`);
            return;
        }
        this._products.push(product);
        console.log(`📦 注文 ${this.orderId} に商品を追加: ${product.name} (${product.price}円)`);
    }


    // カート内の合計金額を計算するメソッド
    public getTotalPrice(): number {
        // reduceを使って配列内の商品の価格を合計する
        return this._products.reduce((sum, currentProduct) => sum + currentProduct.price, 0);
    }


    // 決済を確定させるメソッド
    public checkout(): void {
        const total = this.getTotalPrice();
        if (total === 0) {
            console.log("❌ エラー: カートが空っぽです。チェックアウトできません。");
            return;
        }


        console.log(`\n--- 決済手続きを開始します（注文ID: ${this.orderId}） ---`);
        
        // 自分が持っている決済プロセッサの「processPayment」を叩く
        const isSuccess = this._paymentProcessor.processPayment(total);


        if (isSuccess) {
            this._status = "processing"; // ステータスを「処理中」に進める
            console.log(`✅ 注文が確定しました！現在のステータス: [${this._status}]\n`);
        } else {
            this._status = "cancelled"; // 失敗したらキャンセルにする
            console.log(`❌ 決済に失敗したため、注文はキャンセルされました。[${this._status}]\n`);
        }
    }


    // 現在のステータスを安全に外部から確認するためのゲッター
    public getStatus(): OrderStatus {
        return this._status;
    }
}


// ==========================================
// 4. テスト用の実行コード
// ==========================================
console.log("=== ECサイト注文管理システム テスト開始 ===");


// 1. クレジットカード決済のインスタンスを作成
const cardProcessor = new CreditCardProcessor();


// 2. 注文を作成（注文ID「ORD-2026」に、上記のクレカ決済をセットする）
const myOrder = new Order("ORD-2026", cardProcessor);


// 3. 商品をいくつか追加してみる
const item1: Product = { id: "p001", name: "TypeScript入門書", price: 3200 };
const item2: Product = { id: "p002", name: "開発用ワイヤレスマウス", price: 5800 };
const badItem: Product = { id: "p003", name: "怪しい無料チケット", price: -500 }; // 不正な商品


myOrder.addProduct(item1);
myOrder.addProduct(item2);
myOrder.addProduct(badItem); // これはガード節で弾かれるはず！


// 4. 合計金額の確認と、チェックアウト（決済）の実行
console.log(`現在の合計金額: ${myOrder.getTotalPrice()} 円`);
myOrder.checkout();


// 5. 最終ステータスの確認
console.log(`最終注文ステータス: ${myOrder.getStatus()}`);
console.log("=== ECサイト注文管理システム テスト終了 ===");



リスト4-1
import { mkdir } from "node:fs/promises";


console.log("1. フォルダー作成処理を開始します...");


// 非同期で「project-root」というフォルダーを作成する
mkdir("project-root")
    .then(() => {
        // 作成が正常に成功したときに実行される領域（コールバック）
        console.log("3. 🎉 フォルダーの作成に成功しました！");
    })
    .catch((error: Error) => {
        // 万が一、エラー（既に同じフォルダーがあるなど）が発生したときの領域
        console.log(`❌ エラーが発生しました: ${error.message}`);
    });


console.log("2. 作成命令を出し、次のコードに進みました。");



リスト4-2
import { rename } from "node:fs/promises";


const oldPath: string = "project-root";
const newPath: string = "../renamed-project";


console.log(`[開始] ${oldPath} を ${newPath} に変更します...`);


rename(oldPath, newPath)
    .then(() => {
        console.log("🎉 名前の変更（移動）が完了しました！");
    })
    .catch((error: Error) => {
        console.log(`❌ 変更失敗: ${error.message}`);
    });



リスト4-3
import { readdir } from "node:fs/promises";


const targetDir: string = "."; // 「.」は現在のフォルダー


console.log(`[検索] ${targetDir} の中身をスキャンします...`);


readdir(targetDir)
    .then((files: string[]) => {
        console.log("📂 見つかったファイル一覧:");
        
        // 配列で返されたファイル名をループで1つずつ表示
        for (const file of files) {
            console.log(` - ${file}`);
        }
    })
    .catch((error: Error) => {
        console.log(`❌ リスト取得失敗: ${error.message}`);
    });



リスト4-4
import { readdir } from "node:fs/promises";
// 💡 型定義だけをインポートする「import type」は、strip-only modeで完全に消去されるため安全です
import type { Dirent } from "node:fs";


const targetDir: string = ".";


console.log(`[詳細スキャン] ${targetDir} の中身を属性付きでスキャンします...`);


// options に withFileTypes: true を指定するのが最大のポイント！
readdir(targetDir, { withFileTypes: true })
    .then((items: Dirent[]) => {
        console.log("📂 スキャン完了！アイテムを仕分けします:\n");
        
        for (const item of items) {
            // item.name でファイル名やフォルダー名を取得できる
            const name: string = item.name;


            // item.isDirectory() や item.isFile() で正体を暴く！
            if (item.isDirectory()) {
                console.log(` [フォルダー] 📁 ${name}`);
            } else if (item.isFile()) {
                console.log(` [ファイル]   📄 ${name}`);
            } else {
                console.log(` [その他]     ❓ ${name}`); // シンボリックリンクなど
            }
        }
    })
    .catch((error: Error) => {
        console.error(`❌ 詳細リスト取得失敗: ${error.message}`);
    });



リスト4-5
import { rm } from "node:fs/promises";


const garbageDir: string = "project-root";


console.log(`[削除] ${garbageDir} を消去します...`);


rm(garbageDir, { recursive: true, force: true })
    .then(() => {
        console.log("🗑️ フォルダーを完全に削除しました。");
    })
    .catch((error: Error) => {
        console.log(`❌ 削除失敗: ${error.message}`);
    });



リスト4-6
import { mkdir, rename, readdir, rm } from "node:fs/promises";


// 一連のファイル操作をまとめた「非同期関数」を定義する（頭に async が必須！）
const runFileOperations = async (): Promise<void> => {
    try {
        console.log("--- [async/await モード] ファイル操作を一斉に開始します ---");


        // 1. フォルダー作成
        console.log("① フォルダー 'async-root' を作成します...");
        await mkdir("async-root"); 
        console.log("👉 作成が終わるまで次の行に進まず待機しました。");


        // 2. リネーム
        console.log("② フォルダー名を 'async-renamed' に変更します...");
        await rename("async-root", "async-renamed");
        console.log("👉 名前の変更が完全に完了しました。");


        // 3. カレントディレクトリのリスト取得
        console.log("③ 現在のフォルダー内をスキャンします...");
        const files: string[] = await readdir(".");
        console.log("📂 スキャン成功！見つかったアイテム:");
        for (const file of files) {
            console.log(`  - ${file}`);
        }


        // 4. 削除
        console.log("④ テストで作ったフォルダーを削除して綺麗にします...");
        await rm("async-renamed", { recursive: true, force: true });
        console.log("🗑️ 削除が完了しました。すべてのミッションに成功！");


    } catch (error) {
        // async/await の世界では、エラーは使い慣れた try...catch で一括捕獲する
        if (error instanceof Error) {
            console.error(`❌ 途中で致命的なエラーが発生しました: ${error.message}`);
        }
    }
};


// 作成した非同期メイン関数を実行する
runFileOperations();



リスト4-7
import { mkdirSync, renameSync, readdirSync, rmSync } from "node:fs";


console.log("--- [同期(Sync)モード] 一斉処理を開始します ---");


try {
    // 1. フォルダー作成
    console.log("① フォルダー 'sync-root' を作成します...");
    mkdirSync("sync-root"); // 👈 await も async も一切不要！
    console.log("👉 フォルダーが作成されました。");


    // 2. リネーム
    console.log("② フォルダー名を 'sync-renamed' に変更します...");
    renameSync("sync-root", "sync-renamed");
    console.log("👉 名前が変更されました。");


    // 3. リスト取得
    console.log("③ 現在のフォルダー内をスキャンします...");
    const files: string[] = readdirSync("."); // 結果をダイレクトに変数に受け取れる
    console.log("📂 スキャン成功！見つかったアイテム:");
    for (const file of files) {
        console.log(`  - ${file}`);
    }


    // 4. 削除
    console.log("④ テストで作ったフォルダーを削除します...");
    rmSync("sync-renamed", { recursive: true, force: true });
    console.log("🗑️ 削除完了。すべての同期ミッションが終了しました。");


} catch (error) {
    if (error instanceof Error) {
        console.error(`❌ 同期処理の途中でエラーが発生しました: ${error.message}`);
    }
}


console.log("--- すべてのコードの実行が完全に終了しました ---");



リスト4-8
________________


TypeScript（Node.js）で、特定のフォルダー内を整理する「ファイル自動整理プログラム」を作成してください。コードは、file-organizer.tsというファイルとして作成してください。


【動作仕様】
指定した作業フォルダーの中身をスキャンし、ファイルを見つけたら、そのファイルの「拡張子（txt や png など）」と同じ名前のフォルダーを自動で作成し、その中にファイルを移動（リネーム）して整理するツール。


【技術的な条件（必須）】
1. 整理するフォルダーは、引数でパスを渡して指定すること。
2. node:fs/promises モジュールを使い、async/await スタイルで記述すること。
3. readdir の { withFileTypes: true } を使用し、フォルダー自体は仕分け対象から除外し、**「ファイルのみ」**を仕分け対象にすること。
4. 移動先のフォルダーがまだ存在しない場合のみ mkdir で作成すること。
5. 最新のNode.jsの strip-only mode で直接実行できるよう、実行時コード変形が必要なTypeScript独自構文（パラメータプロパティなど）は一切使わず、純粋な型アノテーションのみで記述すること。
6. 処理の進捗が分かりやすいよう、コンソールに綺麗な進捗ログを出すこと。
________________






リスト4-9
import { readdir, mkdir, rename } from "node:fs/promises";
import { existsSync } from "node:fs";
import { extname, join } from "node:path";
import { argv } from "node:process"; // 引数を受け取るモジュール
import type { Dirent } from "node:fs";


const organizeFolder = async (dirPath: string): Promise<void> => {
    try {
        // 引数で渡されたパスがPC内に存在するかチェック
        if (!existsSync(dirPath)) {
            console.error(`❌ エラー: 指定されたフォルダー「${dirPath}」が存在しません。パスが正しいか確認してください。`);
            return;
        }


        console.log(`\n🧹 【お片付け開始】対象フォルダー: ${dirPath} のスキャンを開始します...`);


        // 1. フォルダー内を詳細スキャン
        const items: Dirent[] = await readdir(dirPath, { withFileTypes: true });


        // 2. ファイルだけを抽出
        const files: Dirent[] = items.filter((item) => item.isFile());


        if (files.length === 0) {
            console.log("✨ 仕分け対象のファイルは見つかりませんでした。フォルダーはすでに綺麗です！");
            return;
        }


        console.log(`📦 見つかったファイル数: ${files.length} 件。仕分けを開始します...\n`);


        // 3. 見つかったファイルをループで1つずつ処理
        for (const file of files) {
            const originalName: string = file.name;


            // 💡 整理対象外にするため、このスクリプト自身（.ts）は仕分けから除外する防衛策
            if (originalName === "file-organizer.ts") {
                continue; 
            }


            const dotExtension: string = extname(originalName);
            const folderName: string = dotExtension ? dotExtension.slice(1).toLowerCase() : "unknown";


            const oldFilePath: string = join(dirPath, originalName);
            const destFolderPath: string = join(dirPath, folderName);
            const newFilePath: string = join(destFolderPath, originalName);


            // 4. 移動先の仕分けフォルダーがなければ、自動作成
            if (!existsSync(destFolderPath)) {
                console.log(`📁 新しい仕分けフォルダーを作成します: [${folderName}]`);
                await mkdir(destFolderPath);
            }


            // 5. ファイルを移動
            await rename(oldFilePath, newFilePath);
            console.log(`   ➔ 📄 [${originalName}] 🚀 移動完了 ➔ [${folderName}/]`);
        }


        console.log("\n🎉 【整理完了】すべてのファイルが仕分けされました！");


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ 整理中にエラーが発生しました: ${error.message}`);
        }
    }
};


// ==========================================
// 🚀 コマンドライン引数の解析ロジック
// ==========================================
// Node.jsの argv には以下の順番でデータが入っています
// argv[0]: nodeコマンドのパス
// argv[1]: 実行されているプログラムファイルのパス
// argv[2]: ユーザーが後ろに入力した最初の引数（＝お片付けしたいフォルダーのパス）
const targetPath: string | undefined = argv[2];


if (!targetPath) {
    console.log("⚠️  使用方法が間違っています！");
    console.log("以下のように、後ろに整理したいフォルダーのパスを半角スペース空けで指定してください。");
    console.log("例: node --experimental-strip-types fs-file-organizer.ts ./my-folder");
} else {
    // ユーザーがパスを無事に渡してくれた場合のみ、メイン処理を起動
    organizeFolder(targetPath);
}



リスト4-10
import { writeFile } from "node:fs/promises";


const fileName: string = "output.txt";
const content: string = "こんにちは！TypeScriptの世界へようこそ。\nこれは writeFile で書き込まれた文章です。";


const writeTextFile = async (path: string, text: string): Promise<void> => {
    try {
        console.log(`[書き込み] ${path} へのファイル作成を開始します...`);


        // ファイルへの書き込みを実行（文字コードとして 'utf-8' を明記するのがプロの作法）
        await writeFile(path, text, "utf-8");


        console.log("🎉 ファイルの書き込みが正常に完了しました！");
    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ 書き込み中にエラーが発生しました: ${error.message}`);
        }
    }
};


// 関数を実行する
writeTextFile(fileName, content);



リスト4-11
import { appendFile } from "node:fs/promises";


const targetFile: string = "output.txt";
const logMessage: string = "\n[追記データ] 2026-05-28: 新しいシステムログが追加されました。";


const appendTextFile = async (path: string, text: string): Promise<void> => {
    try {
        console.log(`[追記] ${path} の末尾にデータを追加します...`);


        // ファイルの末尾にテキストを安全に追記する
        await appendFile(path, text, "utf-8");


        console.log("📝 データの追記が正常に完了しました！");
    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ 追記中にエラーが発生しました: ${error.message}`);
        }
    }
};


// 関数を実行する
appendTextFile(targetFile, logMessage);



リスト4-12
import { readFile } from "node:fs/promises";


const targetFile: string = "output.txt";


const readAllText = async (path: string): Promise<void> => {
    try {
        console.log(`[読込] ${path} の中身を一括で読み込みます...`);


        // ファイルを一括で読み込む（★重要：第2引数の 'utf-8' を忘れないこと！）
        const fileContent: string = await readFile(path, "utf-8");


        console.log("--- 📄 ファイルの中身ここから ---");
        console.log(fileContent);
        console.log("--- 📄 ファイルの中身ここまで ---");


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ 読み込み中にエラーが発生しました: ${error.message}`);
        }
    }
};


// 関数を実行する
readAllText(targetFile);



リスト4-13
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline/promises";


const giantFile: string = "output.txt"; // 今回はテストとして同じファイルを使います


const readLineByLine = async (path: string): Promise<void> => {
    try {
        console.log(`[ストリーム読込] ${path} を1行ずつ丁寧に解析します...`);


        // 1. ファイルからデータを少しずつ流し出す「ストリーム（水路）」を開く
        const fileStream = createReadStream(path, "utf-8");


        // 2. 開いた水路を readline に接続して、1行ずつ区切ってくれるマシーンを作る
        const lineReader = createInterface({
            input: fileStream,
            crlfDelay: Infinity // 改行コードの違いを吸収する設定
        });


        let lineCount: number = 1;


        // 3. 【ここが核心！】for await...of 構文で、1行届くたびにループを回す
        for await (const line of lineReader) {
            console.log(`[${lineCount}行目]: ${line}`);
            lineCount++;
        }


        console.log("✨ 全ての行の読み込みが完了しました！");


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ 読み込み中にエラーが発生しました: ${error.message}`);
        }
    }
};


// 関数を実行する
readLineByLine(giantFile);



リスト4-14
// 1. プログラム内で扱う立体的なオブジェクト（データ）
const userObj = {
    id: 101,
    name: "ひなた",
    skills: ["TypeScript", "Node.js"]
};


console.log("--- 1. 元のオブジェクト（メモリ内のデータ） ---");
console.log(userObj);
console.log(`データの型: ${typeof userObj}`); // object
console.log(`名前へのアクセス: ${userObj.name}`); // ひなた


console.log("\n--- 2. JSON.stringify で【文字列】に変換 ---");
// オブジェクトをJSON文字列に変換（第2,3引数は整形するための設定）
const jsonString: string = JSON.stringify(userObj, null, 2);


console.log(jsonString);
console.log(`変換後の型: ${typeof jsonString}`); // string
// 💡 文字列のため、特定の値だけを取り出すことはできません


console.log("\n--- 3. JSON.parse で【オブジェクト】に逆復元 ---");
// JSON文字列を、再びプログラムで扱えるオブジェクトに戻す
const parsedObj = JSON.parse(jsonString);


console.log(parsedObj);
console.log(`復元後の型: ${typeof parsedObj}`); // object
console.log(`復元データへのアクセス: ${parsedObj.name}`); // ひなた

リスト4-15
import { writeFile, readFile } from "node:fs/promises";


const jsonFilePath: string = "tasks.json";


// タスク1件の形を定義する仕様書（インターフェース）
interface Task {
    id: number;
    title: string;
    isCompleted: boolean;
}


const runJsonDemo = async (): Promise<void> => {
    try {
        // --- 🧪 初期データの準備（3件のタスクオブジェクトの配列） ---
        const initialTasks: Task[] = [
            { id: 1, title: "第4章の原稿を書く", isCompleted: false },
            { id: 2, title: "犬の散歩に行く", isCompleted: true },
            { id: 3, title: "スーパーで買い物", isCompleted: false }
        ];


        // 1. 【書き込み】オブジェクト配列をJSON文字列にしてファイルへ保存
        console.log("① タスクリストを tasks.json に保存します...");
        const jsonText = JSON.stringify(initialTasks, null, 2);
        await writeFile(jsonFilePath, jsonText, "utf-8");
        console.log("👉 保存完了しました。\n");


        // 2. 【読み込み】ファイルからJSON文字列を読み込んでオブジェクトに復元
        console.log("② tasks.json からデータを読み込んで復元します...");
        const fileRawText = await readFile(jsonFilePath, "utf-8");
        // 💡 読み込んだデータを「Task型の配列」として扱うよう明示（型アサーション）
        const tasks: Task[] = JSON.parse(fileRawText) as Task[];
        console.log(`👉 成功。現在 ${tasks.length} 件のタスクがメモリに載っています。\n`);


        // 3. 【検索（Find）】特定の条件に合うデータを1件見つける
        console.log("③ [データ検索] IDが 2 のタスクをピンポイントで探します...");
        const foundTask = tasks.find((t) => t.id === 2);
        if (foundTask) {
            console.log(`🔍 発見しました! タイトル:「${foundTask.title}」（完了状態: ${foundTask.isCompleted}）\n`);
        }


        // 4. 【追加（Create）】配列に新しいタスクデータをプッシュする
        console.log("④ [データ追加] 新しいタスク「TypeScriptの勉強」を追加します...");
        const newTask: Task = {
            id: 4,
            title: "TypeScriptの勉強",
            isCompleted: false
        };
        tasks.push(newTask); // メモリ上の配列の末尾に追加
        console.log(`👉 追加完了。総数が ${tasks.length} 件になりました。\n`);


        // 5. 【削除（Delete）】条件に合わないもの「だけ」を残してファイルをフィルタリングする
        console.log("⑤ [データ削除] すでに完了している（isCompletedがtrue）タスクをすべて消去します...");
        // 配列の filter メソッドを使い、「完了していない（false）のものだけ」で新しい配列を作る
        const filteredTasks = tasks.filter((t) => t.isCompleted === false);
        console.log(`👉 削除完了。残ったタスクは ${filteredTasks.length} 件です。\n`);


        // 6. 【最終保存】編集が終わった最新の配列を、再び上書き保存する
        console.log("⑥ 最終的な最新データを tasks.json に上書き保存して同期します...");
        const finalJsonText = JSON.stringify(filteredTasks, null, 2);
        await writeFile(jsonFilePath, finalJsonText, "utf-8");
        console.log("✨ すべてのデータ操作が安全に完了しました！ファイルを確認してください。");


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ エラーが発生しました: ${error.message}`);
        }
    }
};


// 実行する
runJsonDemo();



リスト4-16
________________


TypeScriptを用いて、JSONファイルを疑似的なデータベースとして扱うための、カプセル化されたクラス、およびその動作をテストするメインロジックを実装してください。


【動作環境・制約条件】
1. ランタイム: Node.js (node:fs/promises などの組み込みモジュールを使用)
2. TypeScriptのトランスパイル方式: 「strip-only mode（型情報の単純除去）」で動作するよう、コンストラクタでの型定義ショートハンド（private filePath: string のような記述）は使わず、プロパティの宣言とコンストラクタ内での代入を行ってください。
3. 日本語のコメントと言語表現: 各処理には、何をしているのか初心者でもわかるように日本語のコメントを添えてください。
4. コードファイルの作成: 生成されたコードは、「json-db.ts」という名前のファイルに保存してください。


【仕様詳細】
1. データ構造の定義 (Userインターフェース)
以下のプロパティを持つ User インターフェースを定義してください。
- id: string
- name: string
- email: string


2. データベースクラスの定義 (JsonUserDatabase)
外部から直接内部のデータを触らせないようカプセル化したクラスを実装してください。


【プロパティ】
- filePath (private, string) : 保存先JSONファイルのパス
- usersMemory (private, User[]) : メモリ上に展開するユーザーデータの配列


【コンストラクタ】
- 引数で filePath を受け取り、内部プロパティに代入する。
- usersMemory は空の配列 [] で初期化する。


【メソッド】
- init(): Promise<void> (public, 非同期)
node:fs の existsSync を使用して、指定された filePath が存在するかチェックする。
- saveToFile(): Promise<void> (private, 非同期)
現在の usersMemory をインデント2個のJSON文字列に変換し、filePath に上書き保存する。
- getAllUsers(): User[] (public)
外部で元の配列が直接破壊されないよう、スプレッド構文などを使って usersMemory のシャローコピーを返す。
- findUserById(id: string): User | undefined (public)
配列の find メソッドを使い、指定されたIDに合致するユーザーを1件返却する。
- registerUser(newUser: User): Promise<void> (public, 非同期)
ユーザーを追加し、saveToFile() を呼んで永続化する。
- removeUser(id: string): Promise<void> (public, 非同期)
対象IDを削除し永続化する。
________________






リスト4-17
import { writeFile, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";


// ==========================================
// 1. データの構造を定義する（仕様書）
// ==========================================
interface User {
    id: string;
    name: string;
    email: string;
}


// ==========================================
// 2. データベースを司る「カプセル化クラス」の定義
// ==========================================
class JsonUserDatabase {
    // 💡 外部から直接触らせない、クラス内部専用のプロパティ（プライベート変数）
    private filePath: string;
    private usersMemory: User[];


    // コンストラクタ（strip-only modeに対応）
    constructor(filePath: string) {
        this.filePath = filePath;
        this.usersMemory = []; // 最初は空の配列で初期化
    }


    /**
     * 【初期化メソッド】ファイルが存在すれば読み込み、なければ空のファイルを作る
     */
    public async init(): Promise<void> {
        try {
            // 同期関数の existsSync を使い、ファイルがこの世にあるか一瞬でチェック
            if (!existsSync(this.filePath)) {
                console.log(`[DB準備] ${this.filePath} が無いため、新規データベースファイルを生成します。`);
                // 空の配列（[]）のJSONを最初に書き込んでおく
                await writeFile(this.filePath, JSON.stringify([], null, 2), "utf-8");
            }


            // ファイルから生のテキストを吸い上げる
            const rawData = await readFile(this.filePath, "utf-8");
            // データをパースして、クラスが内蔵するメモリ領域（usersMemory）にガッチリ格納
            this.usersMemory = JSON.parse(rawData) as User[];
            console.log(`[DB起動] ${this.usersMemory.length} 件のユーザーデータを正常にロードしました。`);
        } catch (error) {
            throw new Error(`データベースの初期化に失敗しました: ${error}`);
        }
    }


    /**
     * 【裏方メソッド】現在のメモリの状態を、自動的にJSONファイルへ同期保存する）
     */
    private async saveToFile(): Promise<void> {
        const jsonText = JSON.stringify(this.usersMemory, null, 2);
        await writeFile(this.filePath, jsonText, "utf-8");
    }


    /**
     * 【全件取得】現在登録されているユーザーの配列をすべて返す
     */
    public getAllUsers(): User[] {
        // 元の配列が外部で直接破壊されないよう、シャローコピー（浅いコピー）して返すのがプロの防衛策
        return [...this.usersMemory];
    }


    /**
     * 【データ検索】IDをキーにして、合致するユーザーを1件返す
     */
    public findUserById(id: string): User | undefined {
        return this.usersMemory.find((user) => user.id === id);
    }


    /**
     * 【データ追加】新しいユーザーを追加しファイル保存まで行う
     */
    public async registerUser(newUser: User): Promise<void> {
        // 重複チェックのエラーハンドリング
        const isDuplicate = this.usersMemory.some((user) => user.id === newUser.id);
        if (isDuplicate) {
            throw new Error(`❌ エラー: ID「${newUser.id}」は既に登録されています。`);
        }


        // メモリ配列への追加
        this.usersMemory.push(newUser);


        // 自動的にハードディスクへ書き込み保存！
        await this.saveToFile();
        console.log(`👤 ユーザー「${newUser.name}」をDBに永続化保存しました。`);
    }


    /**
     * 【データ削除】指定されたIDのユーザーを削除し、ファイルを保存
     */
    public async removeUser(id: string): Promise<void> {
        const targetUser = this.findUserById(id);
        if (!targetUser) {
            throw new Error(`❌ エラー: 削除対象のID「${id}」が見つかりません。`);
        }


        // 条件に合わないユーザーだけで配列を再構成
        this.usersMemory = this.usersMemory.filter((user) => user.id !== id);


        // 自動的にハードディスクへ最新状態を上書き保存
        await this.saveToFile();
        console.log(`🗑️ ID「${id}」（${targetUser.name}）のデータをDBから完全に抹消しました。`);
    }
}


// ==========================================
// 3. メイン実行ロジックでの実戦テスト
// ==========================================
const mainSystem = async (): Promise<void> => {
    // データベースクラスのインスタンスを生成
    const db = new JsonUserDatabase("users-db.json");


    try {
        // ① データベースの起動・ロード
        await db.init();


        console.log("\n--- 🛠️ テスト1: ユーザーの新規登録（永続化） ---");
        await db.registerUser({ id: "U001", name: "アリス", email: "alice@example.com" });
        await db.registerUser({ id: "U002", name: "ボブ", email: "bob@example.com" });
        await db.registerUser({ id: "U003", name: "キャロル", email: "carol@example.com" });


        console.log("\n--- 🛠️ テスト2: 全件取得して現在の状態を表示 ---");
        const allUsers = db.getAllUsers();
        console.log("現在の全ユーザーリスト:", allUsers);


        console.log("\n--- 🛠️ テスト3: データの特定検索 ---");
        const searchId = "U002";
        const target = db.findUserById(searchId);
        if (target) {
            console.log(`🔍 ID [${searchId}] の検索結果 ➔ 名前: ${target.name}, メール: ${target.email}`);
        }


        console.log("\n--- 🛠️ テスト4: 重複エラーの鉄壁ディフェンステスト ---");
        // あえて同じIDで登録を試みる
        await db.registerUser({ id: "U001", name: "偽アリス", email: "fake@example.com" });


    } catch (error) {
        if (error instanceof Error) {
            console.error(error.message);
        }
    }


    try {
        console.log("\n--- 🛠️ テスト5: データの安全削除 ---");
        await db.removeUser("U003"); // キャロルを削除
        
        console.log("最終的なDBの中身一覧:", db.getAllUsers());


    } catch (error) {
        if (error instanceof Error) {
            console.error(error.message);
        }
    }
};


// システムを起動する
mainSystem();



リスト4-18
[
  {
    "id": "U001",
    "name": "アリス",
    "email": "alice@example.com"
  },
  {
    "id": "U002",
    "name": "ボブ",
    "email": "bob@example.com"
  }
]



リスト5-1
________________


Node.js（TypeScript）で、インターネットからデータを1件取得して表示するシンプルなプログラムを作ってください。生成コードはexample.tsに上書きしてください。コード内には初心者向けの丁寧な日本語コメントを入れてください。


【要件】
1. 通信先として「https://jsonplaceholder.typicode.com/posts/1」をfetchで呼び出す、非同期の関数（getSinglePost）を作る。


2. 変数や関数の戻り値には、TypeScriptらしくしっかり型（型定義）をつけてください。


3. 処理の流れを try-catch で囲み、以下の順番で動くようにする。
   ① 「アクセス開始」と画面に出す
   ② データを取得し、成功したか（response.ok）やステータスコードを画面に出す
   ③ もし失敗していたら、エラーを投げて中断する
   ④ 成功していたらデータをJSONとして読み込み、データ全体と、その中のタイトル（postData.title）を画面に出す
   ⑤ 失敗時の処理（catch）では、エラーメッセージを画面に赤字（console.error）で出す


4. 最後に、この関数（getSinglePost）を実行するコードを書く。
________________






リスト5-2
// Node.js v18なら、 fetch はインポートしなくてもOK
const TARGET_URL: string = "https://jsonplaceholder.typicode.com/posts/1";


const getSinglePost = async (): Promise<void> => {
    try {
        console.log(`🌐 1. ${TARGET_URL} へアクセスを開始します...`);


        // fetch関数を呼び出してリクエストを送信
        const response: Response = await fetch(TARGET_URL);


        console.log(`📡 2. サーバーからレスポンスを受信しました。`);
        console.log(`   ・ステータスコード: ${response.status}`); // 200 なら成功
        console.log(`   ・通信成功フラグ (ok): ${response.ok}`);   // true/false


        // 3. 通信が成功したかをチェック
        if (!response.ok) {
            throw new Error(`サーバーエラーが発生しました。ステータス: ${response.status}`);
        }


        // 4. レスポンスの中身をJSONオブジェクトとして解析・復元する
        console.log(`⚡ 3. 届いたデータをJSONとしてパース（解体）します...`);
        const postData = await response.json();


        console.log("\n✨ === [取得成功したデータの内容] ===");
        console.log(postData);
        console.log(`・記事タイトル: ${postData.title}`);


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ 通信に失敗しました: ${error.message}`);
        }
    }
};


// 実行する
getSinglePost();



リスト5-3
________________


前回作った「データを1件取るプログラム」をベースにして、今回は「条件を指定して複数のデータをまとめて取るプログラム」に書き換えてください。コードには初心者向けの日本語コメントを入れてください。


【変更・追加すること】
1. ベースとなるURLを「https://jsonplaceholder.typicode.com/posts」に変更する。
2. 関数名を「getFilteredPosts」に変更する。
3. 前回同様、変数や戻り値の型定義、try-catchでのエラー対策はしっかり残す。


【新しい処理の流れ】
① 検索条件（URLSearchParams）を作る
   - 以下の2つの条件を設定するコードとコメントを書いてください。
     ・ユーザーID（userId）が 「1」
     ・取得件数（_limit）が最大「3」件


② 最終的なURLを組み立てる
   - ベースのURLと上記の検索条件を「?」で繋いで、1つのURL（finalUrl）を作る。
   - 画面に「組み立てたURL：〜」「指定された条件で通信を開始します...」とログを出す。


③ データを「配列」として受け取る
   - 組み立てたURLにfetchでアクセスし、エラーチェックをする。
   - 今回は複数データが返ってくるため、データを「配列（any[]）」として受け取る。
   - 画面に「条件に合致するデータを◯件取得しました。」と出す。


④ データを1件ずつバラして画面に出す
   - 「for...of文」を使って、取得したデータ（配列）をループ処理する。
   - 1件ずつ、以下の形式で画面に出力する。
     「- [ID: データのid] タイトル: データのtitle (作者ID: データのuserId)」


4. 最後に、この新しい関数（getFilteredPosts）を実行する。
________________






リスト5-4
const BASE_URL: string = "https://jsonplaceholder.typicode.com/posts";


const getFilteredPosts = async (): Promise<void> => {
    try {
        // 1. URLSearchParamsのインスタンスを生成し検索条件を設定する
        const params = new URLSearchParams();
        params.append("userId", "1");   // 「ユーザーIDが 1 のデータ」
        params.append("_limit", "3");   // 「最大 3 件まで取得する」


        // 2. ベースURLとパラメータを合体させて、安全な最終URLを作る
        // params.toString() の結果 ➔ "userId=1&_limit=3"
        const finalUrl: string = `${BASE_URL}?${params.toString()}`;
        
        console.log(`構築された安全なURL: ${finalUrl}`);
        console.log("指定された条件で通信を開始します...");


        // 3. 通信を実行
        const response: Response = await fetch(finalUrl);


        if (!response.ok) {
            throw new Error(`サーバーエラー: ${response.status}`);
        }


        // 4. 配列データとしてパース
        const posts = await response.json() as any[];
        
        console.log(`\n条件に合致するデータを ${posts.length} 件取得しました。`);
        
        // 配列の中身をループで綺麗に表示
        for (const post of posts) {
            console.log(`- [ID: ${post.id}] タイトル: ${post.title} (作者ID: ${post.userId})`);
        }


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ パラメータ通信失敗: ${error.message}`);
        }
    }
};


// 実行する
getFilteredPosts();



リスト5-5
{
  "dependencies": {
    "linkedom": "^0.18.2"
  }
}



リスト5-6
________________


TypeScriptとLinkedomを使って、NHKの主要ニュースRSSを取得して表示するプログラムを作ってください。作成したコードはexample.tsに上書きしてください。


【要件】
1. NHKのRSS（https://news.web.nhk/n-data/conf/na/rss/cat0.xml）からニュースデータをfetchで取得する。
（※注意：普通にfetchすると文字化け（圧縮問題）が起きるため、ヘッダーに "Accept-Encoding": "identity" を指定して文字化けを対策してください。）


2. 取得したXMLデータを、LinkedomのDOMParser（"text/xml"）で読み込む。


3. XML内にある「item」タグをループ処理（querySelectorAllとforEach）して、最新の10件を取り出す。
（※注意：Linkedomのバージョン違いで型エラーが起きるのを防ぐため、ループの引数（elementやindex）には型を付けずに省略してください。）


4. 各記事から「タイトル（title）」「リンク（link）」「公開日時（pubDate）」を抜き出す。
   - 各要素のテキストは .textContent を使って取得し、前後の余計な余白は削除してください。
   - 日時は日本時間（ja-JP）の見やすい形式に変換してください。


5. 最後に、コンソールに「第◯位」「題名：〜」「住所：〜」という形で日本語コメント付きで出力してください。
________________






リスト5-7
import { DOMParser } from "linkedom";


// NHKニュースの公式主要ニュースRSS（XML）のURL
const NHK_RSS_URL: string = 
    "https://news.web.nhk/n-data/conf/na/rss/cat0.xml";


const readLatestNews = async (): Promise<void> => {
    try {
        console.log("📡 1. NHKニュースの最新フィード（XML）をダウンロード中...");
        
        // 前節で学ばれた fetch API でXMLテキストを丸ごと取得
        const response: Response = await fetch(NHK_RSS_URL, {
            headers: {
                "Accept-Encoding": "identity" // 圧縮を拒否
            }
        });
        if (!response.ok) {
            throw new Error(`ニュースの取得に失敗しました。ステータス: ${response.status}`);
        }


        // 💡 レスポンスを「テキスト文字列」として受け取る
        const xmlText: string = await response.text();


        console.log("🔍 2. LinkedomでXMLデータを解析しています...");
        // 💡 DOMParserを使ってXMLモードでロード
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlText, "text/xml");


        console.log("\n=======================================================");
        console.log("🇯🇵 【NHK最新ニュース・ヘッドライン】");
        console.log("=======================================================\n");


        // RSS 2.0 規格では、記事1件ごとに <item> タグで囲まれています
        const itemElements = doc.querySelectorAll("item");


        if (itemElements.length === 0) {
            console.log("記事が見つかりませんでした。XMLの構造が変わった可能性があります。");
            return;
        }


        // 見つかった記事を上から順番にループ処理（最新10件に制限）
        itemElements.forEach((element, index) => {
            if (index >= 10) return; // 10件処理したらこれ以降は何もしない


            // 💡 現在の記事の塊（element）の中から、必要なパーツを狙い撃ちして抽出
            const title: string = element.querySelector("title")?.textContent?.trim() || "タイトル不明";
            const link: string = element.querySelector("link")?.textContent?.trim() || "";
            const pubDate: string = element.querySelector("pubDate")?.textContent?.trim() || "";


            // 読みやすくするために日時の表示を少し整形
            // ※ここでは生データを綺麗に出力します
            const cleanDate: string = new Date(pubDate).toLocaleString("ja-JP");


            // コンソールに見やすくフォーマットして出力
            console.log(`🔥 [第 ${index + 1} 位]  ${cleanDate}`);
            console.log(`📌 題名: ${title}`);
            console.log(`🔗 住所: ${link}`);
            console.log("-------------------------------------------------------");
        });


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ ニュース取得エラー: ${error.message}`);
        }
    }
};


// 起動
readLatestNews();



リスト5-8
________________


TypeScriptとLinkedomを使って、ZennのRSSから「自分の気になるキーワード」が含まれる記事だけを自動で探して表示するプログラムを作ってください。生成されたコードはexample.tsを上書きして保存してください。コードには初心者向けの日本語コメントを入れてください。


【要件】
1. ZennのRSS（https://zenn.dev/feed）からデータをfetchで取得する。
  （※注意：文字化けを防ぐため、ヘッダーに "Accept-Encoding": "identity" を必ず指定してください。）


2. 自分が毎朝チェックしたい「関心のあるキーワード」のリスト（例: "typescript", "node", "ai", "next.js" など）を配列で用意する。


3. 取得したXMLデータを、LinkedomのDOMParser（"text/xml"）で読み込む。


4. 処理の流れと条件（ループ処理）
   - 「item」タグをループ処理（querySelectorAllとforEach）で1件ずつ調べる。
   - （※注意：Linkedomのバージョン違いで型エラーが起きるのを防ぐため、ループの引数（elementやindex）には型を付けずに省略してください。）
   - 各記事から「タイトル（title）」「リンク（link）」「執筆者（dc:creator、なければ匿名ユーザー）」を抜き出す。
   - 執筆者タグは特殊な名前空間（dc:creator）が使われているため、getElementsByTagName("dc:creator") などを用いて工夫して取得してください。


5. キーワードが含まれているかの判定
   - 大文字と小文字が混ざっていても正しく判定できるよう、一度タイトルをすべて小文字（.toLowerCase()）に変換してから、自分のキーワードが含まれているか調べる。
   - 配列の便利なメソッド（.someなど）を使って、設定したキーワードのどれか1つでもタイトルに含まれているかをスマートに判定する。


6. 画面への出力
   - キーワードが見つかった記事だけをきれいに整形して出力する。
________________






リスト5-9
import { DOMParser } from "linkedom";


const ZENN_FEED_URL: string = "https://zenn.dev/feed";


// 💡 自分が毎朝チェックしたい「関心のあるキーワード」のリスト
const WATCH_KEYWORDS: string[] = ["typescript", "node", "ai", "next.js"];


const watchTechnicalTrend = async (): Promise<void> => {
    try {
        console.log(`📡 1. Zennの最新トレンドフィードを取得中...`);
        
        const response: Response = await fetch(ZENN_FEED_URL, {
            headers: {
                "Accept-Encoding": "identity" // 圧縮を拒否
            }
        });
        if (!response.ok) throw new Error(`通信失敗: ${response.status}`);


        const xmlText: string = await response.text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlText, "text/xml");


        console.log(`🔍 2. 記事の解析を開始。設定キーワード: [${WATCH_KEYWORDS.join(", ")}]`);
        console.log("\n=======================================================");
        console.log("🚀 【Zenn技術トレンド・キーワード特報】");
        console.log("=======================================================\n");


        let matchCount: number = 0;


        // ZennのRSSフィードを1件ずつスキャン
        const itemElements = doc.querySelectorAll("item");
        itemElements.forEach((element, index) => {
            const title: string = element.querySelector("title")?.textContent?.trim() || "タイトル不明";
            const link: string = element.querySelector("link")?.textContent?.trim() || "";
            
            // 💡 Zenn特有の作者名タグ（dc:creator）対策
            // Linkedom（DOM標準）の仕様に合わせ、getElementsByTagName を使って正確に作者タグを狙い撃ちします
            const creatorElement = element.getElementsByTagName("dc:creator")[0];
            const creator: string = creatorElement?.textContent?.trim() || "匿名ユーザー";


            // 💡 判定を正確にするため、記事タイトルを小文字に変換
            const lowerTitle: string = title.toLowerCase();


            // 設定したキーワードのどれか1つでもタイトルに含まれているか判定
            // Array.prototype.someは、条件に1つでも合致すればtrueを返す
            const isTargetKeywordFound: boolean = WATCH_KEYWORDS.some((keyword) => {
                return lowerTitle.includes(keyword.toLowerCase());
            });


            // キーワードがヒットした場合のみ出力する
            if (isTargetKeywordFound) {
                matchCount++;
                console.log(`🚨 [検知 ${matchCount} 件目]`);
                console.log(`📦 タイトル: ${title}`);
                console.log(`👤 執筆者  : ${creator}`);
                console.log(`🔗 記事URL : ${link}`);
                console.log("-------------------------------------------------------");
            }
        });


        if (matchCount === 0) {
            console.log("☕ 本日のトレンドには、指定されたキーワードに一致する記事はありませんでした。");
        } else {
            console.log(`\n🎉 スキャン完了！ 本日は ${matchCount} 件の重要技術トピックが検挙されました。`);
        }


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ トレンドチェックエラー: ${error.message}`);
        }
    }
};


// 起動
watchTechnicalTrend();



リスト5-10
{
  "dependencies": {
    "@google/genai": "^2.7.0",
    "cheerio": "^1.2.0"
  }
}



リスト5-11
"type": "module",



リスト5-12
import { GoogleGenAI } from "@google/genai";


const askGeminiBasic = async (): Promise<void> => {
    try {
        console.log("🤖 1. Gemini API クライアントを初期化中...");


        // GoogleGenAIクラスのインスタンスを作成
        // APIキーは環境変数から取得します。
        const ai = new GoogleGenAI({
            apiKey: process.env['GEMINI_API_KEY'],
        });


        const prompt: string = "TypeScriptが他のプログラミング言語と比べて優れている点を、一言で最高にカッコよく教えてください。";
        console.log(`💬 2. Geminiにプロンプトを送信します: 「${prompt}」`);


        // gemini-3.5-flashでコンテンツを生成
        const response = await ai.models.generateContent({
            model: "gemini-3.5-flash",
            contents: prompt
        });


        console.log("\n✨ === [Geminiからの回答] ===");
        // response.text プロパティに、AIが考えてくれた文章が文字列として入っています
        console.log(response.text);
        console.log("=============================\n");


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ Geminiとの通信に失敗しました: ${error.message}`);
            console.error("※ APIキーが正しく環境変数に設定されているか確認してください。");
        }
    }
};


// 実行
askGeminiBasic();



リスト5-13
________________


TypeScriptとGeminiの新しいSDK（@google/genai）、そしてLinkedomを使って、海外の最新技術ニュースを自動で取得して日本語で要約するプログラムを作ってください。コードには丁寧な日本語コメントを入れてください。作成したコードは、example.tsを上書きして保存してください。


【要件】
1. 海外 of 技術ニュースRSS（https://news.ycombinator.com/rss）からデータをfetchで取得する。
（※注意：文字化けを防ぐため、ヘッダーに "Accept-Encoding": "identity" を必ず指定してください。）


2. 取得したXMLデータを、LinkedomのDOMParser（"text/xml"）で読み込む。


3. XML内にある「item」タグから、一番最新の記事を1件だけ取得する（querySelector("item")を使用）。
   - 記事から「タイトル（title）」と「リンク（link）」のテキストを .textContent を使って抜き出す。


4. Gemini API（モデル名: gemini-2.5-flash）を起動する。
   - AIへの役割指示（systemInstruction）として以下を設定してください。
     + 「あなたはプロのITコンサルタントです」というキャラクター。
     + 渡された英語のタイトルを「日本語訳」する。
     + ニュースの重要性を「3行の箇条書き（1行50文字以内）」の日本語で要約する。
     + 余計な挨拶や前置きは一切出力しない。


5. 画面への出力
   - 進行状況がわかるログ（📡 1. ダウンロード中... など）を適宜挟んでください。
   - 最後に、Geminiが生成したレポート内容と、元記事のリンクURLを画面に綺麗に出力してください。
________________






リスト5-14
import { DOMParser } from "linkedom";
import { GoogleGenAI } from "@google/genai";


// 海外の最新技術ニュースサイト（Hacker News）のRSS
const TECH_NEWS_URL: string = "https://news.ycombinator.com/rss";


const catchTechnicalTrend = async (): Promise<void> => {
    try {
        console.log("📡 1. 海外の最新技術フィード（XML）をダウンロード中...");
        
        const response: Response = await fetch(TECH_NEWS_URL, {
            headers: {
                "Accept-Encoding": "identity" // 文字化け・圧縮対策
            }
        });
        if (!response.ok) throw new Error(`通信失敗: ${response.status}`);


        const xmlText: string = await response.text();


        console.log("🔍 2. データを解析し、最新記事をピックアップしています...");


        // 💡 LinkedomでXMLデータをパースする
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlText, "text/xml");


        // 💡 最初の <item> タグ（最新記事）を1件だけ狙い撃ちで取得
        const firstItem = doc.querySelector("item");
        if (!firstItem) {
            console.log("❌ 記事が見つかりませんでした。");
            return;
        }


        // 💡 記事の中からタイトルとリンクのテキストを抜き出す
        const title: string = firstItem.querySelector("title")?.textContent?.trim() || "タイトル不明";
        const link: string = firstItem.querySelector("link")?.textContent?.trim() || "";


        console.log(`📌 対象記事を発見:「${title}」`);
        console.log("🤖 3. Gemini APIを起動中... 翻訳と要約を開始します。");


        const ai = new GoogleGenAI({
            apiKey: process.env['GEMINI_API_KEY'],
        });


        const systemInstruction: string = 
            "あなたは技術トレンドに敏感なプロのITコンサルタントです。\n" +
            "提示された「英語のニュースタイトル」を元に、そのニュースがどのような技術的背景や内容であるかを予測・分析し、日本のエンジニア向けに分かりやすく解説してください。\n" +
            "【出力フォーマット条件】\n" +
            "・1行目：タイトルの日本語訳\n" +
            "・2行目以降：このニュースの概要や重要性を「3行の箇条書き」で、かつ1行あたり50文字以内の日本語で要約してください。\n" +
            "・余計な挨拶や前置きの文章は絶対に含めないでください。";


        console.log("📡 4. AIにデータを送信して、要約レポートを生成しています...");
        
        const responseAI = await ai.models.generateContent({
            model: "gemini-2.5-flash",
            contents: `以下の技術ニュースを解析してください:\n\nTitle: ${title}\nURL: ${link}`,
            config: {
                systemInstruction: systemInstruction,
                temperature: 0.3
            }
        });


        console.log("\n💡 === [Geminiが生成した技術トレンド特報] ===");
        console.log(responseAI.text);
        console.log("==================================================\n");
        console.log(`🔗 元記事を詳しく読む場合はこちら: ${link}`);


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ トレンドキャッチャーエラー: ${error.message}`);
        }
    }
};


// 起動
catchTechnicalTrend();



リスト5-15
________________


TypeScript（Node.js）で、指定フォルダー内のテキストファイルを全スキャンしてAIが自動要約する「ドキュメント自動スキャン＆Gemini要約システム」を作成してください。コードを作成したら、folder-summarizer.tsというファイル名で保存してください。


【動作仕様】
1. 引数で受け取った作業フォルダーの中身を readdir(withFileTypes: true) でスキャンします。
2. 拡張子が .txt のファイルだけを狙い撃ちしてフィルタリングします。
3. 各テキストファイルの中身を readFile で読み込みます。
4. 読み込んだ本文を Gemini API（gemini-2.5-flash） に送信し、「この文書の要約を3行の箇条書きの日本語で出力してください」と依頼します。
5. すべてのファイルの要約結果を1つの文章に綺麗に結合し、フォルダー内に summary_report.md というマークダウンファイルとして writeFile で一括保存します。
6. テストがしやすいよう、もしフォルダー内に .txt ファイルが1つもない場合は、自動的にダミーのテキストファイル（「日報.txt」「議事録.txt」など）を2つほどその場に自動生成する親切設計にしてください。


【技術的な条件】
1. @google/genai を使い、環境変数からAPIキーを読み込むこと。
2. 最新のNode.jsの strip-only mode で直接実行できるよう、TypeScript独自構文は使わず、純粋な型アノテーションのみで記述すること。
________________






リスト5-16
import { readdir, readFile, writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join, extname } from "node:path";
import { argv } from "node:process";
import { GoogleGenAI } from "@google/genai";
import type { Dirent } from "node:fs";


/**
 * テスト用のダミーテキストファイルを準備する関数
 * （不要なら削除可）
 */
const createDummyTextsIfEmpty = async (dirPath: string, textFiles: Dirent[]): Promise<void> => {
    if (textFiles.length > 0) return;


    console.log("ℹ️  テキストファイルが空のため、テスト用のダミー文書を2つ生成します...");
    const dummy1: string = "【2026年6月1日 営業部ミーティング議事録】\n出席者：田中、佐藤、鈴木\n議題：新規プロジェクトの進捗について\n決定事項：A社のシステム開発案件について、要件定義の締め切りを今月末まで延長することで顧客と合意した。";
    const dummy2: string = "【業務日報：高橋】\n本日の業務内容：午前中は既存顧客からの問い合わせ対応（バグ修正2件）。午後は新規開発中のCLIツールのリファクタリングを実施。";
    await writeFile(join(dirPath, "project_meeting.txt"), dummy1, "utf-8");
    await writeFile(join(dirPath, "daily_report_takahashi.txt"), dummy2, "utf-8");
};


/**
 * フォルダ内の全テキストファイルをスキャンして要約レポートを作るメイン関数
 */
const summarizeFolderDocs = async (dirPath: string): Promise<void> => {
    try {
        if (!existsSync(dirPath)) {
            console.error(`❌ エラー: フォルダ「${dirPath}」が存在しません。`);
            return;
        }


        // 1. フォルダ内の全アイテムをスキャン
        const items: Dirent[] = await readdir(dirPath, { withFileTypes: true });
        
        // 2. 拡張子が「.txt」のファイルだけを抽出
        let textFiles: Dirent[] = items.filter((item) => item.isFile() && extname(item.name).toLowerCase() === ".txt");


        // テスト用の救済措置（不要なら削除）
        await createDummyTextsIfEmpty(dirPath, textFiles);
        if (textFiles.length === 0) {
            // ダミーを作ったので再スキャン
            const reScannedItems = await readdir(dirPath, { withFileTypes: true });
            textFiles = reScannedItems.filter((item) => item.isFile() && extname(item.name).toLowerCase() === ".txt");
        }


        console.log(`📂 対象フォルダ: ${dirPath} 内に ${textFiles.length} 件のテキストファイルを発見。`);
        console.log("🤖 Gemini APIを起動します...");
        const ai = new GoogleGenAI();


        let reportMarkdown: string = `# 📑 AI自動ドキュメント要約レポート\n\n生成日時: ${new Date().toLocaleString("ja-JP")}\n\n---\n\n`;


        // 3. ループで1ファイルずつ読み込んでGeminiに要約させる
        for (const file of textFiles) {
            const filePath: string = join(dirPath, file.name);
            console.log(`📖 「${file.name}」を分析中...`);
            
            const content: string = await readFile(filePath, "utf-8");


            const response = await ai.models.generateContent({
                model: "gemini-2.5-flash",
                contents: `以下の文書の内容を読み、重要なポイントを3行の箇条書きの日本語で要約してください。余計な前置きは一切不要です。\n\n${content}`,
                config: { temperature: 0.2 }
            });


            // マップダウン形式のレポートに結果を追記していく
            reportMarkdown += `## 📄 ファイル名: ${file.name}\n\n`;
            reportMarkdown += `${response.text}\n\n`;
            reportMarkdown += `---\n\n`;
        }


        // 4. 完成した巨大なレポートをファイルに一括書き出し
        const reportPath: string = join(dirPath, "summary_report.md");
        await writeFile(reportPath, reportMarkdown, "utf-8");
        
        console.log(`\n🎉 【全自動要約完了】レポートが生成されました！`);
        console.log(`📥 格納先: ${reportPath}`);


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ エラーが発生しました: ${error.message}`);
        }
    }
};


// 引数から対象フォルダを受け取って起動
const folderArg = argv[2];
if (!folderArg) {
    console.log("⚠️  使用方法: node --experimental-strip-types ai-folder-summarizer.ts <対象フォルダ名>");
} else {
    summarizeFolderDocs(folderArg);
}



リスト5-17
________________


TypeScript（Node.js）とLinkedomを使って、複数のURLからHTMLを取得して本文を綺麗にマージする「複数Web記事の自動結合・整形・クレンジングスクリプト」を作成してください。コードはweb-article-merger.tsという名前のファイルとして保存してください。


【動作仕様】
1. プログラム内部に、解析対象とするWebページのURLの配列（複数）を定義すること。（今回はテスト用として、公開されているモックHTMLやシンプルなニュース風URLを想定）。
2. ループ処理の中で、各URLに対して fetch を行い、.text() で生HTML文字列を取得します。
3. LinkedomのDOMParser（"text/html"）を使ってHTMLをパースし、一般的な本文タグ（article タグ、または main タグ、無ければ body）の内側にあるテキストだけを .textContent を用いて抽出します。
4. 抽出した文章から、「連続する無駄な空行」や「前後の余計なスペース」を正規表現などを使って綺麗に消し去る（クレンジングする）関数を挟むこと。
5. すべてのURLから削りだした本文テキストの頭に「◆ 出典: URL」という見出しを添えて、1つの文字列に結合します。
6. 最終的な結合データを、現在のフォルダに combined_book.md という名前で writeFile を使って一括出力すること。


【技術的な条件】
- linkedom と Node.js標準の fs/promises を適切に組み合わせること。
- 最新のNode.jsの strip-only mode に準拠し、純粋な型アノテーションのみで記述すること。
- バージョン違いによる型エラーを防ぐため、LinkedomのDOM要素を受け取る変数やループ処理の引数には明示的な型を付けず、型推論に任せるか省略すること。
________________






リスト5-18
import { DOMParser } from "linkedom";
import { writeFile } from "node:fs/promises";


/**
 * 1. 解析対象とするWebページのURLリスト
 */
const TARGET_URLS: string[] = [
    "https://example.com/sample-article-1", // テスト用のページ
    "https://example.com/sample-article-2", // テスト用のページ
    "https://www.w3.org/Consortium/mission" // テスト用のページ
];


/**
 * 4. クレンジング関数
 * 連続する無駄な空行や、各行前後の余計なスペースを正規表現で除去します
 */
const cleanseText = (text: string): string => {
    return text
        .replace(/[ \t]+/g, " ")          // 連続するスペースやタブを1つの半角スペースに
        .replace(/\n\s*\n/g, "\n")       // 連続する空行（改行＋空白のみの行）を1つの改行に
        .split("\n")                     // 一旦行ごとに分割
        .map(line => line.trim())        // 各行の前後の空白を削除
        .filter(line => line.length > 0) // 空行を完全に排除
        .join("\n")                      // 再び結合
        .trim();                         // 全体の一番前後の空白を削除
};


/**
 * メインロジック: 複数URLの巡回、抽出、結合
 */
const startArticleMerging = async (): Promise<void> => {
    let combinedContent: string = "# 📚 複数Web記事の自動結合レポート\n\n";
    combinedContent += `生成日: ${new Date().toLocaleString("ja-JP")}\n\n---\n\n`;


    console.log("🚀 記事の自動結合・整形処理を開始します...");


    for (const url of TARGET_URLS) {
        try {
            console.log(`🌐 取得中: ${url}`);


            // 2. 各URLに対して fetch を行いHTMLを取得
            const response = await fetch(url);
            if (!response.ok) {
                console.error(`   ❌ 通信失敗: ${response.status} (${url})`);
                continue;
            }


            const htmlString = await response.text();




            // 3. LinkedomでHTMLをパース
            // Linkedomの型定義によるエラーを避けるため、DOM要素の変数は型推論に任せます
            const parser = new DOMParser();
            const doc = parser.parseFromString(htmlString, "text/html");




            // 本文タグ（article > main > body の優先順位）を特定
            const contentArea = doc.querySelector("article") || doc.querySelector("main") || doc.querySelector("body");




            if (!contentArea) {
                console.warn(`   ⚠️ 本文エリアが見つかりませんでした: ${url}`);
                continue;
            }


            // .textContent を用いてテキストを抽出し、クレンジングを適用
            const rawText = contentArea.textContent || "";
            const cleanText = cleanseText(rawText);


            // 5. 出典見出しを添えてマージ
            combinedContent += `◆ 出典: ${url}\n\n`;
            combinedContent += `${cleanText}\n\n`;
            combinedContent += "---\n\n";


        } catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            console.error(`   ❌ エラー発生 (${url}): ${message}`);
        }
    }


    // 6. 最終データを combined_book.md に一括出力
    await writeFile("combined_book.md", combinedContent, "utf-8");
    console.log("\n🎉 全ての処理が完了しました！");
    console.log("📥 保存先: combined_book.md");
};


startArticleMerging();



リスト5-19
________________


以下の要件を満たす、TypeScriptで記述された「AI駆動型テックトレンド分析・永続化システム」のコードを生成してください。DOMパーサーにはlinkedomを、Gemini利用には@google/genaiを使ってください。生成されたコードは、ultimate-trend-db.tsという名前のファイルとして保存してください。


【データ構造】
- 保存用ファイルパスの定数 DB_FILE_PATH = "tech_trend_db.json"
- 収集元URLの定数 ZENN_FEED_URL = "https://zenn.dev/feed"
- 以下のプロパティを持つインターフェース AnalyzedTrend を定義する。
    - id: string（記事の一意な識別子）
    - title: string（記事のタイトル）
    - aiPrediction: string（Geminiによる予測解説）
    - aiRank: string（Geminiの査定ランク: A / B / C）
   - analyzedAt: string（実行日時の文字列）


【データベースクラスの設計】
JSONファイルをストレージ、内部メモリ（配列）をキャッシュとする、カプセル化されたクラス TrendDatabase を作成してください。


- コンストラクタ: ファイルパスを受け取り、内部のメモリ用空配列を初期化する。
- init() メソッド: 非同期。ファイルが存在しなければ空配列（[]）のJSONファイルを新規作成。その後、ファイルを読み込んで内部メモリにデータを復元し、保管件数をログ出力する。
- save() メソッド: プライベートかつ非同期。現在の内部メモリの状態をフォーマットされたJSONとしてファイルに書き込む。
- has(id: string) メソッド: 既に同じIDがメモリ内に存在するかを boolean で返す（重複チェック用）。
- insert(item: AnalyzedTrend) メソッド: 非同期。メモリにデータを追加し、自動的に save() を呼び出して永続化する。


【メインクローラー処理のフロー】
全体の処理を try-catch で囲んだ非同期関数として実装し、以下のフローを実装してください。各所に適切な進捗ログを出力すること。


1. DB起動: TrendDatabase をインスタンス化して init() を実行。
2. フィード取得とパース: fetch() で ZENN_FEED_URL からXMLテキストを取得。linkedom の DOMParser を使って text/xml としてパースする。
3. Geminiの初期化: new GoogleGenAI() でインスタンスを作成。
4. 記事ループ処理: XML内の item タグをループでスキャンする。ただし、API制限を考慮して新規に分析・保存する件数は「最大3件」までとし、3件に達したらループを抜ける。
    - 各要素から title と link を取得。URLの末尾（最後のスラッシュ以降）を切り出して articleId とする。
    - 防衛ロジック: db.has(articleId) が true なら、AIの呼び出しを行わずにその記事を continue でスキップする。
    - Geminiによる分析: 以下のプロンプトを組み立て、モデル gemini-3.5-flash で temperature: 0.1 に設定してリクエストを送信する。
        + AIへの指示: 技術アナリストとして将来性やトレンド予測を2行の日本語で解説し、重要度をA～Cでランク付けする。
5. 完了・エラー処理: 新規に処理された件数が0件の場合と、データが蓄積された場合でログを出し分ける。また、catch節では instanceof Error で型ガードを行いエラーを出力する。
________________






リスト5-20
import { writeFile, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { DOMParser } from "linkedom";
import { GoogleGenAI } from "@google/genai";


// 収集元の技術フィードURL
const ZENN_FEED_URL: string = "https://zenn.dev/feed";
const DB_FILE_PATH: string = "tech_trend_db.json";


// 蓄積する分析データの型定義
interface AnalyzedTrend {
    id: string;          // 記事のユニークID（URLの末尾など）
    title: string;       // 記事タイトル
    aiPrediction: string;// Geminiによるトレンド予測・重要性の解説
    aiRank: string;      // Geminiが査定した注目度ランク (A / B / C)
    analyzedAt: string;  // 分析を実行した日時
}


/**
 * 【前章の集大成】JSONファイルをストレージとした、カプセル化されたトレンドデータベースクラス
 */
class TrendDatabase {
    private filePath: string;
    private memoryStorage: AnalyzedTrend[];


    // strip-only modeに対応するため、愚直な互換コンストラクタで宣言
    constructor(filePath: string) {
        this.filePath = filePath;
        this.memoryStorage = [];
    }


    /**
     * データベースの初期化
     */
    public async init(): Promise<void> {
        if (!existsSync(this.filePath)) {
            // ファイルがなければ空の配列のJSONを新規作成（writeFile）
            await writeFile(this.filePath, JSON.stringify([], null, 2), "utf-8");
        }
        // ファイルからデータを読み込んでメモリに復元（readFile + JSON.parse）
        const rawData = await readFile(this.filePath, "utf-8");
        this.memoryStorage = JSON.parse(rawData) as AnalyzedTrend[];
        console.log(`[DB] 📦 データベースロード完了。現在: ${this.memoryStorage.length} 件の分析データを保管中。`);
    }


    /**
     * メモリの最新状態をファイルへ保存するプライベートメソッド
     */
    private async save(): Promise<void> {
        await writeFile(this.filePath, JSON.stringify(this.memoryStorage, null, 2), "utf-8");
    }


    /**
     * すでに同じ記事がデータベースに存在するかチェックする
     */
    public has(id: string): boolean {
        return this.memoryStorage.some((item) => item.id === id);
    }


    /**
     * 新しいAI分析データをデータベースに登録（永続化）
     */
    public async insert(item: AnalyzedTrend): Promise<void> {
        this.memoryStorage.push(item);
        await this.save(); // 自動でファイルに書き込み
    }
}


// ==========================================
// 🏭 メインのAI駆動型クローラー処理
// ==========================================
const runUltimateTrendSystem = async (): Promise<void> => {
    try {
        console.log("=== 🚀 超高度AIテックトレンド分析システム 起動 ===");
        
        // 1. 自家製JSONデータベースの起動
        const db = new TrendDatabase(DB_FILE_PATH);
        await db.init();


        // 2. fetch + Linkedom で最新の技術記事一覧（XML）を取得
        console.log("\n📡 Zennの最新フィードを通信巡回中...");
        const response = await fetch(ZENN_FEED_URL);
        if (!response.ok) throw new Error("フィードの取得に失敗しました。");
        const xmlText = await response.text();
        
        const parser = new DOMParser();
        const doc = parser.parseFromString(xmlText, "text/xml");


        // 3. Gemini APIの初期化
        console.log("🤖 分析官 Gemini API を召喚しています...");
        const ai = new GoogleGenAI();


        let newItemsCount: number = 0;


        // RSSの各記事要素をループスキャン（今回は実演のため最新の「3件」に限定して処理）
        const itemElements = doc.querySelectorAll("item");
        
        for (let i = 0; i < itemElements.length; i++) {
            if (newItemsCount >= 3) break; // 新規3件の分析が完了したら終了


            const element = itemElements[i];
            const title: string = element.querySelector("title")?.textContent?.trim() || "タイトル不明";
            const link: string = element.querySelector("link")?.textContent?.trim() || "";
            
            // URLの末尾などからユニークなIDを即席作成
            const articleId: string = link.split("/").pop() || String(i);


            // 防衛ロジック：すでに保存済みならスキップ！
            if (db.has(articleId)) {
                continue;
            }


            newItemsCount++;
            console.log(`\n🔍 [新規記事検知 ${newItemsCount}件目]: 「${title}」`);
            console.log(`🧠 Geminiがこの技術の将来性を査定中...`);


            // 4. Geminiの知能を使って、記事タイトルから将来性と重要度ランクを分析させる
            const aiPrompt = 
                `プロの技術アナリストとして、以下の技術記事のタイトルから、` +
                `その技術が今後業界に与える影響やトレンド予測を2行の日本語で解説し、` +
                `最後にその重要度を【A：絶対読むべき】【B：余裕があれば】【C：スルーOK】のいずれか1文字の記号のみでランク付けしてください。\n\n` +
                `【出力フォーマット】\n` +
                `解説: (ここに解説)\n` +
                `ランク: (AまたはBまたはC)\n\n` +
                `対象タイトル: ${title}`;


            const aiResponse = await ai.models.generateContent({
                model: "gemini-2.5-flash",
                contents: aiPrompt,
                config: { temperature: 0.1 }
            });


            const aiResultText = aiResponse.text;


            // AIの出力テキストから簡易的に「解説」と「ランク」の文字を切り出す処理
            const lines = aiResultText.split("\n");
            const aiPrediction = lines.find(l => l.startsWith("解説:"))?.replace("解説:", "").trim() || aiResultText;
            const aiRank = lines.find(l => l.startsWith("ランク:"))?.replace("ランク:", "").trim() || "B";


            // 5. 型安全な立体オブジェクトを作成
            const newRecord: AnalyzedTrend = {
                id: articleId,
                title: title,
                aiPrediction: aiPrediction,
                aiRank: aiRank,
                analyzedAt: new Date().toLocaleString("ja-JP")
            };


            // 6. 自作データベースにインサート（自動ファイル同期）
            await db.insert(newRecord);
            console.log(`📥 DBへのデータ永続化が完了しました。 [査定ランク: ${aiRank}]`);
        }


        if (newItemsCount === 0) {
            console.log("\n✨ データベースはすでに最新です。新しく分析すべき未読記事はありませんでした。");
        } else {
            console.log(`\n🎉 本日の新規 ${newItemsCount} 件のAIトレンド分析が、無事に '${DB_FILE_PATH}' に蓄積されました！`);
        }


    } catch (error) {
        if (error instanceof Error) {
            console.error(`❌ システムエラー: ${error.message}`);
        }
    }
};


// システム起動
runUltimateTrendSystem();



リスト5-21
import { readFile } from "node:fs/promises";
import { existsSync } from "node:fs";


/**
 * データベースの保存先 (ultimate-trend-db.ts と同一)
 */
const DB_FILE_PATH = "tech_trend_db.json";


/**
 * データのインターフェース定義
 */
interface AnalyzedTrend {
    id: string;
    title: string;
    aiPrediction: string;
    aiRank: string;
    analyzedAt: string;
}


/**
 * 保存されたテックトレンドDBにアクセスし、検索またはランク抽出を行うメイン関数
 */
const accessTrendDb = async () => {
    try {
        // 1. ファイルの存在確認
        if (!existsSync(DB_FILE_PATH)) {
            console.error(`❌ エラー: データベースファイル '${DB_FILE_PATH}' が存在しません。`);
            console.log("先に 'ultimate-trend-db.ts' を実行してデータを蓄積してください。");
            return;
        }


        // 2. データの読み込み
        const rawData = await readFile(DB_FILE_PATH, "utf-8");
        const trends: AnalyzedTrend[] = JSON.parse(rawData);


        // 3. コマンドライン引数の解析 (node ultimate-trend-db-access.ts [arg])
        const input = process.argv[2];
        let results: AnalyzedTrend[];


        if (!input) {
            // 引数なし: 検索ワードのパラメータを省略するとランクAのものを表示
            console.log("💡 パラメータ未指定のため、デフォルトで【ランクA】の最新トレンドを表示します。");
            results = trends.filter(item => item.aiRank === "A");
        } else if (/^[A-C]$/i.test(input)) {
            // 引数が A, B, C のいずれか1文字の場合: 指定したランクの最新データを抽出
            const targetRank = input.toUpperCase();
            console.log(`⭐ 指定ランク 【${targetRank}】 のデータを抽出します。`);
            results = trends.filter(item => item.aiRank === targetRank);
        } else {
            // それ以外: 検索ワードとして処理 (タイトルとAI予測文を対象)
            console.log(`🔍 キーワード 「${input}」 で検索しています...`);
            results = trends.filter(item => 
                item.title.toLowerCase().includes(input.toLowerCase()) ||
                item.aiPrediction.toLowerCase().includes(input.toLowerCase())
            );
        }


        // 4. 最新順にソート (analyzedAt の降順)
        results.sort((a, b) => new Date(b.analyzedAt).getTime() - new Date(a.analyzedAt).getTime());


        // 5. 整形表示
        if (results.length === 0) {
            console.log("該当するデータは見つかりませんでした。");
            return;
        }


        console.log(`\n=== トレンド分析レポート (全 ${results.length} 件) ===`);
        console.log("=".repeat(60));
        results.forEach((trend, index) => {
            console.log(`[${index + 1}] ${trend.title}`);
            console.log(`   ランク: ${trend.aiRank} | 分析日時: ${new Date(trend.analyzedAt).toLocaleString()}`);
            console.log(`   AI要約: ${trend.aiPrediction}`);
            console.log("-".repeat(60));
        });


    } catch (error) {
        console.error(`❌ エラーが発生しました: ${error instanceof Error ? error.message : String(error)}`);
    }
};


accessTrendDb();



リスト6-1
// 1. 必要なモジュールと型定義をインポートする
import express from "express";
import type { Request, Response } from "express";


// 2. サーバーの土台となるインスタンスを作る
const app = express();


// 3. ルーティングを記述する
app.get("/", (req: Request, res: Response) => {
    // アクセスが来たら、データを送り返す
});


// 4. 指定のポート番号でサーバーを起動する
app.listen(ポート番号);



リスト6-2
________________


TypeScriptとExpressを使って、シンプルなWebサーバーのプログラムを作ってください。作成したコードは example.ts に上書きしてください。コードには初心者向けの日本語コメントを入れてください。


【要件】
1. ポート番号は3000番を使用する。
2. ルートパス（"/"）にアクセスしたら、ブラウザに「Hello World! TypeScript + Express の世界へようこそ！」と返信する。
3. サーバー起動時、コンソールにアクセスURLや終了方法を含んだログを出力する。
________________




リスト6-3
import express from "express";
import type { Request, Response } from "express";


// 1. Expressアプリケーションの初期化
const app = express();


// 待ち受けるポート番号を定義（3000番のポートを開く）
const PORT: number = 3000;


/**
 * 2. ルーティングの設定
 * クライアントがhttp://localhost:3000/にアクセスした際の処理
 */
app.get("/", (req: Request, res: Response) => {
    console.log("📥 クライアントからのアクセス（GETリクエスト）を検知しました！");
    
    // 💡 ブラウザに向けて、最高にシンプルな文字列をレスポンスとして送り返す
    res.send("Hello World! TypeScript + Express の世界へようこそ！");
});


/**
 * 3. サーバーの起動
 * listenメソッドを呼び出すことでサーバーが待受状態に入ります
 */
app.listen(PORT, () => {
    console.log(`🚀 ===================================================`);
    console.log(`🌐 Webサーバーが正常に起動しました！`);
    console.log(`📢 http://localhost:${PORT} でアクセスを待ち受けています...`);
    console.log(`⚠️  終了するには、ターミナルで [Ctrl + C] を押してください。`);
    console.log(`======================================================`);
});



リスト6-4
app.get("/other", (req: Request, res: Response) => {
    console.log("他のページへのアクセスを検知しました。");
    // 💡 ブラウザに向けて、最高にシンプルな文字列をレスポンスとして送り返す
    res.send("※/other にアクセスしています。これはトップページとは別のページです！");
});



リスト6-5
________________


プロジェクトのルートに「public」フォルダを作成してください。そして、その中に「index.html」ファイルを作成してください。ファイルには、以下の要件を満たすシンプルなコードを記述してください。


【要件】
- 言語: 日本語設定（ja）
- 構造: 画面の中央に、白いカード型（.card）のコンテナを配置する。
- 内容: カードの中に「Expressからこんにちは！」というh1見出しと、「この画面は public/index.html から直接配信されています。」というpテキストを入れる。
- CSS: <head> 内の <style> タグにまとめて記述し、モダンで綺麗なスタイルを割り当ててください。
________________






リスト6-6
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Express静的ページ</title>
    <style>
        body {
            font-family: 'Helvetica Neue', Arial, sans-serif;
            background-color: #f4f7f6;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .card {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            text-align: center;
        }
        h1 { color: #333; }
        p { color: #666; }
    </style>
</head>
<body>
    <div class="card">
        <h1>Expressからこんにちは！</h1>
        <p>この画面は <strong>public/index.html</strong> から
           直接配信されています。</p>
    </div>
</body>
</html>



リスト6-7
________________


Expressを使って、静的ファイルを配信するための最小限のWebサーバーコードをTypeScriptで作成してください。


【要件】
1. express をインポートし、アプリケーションを初期化する。
2. express.static("./public") を使用して、「public」フォルダ内の静的ファイルをルート（/）で公開するミドルウェアを設定する。
3. ポート番号は3000を使う。
4. app.listen を使ってサーバーを起動し、起動成功時にコンソールにメッセージを出力する。
________________






リスト6-8
import express from "express";


// Expressアプリケーションの初期化
const app = express();


// 静的ファイルの提供（public内のファイルを公開）
app.use(express.static("./public"));


// 💡 待ち受けるポート番号を定義（3000番のポートを開く）
const PORT: number = 3000;


// サーバーの起動
app.listen(PORT, () => {
    console.log(`🌐 Webサーバーが正常に起動しました！`);
});



リスト6-9
import type { Request, Response, NextFunction } from "express";


const myCustomMiddleware = (req: Request, res: Response, next: NextFunction): void => {
    // 1. ここに先回りして実行したい「共通処理」を書く
    console.log("チェックポイント通過！");


    // 2. 重要：次の処理へリクエストをバケツリレーする
    next();
};


// サーバー全体に登録（すべてのapp.getの手前に割り込むようになる）
app.use(myCustomMiddleware);





リスト6-10
________________


Expressにおいて、リクエストの処理時間を計測してログ出力する「2つのカスタムミドルウェア」を連携させる、TypeScriptのサンプルサーバーコードを生成してください。生成されたコードは、example.tsに上書きしてください。


【ミドルウェアの定義】
以下の2つのミドルウェアを定義し、関数および引数に厳格な型定義を行ってください。


1. `executionTimerMiddleware`（パフォーマンス・タイマー）
   - リクエスト開始時のタイムスタンプを `Date.now()` で取得する。
   - 重要: 取得した開始時刻を、後続のミドルウェアに引き渡すために `res.locals.startTime` に格納する。


2. `accessLoggerMiddleware`（高機能アクセスロガー）
   - `req.method` と `req.url` を取得する。
   - 重要: `res.on("finish", ...)` イベントリスナーを登録し、レスポンスがブラウザへ完全に送信し終わったタイミングで処理時間の計算とログ出力を行う。
   - ログ出力内では、`res.locals.startTime` から開始時刻を回収し、現在の時刻との差分から処理時間（ミリ秒）を計算する。


【ルーティング】
動作検証用として、以下の2つのルートハンドラーを定義する。
  1. ルート（`/`）: 簡単なHTMLメッセージと、`/slow-page` へのリンクを返す。
  2. `/slow-page`: タイマーの動作を確認するため、`setTimeout` を使ってわざと200ミリ秒の遅延（スリープ）を発生させてからHTMLレスポンスを返す。
________________




リスト6-11
import express from "express";
// 💡 ミドルウェア開発に必須の「NextFunction」型をインポート
import type { Request, Response, NextFunction } from "express";


const app = express();
const PORT: number = 3000;


// ==================================================
// 1. カスタムミドルウェアの定義（AI生成セクション）
// ==================================================


/**
 * ミドルウェア①：超精密パフォーマンス・タイマー
 * リクエストが届いた瞬間の「ナノ秒/ミリ秒タイムスタンプ」を計測し、
 * 後ろの処理へ手渡します。
 */
const executionTimerMiddleware = (req: Request, res: Response, next: NextFunction): void => {
    const startTime: number = Date.now(); // 💡 サーバーが電波を受信した瞬間の時刻(ミリ秒)


    // 💡 res.locals のポケットの中に「startTime」という名前でデータを隠し持つ
    res.locals.startTime = startTime;


    // 次のミドルウェア（ロガー）へリクエストをパス！
    next();
};


/**
 * ミドルウェア②：高機能アクセスロガー
 * 通信のメソッドやURLを表示し、さらにレスポンスが「完了した瞬間（finish）」
 * を検知してログを出します。
 */
const accessLoggerMiddleware = (req: Request, res: Response, next: NextFunction): void => {
    const method: string = req.method; // GET や POST などの文字
    const url: string = req.url;       // / や /about などのパス


    // 💡 Expressがデータを返信し終えた際に作動するイベントリスナー
    res.on("finish", () => {
        // タイマーミドルウェアがres.localに入れた開始時刻を回収する
        const startTime = res.locals.startTime as number | undefined;
        let durationText: string = "計測不能";


        if (startTime) {
            const endTime: number = Date.now();
            const duration: number = endTime - startTime; // 経過時間を計算
            durationText = `${duration}ms`;
        }


        // ターミナルに美しいログを出力する
        console.log(`[LOG] 📥 ${method} ${url} | 状態: ${res.statusCode} | 処理時間: ${durationText}`);
    });


    // 超要：これを忘れると止まる！
    next();
};


// ==================================================
// 2. ミドルウェアのサーバー一括登録
// ==================================================
// 上に書いたものから順番に、すべてのリクエストが強制通過する
app.use(executionTimerMiddleware);
app.use(accessLoggerMiddleware);


// ==================================================
// 3. ルーティングの定義
// ==================================================


app.get("/", (req: Request, res: Response) => {
    // ミドルウェアでnext()が押された結果、無事にここに着きます
    res.send("<h1>🏠 ホーム画面</h1><p>ターミナルを見てください！あなたの一挙一動がミドルウェアによってログ監視・計測されています。</p><p><a href='/slow-page'>🐢 重いページへ進む</a></p>");
});


app.get("/slow-page", (req: Request, res: Response) => {
    // 💡 実務のシミュレーション：わざと人工的な遅延を発生させる
    setTimeout(() => {
        res.send("<h1>🐢 激重の処理ページ</h1><p>わざと200ミリ秒待機してからレスポンスを返しました。タイマーのログはどうなっていますか？</p><p><a href='/'>🏠 ホームへ戻る</a></p>");
    }, 200);
});


// サーバー起動
app.listen(PORT, () => {
    console.log(`🚀 ミドルウェア可視化サーバーが http://localhost:${PORT} で起動しました！`);
    console.log("📢 ブラウザで色々なページをカチカチ切り替えて、ターミナルのミリ秒ログを凝視してください。\n");
});



リスト6-12
________________


ExpressのAPIサーバー（TypeScript）と、そこへfetchでアクセスする静的HTMLファイルの2つのコードを作成してください。サーバー側のプログラムはexample.tsを上書きして作成してください。HTMLファイルのコードは、./public/index.htmlを上書きして作成してください。


【サーバー側の要件】
- ポート3000番で起動。
- publicフォルダの静的ファイルを配信する設定を用意する。
- [GET] `/api/greet` というエンドポイント（窓口）を作成する。
- アクセスされたら、`message`（成功文字列）と `timestamp`（現在の時刻文字列）を持つJSONデータを返却する。


【 HTML側の要件】
- 画面中央にカード型のコンテナによるシンプルなデザイン（CSSはhead内に記述）。
- カード内にボタンと、結果表示用のエリアを配置する。
- ボタンを押すと、非同期 `/api/greet` からデータを取得し、ページの結果表示エリアを書き換えてメッセージと時刻を表示する。
________________






リスト6-13
import express from "express";
import type { Request, Response } from "express";


const app = express();
const PORT: number = 3000;


// 1. publicフォルダ内の静的ファイル（index.htmlなど）を自動配信する設定
app.use(express.static("./public"));


// 2. [GET] /api/greet というAPIの窓口（エンドポイント）を作成
app.get("/api/greet", (req: Request, res: Response): void => {
    // クライアント（ブラウザ）に送るデータ
    const responseData = {
        message: "ExpressのAPIからこんにちは！通信に成功しました。",
        timestamp: new Date().toLocaleString("ja-JP")
    };


    // JSON形式でデータを送り返す
    res.json(responseData);
});


// 3. サーバーを起動して3000番ポートで待ち受ける
app.listen(PORT, () => {
    console.log(`🚀 サーバーが http://localhost:${PORT} で起動しました！`);
});



リスト6-14
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>API通信サンプル</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
            display: flex; align-items: center;
            justify-content: center;
            height: 100vh; margin: 0;
        }
        .container {
            background: white;
            padding: 30px; border-radius: 8px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.1);
            text-align: center; max-width: 400px;
        }
        button {
            background-color: #007bff;
            color: white; border: none;
            padding: 10px 20px; font-size: 16px;
            border-radius: 4px; cursor: pointer;
            transition: background 0.2s;
        }
        button:hover {
            background-color: #0056b3;
        }
        .result-box {
            margin-top: 20px; padding: 15px;
            background-color: #e9ecef;
            border-radius: 4px; min-height: 50px;
            color: #333; text-align: left;
            word-break: break-all;
        }
    </style>
</head>
<body>


    <div class="container">
        <h1>API通信テスト</h1>
        <p>下のボタンを押すと、ExpressのAPIからデータを取得します。</p>
        <button id="fetch-btn">データを取得する</button>
        <div class="result-box" id="result-area">
            ここにAPIから届いたデータが表示されます。
        </div>
    </div>


    <script>
        // ボタンと結果表示エリアの要素を取得
        const fetchBtn = document.getElementById("fetch-btn");
        const resultArea = document.getElementById("result-area");


        // ボタンがクリックされた時の処理
        fetchBtn.addEventListener("click", async () => {
            resultArea.innerText = "⏳ 通信中...";
            try {
                // 1. サーバーのAPIエンドポイントへGETリクエストを送信
                const response = await fetch("/api/greet");


                // 2. 通信ステータスが正常（200番台）かチェック
                if (!response.ok) {
                    throw new Error(`HTTPエラー: ${response.status}`);
                }


                // 3. 届いたJSONデータを解析
                const data = await response.json();


                // 4. 画面に結果を書き換えて表示
                resultArea.innerHTML = `
                    <p><strong>メッセージ:</strong> ${data.message}</p>
                    <p><strong>取得時刻:</strong> ${data.timestamp}</p>`;


            } catch (error) {
                // エラーが発生した場合の処理
                resultArea.innerHTML = `
                 <span style="color: red;">
                  ❌ エラー: ${error.message}</span>`;
            }
        });
    </script>
</body>
</html>



リスト6-15
________________


Expressによる「GET/POST対応のAPIサーバー」と、それをブラウザから操作する「メッセージボードのHTML画面」の2つのファイルを、TypeScriptの型安全性を意識して作成してください。サーバー側のコードは、example.tsを上書きして保存してください。HTMLのコードは、./public/index.htmlを上書きしてください。


【サーバー側の要件】
- publicフォルダの静的ファイルを自動配信（express.static）する設定を含める。
- JSONリクエストを解析できるように `express.json()` ミドルウェアを設定する。
- データの型定義として `MessageItem`（id: number, sender: string, text: string）のインターフェースを作成する。
- メモリ上に簡易データベースとして `messageDatabase: MessageItem[]` 配列を用意する。
- 以下の2つのAPIエンドポイント（窓口）を実装する。
  1. [GET] `/api/messages`: データベース配列をそのままJSONで返却する。
  2.*[POST] `/api/messages`: リクエストボディから送信者（sender）と本文（text）を受け取る。どちらかが欠けている場合はステータス「400」とエラーメッセージを返す。問題なければ配列に追加し、ステータス「201」で追加されたオブジェクトを返却する。


【クライアント側の要件】
- 画面中央に白いカード型のコンテナを配置し、そこにUIを表示する（CSSはhead内に記述）。
- 画面の上部に「送信者名」と「メッセージ本文」を入力する「投稿フォーム」、下部に「受信したメッセージ一覧」の表示エリアを配置する。
- 以下のJavaScript（非同期のasync/awaitを使用）を実装する。
  1. ページ読み込み時: API（/api/messages）へGETリクエストを送り、取得した配列データをメッセージ一覧エリアに動的表示する。
  2. フォーム送信時: 入力値をJSON文字列（JSON.stringify）にしてAPIへPOST送信する。送信成功後は入力欄（本文）をクリアし、再びGET処理を実行して画面を最新状態に更新する。
________________






リスト6-16
import express from "express";
import type { Request, Response } from "express";


const app = express();
const PORT: number = 3000;


// メッセージデータの型定義
interface MessageItem {
    id: number;
    sender: string;
    text: string;
}


// メモリ上の簡易データベース
const messageDatabase: MessageItem[] = [
    { id: 1, sender: "システム", 
      text: "掲示板へようこそ！TypeScriptで型安全に開発しています。" }
];


// ミドルウェアの追加
app.use(express.json());
app.use(express.static("./public"));


// [GET] メッセージ一覧の取得
app.get("/api/messages", (req: Request, res: Response): void => {
    res.json(messageDatabase);
});


// [POST] 新規メッセージの投稿
app.post("/api/messages", (req: Request, res: Response): void => {
    const { sender, text } = req.body;


    // バリデーション
    if (!sender || !text) {
        res.status(400).json({ error: "送信者名とメッセージ本文の両方を入力してください。" });
        return;
    }
    // メッセージのオブジェクトを作成
    const newMessage: MessageItem = {
        id: messageDatabase.length + 1,
        sender,
        text
    };
    // 配列に追加しJSONテキストを返送
    messageDatabase.push(newMessage);
    res.status(201).json(newMessage);
});


app.listen(PORT, () => {
    console.log(`🚀 サーバーが起動しました！`);
});



リスト6-17
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TS Message Board</title>
    <style>
        body {
            background-color: #f4f7f9;
            font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
            display: flex; justify-content: center;
            align-items: center; min-height: 100vh; margin: 0;
        }
        .container {
            background: #ffffff;
            padding: 30px; border-radius: 12px;
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
            width: 100%; max-width: 450px;
        }
        h1 { font-size: 1.4rem; color: #333; margin-top: 0; text-align: center; }
        form { display: flex; flex-direction: column; gap: 12px; margin-bottom: 25px; }
        input, textarea {
            padding: 10px; font-size: 0.95rem;
            border: 1px solid #ddd; border-radius: 6px;
        }
        textarea { height: 80px; resize: none; }
        button {
            background: #007bff; color: white;
            border: none; border-radius: 6px;
            padding: 12px; font-weight: bold;
            cursor: pointer; transition: background 0.2s;
        }
        button:hover { background: #0056b3; }
        #messageList { border-top: 1px solid #eee; padding-top: 15px; }
        .message-card {
            padding: 10px; margin-bottom: 8px;
            border-bottom: 1px solid #f0f0f0;
        }
        .message-sender { font-weight: bold; font-size: 0.85rem; color: #007bff; }
        .message-text { margin-top: 4px; color: #444; }
    </style>
</head>
<body>
    <div class="container">
        <h1>📝 メッセージボード</h1>
        
        <form id="boardForm">
            <input type="text" id="sender" placeholder="お名前" required>
            <textarea id="message" placeholder="メッセージを入力してください..." required></textarea>
            <button type="submit">投稿する</button>
        </form>


        <div id="messageList"></div>
    </div>


    <script>
        const form = document.getElementById('boardForm');
        const list = document.getElementById('messageList');


        const fetchMessages = async () => {
            const res = await fetch('/api/messages');
            const data = await res.json();
            list.innerHTML = data.reverse().map(m => `
                <div class="message-card">
                    <div class="message-sender">${m.sender}</div>
                    <div class="message-text">${m.text}</div>
                </div>
            `).join('');
        };


        form.addEventListener('submit', async (e) => {
            e.preventDefault();
            const sender = document.getElementById('sender').value;
            const text = document.getElementById('message').value;


            const res = await fetch('/api/messages', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ sender, text })
            });


            if (res.ok) {
                document.getElementById('message').value = '';
                fetchMessages();
            } else {
                alert('投稿に失敗しました。');
            }
        });


        fetchMessages();
    </script>
</body>
</html>



リスト6-18
// 💡 サーバー側と同じ「お便りデータ」の型定義を共有・使用
interface MessageItem {
    id: number;
    sender: string;
    text: string;
}


// APIのURL
const API_URL: string = "/api/messages";


// DOM要素の取得と厳格な型キャスト（型安全の担保）
const messageList = document.getElementById("messageList") as HTMLDivElement;
const messageForm = document.getElementById("boardForm") as HTMLFormElement;


/**
 * 📡 サーバーからメッセージ一覧をGETで取得して画面に表示する関数
 */
const fetchMessages = async (): Promise<void> => {
    try {
        messageList.innerText = "";
        const response: Response = await fetch(API_URL);
        if (!response.ok) throw new Error("データの取得に失敗しました。");
        
        // 💡 戻り値が MessageItem の配列であることを明示的に指定（型安全）
        const messages = await response.json() as MessageItem[];
        
        messageList.innerHTML = "";
        
        if (messages.length === 0) {
            messageList.innerHTML = "<p style='color:#999; text-align:center;'>メッセージはまだありません。</p>";
            return;
        }


        // 型が確定しているため、msg.id や msg.sender の入力補完が効くようになります
        messages.reverse().forEach((msg: MessageItem) => {
            const item: HTMLDivElement = document.createElement("div");
            item.className = "message-item";
            item.innerHTML = `
                <div class="message-sender">👤 ${escapeHtml(msg.sender)} (ID: ${msg.id})</div>
                <p class="message-text">${escapeHtml(msg.text)}</p>
            `;
            messageList.appendChild(item);
        });
    } catch (error) {
        if (error instanceof Error) {
            messageList.innerHTML = `<p style="color: #dc3545; text-align: center;">❌ エラー: ${error.message}</p>`;
        }
    }
};


/**
 * 📡 フォームが送信された時にPOSTリクエストを送るイベント
 */
messageForm.addEventListener("submit", async (e: SubmitEvent): Promise<void> => {
    e.preventDefault(); // ページ全体の再読み込みを防ぐ
    messageList.innerText = "";


    const senderInput = document.getElementById("sender") as HTMLInputElement;
    const textInput = document.getElementById("message") as HTMLInputElement;


    // 送信データオブジェクトの作成（MessageItemの仕様に準拠）
    const payload: Omit<MessageItem, "id"> = {
        sender: senderInput.value.trim(),
        text: textInput.value.trim()
    };


    try {
        const response: Response = await fetch(API_URL, {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(payload)
        });


        const result = await response.json();


        if (!response.ok) {
            throw new Error(result.error || "送信に失敗しました。");
        }


        textInput.value = "";
        
        // 最新の状態を自動で再取得
        await fetchMessages();


    } catch (error) {
        if (error instanceof Error) {
            messageList.innerHTML = `<p style="color: #dc3545; text-align: center;">❌ エラー: ${error.message}</p>`;
        }
    }
});


// XSS防止用エスケープ関数
function escapeHtml(str: string): string {
    return str.replace(/[&<>"']/g, (match: string) => {
        const escape: Record<string, string> = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
        return escape[match];
    });
}


// 🚀 最初の1回目を実行
fetchMessages();



リスト6-19
<script type="module" src="client.js"></script>



リスト6-20
________________


Express（Node.js）のAPIサーバーと、GeminiによるAI査定機能を搭載した型安全なTODOシステムのフロントエンド画面（TypeScript）のファイルを一括で作成してください。


【全体のファイル構成】
- todo-system.ts  （Expressサーバー側）
- todo.ts    （クライアント側TypeScript）
- public (フォルダー)
  + todo.html  （画面：todo.js を読み込む）
  + todo.js    （tscでコンパイルされて出力されるファイル）


【サーバー側の要件】
- ポート3000番で起動し、public フォルダの静的ファイルを自動配信する。
- express.json() を有効にし、データの型定義として以下を共有・使用する。 
interface TodoItem {
    id: number;
    title: string;
    assignee: string;
    aiDifficulty: number; // Geminiが査定した難易度 (1〜5)
    aiAdvice: string;     // Geminiからのプロのアドバイス
    createdAt: string;
}
- メモリ上に TodoItem配列を用意し、ToDoデータを管理する。
- TodoItemは、起動時にJSONファイルから読み込んで初期化する。
- 以下の3つのエンドポイントを実装する。
    1. [GET] /api/todos: 現在のTODO配列をそのままJSONで返す。
    2. [POST] /api/todos: title と assignee を受け取る（どちらか欠けていたら400エラー）。
        + 【重要】リクエストを受け取ったら、バックエンド側で仮想的に「分析官 Gemini」を呼び出したと仮定し、モックデータ（ダミーデータ）として、そのタスクに応じた aiLevel（1〜5の数値）、aiAdvice（短いアドバイス文字列）を自動生成してオブジェクトに付与する。
        + 新しいIDを採番して配列に追加し、ステータス201で返却する。
    3.  [DELETE] /api/todos/:id: URLパラメータからIDを取得し、該当するタスクを todoDatabase 配列から削除する（存在しない場合は404エラー、成功時は200を返す）。let で宣言された配列を上書きするか、安全に要素を削除する。
- ToDoの追加や削除を行ったら、ただちにJSONファイルに保存する。


【クライアント側の要件】
- 画面中央に白いカード型のコンテナを配置し、モダンなUIにする（CSSはhead内に記述）。
- フォームには「タスク名」と「担当者名」の2つの入力欄と「タスクを追加」ボタンを配置する。
- タスク一覧を表示する <ul> 要素と、エラー表示エリアを配置する。
- 画面最下部で、コンパイル後のJavaScriptファイルである ./todo.js を type="module" で読み込む。


【クライアントロジック側の要件】
- todo.html 内のDOM要素（フォーム、入力欄、リスト等）を as HTMLInputElement などで厳格に型キャストして取得する。
- 以下のJavaScript（非同期のasync/awaitを使用）を実装する。
    1. [GET通信と描画]: ページ読み込み時およびデータ更新時、/api/todos からデータを取得。各タスクのHTMLを動的に生成する。その際、サーバーから届いた aiLevel（難易度レベル）、aiAdvice（アドバイス）、投稿日時といった情報を画面上に表示する。XSS対策のエスケープ関数も含める。
    2.  [POST通信]: フォーム送信時に preventDefault() でリロードを阻止し、タスク名と担当者名をJSONで送信する。成功後は入力欄（タスク名と担当者）をクリアし、再び自動でGET処理を実行して画面を最新状態にする。
    3. [DELETE通信]: 削除ボタンにはループ内のイベントリスナで直接 todo.id を紐付け、クリックされたら stopPropagation() でイベント伝播を防いだ上で /api/todos/ID へDELETEリクエストを送信する。成功後は自動でGET処理を実行して画面を更新する。
________________






リスト6-21
import express from "express";
import type { Request, Response } from "express";
import { GoogleGenAI } from "@google/genai";
import fs from "node:fs";


const app = express();
const PORT: number = 3000;
const FILE_PATH: string = "./todos.json";


// JSONリクエストの解析設定
app.use(express.json());
// 静的ファイルの配信設定
app.use(express.static("./public"));


// ==================================================
// 1. ドメイン型定義（APIの契約書）
// ==================================================


// クライアントがタスクを新規投稿（POST）する際の型
interface CreateTodoRequest {
    title: string;       // タスクのタイトル（例：「4章の復習をする」）
    assignee: string;    // 担当者名
}


// サーバー側でAI分析を終えて保管・返却するタスクデータの完全な型
interface TodoItem {
    id: number;
    title: string;
    assignee: string;
    aiDifficulty: number; // Geminiが査定した難易度 (1〜5)
    aiAdvice: string;     // Geminiからのプロのアドバイス
    createdAt: string;
}


/**
 * 【ヘルパー関数】JSONファイルからTODOリストを読み込む
 */
const loadTodosFromFile = (): TodoItem[] => {
    try {
        // ファイルが存在するか確認
        if (!fs.existsSync(FILE_PATH)) {
            // 存在しない場合は、初期データを入れてファイルを作る
            const initialData: TodoItem[] = [
                { id: 1, title: "システム開発の準備", assignee: "Gemini", completed: false, aiLevel: 1, aiAdvice: "まずはここから始めましょう！" }
            ];
            fs.writeFileSync(FILE_PATH, JSON.stringify(initialData, null, 2), "utf-8");
            return initialData;
        }
        
        // ファイルからテキストデータを読み込み、オブジェクト（配列）にパースして返す
        const fileData = fs.readFileSync(FILE_PATH, "utf-8");
        return JSON.parse(fileData) as TodoItem[];
    } catch (error) {
        console.error("❌ ファイルの読み込みに失敗しました。空の配列を返します。", error);
        return [];
    }
};


/**
 * 【ヘルパー関数】TODOリストをJSONファイルに書き込む
 */
const saveTodosToFile = (todos: TodoItem[]): void => {
    try {
        // 配列を綺麗に整形したJSON文字列にしてファイルに上書き保存 (インデント2スペース)
        fs.writeFileSync(FILE_PATH, JSON.stringify(todos, null, 2), "utf-8");
        console.log("💾 データを todos.json に安全に保存しました。");
    } catch (error) {
        console.error("❌ ファイルの保存に失敗しました。", error);
    }
};


// サーバーの即席メモリデータベース
const todoDatabase: TodoItem[] = loadTodosFromFile();


// ==================================================
//  2. Express APIサーバーのルート実装
// ==================================================


/**
 * API①: タスク一覧取得（GET /api/todos）
 */
app.get("/api/todos", (req: Request, res: Response) => {
    res.json(todoDatabase);
});


/**
 * API②: AI査定付きタスク登録（POST /api/todos）
 */
app.post("/api/todos", async (req: Request, res: Response) => {
    try {
        // 💡 クライアントからの入力を型安全にキャスト
        const body = req.body as CreateTodoRequest;


        if (!body.title || !body.assignee) {
            res.status(400).json({ error: "タスク名（title）と担当者（assignee）は必須です。" });
            return;
        }


        console.log(`[API SERVER] 📥 新規タスクを受信: 「${body.title}」 by ${body.assignee}`);
        console.log(`[API SERVER] 🧠 分析官 Gemini を召喚してタスクを査定中...`);


        // 第5章の知識：Gemini APIの起動（環境変数から自動読み込み）
        const ai = new GoogleGenAI({
            apiKey: process.env['GEMINI_API_KEY'],
        });
        const aiPrompt = 
            `あなたは頼れるシニアエンジニア兼メンターです。後輩が登録した以下のタスクについてアドバイスをしてください。\n\n` +
            `タスク名: ${body.title}\n\n` +
            `【出力フォーマット条件】\n` +
            `必ず以下の2行だけで出力してください。余計な挨拶や解説、マークダウン記号は絶対に含めないでください。\n` +
            `難易度: (1から5の半角数値のみ)\n` +
            `助言: (挫折を防ぐための具体的なアドバイスを1行の日本語で)\n`;


        const aiResponse = await ai.models.generateContent({
            model: "gemini-3.5-flash",
            contents: aiPrompt,
            config: { temperature: 0.2 }
        });


        // AIの返事からデータを取り出す
        const aiText = aiResponse.text;
        const lines = aiText.split("\n");
        
        // 「難易度: 3」のような行から数値を切り出す
        const difficultyLine = lines.find(l => l.startsWith("難易度:")) || "難易度: 3";
        const aiDifficulty = parseInt(difficultyLine.replace("難易度:", "").trim(), 10) || 3;


        // 「助言: 〜〜」の行からテキストを切り出す
        const adviceLine = lines.find(l => l.startsWith("助言:")) || "助言: 頑張りましょう。";
        const aiAdvice = adviceLine.replace("助言:", "").trim();


        // 立体的な完成形オブジェクトの構築
        const newTodo: TodoItem = {
            id: todoDatabase.length + 1,
            title: body.title,
            assignee: body.assignee,
            aiDifficulty: aiDifficulty,
            aiAdvice: aiAdvice,
            createdAt: new Date().toLocaleString("ja-JP")
        };


        // データベースに保存
        todoDatabase.push(newTodo);
        console.log(`[API SERVER] ✅ AI査定完了。難易度[${aiDifficulty}]としてDBに永続化しました。\n`);


        // 💡【重要】変更された配列をファイルに保存
        saveTodosToFile(todoDatabase);


        // 出来上がった高付加価値なJSONを型安全に返却
        res.status(201).json(newTodo);


    } catch (error) {
        console.error("[API SERVER] エラー:", error);
        res.status(500).json({ error: "サーバー内部でAI分析エラーが発生しました。" });
    }
});


/**
 * 【DELETEルート】指定されたIDのTODOを配列から削除するAPI
 * URLの末尾にある「:id」部分が、req.params.id として回収できる
 */
app.delete("/api/todos/:id", (req: Request, res: Response) => {
    const targetId: number = Number(req.params.id);
    console.log(`🗑️ DELETEリクエストを受信: ID [${targetId}] の削除を試みます。`);


    // 1. 該当するIDが、配列の何番目（インデックス）にあるかを検索
    const index = todoDatabase.findIndex(todo => todo.id === targetId);


    // 2. もし見つからなかったら（-1だったら）404エラーを返す
    if (index === -1) {
        res.status(404).json({ error: "指定されたタスクが見つかりません。" });
        return;
    }


    // 3. spliceで、その位置のデータを1件だけ削除する
    todoDatabase.splice(index, 1);


    // 4. 削除後の配列をファイルに保存して永続化する
    saveTodosToFile(todoDatabase);
    
    console.log(`✨ ID [${targetId}] の削除が完了しました。`);
    res.status(200).json({ success: true, message: `ID: ${targetId} を削除しました。` });
});


// ==================================================
//  3. 自動実行制御（サーバー起動 ＆ 自作クライアント通信）
// ==================================================
const SERVER_URL = `http://localhost:${PORT}/api/todos`;


app.listen(PORT, async () => {
    console.log(`======================================================`);
    console.log(`🚀 AI駆動型タスク管理サーバーがポート ${PORT} で起動！`);
    console.log(`======================================================\n`);
});



リスト6-22
// クライアントがタスクを新規投稿（POST）する際の型
interface CreateTodoRequest {
    title: string;       // タスクのタイトル（例：「4章の復習をする」）
    assignee: string;    // 担当者名
}


// TODOデータの型定義（aiLevel を追加！）
interface TodoItem {
    id: number;
    title: string;
    assignee: string;
    aiDifficulty: number; // Geminiが査定した難易度 (1〜5)
    aiAdvice: string;     // Geminiからのプロのアドバイス
    createdAt: string;
}


const API_URL: string = "/api/todos";


const todoForm = document.getElementById("todo-form") as HTMLFormElement;
const todoInput = document.getElementById("todo-input") as HTMLInputElement;
const todoAssignee = document.getElementById("todo-assignee") as HTMLInputElement;
const todoList = document.getElementById("todo-list") as HTMLUListElement;
const errorArea = document.getElementById("error-area") as HTMLDivElement;


/**
 * サーバーからTODO一覧を取得して画面に描画する関数 (GET)
 */
const fetchTodos = async (): Promise<void> => {
    try {
        const response: Response = await fetch(API_URL);
        if (!response.ok) throw new Error("TODO一覧の取得に失敗しました。");


        const todos = await response.json() as TodoItem[];
        todoList.innerHTML = "";


        if (todos.length === 0) {
            errorArea.innerText = "タスクはありません。";
            return;
        }


        todos.reverse().forEach((todo: TodoItem) => {
            const li: HTMLLIElement = document.createElement("li");
            li.className = "todo-item";
            
            // 【AIの評価テキストの冒頭に [Level X] を追加する】
            // サーバーから aiLevel（数値）が届いている場合、[Level X] を作る
            const levelText = todo.aiDifficulty ? `[Level ${todo.aiDifficulty}] ` : "";
            
            // アドバイスのテキストの冒頭にレベルテキストを合体させる
            const fullAdvice = todo.aiAdvice ? `${levelText}${todo.aiAdvice}` : "";


            li.innerHTML = `
                <div class="todo-content">
                    <div class="todo-main-info">
                        <span class="todo-title">${escapeHtml(todo.title)}</span>
                        <span class="todo-assignee">👤 ${escapeHtml(todo.assignee)}</span>
                        <span class="todo-created">📅 ${new Date(todo.createdAt).toLocaleString()}</span>
                    </div>
                    
                    ${fullAdvice ? `<p class="ai-advice">💡 Geminiの助言: ${escapeHtml(fullAdvice)}</p>` : ""}
                </div>
                <button class="delete-btn">削除</button>`;


            const deleteBtn = li.querySelector(".delete-btn") as HTMLButtonElement;
            deleteBtn.addEventListener("click", (e: Event) => {
                e.stopPropagation();
                deleteTodo(todo.id);
            });


            todoList.appendChild(li);
        });
    } catch (error) {
        if (error instanceof Error) {
            errorArea.innerText = `❌ エラー: ${error.message}`;
        }
    }
};


/**
 * 新しいTODOを登録する関数 (POST)
 */
todoForm.addEventListener("submit", async (e: SubmitEvent): Promise<void> => {
    e.preventDefault();
    
    const title: string = todoInput.value.trim();
    const assignee: string = todoAssignee.value.trim(); // 値を回収


    // どちらかが空なら処理しない（HTMLのrequiredでも弾かれます）
    if (!title || !assignee) return;


    // サーバーへ送る荷物に assignee も含める
    const payload: CreateTodoRequest = { 
        title, 
        assignee 
    };


    // 送信前にエラー表示をクリア
    errorArea.innerText = "";


    try {
        const response: Response = await fetch(API_URL, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload)
        });


        if (!response.ok) {
            const result = await response.json();
            throw new Error(result.error || "TODOの登録に失敗しました。");
        }


        // 送信が成功したら入力欄を綺麗にする
        todoInput.value = "";
        todoAssignee.value = "";
        
        await fetchTodos(); 
    } catch (error) {
        if (error instanceof Error) {
            errorArea.innerText = `❌ エラー: ${error.message}`;
        }
    }
});


/**
 * TODOを削除する関数 (DELETE)
 */
const deleteTodo = async (id: number): Promise<void> => {
    try {
        const response: Response = await fetch(`${API_URL}/${id}`, {
            method: "DELETE"
        });
        if (!response.ok) throw new Error("TODOの削除に失敗しました。");
        await fetchTodos();
    } catch (error) {
        if (error instanceof Error) {
            errorArea.innerText = `❌ エラー: ${error.message}`;
        }
    }
};


function escapeHtml(str: string): string {
    return str.replace(/[&<>"']/g, (match: string) => {
        const escape: Record<string, string> = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
        return escape[match];
    });
}


fetchTodos();



リスト6-23
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>型安全なTODOシステム</title>
    <style>
        body {
            font-family: 'Helvetica Neue', Arial, sans-serif;
            background-color: #f8f9fa;
            margin: 0;
            padding: 20px;
            display: flex;
            justify-content: center;
        }
        .container {
            width: 100%;
            max-width: 500px;
            background: white;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.05);
        }
        h1 { color: #333; margin-top: 0; text-align: center; font-size: 24px; }
        
        /* 💡 複数フィールドが綺麗に並ぶよう縦並びのフォームに変更 */
        .form-container { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; }
        .input-row { display: flex; gap: 10px; }
        
        input[type="text"] {
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
            box-sizing: border-box;
        }
        #todo-input { flex: 2; } /* タスク名を広めに */
        #todo-assignee { flex: 1; } /* 担当者名を少し狭めに */


        button.submit-btn {
            background-color: #28a745; color: white;
            border: none; padding: 12px; border-radius: 6px;
            font-size: 14px; font-weight: bold; cursor: pointer;
            transition: background 0.2s;
        }
        button.submit-btn:hover { background-color: #218838; }
        ul { list-style: none; padding: 0; margin: 0; }
        .todo-item {
            display: flex; justify-content: space-between;
            align-items: center; padding: 12px 15px;
            background-color: #fdfdfd; border: 1px solid #eee;
            border-radius: 6px; margin-bottom: 8px;
        }
        .todo-title { font-size: 15px; color: #444; }
        
        /* 💡 担当者用のバッジスタイルを追加 */
        .todo-assignee {
            font-size: 12px; background-color: #e9ecef; color: #495057;
            padding: 2px 8px; border-radius: 20px; margin-left: 8px;
        }


        .delete-btn {
            background-color: #dc3545; color: white;
            border: none; padding: 6px 12px;
            border-radius: 4px; cursor: pointer; font-size: 12px;
        }
        .delete-btn:hover { background-color: #c82333; }
        .error-msg { color: #dc3545; font-weight: bold; font-size: 13px; margin-bottom: 15px; text-align: center; }


    .todo-item {
        display: flex; justify-content: space-between;
        align-items: center; padding: 15px;
        background-color: #fdfdfd; border: 1px solid #eee;
        border-radius: 8px; margin-bottom: 10px;
    }
    .todo-content {
        display: flex; flex-direction: column;
        gap: 6px; flex: 1;
    }
    .todo-main-info {
        display: flex; align-items: center;
        flex-wrap: wrap; gap: 8px;
    }
    
    /* 🤖 Geminiバッジのスタイル */
    .ai-badge {
        font-size: 11px; font-weight: bold; color: white;
        background-color: #6f42c1; padding: 2px 8px;
        border-radius: 4px;
    }


    /* 💡 Geminiアドバイスのスタイル */
    .ai-advice {
        margin: 0; font-size: 13px; color: #555;
        background: #f1f3f9; padding: 6px 10px;
        border-radius: 6px; border-left: 3px solid #6f42c1;
        font-style: italic;
    }
</style>
</head>
<body>


    <div class="container">
        <h1>📝 タスク管理 (TODO)</h1>
        <div id="error-area" class="error-msg"></div>


        <form id="todo-form" class="form-container">
            <div class="input-row">
                <input type="text" id="todo-input" placeholder="タスクを入力..." required>
                <input type="text" id="todo-assignee" placeholder="担当者..." required>
            </div>
            <button type="submit" class="submit-btn">タスクを追加</button>
        </form>


        <ul id="todo-list"></ul>
    </div>


    <script type="module" src="./todo.js"></script>
</body>
</html>
