ts-rails 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.ja.md +449 -0
  2. package/README.md +311 -288
  3. package/README.vi.md +449 -0
  4. package/dist/cli.js +61 -18
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/console.js +7 -7
  7. package/dist/commands/console.js.map +1 -1
  8. package/dist/commands/generate-channel.d.ts +2 -0
  9. package/dist/commands/generate-channel.d.ts.map +1 -0
  10. package/dist/commands/generate-channel.js +58 -0
  11. package/dist/commands/generate-channel.js.map +1 -0
  12. package/dist/commands/generate-concern.js +2 -1
  13. package/dist/commands/generate-concern.js.map +1 -1
  14. package/dist/commands/generate-controller.js +12 -11
  15. package/dist/commands/generate-controller.js.map +1 -1
  16. package/dist/commands/generate-factory.js +2 -1
  17. package/dist/commands/generate-factory.js.map +1 -1
  18. package/dist/commands/generate-job.js +14 -8
  19. package/dist/commands/generate-job.js.map +1 -1
  20. package/dist/commands/generate-mailer.js +24 -15
  21. package/dist/commands/generate-mailer.js.map +1 -1
  22. package/dist/commands/generate-model.js +2 -1
  23. package/dist/commands/generate-model.js.map +1 -1
  24. package/dist/commands/generate-resource.js +21 -17
  25. package/dist/commands/generate-resource.js.map +1 -1
  26. package/dist/commands/generate-scaffold.js +22 -34
  27. package/dist/commands/generate-scaffold.js.map +1 -1
  28. package/dist/commands/generate-service.js +26 -23
  29. package/dist/commands/generate-service.js.map +1 -1
  30. package/dist/commands/generate-test.d.ts +2 -0
  31. package/dist/commands/generate-test.d.ts.map +1 -0
  32. package/dist/commands/generate-test.js +94 -0
  33. package/dist/commands/generate-test.js.map +1 -0
  34. package/dist/commands/generatorHelpers.d.ts +21 -0
  35. package/dist/commands/generatorHelpers.d.ts.map +1 -0
  36. package/dist/commands/generatorHelpers.js +84 -0
  37. package/dist/commands/generatorHelpers.js.map +1 -0
  38. package/dist/commands/notes.js +3 -2
  39. package/dist/commands/notes.js.map +1 -1
  40. package/dist/commands/resolveRailsAppRoot.d.ts +2 -0
  41. package/dist/commands/resolveRailsAppRoot.d.ts.map +1 -0
  42. package/dist/commands/resolveRailsAppRoot.js +41 -0
  43. package/dist/commands/resolveRailsAppRoot.js.map +1 -0
  44. package/dist/commands/routes.js +93 -47
  45. package/dist/commands/routes.js.map +1 -1
  46. package/dist/commands/testAnalyzer.d.ts +22 -0
  47. package/dist/commands/testAnalyzer.d.ts.map +1 -0
  48. package/dist/commands/testAnalyzer.js +190 -0
  49. package/dist/commands/testAnalyzer.js.map +1 -0
  50. package/package.json +3 -1
