@aiquants/daily-report 0.6.0 → 0.6.2
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 +38 -38
- package/dist/client.d.mts +9 -3
- package/dist/client.d.ts +9 -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/server.d.mts +34 -4
- package/dist/server.d.ts +34 -4
- 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/package.json +3 -3
- package/src/client/components/daily-report-detail-list.tsx +28 -6
- package/src/client/components/daily-report-list.tsx +25 -4
- package/src/client/components/daily-report-resolved-content.tsx +28 -4
- package/src/client/config-context.tsx +2 -0
- package/src/client/contexts/daily-report-action-context.tsx +5 -1
- package/src/client/route-helpers.ts +8 -2
- package/src/server/authz.spec.ts +57 -0
- package/src/server/authz.ts +99 -0
- package/src/server/handlers.ts +5 -4
- package/src/server/service.ts +31 -13
- package/src/server.ts +1 -0
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ The standalone build bundles every JSX utility (and maps the shadcn tokens), but
|
|
|
62
62
|
|
|
63
63
|
## Required database schema
|
|
64
64
|
|
|
65
|
-
Six tables: `DailyReportHub` (
|
|
65
|
+
Six tables: `DailyReportHub` (core/cross-source), `DailyReportInternal` (in-app content), `DailyReportComment`, `DailyReportLabel`, `DailyReportHub_Label`, `DailyReportUserStatus` (read status & stars).
|
|
66
66
|
|
|
67
67
|
Existing apps can inject their own drizzle models (structural typing — see `DailyReportTables`). Greenfield projects can generate definitions:
|
|
68
68
|
|
|
@@ -83,18 +83,18 @@ export const dailyReportServer = createDailyReportServer({
|
|
|
83
83
|
db: db as unknown as DailyReportDb, // drizzle mssql handle
|
|
84
84
|
tables: { hub, internal, comment, label, hubLabel, userStatus },
|
|
85
85
|
userTable: Users, // { id, displayName }
|
|
86
|
-
resolveUserId: async (externalId) => {/*
|
|
87
|
-
encodeUserId: (id) => encodeId(id), //
|
|
86
|
+
resolveUserId: async (externalId) => {/* External ID (e.g., OAuth sub) -> internal numeric ID */},
|
|
87
|
+
encodeUserId: (id) => encodeId(id), // Internal ID obfuscation (e.g. sqids)
|
|
88
88
|
authenticate: authenticateInLoader, // (request, { failureRedirect }) => { user?, cookie? }
|
|
89
|
-
redis: redisProvider as unknown as DailyReportRedisProvider, //
|
|
90
|
-
externalSources: [ //
|
|
89
|
+
redis: redisProvider as unknown as DailyReportRedisProvider, // Optional (disables SSE/epoch)
|
|
90
|
+
externalSources: [ // Optional: ingest legacy daily report tables from external systems
|
|
91
91
|
{ sourceType: "legacy", table: LegacyReport, idColumn: LegacyReport.reportId, mapRow: mapLegacyRow },
|
|
92
92
|
],
|
|
93
|
-
|
|
93
|
+
draftLabelNames: ["Draft", "Work in Progress"], // Draft label names (can specify a single string or array of candidates)
|
|
94
94
|
})
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Route mounts (React Router v7, file convention
|
|
97
|
+
Route mounts (React Router v7, flexible file convention):
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
100
|
// daily_report._index/loader.server.ts
|
|
@@ -111,13 +111,13 @@ export const loader = (args) => dailyReportServer.sse.loader(args)
|
|
|
111
111
|
|
|
112
112
|
### DI ports
|
|
113
113
|
|
|
114
|
-
- `authenticate(request, { failureRedirect })` —
|
|
115
|
-
- `resolveUserId(externalId)` —
|
|
116
|
-
- `encodeUserId(id)` —
|
|
117
|
-
- `redis` — `getClient()` (get/incr/xAdd/xRange/xRevRange) + `createClient()` (blocking xRead)
|
|
118
|
-
- `externalSources[]` — `Hub.source_type`
|
|
119
|
-
- `draftLabelName` —
|
|
120
|
-
-
|
|
114
|
+
- `authenticate(request, { failureRedirect })` — Session verification. When `failureRedirect: string` is set, throw a redirect on unauthenticated requests.
|
|
115
|
+
- `resolveUserId(externalId)` — External ID → internal numeric ID (`null` = unregistered). In-process caching can be disabled with `disableUserIdCache` (useful for testing).
|
|
116
|
+
- `encodeUserId(id)` — Obfuscates IDs sent to the client. Inject app-level implementation to preserve existing ID namespaces.
|
|
117
|
+
- `redis` — `getClient()` (get/incr/xAdd/xRange/xRevRange) + `createClient()` (blocking xRead). Structurally matches a `node-redis` v5 client. If omitted, SSE publishing is skipped (with a warning) and epoch is disabled.
|
|
118
|
+
- `externalSources[]` — When `Hub.source_type` matches, performs a `LEFT JOIN` on `Hub.source_id_num = idColumn` and converts fields via `mapRow`.
|
|
119
|
+
- `draftLabelName` / `draftLabelNames` — Database label names representing draft states (single string or array of candidates like `["Draft", "Work in Progress"]`). Used for server-side cross-user visibility filtering (hiding drafts from other users) and `isDraft` evaluation.
|
|
120
|
+
- Primary tuning parameters: `idsTtlMs` (180s) / `businessDateTtlMs` (300s) / `streamKey` / `streamMaxLen` / `loginRedirectPath`.
|
|
121
121
|
|
|
122
122
|
## Client wiring
|
|
123
123
|
|
|
@@ -126,7 +126,7 @@ export const loader = (args) => dailyReportServer.sse.loader(args)
|
|
|
126
126
|
import { createDailyReportClientLoader, DailyReportPage, dailyReportShouldRevalidate } from "@aiquants/daily-report/client"
|
|
127
127
|
|
|
128
128
|
export const shouldRevalidate = dailyReportShouldRevalidate
|
|
129
|
-
export const clientLoader = createDailyReportClientLoader() // /daily_report/api/ids
|
|
129
|
+
export const clientLoader = createDailyReportClientLoader() // Deferred fetching of /daily_report/api/ids
|
|
130
130
|
|
|
131
131
|
export default function Route() {
|
|
132
132
|
const { user, userId, dailyReportIds } = useLoaderData<typeof clientLoader>()
|
|
@@ -138,10 +138,10 @@ export default function Route() {
|
|
|
138
138
|
dailyReportIds={dailyReportIds}
|
|
139
139
|
config={{
|
|
140
140
|
renderHeader: ({ title, annotation }) => <YourHeader title={title} annotation={annotation} />,
|
|
141
|
-
showDevControls: import.meta.env.DEV, // SSE
|
|
142
|
-
draftLabelName: "
|
|
143
|
-
fieldLabels: { //
|
|
144
|
-
businessDate: "
|
|
141
|
+
showDevControls: import.meta.env.DEV, // Toggle SSE subscriptions, etc.
|
|
142
|
+
draftLabelName: "Draft", // Label name for draft states (matches target DB)
|
|
143
|
+
fieldLabels: { // Card/detail header labels (customizable per key)
|
|
144
|
+
businessDate: "Date", content: "Body", comments: "Comments", /* ... override to match app locale */
|
|
145
145
|
},
|
|
146
146
|
// apiBasePath: "/daily_report/api", ssePath: "/sse/daily_report/updates"
|
|
147
147
|
}}
|
|
@@ -151,28 +151,28 @@ export default function Route() {
|
|
|
151
151
|
}
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
`DailyReportPage`
|
|
154
|
+
`DailyReportPage` is layout-agnostic (header rendering via `renderHeader` slot, error boundaries/footer control managed by the app). For fine-grained usage, `DailyReportActionProvider`, `DailyReportResolvedContent`, `useDailyReportDetail`, etc. can be imported individually.
|
|
155
155
|
|
|
156
|
-
###
|
|
156
|
+
### Field Labels (`fieldLabels`)
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
Card and detail headings (`id` / `businessDate` / `author` / `visitTime` / `createdAt` / `updatedAt` / `updatedBy` / `category` / `categoryInfo` / `creationCategory` / `customer` / `subject` / `content` / `interviewers` / `comments` / `tabArticle` / `tabRelations` / `relationsEmpty`) are fully dependency-injected. **The package includes neutral English defaults** (`Business date` / `Customer` / `Content`, etc.), and consuming applications override headings via `fieldLabels`. This ensures the package itself carries no domain-specific wording. Partial overrides merge deeply over defaults (`DailyReportConfigProvider` performs a 1-level deep merge).
|
|
159
159
|
|
|
160
|
-
###
|
|
160
|
+
### External Source Badge Configuration (`sourceTypeConfigs`)
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
Display parameters (label names and badge CSS classes) for reports ingested from external sources can be overridden using `sourceTypeConfigs`. This prevents the package from hardcoding specific third-party service names or designs, leaving display customization to the host application.
|
|
163
163
|
|
|
164
164
|
```tsx
|
|
165
165
|
sourceTypeConfigs: {
|
|
166
166
|
Internal: {
|
|
167
|
-
label: "
|
|
167
|
+
label: "Native",
|
|
168
168
|
badgeClassName: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
|
|
169
169
|
},
|
|
170
|
-
|
|
171
|
-
label: "
|
|
170
|
+
ExternalCRM: {
|
|
171
|
+
label: "External CRM",
|
|
172
172
|
badgeClassName: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
|
|
173
173
|
},
|
|
174
|
-
|
|
175
|
-
label: "
|
|
174
|
+
LegacyPortal: {
|
|
175
|
+
label: "Legacy Portal",
|
|
176
176
|
badgeClassName: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
|
|
177
177
|
},
|
|
178
178
|
}
|
|
@@ -216,17 +216,17 @@ export default defineConfig({
|
|
|
216
216
|
})
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
## Realtime
|
|
219
|
+
## Realtime Architecture
|
|
220
220
|
|
|
221
|
-
1.
|
|
222
|
-
2. `DailyReportSseReader`
|
|
223
|
-
3. `sse.loader`
|
|
224
|
-
4.
|
|
221
|
+
1. Mutation services publish to Redis Stream (`daily-report:sse-stream`) in sequence: `invalidate -> epoch increment -> SSE publish`.
|
|
222
|
+
2. `DailyReportSseReader` uses a single blocking `xRead` loop to fan-out events to all SSE connections (eliminating per-connection Redis TCP connections).
|
|
223
|
+
3. `sse.loader` implements catch-up (`xRange`) using `Last-Event-ID` / `lastEventId` and 5-second keep-alive pings. `recipientRawUserId` is filtered server-side and removed before transmission to prevent internal ID leaks.
|
|
224
|
+
4. On the client, `useDailyReportSseConnection` handles exponential backoff reconnects, and the action context correlates optimistic updates with SSE echoes using `clientTempId`.
|
|
225
225
|
|
|
226
|
-
## API
|
|
226
|
+
## API Surface (Summary)
|
|
227
227
|
|
|
228
|
-
- server
|
|
229
|
-
- client
|
|
230
|
-
- shared
|
|
228
|
+
- **server**: `createDailyReportServer` / `createDailyReportService` / `createDailyReportHandlers` / `defineDailyReportSchema` / `DailyReportSseReader` / `SqlResultCache` / `transformJsonArray` / `jsonResponseWithETag` / `generateETag` / `isStreamIdLte`
|
|
229
|
+
- **client**: `DailyReportPage` / `DailyReportResolvedContent` / `DailyReportList` / `DailyReportDetailList` / `DailyReportActionProvider` / `useDailyReportActionContext` / `useDailyReportDetail` / `useDailyReportPrefetch` / `useDailyReportComments` / `useDailyReportSseConnection` / `DailyReportConfigProvider` / `createDailyReportClientLoader` / `dailyReportShouldRevalidate`
|
|
230
|
+
- **shared**: `DailyReportItem` / `DailyReportDetail` / `DailyReportUser` / `dailyReportSseMessageSchema` (8 discriminated union types) / `normalizeBusinessDateKey` / `mergeComments`
|
|
231
231
|
|
|
232
232
|
MIT
|
package/dist/client.d.mts
CHANGED
|
@@ -61,12 +61,14 @@ type DailyReportDetailListProps = {
|
|
|
61
61
|
userId?: string | null;
|
|
62
62
|
selectedItemId?: number | null;
|
|
63
63
|
onSelectItem?: (id: number | null) => void;
|
|
64
|
+
initialScrollOffset?: number;
|
|
65
|
+
onScrollOffsetChange?: (offset: number) => void;
|
|
64
66
|
};
|
|
65
67
|
/**
|
|
66
68
|
* Daily report detail cards rendered with dynamic height virtual scroll.
|
|
67
69
|
* 動的高さの仮想スクロールで日報詳細カードを描画するコンポーネント。
|
|
68
70
|
*/
|
|
69
|
-
declare const DailyReportDetailList: ({ dailyReportItems, userId, selectedItemId, onSelectItem }: DailyReportDetailListProps) => react.JSX.Element;
|
|
71
|
+
declare const DailyReportDetailList: ({ dailyReportItems, userId, selectedItemId, onSelectItem, initialScrollOffset, onScrollOffsetChange }: DailyReportDetailListProps) => react.JSX.Element;
|
|
70
72
|
|
|
71
73
|
type DailyReportEditFormProps = {
|
|
72
74
|
report: DailyReportDetail;
|
|
@@ -83,12 +85,14 @@ type DailyReportListProps = {
|
|
|
83
85
|
selectedReportHubId: number | null;
|
|
84
86
|
onSelectItem: (reportHubId: number | null) => void;
|
|
85
87
|
userId?: string | null;
|
|
88
|
+
initialScrollOffset?: number;
|
|
89
|
+
onScrollOffsetChange?: (offset: number) => void;
|
|
86
90
|
};
|
|
87
91
|
/**
|
|
88
92
|
* Daily report list cards with key metadata and preview content via virtual scrolling.
|
|
89
93
|
* 主なメタデータと内容プレビューを仮想スクロール表示する日報リスト。
|
|
90
94
|
*/
|
|
91
|
-
declare const DailyReportList: ({ dailyReportItems, initialSelectedBusinessDate, initialSelectedReportHubId, autoMarkRead, selectedReportHubId, onSelectItem, userId }: DailyReportListProps) => react.JSX.Element;
|
|
95
|
+
declare const DailyReportList: ({ dailyReportItems, initialSelectedBusinessDate, initialSelectedReportHubId, autoMarkRead, selectedReportHubId, onSelectItem, userId, initialScrollOffset, onScrollOffsetChange }: DailyReportListProps) => react.JSX.Element;
|
|
92
96
|
|
|
93
97
|
/** ヘッダー描画スロットに渡される引数。 */
|
|
94
98
|
type DailyReportHeaderProps = {
|
|
@@ -126,6 +130,8 @@ type DailyReportFieldLabels = {
|
|
|
126
130
|
type SourceTypeConfig = {
|
|
127
131
|
label: string;
|
|
128
132
|
badgeClassName?: string;
|
|
133
|
+
/** コメント投稿を許可するかどうか (既定 true)。false の場合、該当ソースの日報でコメント投稿フォームを非表示にする。 */
|
|
134
|
+
allowComment?: boolean;
|
|
129
135
|
};
|
|
130
136
|
/** クライアント層の設定一式。 */
|
|
131
137
|
type DailyReportClientConfig = {
|
|
@@ -500,7 +506,7 @@ declare const dailyReportShouldRevalidate: ShouldRevalidateFunction;
|
|
|
500
506
|
* Fetches the deferred daily report id list from the API endpoint.
|
|
501
507
|
* API エンドポイントから日報 ID 一覧を遅延取得する処理。
|
|
502
508
|
*/
|
|
503
|
-
declare const fetchDailyReportIds: (apiBasePath?: string) => Promise<DailyReportItem[]>;
|
|
509
|
+
declare const fetchDailyReportIds: (apiBasePath?: string, forceRefresh?: boolean) => Promise<DailyReportItem[]>;
|
|
504
510
|
/**
|
|
505
511
|
* Creates a React Router clientLoader that merges server data with deferred report ids.
|
|
506
512
|
* サーバーデータへ遅延日報 ID を合成する React Router clientLoader を生成する処理。
|
package/dist/client.d.ts
CHANGED
|
@@ -61,12 +61,14 @@ type DailyReportDetailListProps = {
|
|
|
61
61
|
userId?: string | null;
|
|
62
62
|
selectedItemId?: number | null;
|
|
63
63
|
onSelectItem?: (id: number | null) => void;
|
|
64
|
+
initialScrollOffset?: number;
|
|
65
|
+
onScrollOffsetChange?: (offset: number) => void;
|
|
64
66
|
};
|
|
65
67
|
/**
|
|
66
68
|
* Daily report detail cards rendered with dynamic height virtual scroll.
|
|
67
69
|
* 動的高さの仮想スクロールで日報詳細カードを描画するコンポーネント。
|
|
68
70
|
*/
|
|
69
|
-
declare const DailyReportDetailList: ({ dailyReportItems, userId, selectedItemId, onSelectItem }: DailyReportDetailListProps) => react.JSX.Element;
|
|
71
|
+
declare const DailyReportDetailList: ({ dailyReportItems, userId, selectedItemId, onSelectItem, initialScrollOffset, onScrollOffsetChange }: DailyReportDetailListProps) => react.JSX.Element;
|
|
70
72
|
|
|
71
73
|
type DailyReportEditFormProps = {
|
|
72
74
|
report: DailyReportDetail;
|
|
@@ -83,12 +85,14 @@ type DailyReportListProps = {
|
|
|
83
85
|
selectedReportHubId: number | null;
|
|
84
86
|
onSelectItem: (reportHubId: number | null) => void;
|
|
85
87
|
userId?: string | null;
|
|
88
|
+
initialScrollOffset?: number;
|
|
89
|
+
onScrollOffsetChange?: (offset: number) => void;
|
|
86
90
|
};
|
|
87
91
|
/**
|
|
88
92
|
* Daily report list cards with key metadata and preview content via virtual scrolling.
|
|
89
93
|
* 主なメタデータと内容プレビューを仮想スクロール表示する日報リスト。
|
|
90
94
|
*/
|
|
91
|
-
declare const DailyReportList: ({ dailyReportItems, initialSelectedBusinessDate, initialSelectedReportHubId, autoMarkRead, selectedReportHubId, onSelectItem, userId }: DailyReportListProps) => react.JSX.Element;
|
|
95
|
+
declare const DailyReportList: ({ dailyReportItems, initialSelectedBusinessDate, initialSelectedReportHubId, autoMarkRead, selectedReportHubId, onSelectItem, userId, initialScrollOffset, onScrollOffsetChange }: DailyReportListProps) => react.JSX.Element;
|
|
92
96
|
|
|
93
97
|
/** ヘッダー描画スロットに渡される引数。 */
|
|
94
98
|
type DailyReportHeaderProps = {
|
|
@@ -126,6 +130,8 @@ type DailyReportFieldLabels = {
|
|
|
126
130
|
type SourceTypeConfig = {
|
|
127
131
|
label: string;
|
|
128
132
|
badgeClassName?: string;
|
|
133
|
+
/** コメント投稿を許可するかどうか (既定 true)。false の場合、該当ソースの日報でコメント投稿フォームを非表示にする。 */
|
|
134
|
+
allowComment?: boolean;
|
|
129
135
|
};
|
|
130
136
|
/** クライアント層の設定一式。 */
|
|
131
137
|
type DailyReportClientConfig = {
|
|
@@ -500,7 +506,7 @@ declare const dailyReportShouldRevalidate: ShouldRevalidateFunction;
|
|
|
500
506
|
* Fetches the deferred daily report id list from the API endpoint.
|
|
501
507
|
* API エンドポイントから日報 ID 一覧を遅延取得する処理。
|
|
502
508
|
*/
|
|
503
|
-
declare const fetchDailyReportIds: (apiBasePath?: string) => Promise<DailyReportItem[]>;
|
|
509
|
+
declare const fetchDailyReportIds: (apiBasePath?: string, forceRefresh?: boolean) => Promise<DailyReportItem[]>;
|
|
504
510
|
/**
|
|
505
511
|
* Creates a React Router clientLoader that merges server data with deferred report ids.
|
|
506
512
|
* サーバーデータへ遅延日報 ID を合成する React Router clientLoader を生成する処理。
|