@d19n/youfibre-odin-sdk 1.0.244 → 1.0.246
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 +223 -1100
- package/dist/records-v2/AddressRecord.d.ts +4 -4
- package/dist/records-v2/AddressRecord.js +2 -4
- package/dist/records-v2/CaseRecord.d.ts +27 -20
- package/dist/records-v2/CaseRecord.js +31 -27
- package/dist/records-v2/ChurnRequestRecord.d.ts +3 -2
- package/dist/records-v2/ChurnRequestRecord.js +4 -3
- package/dist/records-v2/ContactRecord.d.ts +2 -2
- package/dist/records-v2/ContactRecord.js +1 -2
- package/dist/records-v2/ContractBuyOutRecord.d.ts +6 -4
- package/dist/records-v2/ContractBuyOutRecord.js +8 -6
- package/dist/records-v2/OntReplacementApprovalRecord.d.ts +2 -2
- package/dist/records-v2/OntReplacementApprovalRecord.js +1 -2
- package/dist/records-v2/OrderRecord.d.ts +17 -16
- package/dist/records-v2/OrderRecord.js +11 -17
- package/dist/records-v2/PICRequestRecord.d.ts +3 -2
- package/dist/records-v2/PICRequestRecord.js +4 -3
- package/dist/records-v2/PaymentMethodRefundRecord.d.ts +3 -2
- package/dist/records-v2/PaymentMethodRefundRecord.js +4 -3
- package/dist/records-v2/WorkOrderRecord.d.ts +152 -140
- package/dist/records-v2/WorkOrderRecord.js +106 -152
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,1197 +1,320 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ODIN SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe SDK for the ODIN platform with comprehensive entity and action support.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
7
|
- [Installation](#installation)
|
|
8
8
|
- [Quick Start](#quick-start)
|
|
9
|
-
- [API Client (External Access)](#api-client-external-access)
|
|
10
|
-
- [DB Client (Backend Services)](#db-client-backend-services)
|
|
11
|
-
- [Working with Records](#working-with-records)
|
|
12
|
-
- [Executing Actions](#executing-actions)
|
|
13
|
-
- [Searching Records](#searching-records)
|
|
14
|
-
- [Linking Records](#linking-records)
|
|
15
|
-
- [Migration Guide](#migration-guide)
|
|
16
9
|
- [Architecture](#architecture)
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
- [API Client](#api-client)
|
|
11
|
+
- [DB Client](#db-client)
|
|
12
|
+
- [Entities](#entities)
|
|
13
|
+
- [Actions](#actions)
|
|
14
|
+
- [Record Wrappers](#record-wrappers)
|
|
15
|
+
- [Events](#events)
|
|
16
|
+
- [Migration Guide](#migration-guide)
|
|
19
17
|
|
|
20
18
|
## Installation
|
|
21
19
|
|
|
22
20
|
```bash
|
|
23
|
-
npm install @youfibre
|
|
21
|
+
npm install @d19n/youfibre-odin-sdk
|
|
24
22
|
```
|
|
25
23
|
|
|
26
|
-
---
|
|
27
|
-
|
|
28
24
|
## Quick Start
|
|
29
25
|
|
|
30
|
-
### API
|
|
26
|
+
### API Client (Frontend/External Services)
|
|
31
27
|
|
|
32
28
|
```typescript
|
|
33
|
-
import { OdinApiClient } from '@youfibre
|
|
29
|
+
import { OdinApiClient } from '@d19n/youfibre-odin-sdk';
|
|
34
30
|
|
|
31
|
+
// Initialize with OAuth credentials
|
|
35
32
|
const odin = new OdinApiClient({
|
|
36
|
-
host:
|
|
37
|
-
clientId: process.env.ODIN_CLIENT_ID
|
|
38
|
-
clientSecret: process.env.ODIN_CLIENT_SECRET
|
|
33
|
+
host: process.env.ODIN_HOST_URL,
|
|
34
|
+
clientId: process.env.ODIN_CLIENT_ID,
|
|
35
|
+
clientSecret: process.env.ODIN_CLIENT_SECRET,
|
|
39
36
|
});
|
|
40
37
|
|
|
41
|
-
// Get
|
|
42
|
-
const order = await odin.orders.get('ORD-123');
|
|
43
|
-
console.log(order.properties.Status);
|
|
44
|
-
|
|
45
|
-
await order.activateOrder({ ActiveDate: '2025-01-15' });
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### DB Access (Backend Services)
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
import { OdinDbClient } from '@youfibre/odin-sdk';
|
|
52
|
-
|
|
53
|
-
// In a NestJS service
|
|
54
|
-
@Injectable()
|
|
55
|
-
export class OrderService {
|
|
56
|
-
constructor(private readonly dbService: DbService) {}
|
|
57
|
-
|
|
58
|
-
async activateOrder(principal: any, orderId: string) {
|
|
59
|
-
const odin = new OdinDbClient(principal, this.dbService);
|
|
60
|
-
|
|
61
|
-
const order = await odin.orders.get(orderId);
|
|
62
|
-
await order.activateOrder({ ActiveDate: new Date().toISOString() });
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## API Client (External Access)
|
|
70
|
-
|
|
71
|
-
Use `OdinApiClient` for external API access from scripts, CLIs, or external services.
|
|
72
|
-
|
|
73
|
-
### Constructor Options
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
// Option 1: OAuth credentials
|
|
77
|
-
const odin = new OdinApiClient({
|
|
78
|
-
host: 'api.youfibre.com',
|
|
79
|
-
clientId: 'your-client-id',
|
|
80
|
-
clientSecret: 'your-client-secret',
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Option 2: Existing JWT token
|
|
84
|
-
const odin = OdinApiClient.withToken('eyJhbG...', 'api.youfibre.com');
|
|
85
|
-
|
|
86
|
-
// Option 3: Existing ApiProvider
|
|
87
|
-
import { ApiProvider } from '@d19n/odin-sdk-generator';
|
|
88
|
-
const provider = new ApiProvider({ host, clientId, clientSecret });
|
|
89
|
-
const odin = new OdinApiClient(provider);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Available Accessors
|
|
93
|
-
|
|
94
|
-
All entity accessors are lazy-loaded on first access:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
odin.orders // OrderAccessor
|
|
98
|
-
odin.contacts // ContactAccessor
|
|
99
|
-
odin.addresses // AddressAccessor
|
|
100
|
-
odin.accounts // AccountAccessor
|
|
101
|
-
odin.invoices // InvoiceAccessor
|
|
102
|
-
odin.workOrders // WorkOrderAccessor
|
|
103
|
-
odin.visits // VisitAccessor
|
|
104
|
-
// ... and all other entities
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## DB Client (Backend Services)
|
|
110
|
-
|
|
111
|
-
Use `OdinDbClient` for direct database access in backend services (NestJS, etc.).
|
|
112
|
-
|
|
113
|
-
### Constructor
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
const odin = new OdinDbClient(principal, dbService);
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
- `principal`: The authenticated user context
|
|
120
|
-
- `dbService`: The database service instance (typically injected)
|
|
121
|
-
|
|
122
|
-
### Example: NestJS Service
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import { Injectable } from '@nestjs/common';
|
|
126
|
-
import { OdinDbClient } from '@youfibre/odin-sdk';
|
|
127
|
-
import { DbService } from '@d19n/schema-manager/dist/db/db.service';
|
|
128
|
-
|
|
129
|
-
@Injectable()
|
|
130
|
-
export class WorkOrderService {
|
|
131
|
-
constructor(private readonly dbService: DbService) {}
|
|
132
|
-
|
|
133
|
-
async completeWorkOrder(principal: any, workOrderId: string) {
|
|
134
|
-
const odin = new OdinDbClient(principal, this.dbService);
|
|
135
|
-
|
|
136
|
-
const workOrder = await odin.workOrders.get(workOrderId);
|
|
137
|
-
|
|
138
|
-
await workOrder.completeWorkOrder({
|
|
139
|
-
CompletedDate: new Date().toISOString(),
|
|
140
|
-
Notes: 'Work completed successfully',
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## Working with Records
|
|
149
|
-
|
|
150
|
-
### Getting Records
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
// Get single record by ID
|
|
154
|
-
const order = await odin.orders.get('uuid-here');
|
|
155
|
-
|
|
156
|
-
// Get multiple records by IDs
|
|
157
|
-
const orders = await odin.orders.getMany(['uuid-1', 'uuid-2', 'uuid-3']);
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Record Properties
|
|
161
|
-
|
|
162
|
-
Records are wrapped with typed access to all properties:
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
38
|
+
// Get a record with typed properties
|
|
165
39
|
const order = await odin.orders.get(orderId);
|
|
40
|
+
console.log(order.properties?.Status);
|
|
41
|
+
|
|
42
|
+
// Execute a simple action
|
|
43
|
+
await order.activateOrder({ ActivationDate: "2025-01-01" });
|
|
44
|
+
|
|
45
|
+
// Execute a multi-step flow action
|
|
46
|
+
const cboRec = await odin.contractBuyOuts.get(cboId);
|
|
47
|
+
await cboRec
|
|
48
|
+
.moveCboToApproved({ journeyId: order.id })
|
|
49
|
+
.approveCbo(cboRec.id, {
|
|
50
|
+
Amount: cboRec.properties.Amount,
|
|
51
|
+
FinalBillVerified: cboRec.properties.FinalBillVerified,
|
|
52
|
+
})
|
|
53
|
+
.execute();
|
|
166
54
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
order.recordNumber // string | undefined
|
|
173
|
-
order.stage // PipelineStageEntity | undefined
|
|
174
|
-
order.createdAt // string | undefined
|
|
175
|
-
order.updatedAt // string | undefined
|
|
176
|
-
|
|
177
|
-
// Entity-specific properties (fully typed)
|
|
178
|
-
order.properties.Status // string
|
|
179
|
-
order.properties.ActiveDate // string
|
|
180
|
-
order.properties.ContractType // string
|
|
181
|
-
// ... all properties are typed
|
|
182
|
-
|
|
183
|
-
// Access raw OdinRecord if needed
|
|
184
|
-
order.raw // OdinRecord<OrderProperties>
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## Executing Actions
|
|
190
|
-
|
|
191
|
-
Actions are typed methods directly on the record wrapper:
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
const order = await odin.orders.get(orderId);
|
|
195
|
-
|
|
196
|
-
// Actions with required properties
|
|
197
|
-
await order.activateOrder({
|
|
198
|
-
ActiveDate: '2025-01-15',
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Actions with optional properties
|
|
202
|
-
await order.updateOrder({
|
|
203
|
-
Status: 'Active',
|
|
204
|
-
Notes: 'Updated via SDK',
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Actions with journey/stage options
|
|
208
|
-
await order.activateOrder(
|
|
209
|
-
{ ActiveDate: '2025-01-15' },
|
|
210
|
-
{ journeyId: 'journey-uuid', stageKey: 'StageActive' }
|
|
211
|
-
);
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Action Results
|
|
215
|
-
|
|
216
|
-
All actions return `ActionResult[]`:
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
const results = await order.activateOrder({ ActiveDate: '2025-01-15' });
|
|
220
|
-
|
|
221
|
-
for (const result of results) {
|
|
222
|
-
console.log(result.id); // Record ID
|
|
223
|
-
console.log(result.entity); // Entity name
|
|
224
|
-
console.log(result.type); // Action type
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Searching Records
|
|
231
|
-
|
|
232
|
-
ODIN uses a structured query format with `OdinQuery` and `OdinFilter` types.
|
|
233
|
-
|
|
234
|
-
### Query Structure
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
interface OdinFilter {
|
|
238
|
-
columnName: string; // Property name (e.g., 'Status', 'Contractor')
|
|
239
|
-
operator: OdinOperator; // 'eq', 'in', 'not in', 'gte', 'lte', 'gt', 'lt', etc.
|
|
240
|
-
value: any; // Filter value(s)
|
|
241
|
-
// Optional:
|
|
242
|
-
linkedEntity?: string; // For filtering on linked records
|
|
243
|
-
linkedRelationType?: string; // 'child' | 'parent'
|
|
244
|
-
caseSensitive?: boolean; // Default: false
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
interface OdinQuery {
|
|
248
|
-
entity: string; // Use 'infer' to auto-detect from accessor
|
|
249
|
-
type: 'and'; // Currently only 'and' is supported
|
|
250
|
-
value: OdinFilter[]; // Array of filters (AND'd together)
|
|
251
|
-
pageSize: number; // Max 1000 per request
|
|
252
|
-
pageNumber: number; // 0-indexed
|
|
253
|
-
sort?: Record<string, { order: 'asc' | 'desc' }>;
|
|
254
|
-
returnProperties?: string[]; // Fields to return
|
|
255
|
-
searchAfter?: any[]; // For cursor-based pagination
|
|
256
|
-
includeDeleted?: boolean;
|
|
257
|
-
includeArchived?: boolean;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Available operators
|
|
261
|
-
type OdinOperator =
|
|
262
|
-
| 'eq' // Equals
|
|
263
|
-
| 'in' // In array
|
|
264
|
-
| 'not in' // Not in array
|
|
265
|
-
| 'gte' // Greater than or equal
|
|
266
|
-
| 'lte' // Less than or equal
|
|
267
|
-
| 'gt' // Greater than
|
|
268
|
-
| 'lt' // Less than
|
|
269
|
-
| 'isNull' // Is null
|
|
270
|
-
| 'notNull' // Is not null
|
|
271
|
-
| 'anyTerm' // Contains any term (text search)
|
|
272
|
-
| 'allTerm' // Contains all terms (text search)
|
|
273
|
-
| 'phrase' // Exact phrase match
|
|
274
|
-
| 'nested'; // Nested object query
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Basic Search
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
const result = await odin.orders.search({
|
|
281
|
-
query: {
|
|
282
|
-
entity: 'infer',
|
|
283
|
-
type: 'and',
|
|
284
|
-
value: [
|
|
285
|
-
{
|
|
286
|
-
columnName: 'Status',
|
|
287
|
-
operator: 'eq',
|
|
288
|
-
value: 'Active',
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
returnProperties: ['id', 'title', 'type', 'properties.*'],
|
|
292
|
-
pageSize: 100,
|
|
293
|
-
pageNumber: 0,
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
console.log(result.records); // OrderRecord[]
|
|
298
|
-
console.log(result.totalRecords); // number
|
|
299
|
-
console.log(result.hasMore); // boolean
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### Search with Multiple Filters
|
|
303
|
-
|
|
304
|
-
Filters are AND'd together:
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
const result = await odin.orders.search({
|
|
308
|
-
query: {
|
|
309
|
-
entity: 'infer',
|
|
310
|
-
type: 'and',
|
|
311
|
-
value: [
|
|
312
|
-
{
|
|
313
|
-
columnName: 'Status',
|
|
314
|
-
operator: 'in',
|
|
315
|
-
value: ['Active', 'Pending'],
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
columnName: 'ContractType',
|
|
319
|
-
operator: 'eq',
|
|
320
|
-
value: 'Residential',
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
columnName: 'ActiveDate',
|
|
324
|
-
operator: 'gte',
|
|
325
|
-
value: '2025-01-01',
|
|
326
|
-
},
|
|
327
|
-
],
|
|
328
|
-
returnProperties: ['id', 'title', 'type', 'properties.*', 'stage'],
|
|
329
|
-
sort: {
|
|
330
|
-
createdAt: { order: 'desc' },
|
|
331
|
-
},
|
|
332
|
-
pageSize: 100,
|
|
333
|
-
pageNumber: 0,
|
|
334
|
-
},
|
|
335
|
-
});
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Excluding Values (NOT IN)
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
const result = await odin.workOrders.search({
|
|
342
|
-
query: {
|
|
343
|
-
entity: 'infer',
|
|
344
|
-
type: 'and',
|
|
345
|
-
value: [
|
|
346
|
-
{
|
|
347
|
-
columnName: 'EntityType',
|
|
348
|
-
operator: 'not in',
|
|
349
|
-
value: ['COLLECTION', 'DISPATCH'],
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
columnName: 'Contractor',
|
|
353
|
-
operator: 'not in',
|
|
354
|
-
value: ['INTERNAL'],
|
|
355
|
-
},
|
|
356
|
-
],
|
|
357
|
-
pageSize: 1000,
|
|
358
|
-
pageNumber: 0,
|
|
359
|
-
},
|
|
360
|
-
});
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Date Range Queries
|
|
364
|
-
|
|
365
|
-
```typescript
|
|
366
|
-
const result = await odin.visits.search({
|
|
367
|
-
query: {
|
|
368
|
-
entity: 'infer',
|
|
369
|
-
type: 'and',
|
|
370
|
-
value: [
|
|
371
|
-
{
|
|
372
|
-
columnName: 'ScheduledDate',
|
|
373
|
-
operator: 'gte',
|
|
374
|
-
value: '2025-01-01',
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
columnName: 'ScheduledDate',
|
|
378
|
-
operator: 'lte',
|
|
379
|
-
value: '2025-01-31',
|
|
380
|
-
},
|
|
381
|
-
],
|
|
382
|
-
sort: {
|
|
383
|
-
ScheduledDate: { order: 'asc' },
|
|
384
|
-
},
|
|
385
|
-
pageSize: 500,
|
|
386
|
-
pageNumber: 0,
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Text Search
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
// Match any term
|
|
395
|
-
const result = await odin.contacts.search({
|
|
396
|
-
query: {
|
|
397
|
-
entity: 'infer',
|
|
398
|
-
type: 'and',
|
|
399
|
-
value: [
|
|
400
|
-
{
|
|
401
|
-
columnName: 'FirstName',
|
|
402
|
-
operator: 'anyTerm',
|
|
403
|
-
value: 'john smith', // Matches 'john' OR 'smith'
|
|
404
|
-
},
|
|
405
|
-
],
|
|
406
|
-
pageSize: 50,
|
|
407
|
-
pageNumber: 0,
|
|
408
|
-
},
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// Match exact phrase
|
|
412
|
-
const result = await odin.contacts.search({
|
|
413
|
-
query: {
|
|
414
|
-
entity: 'infer',
|
|
415
|
-
type: 'and',
|
|
416
|
-
value: [
|
|
417
|
-
{
|
|
418
|
-
columnName: 'FullName',
|
|
419
|
-
operator: 'phrase',
|
|
420
|
-
value: 'John Smith', // Matches exact phrase
|
|
421
|
-
},
|
|
422
|
-
],
|
|
423
|
-
pageSize: 50,
|
|
424
|
-
pageNumber: 0,
|
|
425
|
-
},
|
|
426
|
-
});
|
|
55
|
+
// Create a new record with links
|
|
56
|
+
const note = odin.notes.new();
|
|
57
|
+
await note
|
|
58
|
+
.link({ id: orderId, entity: "OrderModule:Order" })
|
|
59
|
+
.createNote({ Body: "Customer called about billing" });
|
|
427
60
|
```
|
|
428
61
|
|
|
429
|
-
###
|
|
62
|
+
### DB Client (Backend Services)
|
|
430
63
|
|
|
431
64
|
```typescript
|
|
432
|
-
|
|
433
|
-
query: {
|
|
434
|
-
entity: 'infer',
|
|
435
|
-
type: 'and',
|
|
436
|
-
value: [
|
|
437
|
-
{
|
|
438
|
-
columnName: 'CancelledDate',
|
|
439
|
-
operator: 'isNull',
|
|
440
|
-
value: true,
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
columnName: 'ActiveDate',
|
|
444
|
-
operator: 'notNull',
|
|
445
|
-
value: true,
|
|
446
|
-
},
|
|
447
|
-
],
|
|
448
|
-
pageSize: 100,
|
|
449
|
-
pageNumber: 0,
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
```
|
|
65
|
+
import { OdinDbClient } from '@d19n/youfibre-odin-sdk';
|
|
453
66
|
|
|
454
|
-
|
|
67
|
+
// Initialize with principal and dbService (NestJS)
|
|
68
|
+
const odin = new OdinDbClient(principal, this.dbService);
|
|
455
69
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const result = await odin.orders.search({
|
|
460
|
-
query: {
|
|
461
|
-
entity: 'infer',
|
|
462
|
-
type: 'and',
|
|
463
|
-
value: [
|
|
464
|
-
{ columnName: 'Status', operator: 'eq', value: 'Active' },
|
|
465
|
-
],
|
|
466
|
-
returnProperties: [
|
|
467
|
-
'id',
|
|
468
|
-
'title',
|
|
469
|
-
'type',
|
|
470
|
-
'entity',
|
|
471
|
-
'recordNumber',
|
|
472
|
-
'properties.*', // All properties
|
|
473
|
-
'stage', // Pipeline stage
|
|
474
|
-
'links.*', // All linked records
|
|
475
|
-
],
|
|
476
|
-
pageSize: 100,
|
|
477
|
-
pageNumber: 0,
|
|
478
|
-
},
|
|
479
|
-
});
|
|
70
|
+
// Same API as ApiClient
|
|
71
|
+
const workOrder = await odin.workOrders.get(workOrderId);
|
|
72
|
+
await workOrder.completeWorkOrder({ ... });
|
|
480
73
|
```
|
|
481
74
|
|
|
482
|
-
|
|
75
|
+
## Architecture
|
|
483
76
|
|
|
484
|
-
```typescript
|
|
485
|
-
const result = await odin.orders.search({
|
|
486
|
-
query: {
|
|
487
|
-
entity: 'infer',
|
|
488
|
-
type: 'and',
|
|
489
|
-
value: [
|
|
490
|
-
{ columnName: 'Status', operator: 'eq', value: 'Active' },
|
|
491
|
-
],
|
|
492
|
-
sort: {
|
|
493
|
-
createdAt: { order: 'desc' }, // Primary sort
|
|
494
|
-
'properties.Priority': { order: 'asc' }, // Secondary sort
|
|
495
|
-
},
|
|
496
|
-
pageSize: 100,
|
|
497
|
-
pageNumber: 0,
|
|
498
|
-
},
|
|
499
|
-
});
|
|
500
77
|
```
|
|
78
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
79
|
+
│ ODIN SDK │
|
|
80
|
+
├─────────────────────────────────────────────────────────────┤
|
|
81
|
+
│ │
|
|
82
|
+
│ ┌─────────────────┐ ┌─────────────────┐ │
|
|
83
|
+
│ │ OdinApiClient │ │ OdinDbClient │ │
|
|
84
|
+
│ │ (HTTP/OAuth) │ │ (Direct DB) │ │
|
|
85
|
+
│ └────────┬────────┘ └────────┬────────┘ │
|
|
86
|
+
│ │ │ │
|
|
87
|
+
│ └───────────┬───────────────┘ │
|
|
88
|
+
│ │ │
|
|
89
|
+
│ ┌───────────▼───────────┐ │
|
|
90
|
+
│ │ Entity Accessors │ │
|
|
91
|
+
│ │ (orders, contacts) │ │
|
|
92
|
+
│ └───────────┬───────────┘ │
|
|
93
|
+
│ │ │
|
|
94
|
+
│ ┌───────────▼───────────┐ │
|
|
95
|
+
│ │ Record Wrappers │ │
|
|
96
|
+
│ │ (OrderRecord, etc) │ │
|
|
97
|
+
│ └───────────┬───────────┘ │
|
|
98
|
+
│ │ │
|
|
99
|
+
│ ┌───────────▼───────────┐ │
|
|
100
|
+
│ │ Action Methods │ │
|
|
101
|
+
│ │ (activateOrder...) │ │
|
|
102
|
+
│ └───────────────────────┘ │
|
|
103
|
+
│ │
|
|
104
|
+
└─────────────────────────────────────────────────────────────┘
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Key Components
|
|
108
|
+
|
|
109
|
+
| Component | Purpose |
|
|
110
|
+
|-----------|---------|
|
|
111
|
+
| **OdinApiClient** | HTTP-based client for frontend/external services |
|
|
112
|
+
| **OdinDbClient** | Direct database client for backend services |
|
|
113
|
+
| **Accessors** | Entity-specific access (`.orders`, `.contacts`) |
|
|
114
|
+
| **Record Wrappers** | Typed records with action methods |
|
|
115
|
+
| **Action DTOs** | Type-safe action parameters |
|
|
116
|
+
| **Properties** | Typed entity properties interfaces |
|
|
117
|
+
|
|
118
|
+
## Entities
|
|
119
|
+
|
|
120
|
+
This SDK includes **0** entities across **0** modules.
|
|
121
|
+
|
|
122
|
+
## Actions
|
|
123
|
+
|
|
124
|
+
This SDK includes **0** actions.
|
|
125
|
+
|
|
126
|
+
### Action Types
|
|
127
|
+
|
|
128
|
+
| Type | Description |
|
|
129
|
+
|------|-------------|
|
|
130
|
+
| `CREATE` | Creates a new record (no existing record required) |
|
|
131
|
+
| `UPDATE` | Updates an existing record |
|
|
132
|
+
| `STEP_FLOW` | Multi-step workflow with FlowBuilder pattern |
|
|
133
|
+
|
|
134
|
+
### Actions by Entity
|
|
135
|
+
|
|
136
|
+
## Record Wrappers
|
|
137
|
+
|
|
138
|
+
Record wrappers provide typed access to record data and action methods.
|
|
501
139
|
|
|
502
|
-
###
|
|
503
|
-
|
|
504
|
-
Returns ALL matching records (up to ~1M) with automatic pagination:
|
|
140
|
+
### Basic Usage
|
|
505
141
|
|
|
506
142
|
```typescript
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
entity: 'infer',
|
|
510
|
-
type: 'and',
|
|
511
|
-
value: [
|
|
512
|
-
{
|
|
513
|
-
columnName: 'Status',
|
|
514
|
-
operator: 'in',
|
|
515
|
-
value: ['Active', 'Pending'],
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
columnName: 'UpdatedAt',
|
|
519
|
-
operator: 'gte',
|
|
520
|
-
value: '2025-01-01',
|
|
521
|
-
},
|
|
522
|
-
],
|
|
523
|
-
returnProperties: [
|
|
524
|
-
'id',
|
|
525
|
-
'title',
|
|
526
|
-
'type',
|
|
527
|
-
'entity',
|
|
528
|
-
'schemaId',
|
|
529
|
-
'recordNumber',
|
|
530
|
-
'properties.*',
|
|
531
|
-
'stage',
|
|
532
|
-
],
|
|
533
|
-
sort: {
|
|
534
|
-
createdAt: { order: 'asc' }, // Required for consistent pagination
|
|
535
|
-
},
|
|
536
|
-
pageSize: 1000, // Max per request
|
|
537
|
-
pageNumber: 0,
|
|
538
|
-
},
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
console.log(allOrders.length); // Could be thousands
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### Paginate (Generator)
|
|
545
|
-
|
|
546
|
-
For memory-efficient processing of large datasets:
|
|
143
|
+
// Get existing record
|
|
144
|
+
const order = await odin.orders.get(orderId);
|
|
547
145
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
pageNumber: 0,
|
|
561
|
-
},
|
|
562
|
-
})) {
|
|
563
|
-
// Process batch of 500 records
|
|
564
|
-
for (const order of batch) {
|
|
565
|
-
await processOrder(order);
|
|
566
|
-
}
|
|
146
|
+
// Access typed properties
|
|
147
|
+
console.log(order.id); // string
|
|
148
|
+
console.log(order.properties); // OrderProperties
|
|
149
|
+
console.log(order.raw); // Full OdinRecord
|
|
150
|
+
|
|
151
|
+
// Check if record is new (unsaved)
|
|
152
|
+
if (order.isNew) {
|
|
153
|
+
// Can only call CREATE actions
|
|
154
|
+
await order.createOrder({ ... });
|
|
155
|
+
} else {
|
|
156
|
+
// Can call UPDATE actions
|
|
157
|
+
await order.updateOrder({ ... });
|
|
567
158
|
}
|
|
568
159
|
```
|
|
569
160
|
|
|
570
|
-
###
|
|
571
|
-
|
|
572
|
-
Filter by properties on linked entities:
|
|
573
|
-
|
|
574
|
-
```typescript
|
|
575
|
-
// Find orders linked to a specific account
|
|
576
|
-
const result = await odin.orders.search({
|
|
577
|
-
query: {
|
|
578
|
-
entity: 'infer',
|
|
579
|
-
type: 'and',
|
|
580
|
-
value: [
|
|
581
|
-
{
|
|
582
|
-
columnName: 'Status',
|
|
583
|
-
operator: 'eq',
|
|
584
|
-
value: 'Active',
|
|
585
|
-
},
|
|
586
|
-
{
|
|
587
|
-
columnName: 'AccountNumber',
|
|
588
|
-
operator: 'eq',
|
|
589
|
-
value: 'ACC-12345',
|
|
590
|
-
linkedEntity: 'CrmModule:Account',
|
|
591
|
-
linkedRelationType: 'parent',
|
|
592
|
-
},
|
|
593
|
-
],
|
|
594
|
-
pageSize: 100,
|
|
595
|
-
pageNumber: 0,
|
|
596
|
-
},
|
|
597
|
-
});
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
---
|
|
601
|
-
|
|
602
|
-
## Fluent Query Builder
|
|
603
|
-
|
|
604
|
-
For a cleaner, more readable API, use the fluent query builder:
|
|
605
|
-
|
|
606
|
-
### Basic Usage
|
|
161
|
+
### Link Management
|
|
607
162
|
|
|
608
163
|
```typescript
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
.
|
|
613
|
-
.
|
|
164
|
+
// Queue links to be sent with next action
|
|
165
|
+
const note = odin.notes.new();
|
|
166
|
+
await note
|
|
167
|
+
.link({ id: orderId, entity: "OrderModule:Order" })
|
|
168
|
+
.link({ id: contactId, entity: "CrmModule:Contact" })
|
|
169
|
+
.createNote({ Body: "Note content" });
|
|
614
170
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
console.log(results.hasMore); // boolean
|
|
171
|
+
// Unlink (immediate API call)
|
|
172
|
+
await order.unlink(contactId);
|
|
618
173
|
```
|
|
619
174
|
|
|
620
|
-
###
|
|
621
|
-
|
|
622
|
-
All filters are AND'd together:
|
|
175
|
+
### STEP_FLOW Actions (Multi-Step Workflows)
|
|
623
176
|
|
|
624
|
-
|
|
625
|
-
const results = await odin.orders
|
|
626
|
-
.query()
|
|
627
|
-
.where('Status', 'in', ['Active', 'Pending'])
|
|
628
|
-
.where('ContractType', 'eq', 'Residential')
|
|
629
|
-
.where('ActiveDate', 'gte', '2025-01-01')
|
|
630
|
-
.whereNotIn('CancellationReason', ['Duplicate', 'Test'])
|
|
631
|
-
.select('id', 'title', 'properties.*', 'stage')
|
|
632
|
-
.sortBy('createdAt', 'desc')
|
|
633
|
-
.limit(100)
|
|
634
|
-
.execute();
|
|
635
|
-
```
|
|
177
|
+
STEP_FLOW actions allow executing multiple steps in a single transaction using a FlowBuilder pattern.
|
|
636
178
|
|
|
637
|
-
|
|
179
|
+
#### Basic Usage
|
|
638
180
|
|
|
639
181
|
```typescript
|
|
640
|
-
//
|
|
641
|
-
.
|
|
642
|
-
.whereEq('Status', 'Active') // Shorthand
|
|
643
|
-
|
|
644
|
-
// In / Not In
|
|
645
|
-
.where('Status', 'in', ['Active', 'Pending'])
|
|
646
|
-
.whereIn('Status', ['Active', 'Pending']) // Shorthand
|
|
647
|
-
.whereNotIn('Status', ['Cancelled', 'Draft'])
|
|
648
|
-
|
|
649
|
-
// Comparison
|
|
650
|
-
.where('Amount', 'gte', 100)
|
|
651
|
-
.whereGte('Amount', 100) // Shorthand
|
|
652
|
-
.whereLte('Amount', 1000)
|
|
653
|
-
.whereGt('Amount', 0)
|
|
654
|
-
.whereLt('Amount', 10000)
|
|
655
|
-
|
|
656
|
-
// Date Range
|
|
657
|
-
.whereBetween('CreatedAt', '2025-01-01', '2025-01-31')
|
|
658
|
-
|
|
659
|
-
// Null Checks
|
|
660
|
-
.whereNull('CancelledDate')
|
|
661
|
-
.whereNotNull('ActiveDate')
|
|
662
|
-
|
|
663
|
-
// Text Search
|
|
664
|
-
.whereAnyTerm('Description', 'urgent priority') // OR match
|
|
665
|
-
.whereAllTerms('Description', 'urgent priority') // AND match
|
|
666
|
-
.wherePhrase('Title', 'Service Order') // Exact phrase
|
|
667
|
-
|
|
668
|
-
// Linked Entity
|
|
669
|
-
.whereLinked('AccountNumber', 'eq', 'ACC-123', 'CrmModule:Account', 'parent')
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### Projection & Sorting
|
|
182
|
+
// Get the record
|
|
183
|
+
const cboRec = await odin.contractBuyOuts.get(cbo.id);
|
|
673
184
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
.
|
|
677
|
-
.
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
185
|
+
// Execute a multi-step flow
|
|
186
|
+
await cboRec
|
|
187
|
+
.moveCboToApproved({ journeyId: order.id }) // Start flow (returns FlowBuilder)
|
|
188
|
+
.approveCbo(cbo.id, { // UPDATE step - id required first
|
|
189
|
+
Amount: cbo.properties.Amount,
|
|
190
|
+
FinalBillVerified: cbo.properties.FinalBillVerified,
|
|
191
|
+
PreviousProvider: cbo.properties.PreviousProvider,
|
|
192
|
+
})
|
|
193
|
+
.execute(); // Execute the flow
|
|
682
194
|
```
|
|
683
195
|
|
|
684
|
-
|
|
196
|
+
#### Step Method Signatures
|
|
685
197
|
|
|
686
|
-
|
|
687
|
-
// Limit and page
|
|
688
|
-
const page1 = await odin.orders.query()
|
|
689
|
-
.where('Status', 'eq', 'Active')
|
|
690
|
-
.limit(100)
|
|
691
|
-
.page(0)
|
|
692
|
-
.execute();
|
|
693
|
-
|
|
694
|
-
const page2 = await odin.orders.query()
|
|
695
|
-
.where('Status', 'eq', 'Active')
|
|
696
|
-
.limit(100)
|
|
697
|
-
.page(1)
|
|
698
|
-
.execute();
|
|
699
|
-
|
|
700
|
-
// Using offset
|
|
701
|
-
const results = await odin.orders.query()
|
|
702
|
-
.limit(100)
|
|
703
|
-
.offset(200) // Skip first 200, returns 200-299
|
|
704
|
-
.execute();
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### Convenience Methods
|
|
198
|
+
Step methods have different signatures based on their action type:
|
|
708
199
|
|
|
200
|
+
**UPDATE steps** - Require record `id` as first parameter:
|
|
709
201
|
```typescript
|
|
710
|
-
//
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
.where('Status', 'eq', 'Active')
|
|
714
|
-
.getMany();
|
|
715
|
-
|
|
716
|
-
// Get single record
|
|
717
|
-
const order = await odin.orders
|
|
718
|
-
.query()
|
|
719
|
-
.where('OrderNumber', 'eq', 'ORD-12345')
|
|
720
|
-
.getOne(); // Returns OrderRecord | null
|
|
721
|
-
|
|
722
|
-
// Get single record or throw
|
|
723
|
-
const order = await odin.orders
|
|
724
|
-
.query()
|
|
725
|
-
.where('OrderNumber', 'eq', 'ORD-12345')
|
|
726
|
-
.getOneOrFail(); // Throws if not found
|
|
727
|
-
|
|
728
|
-
// Count matching records
|
|
729
|
-
const count = await odin.orders
|
|
730
|
-
.query()
|
|
731
|
-
.where('Status', 'eq', 'Active')
|
|
732
|
-
.count();
|
|
733
|
-
|
|
734
|
-
// Check if any match
|
|
735
|
-
const hasActive = await odin.orders
|
|
736
|
-
.query()
|
|
737
|
-
.where('Status', 'eq', 'Active')
|
|
738
|
-
.exists();
|
|
202
|
+
// UPDATE step signature: (id, properties, options?)
|
|
203
|
+
.approveCbo(id: string, properties: ApproveCboProperties, options?)
|
|
204
|
+
.updateWorkOrder(id: string, properties: UpdateWorkOrderProperties, options?)
|
|
739
205
|
```
|
|
740
206
|
|
|
741
|
-
|
|
742
|
-
|
|
207
|
+
**CREATE steps** - No `id` parameter (creates new record):
|
|
743
208
|
```typescript
|
|
744
|
-
//
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
.where('Status', 'in', ['Active', 'Pending'])
|
|
748
|
-
.where('UpdatedAt', 'gte', '2025-01-01')
|
|
749
|
-
.select('id', 'title', 'properties.*')
|
|
750
|
-
.sortBy('createdAt', 'asc') // Required for consistent pagination
|
|
751
|
-
.limit(1000)
|
|
752
|
-
.executeAll();
|
|
753
|
-
|
|
754
|
-
console.log(allOrders.length); // Could be thousands
|
|
209
|
+
// CREATE step signature: (properties, options?)
|
|
210
|
+
.createFollowUp(properties: CreateFollowUpProperties, options?)
|
|
211
|
+
.createNote(properties: CreateNoteProperties, options?)
|
|
755
212
|
```
|
|
756
213
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
For processing large datasets without loading all into memory:
|
|
214
|
+
#### Complex Multi-Step Example
|
|
760
215
|
|
|
761
216
|
```typescript
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
.where('Status', 'eq', 'Active')
|
|
765
|
-
.sortBy('createdAt', 'asc')
|
|
766
|
-
.limit(500)
|
|
767
|
-
.paginate()
|
|
768
|
-
) {
|
|
769
|
-
// Process batch of 500 records
|
|
770
|
-
for (const order of batch) {
|
|
771
|
-
await processOrder(order);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
### Include Deleted/Archived
|
|
217
|
+
// Get the work order record
|
|
218
|
+
const workOrderRec = await odin.workOrders.get(workOrderId);
|
|
777
219
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
.
|
|
781
|
-
.
|
|
782
|
-
|
|
783
|
-
|
|
220
|
+
// Flow with both UPDATE and CREATE steps
|
|
221
|
+
await workOrderRec
|
|
222
|
+
.completeWorkOrderFlow({ journeyId: parentOrder.id })
|
|
223
|
+
.updateWorkOrder(workOrderRec.id, { // UPDATE step - id required
|
|
224
|
+
Status: "COMPLETE",
|
|
225
|
+
CompletedAt: new Date().toISOString(),
|
|
226
|
+
})
|
|
227
|
+
.createFollowUp({ // CREATE step - no id
|
|
228
|
+
Type: "QUALITY_CHECK",
|
|
229
|
+
Description: "Post-completion review",
|
|
230
|
+
})
|
|
231
|
+
.addGroup("QUALITY") // Optional: add groups
|
|
784
232
|
.execute();
|
|
785
233
|
```
|
|
786
234
|
|
|
787
|
-
|
|
235
|
+
#### With Associations
|
|
788
236
|
|
|
789
237
|
```typescript
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
.where('ContractType', 'eq', 'Residential')
|
|
793
|
-
.select('id', 'title', 'properties.*')
|
|
794
|
-
.sortBy('createdAt', 'desc');
|
|
795
|
-
|
|
796
|
-
// Clone and add more filters
|
|
797
|
-
const activeOrders = await baseQuery
|
|
798
|
-
.clone()
|
|
799
|
-
.where('Status', 'eq', 'Active')
|
|
800
|
-
.execute();
|
|
801
|
-
|
|
802
|
-
const pendingOrders = await baseQuery
|
|
803
|
-
.clone()
|
|
804
|
-
.where('Status', 'eq', 'Pending')
|
|
805
|
-
.execute();
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### Build Query Without Executing
|
|
238
|
+
// Link records during flow execution
|
|
239
|
+
const invoiceRec = await odin.invoices.get(invoiceId);
|
|
809
240
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
.
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
console.log(query);
|
|
819
|
-
// {
|
|
820
|
-
// entity: 'infer',
|
|
821
|
-
// type: 'and',
|
|
822
|
-
// value: [{ columnName: 'Status', operator: 'eq', value: 'Active' }],
|
|
823
|
-
// pageSize: 100,
|
|
824
|
-
// pageNumber: 0
|
|
825
|
-
// }
|
|
241
|
+
await invoiceRec
|
|
242
|
+
.link({ id: contactId, entity: "CrmModule:Contact" }) // Queue link
|
|
243
|
+
.processInvoiceFlow({ journeyId: orderId })
|
|
244
|
+
.approveInvoice(invoiceRec.id, {
|
|
245
|
+
ApprovedBy: userId,
|
|
246
|
+
ApprovedAt: new Date().toISOString(),
|
|
247
|
+
})
|
|
248
|
+
.execute(); // Links are sent with the action
|
|
826
249
|
```
|
|
827
250
|
|
|
828
|
-
|
|
251
|
+
## Events
|
|
829
252
|
|
|
830
|
-
|
|
253
|
+
Subscribe to RabbitMQ events using the exported routing keys:
|
|
831
254
|
|
|
832
255
|
```typescript
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
.where('Status', 'in', ['Active', 'Pending'])
|
|
836
|
-
.where('UpdatedAt', 'gte', '2025-01-01')
|
|
837
|
-
.select('id', 'title', 'properties.*')
|
|
838
|
-
.sortBy('createdAt', 'asc')
|
|
839
|
-
.limit(100)
|
|
840
|
-
.returnQueryPlan();
|
|
841
|
-
|
|
842
|
-
console.log(plan);
|
|
843
|
-
// Shows the Elasticsearch query structure
|
|
844
|
-
```
|
|
845
|
-
|
|
846
|
-
This is equivalent to:
|
|
256
|
+
import { ROUTING_KEY_ORDER_CREATED, ROUTING_KEY_ORDER_UPDATED } from '@d19n/youfibre-odin-sdk/entities';
|
|
257
|
+
import { ROUTING_KEY_ACTIVATE_ORDER } from '@d19n/youfibre-odin-sdk/actions';
|
|
847
258
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
entity: 'infer',
|
|
853
|
-
type: 'and',
|
|
854
|
-
value: [
|
|
855
|
-
{ columnName: 'Status', operator: 'in', value: ['Active', 'Pending'] },
|
|
856
|
-
{ columnName: 'UpdatedAt', operator: 'gte', value: '2025-01-01' },
|
|
857
|
-
],
|
|
858
|
-
returnProperties: ['id', 'title', 'properties.*'],
|
|
859
|
-
sort: { createdAt: { order: 'asc' } },
|
|
860
|
-
pageSize: 100,
|
|
861
|
-
pageNumber: 0,
|
|
862
|
-
},
|
|
259
|
+
// Entity events
|
|
260
|
+
await channel.consume(ROUTING_KEY_ORDER_CREATED, (msg) => {
|
|
261
|
+
const event: OrderCreatedEvent = JSON.parse(msg.content.toString());
|
|
262
|
+
// Handle event
|
|
863
263
|
});
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
---
|
|
867
|
-
|
|
868
|
-
## Linking Records
|
|
869
|
-
|
|
870
|
-
### Queue Links (Sent with Next Action)
|
|
871
|
-
|
|
872
|
-
```typescript
|
|
873
|
-
const order = await odin.orders.get(orderId);
|
|
874
|
-
const contact = await odin.contacts.get(contactId);
|
|
875
|
-
const address = await odin.addresses.get(addressId);
|
|
876
|
-
|
|
877
|
-
// Queue multiple links
|
|
878
|
-
order
|
|
879
|
-
.link(contact)
|
|
880
|
-
.link(address);
|
|
881
|
-
|
|
882
|
-
// Links are sent with the next action
|
|
883
|
-
await order.updateOrder({ Notes: 'Linked contact and address' });
|
|
884
|
-
|
|
885
|
-
// Check pending links
|
|
886
|
-
console.log(order.pendingLinks); // OdinRecordLinkDto[]
|
|
887
|
-
|
|
888
|
-
// Clear without executing
|
|
889
|
-
order.clearPendingLinks();
|
|
890
|
-
```
|
|
891
|
-
|
|
892
|
-
### Immediate Unlink
|
|
893
264
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
265
|
+
// Action events
|
|
266
|
+
await channel.consume(ROUTING_KEY_ACTIVATE_ORDER, (msg) => {
|
|
267
|
+
const event: ActivateOrderMessage = JSON.parse(msg.content.toString());
|
|
268
|
+
// Handle action completion
|
|
269
|
+
});
|
|
898
270
|
```
|
|
899
271
|
|
|
900
|
-
---
|
|
901
|
-
|
|
902
272
|
## Migration Guide
|
|
903
273
|
|
|
904
|
-
### From Legacy
|
|
905
|
-
|
|
906
|
-
#### Before (Legacy)
|
|
274
|
+
### From Legacy Entity APIs
|
|
907
275
|
|
|
908
276
|
```typescript
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
clientId,
|
|
914
|
-
clientSecret,
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
// Get record
|
|
918
|
-
const order = await api.getByPrimaryKey(orderId);
|
|
919
|
-
|
|
920
|
-
// Execute action
|
|
921
|
-
await api.applyAction({
|
|
922
|
-
id: order.id,
|
|
923
|
-
entity: order.entity,
|
|
924
|
-
type: order.type,
|
|
925
|
-
actionKey: 'ActivateOrder',
|
|
926
|
-
properties: { ActiveDate: '2025-01-15' },
|
|
927
|
-
});
|
|
928
|
-
```
|
|
929
|
-
|
|
930
|
-
#### After (New SDK)
|
|
931
|
-
|
|
932
|
-
```typescript
|
|
933
|
-
import { OdinApiClient } from '@youfibre/odin-sdk';
|
|
934
|
-
|
|
935
|
-
const odin = new OdinApiClient({
|
|
936
|
-
host: 'api.youfibre.com',
|
|
937
|
-
clientId,
|
|
938
|
-
clientSecret,
|
|
939
|
-
});
|
|
277
|
+
// ❌ Old (deprecated)
|
|
278
|
+
const orderApi = new OrderApi(authParams);
|
|
279
|
+
const order = await orderApi.get(orderId);
|
|
280
|
+
await orderApi.applyAction(new ActivateOrderDto(order, { ... }));
|
|
940
281
|
|
|
941
|
-
//
|
|
282
|
+
// ✅ New (recommended)
|
|
942
283
|
const order = await odin.orders.get(orderId);
|
|
943
|
-
|
|
944
|
-
// Execute action (typed!)
|
|
945
|
-
await order.activateOrder({ ActiveDate: '2025-01-15' });
|
|
284
|
+
await order.activateOrder({ ... });
|
|
946
285
|
```
|
|
947
286
|
|
|
948
|
-
###
|
|
949
|
-
|
|
950
|
-
#### Before (Legacy)
|
|
287
|
+
### STEP_FLOW Actions
|
|
951
288
|
|
|
952
289
|
```typescript
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
// Execute action
|
|
961
|
-
await db.applyAction({
|
|
962
|
-
id: order.id,
|
|
963
|
-
entity: order.entity,
|
|
964
|
-
type: order.type,
|
|
965
|
-
actionKey: 'ActivateOrder',
|
|
966
|
-
properties: { ActiveDate: '2025-01-15' },
|
|
290
|
+
// ❌ Old (manual DTO construction)
|
|
291
|
+
const dto = new MoveCboToApprovedDto(record, {
|
|
292
|
+
journeyId: order.id,
|
|
293
|
+
steps: [
|
|
294
|
+
{ actionName: "ApproveCBO", id: cbo.id, properties: { Amount: "100" } }
|
|
295
|
+
]
|
|
967
296
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
#### After (New SDK)
|
|
971
|
-
|
|
972
|
-
```typescript
|
|
973
|
-
import { OdinDbClient } from '@youfibre/odin-sdk';
|
|
974
|
-
|
|
975
|
-
const odin = new OdinDbClient(principal, dbService);
|
|
976
|
-
|
|
977
|
-
// Get record
|
|
978
|
-
const order = await odin.orders.get(orderId);
|
|
979
|
-
|
|
980
|
-
// Execute action (typed!)
|
|
981
|
-
await order.activateOrder({ ActiveDate: '2025-01-15' });
|
|
982
|
-
```
|
|
983
|
-
|
|
984
|
-
### Key Differences
|
|
985
|
-
|
|
986
|
-
| Feature | Legacy SDK | New SDK |
|
|
987
|
-
|---------|-----------|---------|
|
|
988
|
-
| Action execution | `api.applyAction({ actionKey, properties })` | `record.activateOrder(properties)` |
|
|
989
|
-
| Type safety | Partial | Full |
|
|
990
|
-
| Record properties | `record.properties.Status` (any) | `record.properties.Status` (typed) |
|
|
991
|
-
| Action properties | Manual interface lookup | Auto-complete in IDE |
|
|
992
|
-
| Linking | Separate API calls | `record.link(target)` fluent API |
|
|
993
|
-
| Multi-entity client | Import each class separately | Single `OdinApiClient` / `OdinDbClient` |
|
|
994
|
-
|
|
995
|
-
### Migration Steps
|
|
996
|
-
|
|
997
|
-
1. **Update imports:**
|
|
998
|
-
```typescript
|
|
999
|
-
// Before
|
|
1000
|
-
import { OrderApi, ContactApi } from '@youfibre/odin-sdk/api-sdk-v2';
|
|
1001
|
-
|
|
1002
|
-
// After
|
|
1003
|
-
import { OdinApiClient } from '@youfibre/odin-sdk';
|
|
1004
|
-
```
|
|
1005
|
-
|
|
1006
|
-
2. **Replace class instantiation:**
|
|
1007
|
-
```typescript
|
|
1008
|
-
// Before
|
|
1009
|
-
const orderApi = new OrderApi(authParams);
|
|
1010
|
-
const contactApi = new ContactApi(authParams);
|
|
1011
|
-
|
|
1012
|
-
// After
|
|
1013
|
-
const odin = new OdinApiClient(authParams);
|
|
1014
|
-
// Access via odin.orders, odin.contacts
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
3. **Update get calls:**
|
|
1018
|
-
```typescript
|
|
1019
|
-
// Before
|
|
1020
|
-
const order = await orderApi.getByPrimaryKey(id);
|
|
1021
|
-
|
|
1022
|
-
// After
|
|
1023
|
-
const order = await odin.orders.get(id);
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
4. **Update action calls:**
|
|
1027
|
-
```typescript
|
|
1028
|
-
// Before
|
|
1029
|
-
await orderApi.applyAction({
|
|
1030
|
-
id: order.id,
|
|
1031
|
-
entity: order.entity,
|
|
1032
|
-
type: order.type,
|
|
1033
|
-
actionKey: 'ActivateOrder',
|
|
1034
|
-
properties: { ActiveDate: date },
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
// After
|
|
1038
|
-
await order.activateOrder({ ActiveDate: date });
|
|
1039
|
-
```
|
|
1040
|
-
|
|
1041
|
-
5. **Update search calls:**
|
|
1042
|
-
```typescript
|
|
1043
|
-
// Before
|
|
1044
|
-
const result = await orderApi.searchRecords({ query: { ... } });
|
|
1045
|
-
|
|
1046
|
-
// After
|
|
1047
|
-
const result = await odin.orders.search({ query: { ... } });
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
---
|
|
1051
|
-
|
|
1052
|
-
## Architecture
|
|
1053
|
-
|
|
1054
|
-
```
|
|
1055
|
-
@youfibre/odin-sdk/
|
|
1056
|
-
├── OdinApiClient.ts # Unified API client
|
|
1057
|
-
├── OdinDbClient.ts # Unified DB client
|
|
1058
|
-
├── EntityRegistry.ts # Dynamic entity access
|
|
1059
|
-
├── index.ts # Barrel exports
|
|
1060
|
-
│
|
|
1061
|
-
├── accessors-v2/ # API entity accessors
|
|
1062
|
-
│ ├── OrderAccessor.ts
|
|
1063
|
-
│ ├── ContactAccessor.ts
|
|
1064
|
-
│ └── ...
|
|
1065
|
-
│
|
|
1066
|
-
├── db-accessors-v2/ # DB entity accessors
|
|
1067
|
-
│ ├── OrderDbAccessor.ts
|
|
1068
|
-
│ ├── ContactDbAccessor.ts
|
|
1069
|
-
│ └── ...
|
|
1070
|
-
│
|
|
1071
|
-
├── records-v2/ # Typed record wrappers
|
|
1072
|
-
│ ├── OrderRecord.ts
|
|
1073
|
-
│ ├── ContactRecord.ts
|
|
1074
|
-
│ └── ...
|
|
1075
|
-
│
|
|
1076
|
-
├── entities-v2/ # Property interfaces
|
|
1077
|
-
│ ├── Order.ts # OrderProperties
|
|
1078
|
-
│ ├── Contact.ts # ContactProperties
|
|
1079
|
-
│ └── ...
|
|
1080
|
-
│
|
|
1081
|
-
├── actions-v2/ # Action DTOs
|
|
1082
|
-
│ ├── ActivateOrderDto.ts
|
|
1083
|
-
│ ├── UpdateContactDto.ts
|
|
1084
|
-
│ └── ...
|
|
1085
|
-
│
|
|
1086
|
-
└── [deprecated]
|
|
1087
|
-
├── api-sdk-v2/ # Legacy API classes
|
|
1088
|
-
└── db-sdk-v2/ # Legacy DB classes
|
|
1089
|
-
```
|
|
1090
|
-
|
|
1091
|
-
### Dependency Flow
|
|
1092
|
-
|
|
1093
|
-
```
|
|
1094
|
-
OdinApiClient / OdinDbClient
|
|
1095
|
-
│
|
|
1096
|
-
▼
|
|
1097
|
-
*Accessor classes (accessors-v2/, db-accessors-v2/)
|
|
1098
|
-
│
|
|
1099
|
-
▼
|
|
1100
|
-
*Record wrappers (records-v2/)
|
|
1101
|
-
│
|
|
1102
|
-
├──▶ *Properties interfaces (entities-v2/)
|
|
1103
|
-
│
|
|
1104
|
-
└──▶ Action methods → *Action DTOs (actions-v2/)
|
|
1105
|
-
```
|
|
1106
|
-
|
|
1107
|
-
---
|
|
1108
|
-
|
|
1109
|
-
## Advanced Usage
|
|
1110
|
-
|
|
1111
|
-
### Dynamic Entity Access
|
|
1112
|
-
|
|
1113
|
-
```typescript
|
|
1114
|
-
import { EntityRegistry, getAccessor } from '@youfibre/odin-sdk';
|
|
1115
|
-
|
|
1116
|
-
// Get accessor dynamically by entity name
|
|
1117
|
-
const accessor = getAccessor('Order', provider);
|
|
1118
|
-
const order = await accessor.get(orderId);
|
|
1119
|
-
|
|
1120
|
-
// List available entities
|
|
1121
|
-
const entities = Object.keys(EntityRegistry);
|
|
1122
|
-
```
|
|
297
|
+
await api.applyAction(dto);
|
|
1123
298
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const provider = odin.provider;
|
|
1131
|
-
|
|
1132
|
-
// Manual authentication (usually not needed)
|
|
1133
|
-
await odin.authenticate();
|
|
1134
|
-
|
|
1135
|
-
// Get current user
|
|
1136
|
-
const principal = await odin.getPrincipal();
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
### Type Imports
|
|
1140
|
-
|
|
1141
|
-
```typescript
|
|
1142
|
-
// Import property types directly
|
|
1143
|
-
import { OrderProperties } from '@youfibre/odin-sdk/entities-v2/Order';
|
|
1144
|
-
import { ContactProperties } from '@youfibre/odin-sdk/entities-v2/Contact';
|
|
1145
|
-
|
|
1146
|
-
// Import action property types
|
|
1147
|
-
import { ActivateOrderProperties } from '@youfibre/odin-sdk/actions-v2/ActivateOrderDto';
|
|
1148
|
-
|
|
1149
|
-
// Import record types
|
|
1150
|
-
import { OrderRecord } from '@youfibre/odin-sdk/records-v2/OrderRecord';
|
|
1151
|
-
```
|
|
1152
|
-
|
|
1153
|
-
---
|
|
1154
|
-
|
|
1155
|
-
## Troubleshooting
|
|
1156
|
-
|
|
1157
|
-
### "Cannot find module" Errors
|
|
1158
|
-
|
|
1159
|
-
Ensure you're importing from the correct paths:
|
|
1160
|
-
|
|
1161
|
-
```typescript
|
|
1162
|
-
// ✅ Correct
|
|
1163
|
-
import { OdinApiClient } from '@youfibre/odin-sdk';
|
|
1164
|
-
|
|
1165
|
-
// ❌ Wrong
|
|
1166
|
-
import { OdinApiClient } from '@youfibre/odin-sdk/dist/src/OdinApiClient';
|
|
1167
|
-
```
|
|
1168
|
-
|
|
1169
|
-
### Type Errors on Properties
|
|
1170
|
-
|
|
1171
|
-
If TypeScript doesn't recognize properties, ensure your SDK is up to date:
|
|
1172
|
-
|
|
1173
|
-
```bash
|
|
1174
|
-
npm update @youfibre/odin-sdk
|
|
299
|
+
// ✅ New (fluent FlowBuilder)
|
|
300
|
+
const cboRec = await odin.contractBuyOuts.get(cbo.id);
|
|
301
|
+
await cboRec
|
|
302
|
+
.moveCboToApproved({ journeyId: order.id })
|
|
303
|
+
.approveCbo(cbo.id, { Amount: "100" })
|
|
304
|
+
.execute();
|
|
1175
305
|
```
|
|
1176
306
|
|
|
1177
|
-
###
|
|
307
|
+
### Key Changes
|
|
1178
308
|
|
|
1179
|
-
|
|
309
|
+
| Old Pattern | New Pattern |
|
|
310
|
+
|-------------|-------------|
|
|
311
|
+
| `new OrderApi(auth).get(id)` | `odin.orders.get(id)` |
|
|
312
|
+
| `api.applyAction(dto)` | `record.actionName(props)` |
|
|
313
|
+
| `new ActionDto(record, props)` | `record.actionName(props)` |
|
|
314
|
+
| `api.address.list(recordId)` | `record.addresses.list()` |
|
|
315
|
+
| Manual pagination | `odin.orders.paginate(query)` |
|
|
316
|
+
| Manual step flow DTOs | `record.flowAction().step().execute()` |
|
|
1180
317
|
|
|
1181
318
|
---
|
|
1182
319
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
To regenerate the SDK after schema changes:
|
|
1186
|
-
|
|
1187
|
-
```bash
|
|
1188
|
-
cd packages/youfibre-odin-sdk
|
|
1189
|
-
npx ts-node src/runner.ts
|
|
1190
|
-
npm run build
|
|
1191
|
-
```
|
|
1192
|
-
|
|
1193
|
-
---
|
|
1194
|
-
|
|
1195
|
-
## License
|
|
1196
|
-
|
|
1197
|
-
Proprietary - YouFibre Ltd.
|
|
320
|
+
*This documentation was auto-generated from ODIN schema definitions.*
|