@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.
Files changed (49) hide show
  1. package/dist/client.d.mts +81 -3
  2. package/dist/client.d.ts +81 -3
  3. package/dist/client.js +4 -4
  4. package/dist/client.js.map +1 -1
  5. package/dist/client.mjs +4 -4
  6. package/dist/client.mjs.map +1 -1
  7. package/dist/index.d.mts +2 -2
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1 -1
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/server.d.mts +434 -6
  14. package/dist/server.d.ts +434 -6
  15. package/dist/server.js +6 -6
  16. package/dist/server.js.map +1 -1
  17. package/dist/server.mjs +6 -6
  18. package/dist/server.mjs.map +1 -1
  19. package/dist/{sse-schema-rbG114od.d.mts → sse-schema-Df49KA7B.d.mts} +643 -249
  20. package/dist/{sse-schema-eXcMG-Ej.d.ts → sse-schema-RHckD7TS.d.ts} +643 -249
  21. package/dist/styles/daily-report.standalone.css +1 -1
  22. package/dist/{types-D1PKubyo.d.mts → types-Ct1ggzy-.d.mts} +30 -1
  23. package/dist/{types-D1PKubyo.d.ts → types-Ct1ggzy-.d.ts} +30 -1
  24. package/package.json +3 -3
  25. package/src/client/components/daily-report-attachment-indicator.spec.tsx +51 -0
  26. package/src/client/components/daily-report-attachment-indicator.tsx +45 -0
  27. package/src/client/components/daily-report-attachment-list.spec.tsx +94 -0
  28. package/src/client/components/daily-report-attachment-list.tsx +112 -0
  29. package/src/client/components/daily-report-detail-list.tsx +14 -7
  30. package/src/client/components/daily-report-list.tsx +19 -11
  31. package/src/client/components/report-views.spec.tsx +340 -0
  32. package/src/client/config-context.tsx +8 -0
  33. package/src/client/contexts/daily-report-action-context.tsx +2 -0
  34. package/src/client/hooks/use-responsive-layout.spec.ts +74 -0
  35. package/src/client/hooks/use-responsive-layout.ts +56 -0
  36. package/src/client/utils/constants.spec.ts +96 -0
  37. package/src/client/utils/constants.ts +47 -2
  38. package/src/client.ts +2 -0
  39. package/src/server/handlers.attachment.spec.ts +470 -0
  40. package/src/server/handlers.ts +426 -1
  41. package/src/server/ports.ts +74 -0
  42. package/src/server/schema.ts +80 -5
  43. package/src/server/service.spec.ts +280 -14
  44. package/src/server/service.ts +308 -25
  45. package/src/server/test-helpers/handlers-config.ts +142 -0
  46. package/src/server.ts +16 -1
  47. package/src/shared/sse-schema.spec.ts +50 -0
  48. package/src/shared/sse-schema.ts +27 -0
  49. package/src/shared/types.ts +31 -0
package/dist/server.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { e as DailyReportInterviewer, c as DailyReportComment, D as DailyReportItem, a as DailyReportDetail, d as DailyReportCommentItem } from './types-D1PKubyo.mjs';
1
+ import { g as DailyReportInterviewer, e as DailyReportComment, a as DailyReportItem, b as DailyReportDetail, D as DailyReportAttachmentSummary, f as DailyReportCommentItem } from './types-Ct1ggzy-.mjs';
2
2
  import * as drizzle_orm_mssql_core from 'drizzle-orm/mssql-core';
3
3
  import { AnyMsSqlColumn } from 'drizzle-orm/mssql-core';
4
4
  import { D as DailyReportLogger } from './logger-D3krZrNK.mjs';
@@ -82,6 +82,79 @@ type DailyReportResolveUserId = (externalUserId: string) => Promise<number | nul
82
82
  * 内部数値ユーザー ID をクライアント公開用に難読化するポート。
83
83
  */
84
84
  type DailyReportEncodeUserId = (id: number) => string;
