@bagooon/chatease-node-client 0.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.
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # @bagooon/chatease-node-client
2
+
3
+ Node.js 向けの **ChatEase チャットボード API クライアント** です。
4
+ サーバーサイド(Node.js)専用で、ブラウザからの利用は想定していません。
5
+
6
+ > ⚠️ This package is **Node.js-only**.
7
+ > Do **NOT** use it in browser environments. Your API token will be exposed.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - ChatEase の「チャットボード生成 API」を安全にラップ
14
+ - 3パターンのメソッドを提供
15
+ - チャットボード生成のみ
16
+ - チャットボード + 初期ステータス
17
+ - チャットボード + 初期ステータス + 初期投稿
18
+ - TypeScript フルサポート(型定義同梱)
19
+ - 実行時バリデーション
20
+ - `timeLimit` の日付妥当性(`YYYY-MM-DD` & 実在日付)
21
+ - `guest.email` の簡易フォーマットチェック
22
+ - `boardUniqueKey` の妥当性チェック
23
+
24
+ ---
25
+
26
+ ## Requirements
27
+
28
+ - Node.js **18+** (グローバル `fetch` が必要)
29
+ - ChatEase のワークスペーススラッグ & API トークン
30
+ - サーバーサイド(Node.js)環境のみ
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install @bagooon/chatease-node-client
38
+ # or
39
+ yarn add @bagooon/chatease-node-client
40
+ # or
41
+ pnpm add @bagooon/chatease-node-client
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Quick Start
47
+
48
+ ```ts
49
+ import { ChatEaseClient } from '@hashimoto-giken/chatease-node-client'
50
+
51
+ const chatease = new ChatEaseClient({
52
+ apiToken: process.env.CHATEASE_API_TOKEN!,
53
+ workspaceSlug: 'your-workspace-slug',
54
+ })
55
+
56
+ // 1) チャットボードのみ生成
57
+ const res1 = await chatease.createBoard({
58
+ title: 'お問い合わせ #1001',
59
+ guest: {
60
+ name: '田中太郎',
61
+ email: 'taro@example.com',
62
+ },
63
+ boardUniqueKey: '20260225-1001',
64
+ })
65
+
66
+ console.log(res1.guestURL)
67
+
68
+ // 2) 初期ステータス付きで生成
69
+ const res2 = await chatease.createBoardWithStatus({
70
+ title: '見積依頼 #1002',
71
+ guest: {
72
+ name: 'Suzuki Hanako',
73
+ email: 'hanako@example.com',
74
+ },
75
+ boardUniqueKey: '20260225-1002',
76
+ initialStatus: {
77
+ statusKey: 'scheduled_for_response',
78
+ timeLimit: '2026-02-28', // YYYY-MM-DD
79
+ },
80
+ })
81
+
82
+ // 3) 初期ステータス + 初期投稿付きで生成
83
+ const res3 = await chatease.createBoardWithStatusAndMessage({
84
+ title: 'デザイン相談 #1003',
85
+ guest: {
86
+ name: 'John Smith',
87
+ email: 'john@example.com',
88
+ },
89
+ boardUniqueKey: '20260225-1003',
90
+ initialStatus: {
91
+ statusKey: 'scheduled_for_proof',
92
+ timeLimit: '2026-03-05',
93
+ },
94
+ initialGuestComment: {
95
+ content: 'ロゴデザインについて相談したいです。現在の案を添付しました。',
96
+ },
97
+ })
98
+ ```
99
+
100
+ ---
101
+
102
+ ## API
103
+
104
+ ### `new ChatEaseClient(options)`
105
+
106
+ ```ts
107
+ interface ChatEaseClientOptions {
108
+ apiToken: string
109
+ workspaceSlug: string
110
+ baseUrl?: string // default: 'https://chatease.jp'
111
+ }
112
+ ```
113
+
114
+ - `apiToken` – ChatEase の API トークン
115
+ - `workspaceSlug` – ワークスペースの slug
116
+ - `baseUrl` – ステージングなどを使う場合に差し替え。通常は指定不要。
117
+
118
+ ブラウザ環境(window が存在する)で呼び出すと、即座にエラーを投げます。
119
+
120
+ ---
121
+
122
+ ### `createBoard(params)`
123
+
124
+ ```ts
125
+ interface GuestInfo {
126
+ name: string
127
+ email: string
128
+ }
129
+
130
+ interface CreateBoardBaseParams {
131
+ title: string
132
+ guest: GuestInfo
133
+ boardUniqueKey: string
134
+ inReplyTo?: string
135
+ }
136
+
137
+ createBoard(params: CreateBoardBaseParams): Promise<CreateBoardResponse>
138
+ ```
139
+
140
+ 最低限の情報でチャットボードを生成します。
141
+
142
+ - `boardUniqueKey` は同じ値で再度呼び出すと、既存ボードが返ってくる仕様です
143
+ - `guest.email` は簡易フォーマットチェックが行われます
144
+ - `boardUniqueKey` は空文字や空白を含む値は拒否されます
145
+
146
+ ---
147
+
148
+ ### `createBoardWithStatus(params)`
149
+
150
+ ```ts
151
+ type ChatEaseStatusKey =
152
+ | 'scheduled_for_proof'
153
+ | 'scheduled_for_response'
154
+ | 'scheduled_for_completion'
155
+ | 'waiting_for_reply'
156
+
157
+ type InitialStatus =
158
+ | {
159
+ statusKey:
160
+ | 'scheduled_for_proof'
161
+ | 'scheduled_for_response'
162
+ | 'scheduled_for_completion'
163
+ timeLimit: string // YYYY-MM-DD
164
+ }
165
+ | {
166
+ statusKey: 'waiting_for_reply'
167
+ timeLimit?: never
168
+ }
169
+
170
+ interface CreateBoardWithStatusParams extends CreateBoardBaseParams {
171
+ initialStatus: InitialStatus
172
+ }
173
+
174
+ createBoardWithStatus(
175
+ params: CreateBoardWithStatusParams
176
+ ): Promise<CreateBoardResponse>
177
+ ```
178
+
179
+ - `scheduled_for_*` の場合は timeLimit が必須
180
+ - `waiting_for_reply` の場合は timeLimit は指定できません(型でも実行時でも防止)
181
+
182
+ ---
183
+
184
+ ### `createBoardWithStatusAndMessage(params)`
185
+
186
+ ```ts
187
+ interface InitialGuestComment {
188
+ content: string
189
+ }
190
+
191
+ interface CreateBoardWithStatusAndMessageParams
192
+ extends CreateBoardBaseParams {
193
+ initialStatus: InitialStatus
194
+ initialGuestComment: InitialGuestComment
195
+ }
196
+
197
+ createBoardWithStatusAndMessage(
198
+ params: CreateBoardWithStatusAndMessageParams
199
+ ): Promise<CreateBoardResponse>
200
+ ```
201
+
202
+ 初回のゲスト投稿までまとめて登録する場合に使います。
203
+
204
+ ---
205
+
206
+ ### `CreateBoardResponse`
207
+
208
+ ```ts
209
+ interface CreateBoardResponse {
210
+ slug: string
211
+ hostURL: string
212
+ guestURL: string
213
+ }
214
+ ```
215
+
216
+ - `slug` – ボードの識別子
217
+ - `hostURL` – ホスト(管理側)用 URL
218
+ - `guestURL` – ゲスト側 URL(メールに貼るなど)
219
+
220
+ ---
221
+
222
+ ## Validation
223
+
224
+ このクライアントは、API 呼び出し前に以下の実行時チェックを行います:
225
+
226
+ - guest.email – 簡易メール形式チェック
227
+ - boardUniqueKey
228
+ - 空文字禁止
229
+ - 前後の空白禁止
230
+ - 空白文字(スペース・タブ・改行など)を含まない
231
+ - 最大 255 文字まで
232
+ - initialStatus.timeLimit
233
+ - scheduled_for_* の場合は必須
234
+ - YYYY-MM-DD 形式
235
+ - 実在する日付か確認(うるう年を含む)
236
+
237
+ バリデーションエラーの場合、API は呼び出されず Error が投げられます。
238
+
239
+ ---
240
+
241
+ ## Development
242
+
243
+ ローカル開発用コマンド:
244
+
245
+ ```bash
246
+ # 依存関係インストール
247
+ npm install
248
+
249
+ # テスト実行(1回)
250
+ npm run test
251
+
252
+ # テストをウォッチモードで実行
253
+ npm run test:watch
254
+
255
+ # ビルド
256
+ npm run build
257
+ ```
258
+
259
+ リリース前チェック:
260
+
261
+ ```bash
262
+ # テスト & ビルド
263
+ npm run test
264
+ npm run build
265
+ ```
266
+
267
+ `npm publish` 時には自動的に `npm run test && npm run build` が実行されます。
268
+
269
+ ---
270
+
271
+ ## License
272
+
273
+ MIT
@@ -0,0 +1,22 @@
1
+ import type { ChatEaseClientOptions, CreateBoardBaseParams, CreateBoardWithStatusParams, CreateBoardWithStatusAndMessageParams, CreateBoardResponse } from './types.js';
2
+ export declare class ChatEaseClient {
3
+ private readonly apiToken;
4
+ private readonly workspaceSlug;
5
+ private readonly baseUrl;
6
+ constructor(options: ChatEaseClientOptions);
7
+ createBoard(params: CreateBoardBaseParams): Promise<CreateBoardResponse>;
8
+ createBoardWithStatus(params: CreateBoardWithStatusParams): Promise<CreateBoardResponse>;
9
+ createBoardWithStatusAndMessage(params: CreateBoardWithStatusAndMessageParams): Promise<CreateBoardResponse>;
10
+ /**
11
+ * 実処理 + 共通バリデーション
12
+ */
13
+ private _createBoard;
14
+ /**
15
+ * 呼び出しパラメータのバリデーション
16
+ * - email 形式チェック
17
+ * - boardUniqueKey 妥当性チェック
18
+ * - initialStatus があれば timeLimit を検証
19
+ */
20
+ private validateParams;
21
+ private buildUrl;
22
+ }
package/dist/client.js ADDED
@@ -0,0 +1,83 @@
1
+ import { isValidBoardUniqueKey, isValidEmail, validateInitialStatus, } from './validators.js';
2
+ export class ChatEaseClient {
3
+ constructor(options) {
4
+ if (typeof window !== 'undefined') {
5
+ throw new Error('ChatEaseClient is for Node.js only. Do not use it in the browser (API token leak risk).');
6
+ }
7
+ if (!options.apiToken) {
8
+ throw new Error('ChatEaseClient: apiToken is required');
9
+ }
10
+ if (!options.workspaceSlug) {
11
+ throw new Error('ChatEaseClient: workspaceSlug is required');
12
+ }
13
+ this.apiToken = options.apiToken;
14
+ this.workspaceSlug = options.workspaceSlug;
15
+ this.baseUrl = options.baseUrl ?? 'https://chatease.jp';
16
+ if (typeof fetch !== 'function') {
17
+ throw new Error('ChatEaseClient requires global fetch (Node.js v18+).');
18
+ }
19
+ }
20
+ async createBoard(params) {
21
+ return this._createBoard(params);
22
+ }
23
+ async createBoardWithStatus(params) {
24
+ return this._createBoard(params);
25
+ }
26
+ async createBoardWithStatusAndMessage(params) {
27
+ return this._createBoard(params);
28
+ }
29
+ /**
30
+ * 実処理 + 共通バリデーション
31
+ */
32
+ async _createBoard(params) {
33
+ this.validateParams(params);
34
+ const body = {
35
+ workspaceSlug: this.workspaceSlug,
36
+ ...params,
37
+ };
38
+ const res = await fetch(this.buildUrl('/api/v1/board'), {
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ 'X-Chatease-API-Token': this.apiToken,
43
+ },
44
+ body: JSON.stringify(body),
45
+ });
46
+ if (!res.ok) {
47
+ const text = await res.text().catch(() => '');
48
+ const message = [
49
+ `ChatEase API error: ${res.status} ${res.statusText}`,
50
+ text && `Body: ${text}`,
51
+ ]
52
+ .filter(Boolean)
53
+ .join(' - ');
54
+ throw new Error(message);
55
+ }
56
+ const json = (await res.json());
57
+ return json;
58
+ }
59
+ /**
60
+ * 呼び出しパラメータのバリデーション
61
+ * - email 形式チェック
62
+ * - boardUniqueKey 妥当性チェック
63
+ * - initialStatus があれば timeLimit を検証
64
+ */
65
+ validateParams(params) {
66
+ const { guest, boardUniqueKey } = params;
67
+ if (!guest?.email) {
68
+ throw new Error('guest.email is required');
69
+ }
70
+ if (!isValidEmail(guest.email)) {
71
+ throw new Error(`guest.email is invalid: ${guest.email}`);
72
+ }
73
+ if (!isValidBoardUniqueKey(boardUniqueKey)) {
74
+ throw new Error(`boardUniqueKey is invalid. It must be a non-empty string without whitespace and <= 255 chars. Got: "${boardUniqueKey}"`);
75
+ }
76
+ if ('initialStatus' in params && params.initialStatus) {
77
+ validateInitialStatus(params.initialStatus);
78
+ }
79
+ }
80
+ buildUrl(path) {
81
+ return `${this.baseUrl.replace(/\/+$/, '')}${path}`;
82
+ }
83
+ }
@@ -0,0 +1,2 @@
1
+ export * from './types.js';
2
+ export * from './client.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // src/index.ts
2
+ export * from './types.js';
3
+ export * from './client.js';
@@ -0,0 +1,54 @@
1
+ export type ChatEaseStatusKey = 'scheduled_for_proof' | 'scheduled_for_response' | 'scheduled_for_completion' | 'waiting_for_reply';
2
+ type ScheduledStatusKey = 'scheduled_for_proof' | 'scheduled_for_response' | 'scheduled_for_completion';
3
+ type NonScheduledStatusKey = 'waiting_for_reply';
4
+ export type InitialStatus = {
5
+ statusKey: ScheduledStatusKey;
6
+ /**
7
+ * scheduled_for_* の場合は必須
8
+ * YYYY-MM-DD
9
+ */
10
+ timeLimit: string;
11
+ } | {
12
+ statusKey: NonScheduledStatusKey;
13
+ /**
14
+ * scheduled 以外は timeLimit を指定させない
15
+ */
16
+ timeLimit?: never;
17
+ };
18
+ export interface ChatEaseClientOptions {
19
+ apiToken: string;
20
+ workspaceSlug: string;
21
+ baseUrl?: string;
22
+ }
23
+ export interface GuestInfo {
24
+ name: string;
25
+ email: string;
26
+ }
27
+ export interface InitialGuestComment {
28
+ content: string;
29
+ }
30
+ export interface CreateBoardBaseParams {
31
+ title: string;
32
+ guest: GuestInfo;
33
+ boardUniqueKey: string;
34
+ inReplyTo?: string;
35
+ }
36
+ export interface CreateBoardWithStatusParams extends CreateBoardBaseParams {
37
+ initialStatus: InitialStatus;
38
+ }
39
+ export interface CreateBoardWithStatusAndMessageParams extends CreateBoardBaseParams {
40
+ initialStatus: InitialStatus;
41
+ initialGuestComment: InitialGuestComment;
42
+ }
43
+ export interface CreateBoardRequestBody extends CreateBoardBaseParams {
44
+ workspaceSlug: string;
45
+ inReplyTo?: string;
46
+ initialStatus?: InitialStatus;
47
+ initialGuestComment?: InitialGuestComment;
48
+ }
49
+ export interface CreateBoardResponse {
50
+ slug: string;
51
+ hostURL: string;
52
+ guestURL: string;
53
+ }
54
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ // src/types.ts
2
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { InitialStatus } from './types.js';
2
+ /**
3
+ * YYYY-MM-DD 形式 & 実在する日付かをチェック
4
+ */
5
+ export declare function isValidISODate(dateStr: string): boolean;
6
+ /**
7
+ * メールアドレスの簡易バリデーション
8
+ * RFC完全準拠ではなく、実務的な最低限チェック。
9
+ * 「本気の検証」は呼び出し側に任せる。
10
+ */
11
+ export declare function isValidEmail(email: string): boolean;
12
+ /**
13
+ * boardUniqueKey のバリデーション
14
+ *
15
+ * ここは仕様に合わせて調整する想定。
16
+ * 現状は:
17
+ * - 空文字禁止
18
+ * - 前後空白を許可しない(trimして変化するならNG)
19
+ * - 制御文字・空白を含まない
20
+ * - 長さ 1〜255 文字
21
+ */
22
+ export declare function isValidBoardUniqueKey(key: string): boolean;
23
+ /**
24
+ * InitialStatus の timeLimit を検証
25
+ * scheduled_for_* のときは timeLimit 必須(型でも実行時でも保証)
26
+ */
27
+ export declare function validateInitialStatus(status: InitialStatus): void;
@@ -0,0 +1,63 @@
1
+ // src/validators.ts
2
+ const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
3
+ /**
4
+ * YYYY-MM-DD 形式 & 実在する日付かをチェック
5
+ */
6
+ export function isValidISODate(dateStr) {
7
+ if (!ISO_DATE_REGEX.test(dateStr))
8
+ return false;
9
+ const [y, m, d] = dateStr.split('-').map(Number);
10
+ // UTC で日付オブジェクトを作成(タイムゾーン非依存)
11
+ const date = new Date(Date.UTC(y, m - 1, d));
12
+ return (date.getUTCFullYear() === y &&
13
+ date.getUTCMonth() === m - 1 &&
14
+ date.getUTCDate() === d);
15
+ }
16
+ /**
17
+ * メールアドレスの簡易バリデーション
18
+ * RFC完全準拠ではなく、実務的な最低限チェック。
19
+ * 「本気の検証」は呼び出し側に任せる。
20
+ */
21
+ export function isValidEmail(email) {
22
+ // かなり緩めのチェック(@の前後にそれなりの文字列があるか)
23
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
24
+ return EMAIL_REGEX.test(email);
25
+ }
26
+ /**
27
+ * boardUniqueKey のバリデーション
28
+ *
29
+ * ここは仕様に合わせて調整する想定。
30
+ * 現状は:
31
+ * - 空文字禁止
32
+ * - 前後空白を許可しない(trimして変化するならNG)
33
+ * - 制御文字・空白を含まない
34
+ * - 長さ 1〜255 文字
35
+ */
36
+ export function isValidBoardUniqueKey(key) {
37
+ if (!key)
38
+ return false;
39
+ if (key.trim() !== key)
40
+ return false;
41
+ if (key.length > 255)
42
+ return false;
43
+ // 空白や制御文字を禁止(必要に応じて緩めてもOK)
44
+ const INVALID_CHARS_REGEX = /[\s]/;
45
+ if (INVALID_CHARS_REGEX.test(key))
46
+ return false;
47
+ return true;
48
+ }
49
+ /**
50
+ * InitialStatus の timeLimit を検証
51
+ * scheduled_for_* のときは timeLimit 必須(型でも実行時でも保証)
52
+ */
53
+ export function validateInitialStatus(status) {
54
+ if ('timeLimit' in status) {
55
+ const { timeLimit } = status;
56
+ if (!timeLimit) {
57
+ throw new Error('initialStatus.timeLimit is required when statusKey is scheduled_for_*');
58
+ }
59
+ if (!isValidISODate(timeLimit)) {
60
+ throw new Error(`initialStatus.timeLimit must be a valid date in YYYY-MM-DD format. Got: ${timeLimit}`);
61
+ }
62
+ }
63
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@bagooon/chatease-node-client",
3
+ "version": "0.1.0",
4
+ "description": "Node.js-only client for ChatEase board API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepare": "npm run build",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "test:ui": "vitest --ui",
23
+ "prepublishOnly": "npm run test && npm run build"
24
+ },
25
+ "keywords": [
26
+ "chatease",
27
+ "chat",
28
+ "board",
29
+ "api",
30
+ "node"
31
+ ],
32
+ "author": "Hashimoto Giken",
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "browser": {
38
+ "./dist/index.js": false
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^25.3.0",
42
+ "@vitest/ui": "^4.0.18",
43
+ "typescript": "^5.9.3",
44
+ "vitest": "^4.0.18"
45
+ }
46
+ }