@okrlinkhub/okrhub 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.
Files changed (148) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +579 -0
  3. package/dist/client/_generated/_ignore.d.ts +1 -0
  4. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  5. package/dist/client/_generated/_ignore.js +3 -0
  6. package/dist/client/_generated/_ignore.js.map +1 -0
  7. package/dist/client/index.d.ts +593 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +704 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/component/_generated/api.d.ts +72 -0
  12. package/dist/component/_generated/api.d.ts.map +1 -0
  13. package/dist/component/_generated/api.js +31 -0
  14. package/dist/component/_generated/api.js.map +1 -0
  15. package/dist/component/_generated/component.d.ts +1986 -0
  16. package/dist/component/_generated/component.d.ts.map +1 -0
  17. package/dist/component/_generated/component.js +11 -0
  18. package/dist/component/_generated/component.js.map +1 -0
  19. package/dist/component/_generated/dataModel.d.ts +46 -0
  20. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  21. package/dist/component/_generated/dataModel.js +11 -0
  22. package/dist/component/_generated/dataModel.js.map +1 -0
  23. package/dist/component/_generated/server.d.ts +121 -0
  24. package/dist/component/_generated/server.d.ts.map +1 -0
  25. package/dist/component/_generated/server.js +78 -0
  26. package/dist/component/_generated/server.js.map +1 -0
  27. package/dist/component/convex.config.d.ts +3 -0
  28. package/dist/component/convex.config.d.ts.map +1 -0
  29. package/dist/component/convex.config.js +3 -0
  30. package/dist/component/convex.config.js.map +1 -0
  31. package/dist/component/entities/batch.d.ts +121 -0
  32. package/dist/component/entities/batch.d.ts.map +1 -0
  33. package/dist/component/entities/batch.js +81 -0
  34. package/dist/component/entities/batch.js.map +1 -0
  35. package/dist/component/entities/index.d.ts +13 -0
  36. package/dist/component/entities/index.d.ts.map +1 -0
  37. package/dist/component/entities/index.js +22 -0
  38. package/dist/component/entities/index.js.map +1 -0
  39. package/dist/component/entities/indicatorForecasts.d.ts +61 -0
  40. package/dist/component/entities/indicatorForecasts.d.ts.map +1 -0
  41. package/dist/component/entities/indicatorForecasts.js +180 -0
  42. package/dist/component/entities/indicatorForecasts.js.map +1 -0
  43. package/dist/component/entities/indicatorValues.d.ts +77 -0
  44. package/dist/component/entities/indicatorValues.d.ts.map +1 -0
  45. package/dist/component/entities/indicatorValues.js +218 -0
  46. package/dist/component/entities/indicatorValues.js.map +1 -0
  47. package/dist/component/entities/indicators.d.ts +90 -0
  48. package/dist/component/entities/indicators.d.ts.map +1 -0
  49. package/dist/component/entities/indicators.js +239 -0
  50. package/dist/component/entities/indicators.js.map +1 -0
  51. package/dist/component/entities/initiatives.d.ts +103 -0
  52. package/dist/component/entities/initiatives.d.ts.map +1 -0
  53. package/dist/component/entities/initiatives.js +275 -0
  54. package/dist/component/entities/initiatives.js.map +1 -0
  55. package/dist/component/entities/keyResults.d.ts +111 -0
  56. package/dist/component/entities/keyResults.d.ts.map +1 -0
  57. package/dist/component/entities/keyResults.js +284 -0
  58. package/dist/component/entities/keyResults.js.map +1 -0
  59. package/dist/component/entities/milestones.d.ts +93 -0
  60. package/dist/component/entities/milestones.d.ts.map +1 -0
  61. package/dist/component/entities/milestones.js +249 -0
  62. package/dist/component/entities/milestones.js.map +1 -0
  63. package/dist/component/entities/objectives.d.ts +99 -0
  64. package/dist/component/entities/objectives.d.ts.map +1 -0
  65. package/dist/component/entities/objectives.js +261 -0
  66. package/dist/component/entities/objectives.js.map +1 -0
  67. package/dist/component/entities/risks.d.ts +126 -0
  68. package/dist/component/entities/risks.d.ts.map +1 -0
  69. package/dist/component/entities/risks.js +315 -0
  70. package/dist/component/entities/risks.js.map +1 -0
  71. package/dist/component/externalId.d.ts +79 -0
  72. package/dist/component/externalId.d.ts.map +1 -0
  73. package/dist/component/externalId.js +124 -0
  74. package/dist/component/externalId.js.map +1 -0
  75. package/dist/component/lib/hmac.d.ts +18 -0
  76. package/dist/component/lib/hmac.d.ts.map +1 -0
  77. package/dist/component/lib/hmac.js +35 -0
  78. package/dist/component/lib/hmac.js.map +1 -0
  79. package/dist/component/lib/index.d.ts +7 -0
  80. package/dist/component/lib/index.d.ts.map +1 -0
  81. package/dist/component/lib/index.js +6 -0
  82. package/dist/component/lib/index.js.map +1 -0
  83. package/dist/component/lib/types.d.ts +30 -0
  84. package/dist/component/lib/types.d.ts.map +1 -0
  85. package/dist/component/lib/types.js +7 -0
  86. package/dist/component/lib/types.js.map +1 -0
  87. package/dist/component/lib/validation.d.ts +15 -0
  88. package/dist/component/lib/validation.d.ts.map +1 -0
  89. package/dist/component/lib/validation.js +29 -0
  90. package/dist/component/lib/validation.js.map +1 -0
  91. package/dist/component/okrhub.d.ts +31 -0
  92. package/dist/component/okrhub.d.ts.map +1 -0
  93. package/dist/component/okrhub.js +45 -0
  94. package/dist/component/okrhub.js.map +1 -0
  95. package/dist/component/schema.d.ts +943 -0
  96. package/dist/component/schema.d.ts.map +1 -0
  97. package/dist/component/schema.js +437 -0
  98. package/dist/component/schema.js.map +1 -0
  99. package/dist/component/sync/http.d.ts +30 -0
  100. package/dist/component/sync/http.d.ts.map +1 -0
  101. package/dist/component/sync/http.js +114 -0
  102. package/dist/component/sync/http.js.map +1 -0
  103. package/dist/component/sync/index.d.ts +7 -0
  104. package/dist/component/sync/index.d.ts.map +1 -0
  105. package/dist/component/sync/index.js +7 -0
  106. package/dist/component/sync/index.js.map +1 -0
  107. package/dist/component/sync/processor.d.ts +20 -0
  108. package/dist/component/sync/processor.d.ts.map +1 -0
  109. package/dist/component/sync/processor.js +67 -0
  110. package/dist/component/sync/processor.js.map +1 -0
  111. package/dist/component/sync/queue.d.ts +40 -0
  112. package/dist/component/sync/queue.d.ts.map +1 -0
  113. package/dist/component/sync/queue.js +176 -0
  114. package/dist/component/sync/queue.js.map +1 -0
  115. package/dist/react/index.d.ts +2 -0
  116. package/dist/react/index.d.ts.map +1 -0
  117. package/dist/react/index.js +6 -0
  118. package/dist/react/index.js.map +1 -0
  119. package/package.json +117 -0
  120. package/src/client/_generated/_ignore.ts +1 -0
  121. package/src/client/index.ts +1004 -0
  122. package/src/component/_generated/api.ts +88 -0
  123. package/src/component/_generated/component.ts +2685 -0
  124. package/src/component/_generated/dataModel.ts +60 -0
  125. package/src/component/_generated/server.ts +156 -0
  126. package/src/component/convex.config.ts +3 -0
  127. package/src/component/entities/batch.ts +90 -0
  128. package/src/component/entities/index.ts +64 -0
  129. package/src/component/entities/indicatorForecasts.ts +205 -0
  130. package/src/component/entities/indicatorValues.ts +254 -0
  131. package/src/component/entities/indicators.ts +290 -0
  132. package/src/component/entities/initiatives.ts +342 -0
  133. package/src/component/entities/keyResults.ts +334 -0
  134. package/src/component/entities/milestones.ts +296 -0
  135. package/src/component/entities/objectives.ts +294 -0
  136. package/src/component/entities/risks.ts +383 -0
  137. package/src/component/externalId.ts +172 -0
  138. package/src/component/lib/hmac.ts +53 -0
  139. package/src/component/lib/index.ts +7 -0
  140. package/src/component/lib/types.ts +31 -0
  141. package/src/component/lib/validation.ts +41 -0
  142. package/src/component/okrhub.ts +110 -0
  143. package/src/component/schema.ts +574 -0
  144. package/src/component/sync/http.ts +138 -0
  145. package/src/component/sync/index.ts +11 -0
  146. package/src/component/sync/processor.ts +77 -0
  147. package/src/component/sync/queue.ts +201 -0
  148. package/src/react/index.ts +7 -0