85
+ /**
86
+ * Reversible obfuscation codec for an internal numeric id exposed to clients.
87
+ * クライアントへ公開する内部数値 ID の可逆難読化コーデック。
88
+ *
89
+ * これは難読化であって認可ではない。トークンを能力 (capability) として扱ってはならず、
90
+ * 認可は必ずサーバー側の述語で行うこと。ID 空間を混ぜないため、リソース種別ごとに
91
+ * 別インスタンス (別 alphabet 等) を注入することを推奨する。
92
+ */
93
+ type DailyReportIdCodec = {
94
+ /** 内部数値 ID をトークンへ変換する。 */
95
+ encode: (id: number) => string;
96
+ /** トークンを内部数値 ID へ戻す。復号できない場合は null。 */
97
+ decode: (token: string) => number | null;
98
+ };
99
+ /**
100
+ * Failure kinds a caller maps onto HTTP statuses.
101
+ * 呼び出し側が HTTP ステータスへ写像する失敗種別。
102
+ *
103
+ * - `not_found`: 実体が存在しない (消失・未正規化 URI)
104
+ * - `denied`: ストレージ側で拒否された (サンドボックス外・ACL)
105
+ * - `too_large`: 上限超過 (事前サイズ判定またはワイヤ上限)
106
+ * - `invalid_path`: 受け入れないスキーム・不正なパス (データ不備)
107
+ * - `unavailable`: ストレージサービスへ到達できない・締め切り超過
108
+ */
109
+ type DailyReportAttachmentFailure = "not_found" | "denied" | "too_large" | "invalid_path" | "unavailable";
110
+ /** 添付ファイル読み取りの成功結果。 */
111
+ type DailyReportAttachmentBytes = {
112
+ ok: true;
113
+ /**
114
+ * Byte view backed by an `ArrayBuffer`.
115
+ * `ArrayBuffer` を裏に持つバイトビュー。
116
+ *
117
+ * 素の `Uint8Array` と書くと `Uint8Array<ArrayBufferLike>` へ広がり `BodyInit` へ代入できない
118
+ * (TypeScript 5.7 以降の既定型引数)。`Response` のボディへそのまま渡すため narrow 型で受ける。
119
+ * `head` が true の呼び出しでは長さ 0 のビューを返してよい (実サイズは `size` で申告する)。
120
+ */
121
+ bytes: Uint8Array<ArrayBuffer>;
122
+ /** バックエンドが申告した MIME 型。判定できない場合は null。 */
123
+ contentType?: string | null;
124
+ /**
125
+ * Entity size in bytes, independent of how many bytes `bytes` actually holds.
126
+ * 実体のバイト数。`bytes` が実際に保持している長さとは独立に申告する値。
127
+ *
128
+ * HEAD 応答の `Content-Length` は同じ URI への GET が返す値と一致しなければならない
129
+ * (RFC 9110 §9.3.2)。`head` が true の呼び出しは本体を読まないため `bytes` は空になるので、
130
+ * ここで実サイズを申告しないと HEAD が常に `Content-Length: 0` を返してしまう。
131
+ * 申告できない場合は省略してよく、そのとき呼び出し側は `bytes.byteLength` を使う。
132
+ */
133
+ size?: number;
134
+ };
135
+ /** 添付ファイル読み取りの失敗結果。 */
136
+ type DailyReportAttachmentError = {
137
+ ok: false;
138
+ reason: DailyReportAttachmentFailure;
139
+ /** 診断ログ用の生のストレージエラーコード。HTTP ステータスの決定には使わない。 */
140
+ code?: string;
141
+ };
142
+ /**
143
+ * Reads attachment bytes from the host application's storage backend.
144
+ * ホストアプリのストレージバックエンドから添付ファイルのバイト列を読み取るポート。
145
+ *
146
+ * `filePath` はサーバー内部にとどまり、クライアントへは決して渡らない。
147
+ * 失敗は種別付きで返すこと (例外を投げない・単一の null へ潰さない)。
148
+ * 種別を潰すと「実体が消えた」「上限超過」「サービス停止」が運用上区別できなくなる。
149
+ *
150
+ * `head` が true のときは実体を読まずメタデータのみ取得する。HEAD 要求で本体を読んで
151
+ * 捨てると、認証済み呼び出し元が任意に転送量とレイテンシを増幅できる経路になる。
152
+ */
153
+ type DailyReportReadAttachment = (filePath: string, options: {
154
+ maxBytes: number;
155
+ head: boolean;
156
+ principal?: string;
157
+ }) => Promise<DailyReportAttachmentBytes | DailyReportAttachmentError>;
85
158
 
