npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gtfs-jp/loader

v4.0.0

Published

Browser SQLite WASM loader for GTFS-JP v4 data (in-memory / OPFS)

Readme

@gtfs-jp/loader

ブラウザ上で GTFS-JP v4 ZIP を SQLite WASM に取り込み、型安全にクエリできるローダーです。ストレージは in-memory と OPFS ( 永続化) を選択できます。

Install

npm install @gtfs-jp/loader @gtfs-jp/types kysely

kysely が peer dependency として必要です。

Vite を使う場合は、SQLite WASM の build asset 名を固定するために @gtfs-jp/loader/vite の plugin を追加してください。

import { defineConfig } from 'vite';
import { gtfsLoaderPlugin } from '@gtfs-jp/loader/vite';

export default defineConfig({
  plugins: [gtfsLoaderPlugin()],
});

Basic Usage

import { createGtfsLoader } from '@gtfs-jp/loader';

const loader = createGtfsLoader({ storage: 'memory' });
await loader.open();

// ZIP インポート (進捗コールバック付き)
const result = await loader.importZip(file, {
  onProgress: (event) => console.log(event.phase, event.message),
});

console.log(`${result.tablesImported} tables, ${result.rowsImported} rows`);

// Kysely で型付きクエリ
const routes = await loader
  .db()
  .selectFrom('routes')
  .selectAll()
  .orderBy('route_id')
  .limit(100)
  .execute();

await loader.close();

Storage Modes

// in-memory (揮発性、高速)
createGtfsLoader({ storage: 'memory' });

// OPFS (永続化、ブラウザ再起動後も保持)
createGtfsLoader({ storage: 'opfs', filename: 'gtfs.sqlite3' });

Custom Database Provider

独自の SQLite/Kysely provider を使う場合は database を渡せます。

import { createGtfsLoader, type GtfsDatabaseProvider } from '@gtfs-jp/loader';

const database: GtfsDatabaseProvider = {
  async open() {},
  async close(_options) {},
  async reset() {},
  db() {
    return customKyselyDb;
  },
  async exportBytes() {
    return await exportSqliteBytes();
  },
  async importBytes(bytes) {
    await importSqliteBytes(bytes);
  },
};

const loader = createGtfsLoader({
  database,
  schema,
});

OPFS を使用するには、ページに以下のヘッダーが必要です:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Vite build/preview では上記に加えて gtfsLoaderPlugin() を使い、sqlite3.wasm と sqlite3-opfs-async-proxy.js が assets/ 配下の固定名で配信されるようにしてください。

Import Options

await loader.importZip(file, {
  parseWorkerConcurrency: 8, // CSV パース並列数
  dbWriteConcurrency: 4, // DB 書き込み並列数
  parseChunkRowCount: 1000, // パースチャンクサイズ
  insertBatchRowCount: 500, // INSERT バッチサイズ
  opfsImportMode: 'memory-stage', // OPFS時: メモリで構築してから反映 (default)
  onProgress: (event) => {
    // event.phase: "prepare" | "import" | "derive" | "done" | "error"
    // event.targetName, event.state, event.rowsWritten ...
  },
});

SQLiteデータベースはバイト列としてエクスポートできます。

const sqliteBytes = await loader.exportBytes();

既知の GTFS-JP テーブルでは、入力ヘッダに存在しない非必須列もスキーマに基づいて DB 上へ作成されます。これらの列の値は NULL として補完されるため、selectAll() や introspection で安定した列集合を扱えます。

Derived Tables

ソーステーブルから派生テーブルを定義し、インポート時に自動 materialize できます。

import { createGtfsLoader, defineGtfsSchema, derivedTable } from '@gtfs-jp/loader';

const routeSummary = derivedTable({
  name: 'route_summary',
  dependsOn: ['routes', 'trips'],
  columns: {
    route_id: { kind: 'string', nullable: false },
    trip_count: { kind: 'number', nullable: false },
  },
  build: {
    kind: 'sql',
    selectSql: `
      SELECT r.route_id, COUNT(t.trip_id) AS trip_count
      FROM routes r LEFT JOIN trips t ON r.route_id = t.route_id
      GROUP BY r.route_id
    `,
  },
});

const schema = defineGtfsSchema({
  derivedTables: [routeSummary],
});

const loader = createGtfsLoader({ schema });

派生テーブルは db() で型安全にクエリできます:

const summary = await loader
  .db()
  .selectFrom('route_summary') // 型補完が効く
  .selectAll()
  .execute();
// summary[0].route_id: string, summary[0].trip_count: number

Build Strategies

SQL — 単一 SELECT 文:

build: {
  kind: "sql",
  selectSql: "SELECT route_id, COUNT(*) as cnt FROM trips GROUP BY route_id",
}

SQL Script — 複数 SQL 文 (DDL/DML):

build: {
  kind: "sqlScript",
  statements: [
    "INSERT INTO my_table SELECT * FROM source WHERE condition",
  ],
}

JS — TypeScript で変換ロジックを記述:

build: {
  kind: "js",
  async *run(context) {
    const stops = await context.readSource("stops", {
      columns: ["stop_id", "stop_name", "stop_lat", "stop_lon"],
    });
    for (const stop of stops) {
      yield { stop_id: stop.stop_id, label: `${stop.stop_name} (${stop.stop_lat})` };
    }
  },
}

JS builder の context には以下のメソッドがあります:

| メソッド | 説明 | | ---------------------------------- | -------------------------------------- | | readSource(table, opts?) | ソーステーブルを型付きで読み取り | | readOptionalSource(table, opts?) | テーブルが存在しない場合は空配列を返す | | hasSource(table) | ソーステーブルの存在確認 | | query(sql, bind?) | 任意の SQL を実行して結果を取得 | | exec(sql, bind?) | 任意の SQL を実行 (結果なし) | | emitProgress(progress) | 進捗を通知 |

API

createGtfsLoader(options?)

ローダーインスタンスを生成します。

| オプション | 型 | デフォルト | 説明 | | --------------------- | ---------------------- | ---------- | --------------------------------------- | | database | GtfsDatabaseProvider | — | カスタム DB provider | | storage | "memory" \| "opfs" | "memory" | ストレージモード | | filename | string | — | OPFS ファイル名 | | worker | Worker | — | カスタム Worker インスタンス | | strictGtfsTableName | boolean | true | 未知のテーブル名でエラーにするか | | schema | GtfsSchemaDefinition | — | 派生テーブル定義を含むスキーマ | | runtime | object | — | JS builder に渡すランタイムコンテキスト |

GtfsLoader メソッド

| メソッド | 説明 | | --------------------------- | ------------------------------------------------------------------------------------------------------------------ | | open() | DB を開く | | close(options?) | DB を閉じる ({ unlink: true } で OPFS ファイル削除) | | reset() | DB を削除して再作成 | | importZip(file, options?) | GTFS-JP ZIP をインポート | | validate() | 必須テーブルの存在を検証し GtfsValidationResult を返す | | db() | Kysely インスタンスを返す。スキーマに派生テーブルが含まれる場合、それらも型付きでクエリ可能 | | listTables() | 全テーブル名を取得 | | listGtfsTables() | GTFS テーブル名のみ取得 | | hasTable(name) | テーブルの存在確認 |

License

MIT