package/README.md ADDED
@@ -0,0 +1,579 @@
1
+ # @okrlinkhub/okrhub
2
+
3
+ > Convex component for syncing OKR data (Objectives, Key Results, Risks, Initiatives) to LinkHub via secure HMAC-authenticated API.
4
+
5
+ [![npm version](https://badge.fury.io/js/@okrlinkhub%2Fokrhub.svg)](https://badge.fury.io/js/@okrlinkhub%2Fokrhub)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Convex](https://img.shields.io/badge/Convex-1.31+-purple.svg)](https://convex.dev/)
9
+
10
+ ## Overview
11
+
12
+ OKRHub is a Convex component that enables external applications to sync their OKR data to LinkHub. It provides:
13
+
14
+ - **One-way sync**: Data flows from your app to LinkHub
15
+ - **Queue-based processing**: Async processing with retry logic
16
+ - **HMAC authentication**: Secure API communication with cryptographic signatures
17
+ - **External ID mapping**: Use your own IDs, LinkHub handles the mapping
18
+ - **Company isolation**: Each API key is scoped to a specific company
19
+
20
+ ## Architecture
21
+
22
+ ```
23
+ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
24
+ │ Your App │ │ @okrlinkhub/okrhub │ │ LinkHub │
25
+ │ (Convex) │ │ (Component) │ │ (Server) │
26
+ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘
27
+ │ │ │
28
+ │ insertObjective() │ │
29
+ │ ────────────────────────► │ │
30
+ │ │ Saves to syncQueue │
31
+ │ │ ─────────┐ │
32
+ │ │ │ │
33
+ │ │ ◄────────┘ │
34
+ │ │ │
35
+ │ processSyncQueue() │ │
36
+ │ ────────────────────────► │ │
37
+ │ │ POST /ingest/okr/v1/* │
38
+ │ │ Headers: │
39
+ │ │ X-OKRHub-Signature │
40
+ │ │ X-OKRHub-Version │
41
+ │ │ X-OKRHub-Key-Prefix │
42
+ │ │ ─────────────────────────►│
43
+ │ │ │ Verify HMAC
44
+ │ │ │ Create/Update entity
45
+ │ │ │ Create ID mapping
46
+ │ │ { success: true } │
47
+ │ │ ◄─────────────────────────│
48
+ │ │ │
49
+ │ Update syncQueue status │ │
50
+ │ ◄──────────────────────── │ │
51
+ ```
52
+
53
+ ## Requirements
54
+
55
+ - Node.js 18+
56
+ - Convex 1.31.6+
57
+ - React 18.3.1+ or 19.0.0+ (for React hooks)
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ npm install @okrlinkhub/okrhub convex
63
+ ```
64
+
65
+ Or with yarn:
66
+
67
+ ```bash
68
+ yarn add @okrlinkhub/okrhub convex
69
+ ```
70
+
71
+ Or with pnpm:
72
+
73
+ ```bash
74
+ pnpm add @okrlinkhub/okrhub convex
75
+ ```
76
+
77
+ ## Quick Start
78
+
79
+ ### 1. Add the component to your Convex app
80
+
81
+ ```typescript
82
+ // convex/convex.config.ts
83
+ import { defineApp } from "convex/server";
84
+ import okrhub from "@okrlinkhub/okrhub/convex.config";
85
+
86
+ const app = defineApp();
87
+ app.use(okrhub);
88
+
89
+ export default app;
90
+ ```
91
+
92
+ ### 2. Expose the API in your app
93
+
94
+ ```typescript
95
+ // convex/okrhub.ts
96
+ import { components } from "./_generated/api";
97
+ import { exposeApi } from "@okrlinkhub/okrhub";
98
+
99
+ export const {
100
+ insertObjective,
101
+ insertKeyResult,
102
+ insertRisk,
103
+ insertInitiative,
104
+ insertIndicator,
105
+ insertMilestone,
106
+ processSyncQueue,
107
+ getPendingSyncItems,
108
+ } = exposeApi(components.okrhub, {
109
+ auth: async (ctx, operation) => {
110
+ const identity = await ctx.auth.getUserIdentity();
111
+ if (!identity) throw new Error("Unauthorized");
112
+ },
113
+ });
114
+ ```
115
+
116
+ ### 3. Set environment variables
117
+
118
+ Create a `.env.local` file in your project root:
119
+
120
+ ```bash
121
+ # LinkHub site endpoint URL
122
+ LINKHUB_API_URL=https://your-linkhub.convex.site
123
+
124
+ # API Key Prefix (first 12 characters of your API key)
125
+ LINKHUB_API_KEY_PREFIX=okr_xxxxxxxx
126
+
127
+ # Signing Secret for HMAC authentication
128
+ LINKHUB_SIGNING_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
129
+ ```
130
+
131
+ ### 4. Use in your app
132
+
133
+ ```typescript
134
+ import { generateExternalId } from "@okrlinkhub/okrhub";
135
+ import { useMutation } from "convex/react";
136
+ import { api } from "../convex/_generated/api";
137
+
138
+ function CreateObjective() {
139
+ const insertObjective = useMutation(api.okrhub.insertObjective);
140
+
141
+ const handleCreate = async () => {
142
+ const externalId = generateExternalId("my-app", "objective");
143
+ const teamExternalId = generateExternalId("my-app", "team");
144
+
145
+ await insertObjective({
146
+ objective: {
147
+ externalId,
148
+ title: "Increase Revenue Q1",
149
+ description: "Focus on expanding sales channels",
150
+ teamExternalId,
151
+ },
152
+ });
153
+ };
154
+
155
+ return <button onClick={handleCreate}>Create Objective</button>;
156
+ }
157
+ ```
158
+
159
+ ## Authentication
160
+
161
+ OKRHub uses HMAC-SHA256 authentication to secure communication with LinkHub.
162
+
163
+ ### How it works
164
+
165
+ 1. **API Key Creation**: In LinkHub, create an API key which generates:
166
+ - `apiKey`: Full API key (e.g., `okr_9d78c3eb...`) - store securely
167
+ - `keyPrefix`: First 12 characters for identification
168
+ - `signingSecret`: HMAC signing secret (e.g., `whsec_...`) - used to sign requests
169
+
170
+ 2. **Request Signing**: Every request is signed using the `signingSecret`:
171
+ ```
172
+ signature = HMAC-SHA256(payload, signingSecret)
173
+ ```
174
+
175
+ 3. **Headers Sent**:
176
+ - `X-OKRHub-Signature`: HMAC signature of the request body
177
+ - `X-OKRHub-Version`: Component version (for compatibility)
178
+ - `X-OKRHub-Key-Prefix`: API key prefix for identification
179
+
180
+ 4. **Server Verification**: LinkHub verifies the signature using the stored `signingSecret`
181
+
182
+ ### Security Best Practices
183
+
184
+ - Never expose the `signingSecret` in client-side code
185
+ - Use environment variables for all credentials
186
+ - Rotate API keys periodically
187
+ - Use granular permissions when possible
188
+
189
+ ## External ID Format
190
+
191
+ All entities use external IDs in the format: `{sourceApp}:{entityType}:{uuid}`
192
+
193
+ Example: `my-app:objective:550e8400-e29b-41d4-a716-446655440000`
194
+
195
+ ```typescript
196
+ import {
197
+ generateExternalId,
198
+ validateExternalId,
199
+ parseExternalId
200
+ } from "@okrlinkhub/okrhub";
201
+
202
+ // Generate a new external ID
203
+ const id = generateExternalId("my-app", "objective");
204
+ // "my-app:objective:550e8400-e29b-41d4-a716-446655440000"
205
+
206
+ // Validate format
207
+ const isValid = validateExternalId(id); // true
208
+
209
+ // Parse components
210
+ const parsed = parseExternalId(id);
211
+ // { sourceApp: "my-app", entityType: "objective", uuid: "..." }
212
+ ```
213
+
214
+ ### Supported Entity Types
215
+
216
+ | Entity Type | Description |
217
+ |-------------|-------------|
218
+ | `objective` | Strategic objectives |
219
+ | `keyResult` | Key results linked to objectives |
220
+ | `risk` | Risks linked to key results |
221
+ | `initiative` | Initiatives to mitigate risks |
222
+ | `indicator` | Metrics and KPIs |
223
+ | `milestone` | Milestone targets for indicators |
224
+ | `team` | Teams (for reference mapping) |
225
+ | `user` | Users (for reference mapping) |
226
+ | `company` | Companies (for reference mapping) |
227
+
228
+ ## Entity Payloads
229
+
230
+ ### Objective
231
+
232
+ ```typescript
233
+ {
234
+ externalId: string; // Required: unique ID
235
+ title: string; // Required: objective title
236
+ description: string; // Required: objective description
237
+ teamExternalId: string; // Required: reference to team
238
+ createdAt?: number; // Optional: timestamp
239
+ updatedAt?: number; // Optional: timestamp
240
+ }
241
+ ```
242
+
243
+ ### Key Result
244
+
245
+ ```typescript
246
+ {
247
+ externalId: string; // Required
248
+ indicatorExternalId: string; // Required: linked indicator
249
+ teamExternalId: string; // Required: team reference
250
+ weight: number; // Required: weight (0-100)
251
+ objectiveExternalId?: string; // Optional: linked objective
252
+ impact?: number; // Optional
253
+ forecastValue?: number; // Optional
254
+ targetValue?: number; // Optional
255
+ }
256
+ ```
257
+
258
+ ### Risk
259
+
260
+ ```typescript
261
+ {
262
+ externalId: string; // Required
263
+ description: string; // Required
264
+ teamExternalId: string; // Required
265
+ priority: "lowest" | "low" | "medium" | "high" | "highest";
266
+ keyResultExternalId?: string; // Optional: linked key result
267
+ indicatorExternalId?: string; // Optional: linked indicator
268
+ triggerValue?: number; // Optional
269
+ isRed?: boolean; // Optional
270
+ }
271
+ ```
272
+
273
+ ### Initiative
274
+
275
+ ```typescript
276
+ {
277
+ externalId: string; // Required
278
+ description: string; // Required
279
+ teamExternalId: string; // Required
280
+ assigneeExternalId: string; // Required: user reference
281
+ createdByExternalId: string; // Required: user reference
282
+ priority: "lowest" | "low" | "medium" | "high" | "highest";
283
+ riskExternalId?: string; // Optional: linked risk
284
+ status?: "ON_TIME" | "OVERDUE" | "FINISHED";
285
+ externalUrl?: string; // Optional: external link
286
+ notes?: string; // Optional
287
+ }
288
+ ```
289
+
290
+ ### Indicator
291
+
292
+ ```typescript
293
+ {
294
+ externalId: string; // Required
295
+ companyExternalId: string; // Required: company reference
296
+ description: string; // Required
297
+ symbol: string; // Required: unit symbol
298
+ periodicity: "weekly" | "monthly" | "quarterly" | "semesterly" | "yearly";
299
+ assigneeExternalId?: string; // Optional: responsible user
300
+ isReverse?: boolean; // Optional: lower is better
301
+ type?: "OUTPUT" | "OUTCOME"; // Optional
302
+ }
303
+ ```
304
+
305
+ ## Processing the Sync Queue
306
+
307
+ Entities are first stored in a sync queue, then processed asynchronously. Set up a cron job for automatic processing:
308
+
309
+ ```typescript
310
+ // convex/crons.ts
311
+ import { cronJobs } from "convex/server";
312
+ import { api } from "./_generated/api";
313
+
314
+ const crons = cronJobs();
315
+
316
+ crons.interval(
317
+ "process okrhub sync queue",
318
+ { minutes: 1 },
319
+ api.okrhub.processSyncQueue,
320
+ { batchSize: 50 }
321
+ );
322
+
323
+ export default crons;
324
+ ```
325
+
326
+ Or process manually:
327
+
328
+ ```typescript
329
+ // From Dashboard or action
330
+ await ctx.runAction(api.okrhub.processSyncQueue, {
331
+ batchSize: 10,
332
+ });
333
+ ```
334
+
335
+ ### Queue States
336
+
337
+ | Status | Description |
338
+ |--------|-------------|
339
+ | `pending` | Waiting to be processed |
340
+ | `processing` | Currently being sent to LinkHub |
341
+ | `success` | Successfully synced |
342
+ | `failed` | Failed after max retries |
343
+
344
+ ## Initial Setup
345
+
346
+ Before syncing entities, you need to set up reference mappings in LinkHub for entities that are referenced by external IDs (teams, users, companies).
347
+
348
+ ### 1. Create API Key in LinkHub
349
+
350
+ In the LinkHub Convex Dashboard, call `apiKeys:createForSetup`:
351
+
352
+ ```json
353
+ {
354
+ "name": "My App Integration",
355
+ "companyId": "your_company_id",
356
+ "createdByUserId": "your_user_id",
357
+ "permissions": ["ingest:all"]
358
+ }
359
+ ```
360
+
361
+ Save the returned `apiKey`, `keyPrefix`, and `signingSecret`.
362
+
363
+ ### 2. Create Reference Mappings
364
+
365
+ For each team/user/company referenced by your external IDs, create a mapping in LinkHub using `ingest:createMappingForSetup`:
366
+
367
+ ```json
368
+ {
369
+ "externalId": "my-app:team:00000000-0000-0000-0000-000000000001",
370
+ "entityType": "team",
371
+ "convexId": "existing_team_id_in_linkhub",
372
+ "tableName": "teams",
373
+ "sourceApp": "my-app",
374
+ "companyId": "your_company_id"
375
+ }
376
+ ```
377
+
378
+ ### 3. Configure Environment
379
+
380
+ Add credentials to your `.env.local`:
381
+
382
+ ```bash
383
+ LINKHUB_API_URL=https://your-linkhub.convex.site
384
+ LINKHUB_API_KEY_PREFIX=okr_xxxxxxxx
385
+ LINKHUB_SIGNING_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx
386
+ ```
387
+
388
+ ### 4. Test Sync
389
+
390
+ Insert an entity and process the queue:
391
+
392
+ ```typescript
393
+ // Insert
394
+ await insertObjective({
395
+ objective: {
396
+ externalId: generateExternalId("my-app", "objective"),
397
+ title: "Test Objective",
398
+ description: "Testing the sync",
399
+ teamExternalId: "my-app:team:00000000-0000-0000-0000-000000000001",
400
+ },
401
+ });
402
+
403
+ // Process
404
+ const result = await processSyncQueue({ batchSize: 10 });
405
+ // { processed: 1, succeeded: 1, failed: 0 }
406
+ ```
407
+
408
+ ## HTTP Routes (Optional)
409
+
410
+ Register HTTP routes for REST API access:
411
+
412
+ ```typescript
413
+ // convex/http.ts
414
+ import { httpRouter } from "convex/server";
415
+ import { components } from "./_generated/api";
416
+ import { registerRoutes } from "@okrlinkhub/okrhub";
417
+
418
+ const http = httpRouter();
419
+ registerRoutes(http, components.okrhub, { pathPrefix: "/api/okrhub" });
420
+
421
+ export default http;
422
+ ```
423
+
424
+ ## Payload Validators
425
+
426
+ All payload types are validated using Convex validators:
427
+
428
+ ```typescript
429
+ import {
430
+ objectivePayloadValidator,
431
+ keyResultPayloadValidator,
432
+ riskPayloadValidator,
433
+ initiativePayloadValidator,
434
+ indicatorPayloadValidator,
435
+ milestonePayloadValidator,
436
+ } from "@okrlinkhub/okrhub/schema";
437
+ ```
438
+
439
+ ## Development
440
+
441
+ ```bash
442
+ # Install dependencies
443
+ npm install
444
+
445
+ # Run dev server (backend + frontend + build watcher)
446
+ npm run dev
447
+
448
+ # Build
449
+ npm run build
450
+
451
+ # Run tests
452
+ npm test
453
+
454
+ # Type check
455
+ npm run typecheck
456
+ ```
457
+
458
+ ## Example App
459
+
460
+ The `example/` directory contains a working example app demonstrating the component usage.
461
+
462
+ ```bash
463
+ cd example
464
+ npm install
465
+ npm run dev
466
+ ```
467
+
468
+ ## API Reference
469
+
470
+ ### Core Functions
471
+
472
+ #### `exposeApi(component, options)`
473
+
474
+ Exposes the OKRHub component API with authentication.
475
+
476
+ ```typescript
477
+ import { exposeApi } from "@okrlinkhub/okrhub";
478
+ import { components } from "./_generated/api";
479
+
480
+ export const {
481
+ insertObjective,
482
+ insertKeyResult,
483
+ insertRisk,
484
+ insertInitiative,
485
+ insertIndicator,
486
+ insertMilestone,
487
+ insertIndicatorValue,
488
+ insertIndicatorForecast,
489
+ processSyncQueue,
490
+ getPendingSyncItems,
491
+ } = exposeApi(components.okrhub, {
492
+ auth: async (ctx, operation) => {
493
+ // Your authentication logic
494
+ const identity = await ctx.auth.getUserIdentity();
495
+ if (!identity) throw new Error("Unauthorized");
496
+ },
497
+ });
498
+ ```
499
+
500
+ #### External ID Utilities
501
+
502
+ ```typescript
503
+ import {
504
+ generateExternalId,
505
+ validateExternalId,
506
+ parseExternalId,
507
+ extractSourceApp,
508
+ extractEntityType,
509
+ sameSourceApp,
510
+ OKRHUB_VERSION,
511
+ ENTITY_TYPES,
512
+ } from "@okrlinkhub/okrhub";
513
+ ```
514
+
515
+ #### HTTP Routes Registration
516
+
517
+ ```typescript
518
+ import { registerRoutes } from "@okrlinkhub/okrhub";
519
+
520
+ registerRoutes(httpRouter, components.okrhub, {
521
+ pathPrefix: "/api/okrhub",
522
+ });
523
+ ```
524
+
525
+ ### React Hooks
526
+
527
+ ```typescript
528
+ import { useOKRHub } from "@okrlinkhub/okrhub/react";
529
+ import { useQuery } from "convex/react";
530
+
531
+ function MyComponent() {
532
+ const { getPendingSyncItems } = useOKRHub();
533
+ const pendingItems = useQuery(getPendingSyncItems);
534
+
535
+ // Use pendingItems...
536
+ }
537
+ ```
538
+
539
+ ## Related Packages
540
+
541
+ - [@okrlinkhub/ui-kit](https://github.com/okrlinkhub/linkhub-ui-kit) - React components for displaying OKR data
542
+
543
+ ## Troubleshooting
544
+
545
+ ### "Team not found for externalId"
546
+
547
+ You need to create a mapping for the team before syncing entities that reference it. See [Initial Setup](#initial-setup).
548
+
549
+ ### "Invalid signature"
550
+
551
+ Check that:
552
+ 1. `LINKHUB_SIGNING_SECRET` matches the `signingSecret` from API key creation
553
+ 2. `LINKHUB_API_KEY_PREFIX` matches the `keyPrefix` from API key creation
554
+ 3. The API key is active and not expired
555
+
556
+ ### "Client version too old"
557
+
558
+ Update the component: `npm update @okrlinkhub/okrhub`
559
+
560
+ ### Queue items stuck in "processing" state
561
+
562
+ This can happen if the processing action crashes. Reset them manually:
563
+
564
+ ```typescript
565
+ // In Convex Dashboard
566
+ await ctx.runMutation(internal.okrhub.sync.resetStuckItems, {});
567
+ ```
568
+
569
+ ## Contributing
570
+
571
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
572
+
573
+ ## Versioning
574
+
575
+ This project uses [Semantic Versioning](https://semver.org/). For the versions available, see the [CHANGELOG.md](CHANGELOG.md) file.
576
+
577
+ ## License
578
+
579
+ This project is licensed under the Apache-2.0 License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=_ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_ignore.d.ts","sourceRoot":"","sources":["../../../src/client/_generated/_ignore.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // This is only here so convex-test can detect a _generated folder
3
+ //# sourceMappingURL=_ignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_ignore.js","sourceRoot":"","sources":["../../../src/client/_generated/_ignore.ts"],"names":[],"mappings":";AAAA,kEAAkE"}