86
159
  /**
87
160
  * In-memory SQL result cache with TTL, snapshot, and cross-worker epoch (via redis) support.
@@ -271,18 +344,48 @@ type DailyReportUserStatusTable = {
271
344
  updatedAt: AnyMsSqlColumn;
272
345
  updatedBy: AnyMsSqlColumn;
273
346
  };
274
- /** サービスへ注入するテーブル一式 (アプリ既存モデルまたは本ファクトリ生成物)。 */
347
+ /**
348
+ * DailyReportAttachment テーブルの構造形。
349
+ *
350
+ * `uploadedBy` は外部取込ソース由来の行に投稿者が存在しないため NULL 許容。
351
+ * `sourceKey` は取込の冪等 upsert の自然キー。
352
+ * `objectCheckedAt` / `objectMissingAt` から実体存在の三値 (unknown / present / absent) を導出する。
353
+ */
354
+ type DailyReportAttachmentTable = {
355
+ id: AnyMsSqlColumn;
356
+ hubId: AnyMsSqlColumn;
357
+ uploadedBy: AnyMsSqlColumn;
358
+ sourceKey: AnyMsSqlColumn;
359
+ fileName: AnyMsSqlColumn;
360
+ filePath: AnyMsSqlColumn;
361
+ fileType: AnyMsSqlColumn;
362
+ fileSize: AnyMsSqlColumn;
363
+ objectCheckedAt: AnyMsSqlColumn;
364
+ objectMissingAt: AnyMsSqlColumn;
365
+ createdAt: AnyMsSqlColumn;
366
+ createdBy: AnyMsSqlColumn;
367
+ updatedAt: AnyMsSqlColumn;
368
+ updatedBy: AnyMsSqlColumn;
369
+ };
370
+ /**
371
+ * サービスへ注入するテーブル一式 (アプリ既存モデルまたは本ファクトリ生成物)。
372
+ *
373
+ * `attachment` のみ任意。添付テーブルを持たない消費アプリでも本パッケージを使えるようにするため。
374
+ * 未注入時は詳細の `attachments` が常に空配列になり、添付エンドポイントは全トークンで 404 を返す
375
+ * (意図した縮退動作)。
376
+ */
275
377
  type DailyReportTables = {
276
378
  hub: DailyReportHubTable;
277
379
  internal: DailyReportInternalTable;
278
380
  comment: DailyReportCommentTable;
381
+ attachment?: DailyReportAttachmentTable;
279
382
  label: DailyReportLabelTable;
280
383
  hubLabel: DailyReportHubLabelTable;
281
384
  userStatus: DailyReportUserStatusTable;
282
385
  };
