@aiquants/daily-report 0.6.2 → 0.7.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/dist/client.d.mts +81 -3
- package/dist/client.d.ts +81 -3
- package/dist/client.js +4 -4
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +4 -4
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +434 -6
- package/dist/server.d.ts +434 -6
- package/dist/server.js +6 -6
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +6 -6
- package/dist/server.mjs.map +1 -1
- package/dist/{sse-schema-rbG114od.d.mts → sse-schema-Df49KA7B.d.mts} +643 -249
- package/dist/{sse-schema-eXcMG-Ej.d.ts → sse-schema-RHckD7TS.d.ts} +643 -249
- package/dist/styles/daily-report.standalone.css +1 -1
- package/dist/{types-D1PKubyo.d.mts → types-Ct1ggzy-.d.mts} +30 -1
- package/dist/{types-D1PKubyo.d.ts → types-Ct1ggzy-.d.ts} +30 -1
- package/package.json +3 -3
- package/src/client/components/daily-report-attachment-indicator.spec.tsx +51 -0
- package/src/client/components/daily-report-attachment-indicator.tsx +45 -0
- package/src/client/components/daily-report-attachment-list.spec.tsx +94 -0
- package/src/client/components/daily-report-attachment-list.tsx +112 -0
- package/src/client/components/daily-report-detail-list.tsx +14 -7
- package/src/client/components/daily-report-list.tsx +19 -11
- package/src/client/components/report-views.spec.tsx +340 -0
- package/src/client/config-context.tsx +8 -0
- package/src/client/contexts/daily-report-action-context.tsx +2 -0
- package/src/client/hooks/use-responsive-layout.spec.ts +74 -0
- package/src/client/hooks/use-responsive-layout.ts +56 -0
- package/src/client/utils/constants.spec.ts +96 -0
- package/src/client/utils/constants.ts +47 -2
- package/src/client.ts +2 -0
- package/src/server/handlers.attachment.spec.ts +470 -0
- package/src/server/handlers.ts +426 -1
- package/src/server/ports.ts +74 -0
- package/src/server/schema.ts +80 -5
- package/src/server/service.spec.ts +280 -14
- package/src/server/service.ts +308 -25
- package/src/server/test-helpers/handlers-config.ts +142 -0
- package/src/server.ts +16 -1
- package/src/shared/sse-schema.spec.ts +50 -0
- package/src/shared/sse-schema.ts +27 -0
- package/src/shared/types.ts +31 -0
package/src/server.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { createEpochStore, SqlResultCache } from "./server/cache"
|
|
6
6
|
import type { DailyReportExternalSource } from "./server/external-source"
|
|
7
7
|
import { createDailyReportHandlers } from "./server/handlers"
|
|
8
|
-
import type { DailyReportAuthenticate } from "./server/ports"
|
|
8
|
+
import type { DailyReportAuthenticate, DailyReportReadAttachment } from "./server/ports"
|
|
9
9
|
import { createDailyReportService, type DailyReportServiceConfig } from "./server/service"
|
|
10
10
|
import { DailyReportSseReader } from "./server/sse-reader"
|
|
11
11
|
|
|
@@ -24,6 +24,14 @@ export * from "./server/sse-reader"
|
|
|
24
24
|
export type DailyReportServerConfig = Omit<DailyReportServiceConfig, "cache" | "epochs"> & {
|
|
25
25
|
/** リクエスト認証ポート。 */
|
|
26
26
|
authenticate: DailyReportAuthenticate
|
|
27
|
+
/** 添付ファイル読み取りポート。未注入なら添付エンドポイントは常に 404。 */
|
|
28
|
+
readAttachment?: DailyReportReadAttachment
|
|
29
|
+
/** 添付ファイルの最大バイト数 (既定 32 MiB)。サービス側のワイヤ上限より低く保つこと。 */
|
|
30
|
+
attachmentMaxBytes?: number
|
|
31
|
+
/** 添付エンドポイントの 1 分あたり呼び出し上限 (ユーザー単位・既定 60)。 */
|
|
32
|
+
attachmentRateLimitPerMinute?: number
|
|
33
|
+
/** 添付読み取りの同時実行上限 (プロセス単位・既定 4)。 */
|
|
34
|
+
attachmentConcurrency?: number
|
|
27
35
|
/** 未ログイン時のリダイレクト先 (index.loader 用、既定 "/auth/login")。 */
|
|
28
36
|
loginRedirectPath?: string
|
|
29
37
|
/** SQL 結果キャッシュの既定 TTL (既定 60,000ms)。 */
|
|
@@ -57,10 +65,17 @@ export function createDailyReportServer(config: DailyReportServerConfig) {
|
|
|
57
65
|
logger: config.logger,
|
|
58
66
|
})
|
|
59
67
|
|
|
68
|
+
// ❗ この呼び出しはスプレッドではなく明示列挙である。新しいポートをここへ足し忘れると
|
|
69
|
+
// アプリが注入しても黙って捨てられ、添付エンドポイントは永久に 404 を返す。
|
|
60
70
|
const handlers = createDailyReportHandlers({
|
|
61
71
|
authenticate: config.authenticate,
|
|
62
72
|
service,
|
|
63
73
|
encodeUserId: config.encodeUserId,
|
|
74
|
+
attachmentIdCodec: config.attachmentIdCodec,
|
|
75
|
+
readAttachment: config.readAttachment,
|
|
76
|
+
attachmentMaxBytes: config.attachmentMaxBytes,
|
|
77
|
+
attachmentRateLimitPerMinute: config.attachmentRateLimitPerMinute,
|
|
78
|
+
attachmentConcurrency: config.attachmentConcurrency,
|
|
64
79
|
redis: config.redis,
|
|
65
80
|
sseReader,
|
|
66
81
|
streamKey: service.streamKey,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-compatibility tests for the SSE detail schema.
|
|
3
|
+
* SSE 詳細スキーマのワイヤ互換テスト。
|
|
4
|
+
*
|
|
5
|
+
* SSE の実体は永続 Redis Stream であり、配備をまたいで**旧形式のエントリが残る**。
|
|
6
|
+
* 共有リーダーはプロセス起動時にストリーム先頭から再生し、再接続クライアントの
|
|
7
|
+
* catch-up も同じエントリを読み直す。したがって受信側スキーマに既定値なしの
|
|
8
|
+
* 必須キーを足すと、そのキーを持たない配備前のエントリが fail-closed で全て捨てられ、
|
|
9
|
+
* 再接続タブへ更新が永久に届かなくなる。ここではその回帰を防ぐ。
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from "vitest"
|
|
12
|
+
import { dailyReportDetailSchema, dailyReportSseMessageSchema } from "./sse-schema"
|
|
13
|
+
|
|
14
|
+
/** 添付機能を入れる前に publish された `report-update` エントリ (attachments キーを持たない)。 */
|
|
15
|
+
const legacyDetail = {
|
|
16
|
+
reportHubId: 12,
|
|
17
|
+
interviewers: [],
|
|
18
|
+
comments: [],
|
|
19
|
+
isRead: false,
|
|
20
|
+
isStarred: false,
|
|
21
|
+
labels: [],
|
|
22
|
+
commentItems: [],
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("dailyReportDetailSchema / 旧形式エントリの受理", () => {
|
|
26
|
+
it("attachments キーが無い旧ペイロードを空配列として受理する", () => {
|
|
27
|
+
const parsed = dailyReportDetailSchema.parse(legacyDetail)
|
|
28
|
+
expect(parsed.attachments).toEqual([])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("Stream に残る旧 report-update メッセージを捨てない", () => {
|
|
32
|
+
const result = dailyReportSseMessageSchema.safeParse({ type: "report-update", reportHubId: 12, report: legacyDetail, clientTempId: "c1" })
|
|
33
|
+
expect(result.success).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("新形式の attachments はそのまま通す", () => {
|
|
37
|
+
const parsed = dailyReportDetailSchema.parse({
|
|
38
|
+
...legacyDetail,
|
|
39
|
+
attachments: [{ id: "TOK", fileName: "a.png", fileType: "image/png", fileSize: 3, createdAt: null, state: "present" }],
|
|
40
|
+
})
|
|
41
|
+
expect(parsed.attachments).toHaveLength(1)
|
|
42
|
+
expect(parsed.attachments[0].state).toBe("present")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("attachments の要素が壊れている場合は拒否する (既定値で握り潰さない)", () => {
|
|
46
|
+
// 欠損キーの救済と「不正な値の受理」は別物。後者は fail-closed のままにする
|
|
47
|
+
const result = dailyReportDetailSchema.safeParse({ ...legacyDetail, attachments: [{ id: "TOK", state: "bogus" }] })
|
|
48
|
+
expect(result.success).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
package/src/shared/sse-schema.ts
CHANGED
|
@@ -33,6 +33,25 @@ export const dailyReportCommentItemSchema = z.object({
|
|
|
33
33
|
isMine: z.boolean(),
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 添付ファイル 1 件のスキーマ。
|
|
38
|
+
*
|
|
39
|
+
* 周囲のフィールドは `.nullish()` だが、ここは意図して `.nullable()` を使う。
|
|
40
|
+
* `.nullish()` は推論キーを任意 (`?`) にするため、`types.ts` 側の必須 `fileType: string | null`
|
|
41
|
+
* へ代入できず、ファイル末尾の `_AssertDetailCompat` が `never` に落ちてコンパイルエラーになる。
|
|
42
|
+
* 既存の `.nullish()` フィールドはいずれも `types.ts` 側が `?` 付きの任意キーとペアになっており、
|
|
43
|
+
* 「`.nullish()` ↔ 任意キー」「`.nullable()` ↔ 必須 `| null`」の対応で揃っている。
|
|
44
|
+
* `| undefined` を混ぜないことは、詳細型を消費する DuckDB ステージ行の要請でもある。
|
|
45
|
+
*/
|
|
46
|
+
export const dailyReportAttachmentItemSchema = z.object({
|
|
47
|
+
id: z.string(),
|
|
48
|
+
fileName: z.string(),
|
|
49
|
+
fileType: z.string().nullable(),
|
|
50
|
+
fileSize: z.number().nullable(),
|
|
51
|
+
createdAt: z.string().nullable(),
|
|
52
|
+
state: z.enum(["present", "absent", "unknown"]),
|
|
53
|
+
})
|
|
54
|
+
|
|
36
55
|
export const dailyReportDetailSchema = z.object({
|
|
37
56
|
reportHubId: z.number(),
|
|
38
57
|
date: z.string().nullish(),
|
|
@@ -56,6 +75,14 @@ export const dailyReportDetailSchema = z.object({
|
|
|
56
75
|
isStarred: z.boolean(),
|
|
57
76
|
labels: z.array(dailyReportLabelDefSchema),
|
|
58
77
|
commentItems: z.array(dailyReportCommentItemSchema),
|
|
78
|
+
// ❗ `.default([])` は必須。SSE の実体は永続 Redis Stream (MAXLEN 保持) であり、
|
|
79
|
+
// 配備をまたいで**旧形式のエントリが残る**。共有リーダーはプロセス起動時に先頭から
|
|
80
|
+
// 再生し、再接続クライアントの catch-up も同じエントリを読み直すため、
|
|
81
|
+
// このキーを必須にすると添付機能を入れる前に publish された全エントリが
|
|
82
|
+
// fail-closed で捨てられ、再接続タブへ更新が永久に届かなくなる。
|
|
83
|
+
// 送信側の詰め忘れ検知は types.ts の必須フィールドと `_AssertDetailCompat`
|
|
84
|
+
// (出力型は既定値適用後なので必須のまま) が担っており、ここを緩めても失われない。
|
|
85
|
+
attachments: z.array(dailyReportAttachmentItemSchema).default([]),
|
|
59
86
|
})
|
|
60
87
|
|
|
61
88
|
// --- SSE メッセージスキーマ(8種) ---
|
package/src/shared/types.ts
CHANGED
|
@@ -30,6 +30,32 @@ export type DailyReportLabelDef = {
|
|
|
30
30
|
color?: string | null
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Verification state of an attachment's backing object.
|
|
35
|
+
* 添付ファイルの実体の検証状態。
|
|
36
|
+
*
|
|
37
|
+
* 三値にする理由: 存在確認そのものが失敗した場合 (権限・通信障害) を偽の真偽で埋めないため。
|
|
38
|
+
* `unknown` は「まだ検証していない / 検証できなかった」であり、`absent` (消失を確認した) とは異なる。
|
|
39
|
+
*/
|
|
40
|
+
export type DailyReportAttachmentState = "present" | "absent" | "unknown"
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One attachment as exposed to the client.
|
|
44
|
+
* クライアントへ公開する添付ファイル 1 件。
|
|
45
|
+
*
|
|
46
|
+
* `id` は難読化トークンであり内部数値 ID ではない。ストレージパス (`filePath`) は
|
|
47
|
+
* サーバー内部にとどまり、この型には現れない。
|
|
48
|
+
* `fileSize` / `createdAt` が null になり得るのは、サイズや作成日時を報告しない取込元があるため。
|
|
49
|
+
*/
|
|
50
|
+
export type DailyReportAttachmentSummary = {
|
|
51
|
+
id: string
|
|
52
|
+
fileName: string
|
|
53
|
+
fileType: string | null
|
|
54
|
+
fileSize: number | null
|
|
55
|
+
createdAt: string | null
|
|
56
|
+
state: DailyReportAttachmentState
|
|
57
|
+
}
|
|
58
|
+
|
|
33
59
|
/** リレーショナルコメント 1 件。`userId` は難読化済み文字列。 */
|
|
34
60
|
export type DailyReportCommentItem = {
|
|
35
61
|
id: number
|
|
@@ -64,6 +90,11 @@ export type DailyReportDetail = {
|
|
|
64
90
|
isStarred: boolean
|
|
65
91
|
labels: DailyReportLabelDef[]
|
|
66
92
|
commentItems: DailyReportCommentItem[]
|
|
93
|
+
/**
|
|
94
|
+
* 添付ファイル一覧。必須にしているのは詰め忘れをコンパイル時に落とすため。
|
|
95
|
+
* 任意にすると `?? []` で黙って空配列になり、SSE 経由でクライアントの既存一覧を空で上書きする。
|
|
96
|
+
*/
|
|
97
|
+
attachments: DailyReportAttachmentSummary[]
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
/**
|