package/README.ja.md ADDED
@@ -0,0 +1,449 @@
1
+ # ts-rails
2
+
3
+ **TypeScript と Express 向けの Rails スタイルアプリケーション層** — ルーティング、コントローラ、ジェネレータ、リアルタイムチャネル、メーラー、バックグラウンドジョブ、および Irwin エコシステム向けの共通ユーティリティ。
4
+
5
+ | 言語 | ドキュメント |
6
+ |------|----------------|
7
+ | English | [README.md](./README.md) |
8
+ | Tiếng Việt | [README.vi.md](./README.vi.md) |
9
+ | **日本語**(本書) | [README.ja.md](./README.ja.md) |
10
+
11
+ ---
12
+
13
+ ## 目次
14
+
15
+ 1. [概要](#概要)
16
+ 2. [インストール](#インストール)
17
+ 3. [想定プロジェクト構成](#想定プロジェクト構成)
18
+ 4. [ランタイムライブラリ](#ランタイムライブラリ)
19
+ 5. [サブパス export](#サブパス-export)
20
+ 6. [CLI](#cli)
21
+ 7. [ジェネレータ一覧](#ジェネレータ一覧)
22
+ 8. [関連プロジェクト](#関連プロジェクト)
23
+ 9. [ライセンス](#ライセンス)
24
+
25
+ ---
26
+
27
+ ## 概要
28
+
29
+ `ts-rails` は **アプリ内フレームワーク**(プロジェクト全体のスキャフォールドツールではありません)。アプリケーションは次のように構成します。
30
+
31
+ - HTTP と Socket.IO のライフサイクル用に `RailsApplication` を継承する。
32
+ - `RailsRoute` でルートを宣言する(`resource()`、`action()`、権限、Swagger メタデータ)。
33
+ - ベースクラスの上にコントローラ、サービス、ジョブ、メーラー、チャネルを実装する。
34
+
35
+ | 領域 | 主な API |
36
+ |------|----------|
37
+ | Application | `RailsApplication`、`MiddlewareFactory`、`loadConcerns`、`bootstrap()`、`getRoutes()` |
38
+ | Routing | `RailsRoute`、`RestActions`、`action()`、`resource()` |
39
+ | Controller | `RailsController`、`@BeforeAction`、`@AfterAction`、ストロングパラメータ、`ApiResponse` |
40
+ | Real-time | `RailsChannel`、`channelClasses` による登録 |
41
+ | Mail | `RailsMailer`、`MailerAdapter` |
42
+ | Job | `RailsJob`、`JobAdapter`、`performLater()` |
43
+ | API ドキュメント | ルートの `document` から Swagger レジストリ |
44
+ | ユーティリティ | `logger`、`Cache`、ページネーション、ビューヘルパー、`AppError` 階層 |
45
+
46
+ **新規プロジェクト**や機能パック(`auth`、`admin` など)は **[irwin-cli](../irwin-cli/PLAN.md)**(計画中)を使用してください。本パッケージは **既存アプリ内のランタイムとジェネレータ** に焦点を当てます。
47
+
48
+ ---
49
+
50
+ ## インストール
51
+
52
+ ```bash
53
+ npm install ts-rails
54
+ # または
55
+ yarn add ts-rails
56
+ # または
57
+ pnpm add ts-rails
58
+ ```
59
+
60
+ ### ピア依存関係
61
+
62
+ アプリで実際に使うピアをインストールしてください(コアには Express が必須。他は機能に応じて):
63
+
64
+ | パッケージ | 用途 |
65
+ |------------|------|
66
+ | `express` | HTTP サーバー |
67
+ | `reflect-metadata`、`class-validator`、`class-transformer` | ストロングパラメータ / バリデーション |
68
+ | `cookie-parser`、`method-override` | 標準ミドルウェア |
69
+ | `socket.io` | チャネル |
70
+ | `nodemailer` | メーラー |
71
+ | `swagger-ui-express` | Swagger UI(アプリ側) |
72
+ | `dayjs` | ビューヘルパー `timeAgo` |
73
+ | `pluralize` | CLI ジェネレータ |
74
+ | `http-errors` | 404 / エラー処理 |
75
+
76
+ ### CLI はローカルで実行
77
+
78
+ `rails` バイナリは本パッケージに含まれます。**アプリルートからローカルインストール経由で実行**してください(Windows でパスに空白がある場合、グローバル実行は失敗しやすいです):
79
+
80
+ ```bash
81
+ pnpm exec rails routes
82
+ pnpm exec rails g controller Posts
83
+ ```
84
+
85
+ CLI は `app/controllers` が見つかるまで親ディレクトリをたどってアプリルートを解決します(`INIT_CWD`、`PNPM_SCRIPT_SRC_DIR`、`npm_config_local_prefix` も参照)。
86
+
87
+ ---
88
+
89
+ ## 想定プロジェクト構成
90
+
91
+ ジェネレータと規約は Irwin スタイルのツリーを前提とします:
92
+
93
+ ```text
94
+ your-app/
95
+ ├── app/
96
+ │ ├── controllers/ # CLI ルート検出に必須
97
+ │ ├── views/
98
+ │ ├── services/
99
+ │ ├── jobs/
100
+ │ ├── mailers/
101
+ │ └── channels/
102
+ ├── configs/
103
+ │ ├── application.ts # RailsApplication を継承
104
+ │ ├── routes/ # *.route.ts(app/ 外)
105
+ │ └── db/schema.prisma # `g model` 用
106
+ └── __tests__/ # `g test` の出力 (*.test.ts)
107
+ ```
108
+
109
+ ---
110
+
111
+ ## ランタイムライブラリ
112
+
113
+ ### RailsApplication
114
+
115
+ `RailsApplication` は Express アプリ、標準ミドルウェア、ルートマウント、エラーハンドラ、HTTP サーバー、Socket.IO を管理します。
116
+
117
+ ```typescript
118
+ import { RailsApplication } from "ts-rails";
119
+ import { Route } from "./configs/routes";
120
+
121
+ export class Application extends RailsApplication {
122
+ constructor() {
123
+ super();
124
+ this.port = process.env.PORT ?? "8000";
125
+ }
126
+
127
+ protected mountRoutes() {
128
+ this.app.use("/", Route.draw());
129
+ }
130
+
131
+ public async initialize() {
132
+ // DB、セッション、メーラー、キャッシュなど
133
+ this.bootstrap();
134
+ }
135
+ }
136
+ ```
137
+
138
+ **静的設定**(`bootstrap()` の前に設定):
139
+
140
+ | プロパティ | 目的 |
141
+ |------------|------|
142
+ | `middlewareFactory` | `requestId`、`requestLogging`、`rateLimit`(必須) |
143
+ | `sessionMiddleware` | 任意。Socket.IO 認証チェーン用 |
144
+ | `mailerAdapter` | アプリ全体のメール送信 |
145
+ | `jobAdapter` | キュー(BullMQ など) |
146
+ | `jobClasses` | 登録ジョブクラス |
147
+ | `channelClasses` | Socket.IO チャネルクラス |
148
+ | `cacheStore` | アプリ全体キャッシュ |
149
+ | `loggerAdapter` | ロギング |
150
+ | `hasher` | パスワードハッシュ(`Security`) |
151
+
152
+ **ライフサイクル**(`bootstrap()` の順序):
153
+
154
+ 1. `setupStandardMiddlewares()` — JSON、Cookie、method override、`res.locals.h` 注入
155
+ 2. `mountRoutes()` — サブクラスでオーバーライド
156
+ 3. `getRoutes()` — イントロスペクション + Swagger パス登録
157
+ 4. `setupSwagger()` — アプリでオーバーライド
158
+ 5. `setupErrorHandlers()` — 404 + グローバル(`AppError`、API と HTML)
159
+ 6. `startBackgroundProcessor()` — ワーカー用(Lambda / `IRWIN_CONSOLE` ではスキップ)
160
+
161
+ **Concerns** — コントローラプロトタイプに共通メソッドを混ぜ込む:
162
+
163
+ ```typescript
164
+ this.loadConcerns(ApplicationController.prototype, "app/controllers/concerns");
165
+ ```
166
+
167
+ ### ルーティング(`RailsRoute`)
168
+
169
+ ```typescript
170
+ import { RailsRoute, action, RestActions } from "ts-rails";
171
+ import { UsersController } from "@controllers/users.controller";
172
+
173
+ export class AppRoute extends RailsRoute {
174
+ draw() {
175
+ this.resource("/users", UsersController, {
176
+ only: [RestActions.Index, RestActions.Show, RestActions.Create],
177
+ setPermissionFor: "USER_MANAGEMENT",
178
+ document: {
179
+ tags: ["Users"],
180
+ summary: "ユーザー CRUD",
181
+ body: CreateUserValidator,
182
+ },
183
+ });
184
+
185
+ this.get("/profile", action(UsersController, "profile"));
186
+ }
187
+ }
188
+ ```
189
+
190
+ - **`resource()`** — REST 7 アクション(`only` / `except`、RBAC、`document` で OpenAPI)。
191
+ - **`action(Controller, "methodName")`** — コントローラメソッドをバインド(`@BeforeAction` / `@AfterAction` を実行)。
192
+ - **ファイルアップロード** — ルートに `upload` オプション(multer、制限、`fileFilter`)。
193
+
194
+ 権限は `RailsRoute.permissionFactory` と `RailsRoute.actionPermissionMap` で接続します。
195
+
196
+ ### コントローラ(`RailsController`)
197
+
198
+ | メソッド / プロパティ | 説明 |
199
+ |----------------------|------|
200
+ | `this.params(Validator).permit(...)` | params/query/body をマージ、`class-validator`、ホワイトリスト |
201
+ | `this.render(view, locals)` | Pug/EJS ビュー |
202
+ | `this.renderJson(data, status?)` | `ApiResponse` 経由の JSON |
203
+ | `this.redirect(path)` | HTTP リダイレクト |
204
+ | `this.flash(type, message)` | フラッシュミドルウェアが必要 |
205
+ | `this.io` | `req.app.get("io")` の Socket.IO |
206
+ | `this.t(key, options?)` | `res.locals.t` の i18n |
207
+
208
+ **フィルタ**(クラスデコレータ):
209
+
210
+ ```typescript
211
+ import { RailsController, BeforeAction, AfterAction } from "ts-rails";
212
+
213
+ @BeforeAction("authenticate", { except: ["index"] })
214
+ @AfterAction("logActivity", { only: ["create", "update"] })
215
+ export class UsersController extends RailsController {
216
+ async authenticate() {
217
+ if (!this.req.session?.userId) {
218
+ this.res.status(401).json({ success: false, message: "Unauthorized" });
219
+ return false; // チェーン停止
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### ストロングパラメータ
226
+
227
+ ```typescript
228
+ export class CreateUserValidator {
229
+ @IsEmail()
230
+ email: string;
231
+
232
+ @IsString()
233
+ @MinLength(3)
234
+ name: string;
235
+ }
236
+
237
+ async create() {
238
+ const attrs = await this.params(CreateUserValidator).permit("email", "name");
239
+ // 不正入力 → 422 UnprocessableEntityError
240
+ }
241
+ ```
242
+
243
+ ### API レスポンスとページネーション
244
+
245
+ ```typescript
246
+ import { parsePagination, buildPaginatedResponse } from "ts-rails/pagination";
247
+
248
+ async index() {
249
+ const { page, perPage, skip } = parsePagination(this.req.query);
250
+ const [rows, total] = await fetchPage(skip, perPage);
251
+ return this.renderJson(buildPaginatedResponse(rows, total, { page, perPage }));
252
+ }
253
+ ```
254
+
255
+ ### HTTP エラー
256
+
257
+ ```typescript
258
+ import {
259
+ BadRequestError,
260
+ UnauthorizedError,
261
+ ForbiddenError,
262
+ NotFoundError,
263
+ UnprocessableEntityError,
264
+ } from "ts-rails/errors";
265
+ ```
266
+
267
+ `RailsApplication` のエラーミドルウェアが処理します(`/api/*` は JSON、それ以外は HTML エラービュー)。
268
+
269
+ ### リアルタイムチャネル(`RailsChannel`)
270
+
271
+ ```typescript
272
+ import { RailsChannel } from "ts-rails";
273
+
274
+ export class ChatChannel extends RailsChannel {
275
+ subscribe() {
276
+ this.socket.on("message", (data) => {
277
+ this.broadcastTo("room_1", "message", data);
278
+ });
279
+ }
280
+ }
281
+ ```
282
+
283
+ 登録例:
284
+
285
+ ```typescript
286
+ import * as channels from "@channels";
287
+ RailsApplication.channelClasses = Object.values(channels);
288
+ ```
289
+
290
+ ### メーラー(`RailsMailer`)
291
+
292
+ ```typescript
293
+ export class UserMailer extends RailsMailer {
294
+ static async welcome(user: { email: string; name: string }) {
295
+ await this.deliver({
296
+ to: user.email,
297
+ subject: "ようこそ",
298
+ html: `<p>こんにちは ${user.name}</p>`,
299
+ });
300
+ }
301
+ }
302
+ ```
303
+
304
+ `RailsApplication.mailerAdapter` を設定するか、`ApplicationMailer` で `getTransporter()` をオーバーライドします。
305
+
306
+ ### バックグラウンドジョブ(`RailsJob`)
307
+
308
+ ```typescript
309
+ export class SyncDataJob extends RailsJob {
310
+ async perform(payload: unknown) {
311
+ // 処理
312
+ }
313
+ }
314
+
315
+ await SyncDataJob.performLater({ id: 1 });
316
+ ```
317
+
318
+ 非同期キューには `RailsApplication.jobAdapter` を設定。生成ジョブは `ApplicationJob` を継承し、`static cron` で `node-cron` に対応可能。
319
+
320
+ ### ビューヘルパー
321
+
322
+ テンプレートでは `h`(`res.locals.h`)として利用:
323
+
324
+ ```typescript
325
+ import { viewHelpers as h } from "ts-rails";
326
+
327
+ h.timeAgo(new Date());
328
+ h.numberToCurrency(50000, "VND");
329
+ h.truncate("長い文字列", 20);
330
+ h.assetPath("javascripts/main.ts"); // 本番は Vite manifest
331
+ ```
332
+
333
+ ### Swagger
334
+
335
+ ルートの `document` で OpenAPI パスを登録。アプリの `setupSwagger()` で `setupSwaggerUI` を使用。`document.body` の Validator クラスは `class-validator` メタデータから JSON Schema にマップされます。
336
+
337
+ ### ロガーとキャッシュ
338
+
339
+ ```typescript
340
+ import { logger } from "ts-rails/logger";
341
+ import { Cache } from "ts-rails/cache";
342
+
343
+ logger.info("起動しました");
344
+ await Cache.set("key", value, { ttl: 3600 });
345
+ ```
346
+
347
+ 初期化で `RailsApplication.loggerAdapter` と `RailsApplication.cacheStore` を設定します。
348
+
349
+ ---
350
+
351
+ ## サブパス export
352
+
353
+ | import | モジュール |
354
+ |--------|------------|
355
+ | `ts-rails` | 公開 API 一式 |
356
+ | `ts-rails/logger` | ロガー |
357
+ | `ts-rails/cache` | キャッシュ |
358
+ | `ts-rails/errors` | HTTP エラークラス |
359
+ | `ts-rails/pagination` | `parsePagination`、`buildPaginatedResponse` |
360
+
361
+ ---
362
+
363
+ ## CLI
364
+
365
+ コマンド名: **`rails`**。エイリアス: `g` → `generate`、`c` → `console`、`r` → `routes`、`n` → `notes`。
366
+
367
+ | コマンド | 説明 |
368
+ |----------|------|
369
+ | `rails g <type> <name> [fields...]` | ジェネレータ実行(`cwd` = アプリルート) |
370
+ | `rails routes` | ルート一覧(`configs/application` を読み込み) |
371
+ | `rails console` | アプリコンテキスト付き REPL(`IRWIN_CONSOLE=1`) |
372
+ | `rails notes` | `TODO` / `FIXME` / `OPTIMIZE` コメント一覧(`bin/rails notes` と同様) |
373
+
374
+ ### `rails notes`
375
+
376
+ **`app/`**、**`configs/`**、**`lib/`**、**`__tests__/`** 内の `.ts` / `.js` / `.pug` を走査し、タグごとに色分けしてパスと行番号を表示します。
377
+
378
+ ```bash
379
+ pnpm exec rails notes
380
+ pnpm exec rails n
381
+ ```
382
+
383
+ ### ジェネレータ構文
384
+
385
+ ```bash
386
+ pnpm exec rails g <type> <Name> [field:type ...] [--api]
387
+ ```
388
+
389
+ **名前空間:** `Admin/User`、`admin/user`、`Admin:User` → `app/.../admin/`、`configs/routes/admin/`。
390
+
391
+ **フィールド型:**
392
+ `string`、`text`、`integer`、`int`、`float`、`decimal`、`boolean`、`date`、`datetime`、`json`。
393
+
394
+ ---
395
+
396
+ ## ジェネレータ一覧
397
+
398
+ | type | 生成物 |
399
+ |------|--------|
400
+ | `scaffold` | コントローラ、Pug ビュー(`--api` 除く)、`configs/routes/` のルート |
401
+ | `resource` | API 向けコントローラ + ルート |
402
+ | `controller` | コントローラのみ |
403
+ | `service` | `ApplicationService` / `{Namespace}Service` を継承する `*.service.ts` |
404
+ | `model` | `configs/db/schema.prisma` に Prisma モデル追加 |
405
+ | `mailer` | `ApplicationMailer` を継承 |
406
+ | `job` | `ApplicationJob` を継承(`static cron`、`perform`) |
407
+ | `channel` | `ApplicationChannel` を継承 |
408
+ | `factory` | テスト用ファクトリ stub |
409
+ | `concern` | `app/controllers/concerns/` の mixin |
410
+ | `test` | ソースから Jest stub(public メソッドごとに `it.todo`) |
411
+
412
+ ### 例
413
+
414
+ ```bash
415
+ pnpm exec rails g scaffold Product name:string price:decimal description:text
416
+ pnpm exec rails g resource Order total:decimal status:string --api
417
+ pnpm exec rails g controller Admin/Dashboard
418
+ pnpm exec rails g service Payment/Process
419
+ pnpm exec rails g mailer UserNotification
420
+ pnpm exec rails g job SyncInventory
421
+ pnpm exec rails g channel Chat
422
+ pnpm exec rails g model Category name:string
423
+ pnpm exec rails g factory User
424
+ pnpm exec rails g concern Timestampable
425
+ pnpm exec rails g test app/controllers/home.controller.ts
426
+ ```
427
+
428
+ **注意:**
429
+
430
+ - ルートは **`configs/routes/`** に出力(`app/routes` ではない)。
431
+ - サービスは必要に応じて名前空間の基底クラスを自動作成。
432
+ - `g scaffold` は隣接 `*.spec.ts` を生成しない。`g test` を使用。
433
+ - 生成コードのリダイレクトはテンプレートリテラルで正しいパスを使用。
434
+
435
+ ---
436
+
437
+ ## 関連プロジェクト
438
+
439
+ | リポジトリ | 役割 |
440
+ |------------|------|
441
+ | [irwin-framework](../irwin-framework/) | 全機能のリファレンスアプリ |
442
+ | [irwin-cli](../irwin-cli/) | `irwin new`、`irwin add`(計画) |
443
+ | **ts-rails**(本リポジトリ) | アプリ内ランタイム + `rails g` / `routes` / `console` / `notes` |
444
+
445
+ ---
446
+
447
+ ## ライセンス
448
+
449
+ MIT — Hoan Pham およびコントリビューターにより開発。