283
386
  /**
284
- * Generic factory: build the six daily-report tables under an arbitrary schema name.
285
- * 任意のスキーマ名の下に日報 6 テーブルを生成する汎用ファクトリ。
387
+ * Generic factory: build the seven daily-report tables under an arbitrary schema name.
388
+ * 任意のスキーマ名の下に日報 7 テーブルを生成する汎用ファクトリ。
286
389
  *
287
390
  * 制約・索引名は `${schemaName}_<Table>_...` 規約で生成する。
288
391
  * ユーザーテーブルへの FK は注入された `userTable` を参照する。
@@ -777,6 +880,237 @@ declare function defineDailyReportSchema<S extends string>(schemaName: S, opts:
777
880
  };
778
881
  dialect: "mssql";
779
882
  }>;
883
+ attachment: drizzle_orm_mssql_core.MsSqlTableWithColumns<{
884
+ name: "DailyReportAttachment";
885
+ schema: S;
886
+ columns: {
887
+ id: drizzle_orm_mssql_core.MsSqlColumn<{
888
+ name: string;
889
+ tableName: "DailyReportAttachment";
890
+ dataType: "number int53";
891
+ data: number;
892
+ driverParam: string;
893
+ notNull: true;
894
+ hasDefault: true;
895
+ isPrimaryKey: false;
896
+ isAutoincrement: false;
897
+ hasRuntimeDefault: false;
898
+ enumValues: undefined;
899
+ baseColumn: never;
900
+ identity: undefined;
901
+ generated: {};
902
+ }, {}>;
903
+ hubId: drizzle_orm_mssql_core.MsSqlColumn<{
904
+ name: string;
905
+ tableName: "DailyReportAttachment";
906
+ dataType: "number int53";
907
+ data: number;
908
+ driverParam: string;
909
+ notNull: true;
910
+ hasDefault: false;
911
+ isPrimaryKey: false;
912
+ isAutoincrement: false;
913
+ hasRuntimeDefault: false;
914
+ enumValues: undefined;
915
+ baseColumn: never;
916
+ identity: undefined;
917
+ generated: undefined;
918
+ }, {}>;
919
+ uploadedBy: drizzle_orm_mssql_core.MsSqlColumn<{
920
+ name: string;
921
+ tableName: "DailyReportAttachment";
922
+ dataType: "number int53";
923
+ data: number;
924
+ driverParam: string;
925
+ notNull: false;
926
+ hasDefault: false;
927
+ isPrimaryKey: false;
928
+ isAutoincrement: false;
929
+ hasRuntimeDefault: false;
930
+ enumValues: undefined;
931
+ baseColumn: never;
932
+ identity: undefined;
933
+ generated: undefined;
934
+ }, {}>;
935
+ sourceKey: drizzle_orm_mssql_core.MsSqlColumn<{
936
+ name: string;
937
+ tableName: "DailyReportAttachment";
938
+ dataType: "string";
939
+ data: string;
940
+ driverParam: string | number;
941
+ notNull: true;
942
+ hasDefault: false;
943
+ isPrimaryKey: false;
944
+ isAutoincrement: false;
945
+ hasRuntimeDefault: false;
946
+ enumValues: [string, ...string[]];
947
+ baseColumn: never;
948
+ identity: undefined;
949
+ generated: undefined;
950
+ }, {}>;
951
+ fileName: drizzle_orm_mssql_core.MsSqlColumn<{
952
+ name: string;
953
+ tableName: "DailyReportAttachment";
954
+ dataType: "string";
955
+ data: string;
956
+ driverParam: string | number;
957
+ notNull: true;
958
+ hasDefault: false;
959
+ isPrimaryKey: false;
960
+ isAutoincrement: false;
961
+ hasRuntimeDefault: false;
962
+ enumValues: [string, ...string[]];
963
+ baseColumn: never;
964
+ identity: undefined;
965
+ generated: undefined;
966
+ }, {}>;
967
+ filePath: drizzle_orm_mssql_core.MsSqlColumn<{
968
+ name: string;
969
+ tableName: "DailyReportAttachment";
970
+ dataType: "string";
971
+ data: string;
972
+ driverParam: string | number;
973
+ notNull: true;
974
+ hasDefault: false;
975
+ isPrimaryKey: false;
976
+ isAutoincrement: false;
977
+ hasRuntimeDefault: false;
978
+ enumValues: [string, ...string[]];
979
+ baseColumn: never;
980
+ identity: undefined;
981
+ generated: undefined;
982
+ }, {}>;
983
+ fileType: drizzle_orm_mssql_core.MsSqlColumn<{
984
+ name: string;
985
+ tableName: "DailyReportAttachment";
986
+ dataType: "string";
987
+ data: string;
988
+ driverParam: string | number;
989
+ notNull: false;
990
+ hasDefault: false;
991
+ isPrimaryKey: false;
992
+ isAutoincrement: false;
993
+ hasRuntimeDefault: false;
994
+ enumValues: [string, ...string[]];
995
+ baseColumn: never;
996
+ identity: undefined;
997
+ generated: undefined;
998
+ }, {}>;
999
+ fileSize: drizzle_orm_mssql_core.MsSqlColumn<{
1000
+ name: string;
1001
+ tableName: "DailyReportAttachment";
1002
+ dataType: "number int53";
1003
+ data: number;
1004
+ driverParam: string;
1005
+ notNull: false;
1006
+ hasDefault: false;
1007
+ isPrimaryKey: false;
1008
+ isAutoincrement: false;
1009
+ hasRuntimeDefault: false;
1010
+ enumValues: undefined;
1011
+ baseColumn: never;
1012
+ identity: undefined;
1013
+ generated: undefined;
1014
+ }, {}>;
1015
+ objectCheckedAt: drizzle_orm_mssql_core.MsSqlColumn<{
1016
+ name: string;
1017
+ tableName: "DailyReportAttachment";
1018
+ dataType: "object date";
1019
+ data: Date;
1020
+ driverParam: string | Date;
1021
+ notNull: false;
1022
+ hasDefault: false;
1023
+ isPrimaryKey: false;
1024
+ isAutoincrement: false;
1025
+ hasRuntimeDefault: false;
1026
+ enumValues: undefined;
1027
+ baseColumn: never;
1028
+ identity: undefined;
1029
+ generated: undefined;
1030
+ }, {}>;
1031
+ objectMissingAt: drizzle_orm_mssql_core.MsSqlColumn<{
1032
+ name: string;
1033
+ tableName: "DailyReportAttachment";
1034
+ dataType: "object date";
1035
+ data: Date;
1036
+ driverParam: string | Date;
1037
+ notNull: false;
1038
+ hasDefault: false;
1039
+ isPrimaryKey: false;
1040
+ isAutoincrement: false;
1041
+ hasRuntimeDefault: false;
1042
+ enumValues: undefined;
1043
+ baseColumn: never;
1044
+ identity: undefined;
1045
+ generated: undefined;
1046
+ }, {}>;
1047
+ createdAt: drizzle_orm_mssql_core.MsSqlColumn<{
1048
+ name: string;
1049
+ tableName: "DailyReportAttachment";
1050
+ dataType: "object date";
1051
+ data: Date;
1052
+ driverParam: string | Date;
1053
+ notNull: true;
1054
+ hasDefault: false;
1055
+ isPrimaryKey: false;
1056
+ isAutoincrement: false;
1057
+ hasRuntimeDefault: false;
1058
+ enumValues: undefined;
1059
+ baseColumn: never;
1060
+ identity: undefined;
1061
+ generated: undefined;
1062
+ }, {}>;
1063
+ createdBy: drizzle_orm_mssql_core.MsSqlColumn<{
1064
+ name: string;
1065
+ tableName: "DailyReportAttachment";
1066
+ dataType: "string";
1067
+ data: string;
1068
+ driverParam: string | number;
1069
+ notNull: true;
1070
+ hasDefault: false;
1071
+ isPrimaryKey: false;
1072
+ isAutoincrement: false;
1073
+ hasRuntimeDefault: false;
1074
+ enumValues: [string, ...string[]];
1075
+ baseColumn: never;
1076
+ identity: undefined;
1077
+ generated: undefined;
1078
+ }, {}>;
1079
+ updatedAt: drizzle_orm_mssql_core.MsSqlColumn<{
1080
+ name: string;
1081
+ tableName: "DailyReportAttachment";
1082
+ dataType: "object date";
1083
+ data: Date;
1084
+ driverParam: string | Date;
1085
+ notNull: true;
1086
+ hasDefault: false;
1087
+ isPrimaryKey: false;
1088
+ isAutoincrement: false;
1089
+ hasRuntimeDefault: false;
1090
+ enumValues: undefined;
1091
+ baseColumn: never;
1092
+ identity: undefined;
1093
+ generated: undefined;
1094
+ }, {}>;
1095
+ updatedBy: drizzle_orm_mssql_core.MsSqlColumn<{
1096
+ name: string;
1097
+ tableName: "DailyReportAttachment";
1098
+ dataType: "string";
1099
+ data: string;
1100
+ driverParam: string | number;
1101
+ notNull: true;
1102
+ hasDefault: false;
1103
+ isPrimaryKey: false;
1104
+ isAutoincrement: false;
1105
+ hasRuntimeDefault: false;
1106
+ enumValues: [string, ...string[]];
1107
+ baseColumn: never;
1108
+ identity: undefined;
1109
+ generated: undefined;
1110
+ }, {}>;
1111
+ };
1112
+ dialect: "mssql";
1113
+ }>;
780
1114
  label: drizzle_orm_mssql_core.MsSqlTableWithColumns<{
781
1115
  name: "DailyReportLabel";
782
1116
  schema: S;
@@ -1222,7 +1556,7 @@ interface DailyReportDb {
1222
1556
  type DailyReportServiceConfig = {
1223
1557
  /** drizzle mssql データベースハンドル。 */
1224
1558
  db: DailyReportDb;
1225
- /** 日報 6 テーブル (アプリ既存モデルまたは defineDailyReportSchema 生成物) */
1559
+ /** 日報 7 テーブル (アプリ既存モデルまたは defineDailyReportSchema 生成物)。`attachment` のみ任意。 */
1226
1560
  tables: DailyReportTables;
1227
1561
  /** 表示名解決に使う外部ユーザーテーブル ({ id, displayName })。 */
1228
1562
  userTable: DailyReportUserTable;
@@ -1254,6 +1588,15 @@ type DailyReportServiceConfig = {
1254
1588
  businessDateTtlMs?: number;
1255
1589
  /** 外部 ID → 内部 ID のプロセス内キャッシュを無効化 (テスト用)。 */
1256
1590
  disableUserIdCache?: boolean;
1591
+ /**
1592
+ * 添付 ID の難読化コーデック。
1593
+ *
1594
+ * 任意にしているのは、添付テーブルを注入しない消費アプリと既存テストのフィクスチャを
1595
+ * 壊さないため。未注入なら添付一覧は常に空になる (縮退動作)。
1596
+ * ユーザー ID 用のコーデックとは **別インスタンス**を渡すこと。同一だと
1597
+ * レスポンス中の `userId` トークンがそのまま添付トークンとして解釈できてしまう。
1598
+ */
1599
+ attachmentIdCodec?: DailyReportIdCodec;
1257
1600
  /** ロガー (既定は console ベース)。 */
1258
1601
  logger?: DailyReportLogger;
1259
1602
  };
@@ -1267,6 +1610,7 @@ declare function createDailyReportService(config: DailyReportServiceConfig): {
1267
1610
  streamKey: string;
1268
1611
  streamMaxLen: number;
1269
1612
  clearCache: () => Promise<void>;
1613
+ invalidateHubReadCaches: (hubIds: readonly number[], businessDates: readonly string[]) => Promise<void>;
1270
1614
  getUserIdByExternalId: (externalId: string) => Promise<number | null>;
1271
1615
  getDailyReportIdsByExternalId: (externalId: string, { forceRefresh, snapshot, ttlMsOverride }?: SqlResultCacheQueryOptions) => Promise<DailyReportItem[]>;
1272
1616
  getDailyReportsByBusinessDate: (businessDate: string, { forceRefresh, snapshot, ttlMsOverride }?: SqlResultCacheQueryOptions) => Promise<DailyReportDetail[]>;
@@ -1274,6 +1618,15 @@ declare function createDailyReportService(config: DailyReportServiceConfig): {
1274
1618
  getDailyReportDetailById: (reportHubId: number, userId: number, { forceRefresh, snapshot, ttlMsOverride }?: SqlResultCacheQueryOptions) => Promise<DailyReportDetail | null>;
1275
1619
  getDailyReportDetailByIdByExternalId: (reportHubId: number, externalId: string, options?: SqlResultCacheQueryOptions) => Promise<DailyReportDetail | null>;
1276
1620
  getDraftLabelId: () => Promise<number | null>;
1621
+ getAttachmentsByHubIds: (hubIds: number[]) => Promise<Map<number, DailyReportAttachmentSummary[]>>;
1622
+ getAttachmentForUser: (attachmentId: number, userId: number) => Promise<{
1623
+ fileName: string;
1624
+ filePath: string;
1625
+ fileType: string | null;
1626
+ fileSize: number | null;
1627
+ } | null>;
1628
+ markAttachmentMissing: (attachmentId: number) => Promise<void>;
1629
+ markAttachmentPresent: (attachmentId: number) => Promise<void>;
1277
1630
  setStarStatus: (userId: number, reportHubId: number, businessDate: string | null, isStarred: boolean, clientTempId: string) => Promise<DailyReportUserStatusRow>;
1278
1631
  setReadStatus: (userId: number, reportHubId: number, businessDate: string | null, isRead: boolean, clientTempId: string) => Promise<DailyReportUserStatusRow>;
1279
1632
  addComment: (userId: number, reportHubId: number, content: string, businessDate: string | null, clientTempId: string) => Promise<DailyReportCommentItem>;
@@ -1406,6 +1759,41 @@ type LoaderArgs = {
1406
1759
  request: Request;
1407
1760
  params: Record<string, string | undefined>;
1408
1761
  };
1762
+ /**
1763
+ * Accepts a media type only when it is header-safe and well-formed.
1764
+ * ヘッダー安全かつ MIME 型の形をしている場合のみ採用する処理。
1765
+ *
1766
+ * CR/LF や 0x00-0xFF 外の文字が混ざった値を `new Headers()` へ渡すと TypeError になり、
1767
+ * 認可済みのダウンロードが 500 になる。
1768
+ *
1769
+ * **これは第 2 層である。** 現在の配信経路では、注入値はインライン許可リストに完全一致しないため
1770
+ * 必ず `application/octet-stream` へ落ち、ヘッダーには到達しない。つまり本関数を外しても
1771
+ * 現状の挙動は変わらない (ミューテーションテストで確認済み)。
1772
+ * 許可判定を前方一致や「DB の型を信じる」形へ変えた瞬間に効き始める防御なので、
1773
+ * 到達不能なまま放置せず **直接の単体テストで固定する**目的でエクスポートしている。
1774
+ *
1775
+ * @param value Candidate media type. 候補となる media type。
1776
+ * @returns The value when acceptable, otherwise null. 採用可なら値、それ以外は null。
1777
+ */
1778
+ declare const sanitizeMediaType: (value: string | null | undefined) => string | null;
1779
+ /**
1780
+ * Percent-encodes a file name for the RFC 8187 `filename*` parameter.
1781
+ * RFC 8187 の `filename*` 用にファイル名をパーセントエンコードする処理。
1782
+ *
1783
+ * `encodeURIComponent` は `'` `(` `)` `*` を残すが、いずれも RFC 8187 の attr-char ではない。
1784
+ *
1785
+ * @param name Raw file name. 生のファイル名。
1786
+ * @returns Encoded value. エンコード済みの値。
1787
+ */
1788
+ declare const encodeRfc8187: (name: string) => string;
1789
+ /**
1790
+ * Builds the ASCII fallback used by the plain `filename` parameter.
1791
+ * 素の `filename` パラメータ用の ASCII フォールバックを組み立てる処理。
1792
+ *
1793
+ * @param name Raw file name. 生のファイル名。
1794
+ * @returns ASCII-only, quote-free name. ASCII のみで引用符を含まない名前。
1795
+ */
1796
+ declare const asciiFallbackFileName: (name: string) => string;
1409
1797
  type DailyReportHandlersConfig = {
1410
1798
  /** リクエスト認証ポート。 */
1411
1799
  authenticate: DailyReportAuthenticate;
@@ -1413,6 +1801,19 @@ type DailyReportHandlersConfig = {
1413
1801
  service: DailyReportService;
1414
1802
  /** 内部数値 ID の難読化ポート。 */
1415
1803
  encodeUserId: DailyReportEncodeUserId;
1804
+ /**
1805
+ * 添付 ID の難読化コーデック。未注入なら添付エンドポイントは常に 404。
1806
+ * ユーザー ID 用とは別インスタンスを渡すこと (ID 空間の分離)。
1807
+ */
1808
+ attachmentIdCodec?: DailyReportIdCodec;
1809
+ /** 添付ファイル読み取りポート。未注入なら添付エンドポイントは常に 404。 */
1810
+ readAttachment?: DailyReportReadAttachment;
1811
+ /** 添付ファイルの最大バイト数 (既定 32 MiB)。サービス側のワイヤ上限より低く保つこと。 */
1812
+ attachmentMaxBytes?: number;
1813
+ /** 添付エンドポイントの 1 分あたり呼び出し上限 (ユーザー単位・既定 60)。 */
1814
+ attachmentRateLimitPerMinute?: number;
1815
+ /** 添付読み取りの同時実行上限 (プロセス単位・既定 4)。 */
1816
+ attachmentConcurrency?: number;
1416
1817
  /** SSE 用 redis プロバイダー (catch-up の xRange / xRevRange に使用)。 */
1417
1818
  redis?: DailyReportRedisProvider;
1418
1819
  /** SSE Fan-Out 共有リーダー。 */
@@ -1447,6 +1848,9 @@ declare function createDailyReportHandlers(config: DailyReportHandlersConfig): {
1447
1848
  sse: {
1448
1849
  loader: ({ request, params }: LoaderArgs) => Promise<Response>;
1449
1850
  };
1851
+ attachment: {
1852
+ loader: ({ request, params }: LoaderArgs) => Promise<Response>;
1853
+ };
1450
1854
  };
1451
1855
 
1452
1856
  /**
@@ -1464,6 +1868,14 @@ declare const jsonResponseWithETag: (request: Request, cookie: string | null, pa
1464
1868
  type DailyReportServerConfig = Omit<DailyReportServiceConfig, "cache" | "epochs"> & {
1465
1869
  /** リクエスト認証ポート。 */
1466
1870
  authenticate: DailyReportAuthenticate;
1871
+ /** 添付ファイル読み取りポート。未注入なら添付エンドポイントは常に 404。 */
1872
+ readAttachment?: DailyReportReadAttachment;
1873
+ /** 添付ファイルの最大バイト数 (既定 32 MiB)。サービス側のワイヤ上限より低く保つこと。 */
1874
+ attachmentMaxBytes?: number;
1875
+ /** 添付エンドポイントの 1 分あたり呼び出し上限 (ユーザー単位・既定 60)。 */
1876
+ attachmentRateLimitPerMinute?: number;
1877
+ /** 添付読み取りの同時実行上限 (プロセス単位・既定 4)。 */
1878
+ attachmentConcurrency?: number;
1467
1879
  /** 未ログイン時のリダイレクト先 (index.loader 用、既定 "/auth/login")。 */
1468
1880
  loginRedirectPath?: string;
1469
1881
  /** SQL 結果キャッシュの既定 TTL (既定 60,000ms)。 */
@@ -1513,11 +1925,18 @@ declare function createDailyReportServer(config: DailyReportServerConfig): {
1513
1925
  params: Record<string, string | undefined>;
1514
1926
  }) => Promise<Response>;
1515
1927
  };
1928
+ attachment: {
1929
+ loader: ({ request, params }: {
1930
+ request: Request;
1931
+ params: Record<string, string | undefined>;
1932
+ }) => Promise<Response>;
1933
+ };
1516
1934
  /** データアクセスサービス (CRUD + キャッシュ + SSE publish)。 */
1517
1935
  service: {
1518
1936
  streamKey: string;
1519
1937
  streamMaxLen: number;
1520
1938
  clearCache: () => Promise<void>;
1939
+ invalidateHubReadCaches: (hubIds: readonly number[], businessDates: readonly string[]) => Promise<void>;
1521
1940
  getUserIdByExternalId: (externalId: string) => Promise<number | null>;
1522
1941
  getDailyReportIdsByExternalId: (externalId: string, { forceRefresh, snapshot, ttlMsOverride }?: SqlResultCacheQueryOptions) => Promise<DailyReportItem[]>;
1523
1942
  getDailyReportsByBusinessDate: (businessDate: string, { forceRefresh, snapshot, ttlMsOverride }?: SqlResultCacheQueryOptions) => Promise<DailyReportDetail[]>;
@@ -1525,6 +1944,15 @@ declare function createDailyReportServer(config: DailyReportServerConfig): {
1525
1944
  getDailyReportDetailById: (reportHubId: number, userId: number, { forceRefresh, snapshot, ttlMsOverride }?: SqlResultCacheQueryOptions) => Promise<DailyReportDetail | null>;
1526
1945
  getDailyReportDetailByIdByExternalId: (reportHubId: number, externalId: string, options?: SqlResultCacheQueryOptions) => Promise<DailyReportDetail | null>;
1527
1946
  getDraftLabelId: () => Promise<number | null>;
1947
+ getAttachmentsByHubIds: (hubIds: number[]) => Promise<Map<number, DailyReportAttachmentSummary[]>>;
1948
+ getAttachmentForUser: (attachmentId: number, userId: number) => Promise<{
1949
+ fileName: string;
1950
+ filePath: string;
1951
+ fileType: string | null;
1952
+ fileSize: number | null;
1953
+ } | null>;
1954
+ markAttachmentMissing: (attachmentId: number) => Promise<void>;
1955
+ markAttachmentPresent: (attachmentId: number) => Promise<void>;
1528
1956
  setStarStatus: (userId: number, reportHubId: number, businessDate: string | null, isStarred: boolean, clientTempId: string) => Promise<DailyReportUserStatusRow>;
1529
1957
  setReadStatus: (userId: number, reportHubId: number, businessDate: string | null, isRead: boolean, clientTempId: string) => Promise<DailyReportUserStatusRow>;
1530
1958
  addComment: (userId: number, reportHubId: number, content: string, businessDate: string | null, clientTempId: string) => Promise<DailyReportCommentItem>;
@@ -1554,4 +1982,4 @@ declare function createDailyReportServer(config: DailyReportServerConfig): {
1554
1982
  streamKey: string;
1555
1983
  };
1556
1984
 
1557
- export { type AuthzResourceItem, type DailyReportAuthResult, type DailyReportAuthenticate, type DailyReportCommentRow, type DailyReportCommentTable, type DailyReportDb, type DailyReportEncodeUserId, type DailyReportExternalSource, type DailyReportHandlersConfig, type DailyReportHubLabelTable, type DailyReportHubRow, type DailyReportHubTable, type DailyReportInternalRow, type DailyReportInternalTable, type DailyReportLabelTable, type DailyReportRedisBlockingClient, type DailyReportRedisClient, type DailyReportRedisProvider, type DailyReportResolveUserId, type DailyReportServerConfig, type DailyReportService, type DailyReportServiceConfig, type DailyReportSourceTypeInput, DailyReportSseReader, type DailyReportSseReaderConfig, type DailyReportTables, type DailyReportUserStatusRow, type DailyReportUserStatusTable, type DailyReportUserTable, type EpochStore, type ExternalReportFields, type RedisStreamMessage, SqlResultCache, type SqlResultCacheQueryOptions, type StreamEntry, createDailyReportHandlers, createDailyReportServer, createDailyReportService, createEpochStore, defineDailyReportAuthzResources, defineDailyReportSchema, generateETag, isStreamIdLte, jsonResponseWithETag, seedDailyReportAuthzResources, transformJsonArray };
1985
+ export { type AuthzResourceItem, type DailyReportAttachmentBytes, type DailyReportAttachmentError, type DailyReportAttachmentFailure, type DailyReportAttachmentTable, type DailyReportAuthResult, type DailyReportAuthenticate, type DailyReportCommentRow, type DailyReportCommentTable, type DailyReportDb, type DailyReportEncodeUserId, type DailyReportExternalSource, type DailyReportHandlersConfig, type DailyReportHubLabelTable, type DailyReportHubRow, type DailyReportHubTable, type DailyReportIdCodec, type DailyReportInternalRow, type DailyReportInternalTable, type DailyReportLabelTable, type DailyReportReadAttachment, type DailyReportRedisBlockingClient, type DailyReportRedisClient, type DailyReportRedisProvider, type DailyReportResolveUserId, type DailyReportServerConfig, type DailyReportService, type DailyReportServiceConfig, type DailyReportSourceTypeInput, DailyReportSseReader, type DailyReportSseReaderConfig, type DailyReportTables, type DailyReportUserStatusRow, type DailyReportUserStatusTable, type DailyReportUserTable, type EpochStore, type ExternalReportFields, type RedisStreamMessage, SqlResultCache, type SqlResultCacheQueryOptions, type StreamEntry, asciiFallbackFileName, createDailyReportHandlers, createDailyReportServer, createDailyReportService, createEpochStore, defineDailyReportAuthzResources, defineDailyReportSchema, encodeRfc8187, generateETag, isStreamIdLte, jsonResponseWithETag, sanitizeMediaType, seedDailyReportAuthzResources, transformJsonArray };