@d19n/youfibre-odin-sdk 1.0.292 → 1.0.294

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 (3) hide show
  1. package/README.md +109 -18
  2. package/dist/runner.js +105 -28
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -52,11 +52,18 @@ await cboRec
52
52
  })
53
53
  .execute();
54
54
 
55
- // Create a new record with links
55
+ // Create a new record with generic links
56
56
  const note = odin.notes.new();
57
57
  await note
58
58
  .link({ id: orderId, entity: "OrderModule:Order" })
59
59
  .createNote({ Body: "Customer called about billing" });
60
+
61
+ // Create a record with typed associations (builder pattern)
62
+ await odin.referrals.new()
63
+ .createOrderReferral({})
64
+ .linkCampaignReferrals(campaign) // Typed association method
65
+ .linkOrderReferral({ id: orderId, entity: "OrderModule:Order" })
66
+ .execute();
60
67
  ```
61
68
 
62
69
  ### DB Client (Backend Services)
@@ -146,7 +153,7 @@ const order = await odin.orders.get(orderId);
146
153
  // Access typed properties
147
154
  console.log(order.id); // string
148
155
  console.log(order.properties); // OrderProperties
149
- console.log(order.raw); // Full OdinRecord
156
+ console.log(order.record); // Full OdinRecord
150
157
 
151
158
  // Check if record is new (unsaved)
152
159
  if (order.isNew) {
@@ -160,6 +167,8 @@ if (order.isNew) {
160
167
 
161
168
  ### Link Management
162
169
 
170
+ #### Generic Links (.link())
171
+
163
172
  ```typescript
164
173
  // Queue links to be sent with next action
165
174
  const note = odin.notes.new();
@@ -172,6 +181,19 @@ await note
172
181
  await order.unlink(contactId);
173
182
  ```
174
183
 
184
+ #### Named Associations (Builder Pattern)
185
+
186
+ Actions with association fields generate typed `.link*()` methods:
187
+
188
+ ```typescript
189
+ // IDE autocomplete shows available association methods
190
+ await odin.referrals.new()
191
+ .createOrderReferral({})
192
+ .linkCampaignReferrals({ id: campaignId, entity: "CrmModule:Campaign" })
193
+ .linkOrderReferral(order) // Pass OdinRecord directly
194
+ .execute();
195
+ ```
196
+
175
197
  ### STEP_FLOW Actions (Multi-Step Workflows)
176
198
 
177
199
  STEP_FLOW actions allow executing multiple steps in a single transaction using a FlowBuilder pattern.
@@ -232,14 +254,14 @@ await workOrderRec
232
254
  .execute();
233
255
  ```
234
256
 
235
- #### With Associations
257
+ #### With Associations (Generic .link())
236
258
 
237
259
  ```typescript
238
- // Link records during flow execution
260
+ // Queue generic links to be sent with next action
239
261
  const invoiceRec = await odin.invoices.get(invoiceId);
240
262
 
241
263
  await invoiceRec
242
- .link({ id: contactId, entity: "CrmModule:Contact" }) // Queue link
264
+ .link({ id: contactId, entity: "CrmModule:Contact" }) // Generic link
243
265
  .processInvoiceFlow({ journeyId: orderId })
244
266
  .approveInvoice(invoiceRec.id, {
245
267
  ApprovedBy: userId,
@@ -248,25 +270,94 @@ await invoiceRec
248
270
  .execute(); // Links are sent with the action
249
271
  ```
250
272
 
273
+ #### With Named Associations (Builder Pattern)
274
+
275
+ Actions with association fields generate typed `.link*()` methods:
276
+
277
+ ```typescript
278
+ // Typed association methods - IDE autocomplete shows available links
279
+ await odin.referrals.new()
280
+ .createOrderReferral({})
281
+ .linkCampaignReferrals({ id: campaignId, entity: "CrmModule:Campaign" })
282
+ .linkOrderReferral(order) // Can pass OdinRecord directly
283
+ .execute();
284
+
285
+ // Mix generic .link() with named associations
286
+ await odin.referrals.new()
287
+ .link({ id: accountId, entity: "CrmModule:Account" }) // Generic link
288
+ .createOrderReferral({})
289
+ .linkCampaignReferrals(campaign) // Named association
290
+ .execute();
291
+
292
+ // Inspect payload before executing
293
+ const builder = odin.referrals.new()
294
+ .createOrderReferral({})
295
+ .linkCampaignReferrals({ id: campaignId, entity: "CrmModule:Campaign" });
296
+
297
+ console.log(builder.getData()); // View what will be sent
298
+ await builder.execute(); // Then execute
299
+ ```
300
+
251
301
  ## Events
252
302
 
253
- Subscribe to RabbitMQ events using the exported routing keys:
303
+ Subscribe to RabbitMQ events using `@RabbitSubscribe` with SDK routing keys:
254
304
 
255
305
  ```typescript
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';
306
+ import {
307
+ WORK_ORDER,
308
+ SUB_DB_RECORD_UPDATED,
309
+ SUB_DB_RECORD_CREATED,
310
+ } from '@d19n/youfibre-odin-sdk/entities';
311
+ import { IDbRecordUpdated, IDbRecordCreated } from '@d19n/odin-types/dist/core';
312
+ import { LogsConstants } from '@d19n/logs/dist/constants';
313
+
314
+ // Subscribe to record updated events
315
+ @RabbitSubscribe({
316
+ exchange: process.env.ODIN_ORG_ID,
317
+ routingKey: `FieldServiceModule.${WORK_ORDER}.${SUB_DB_RECORD_UPDATED}`,
318
+ queue: `${process.env.DOMAIN}.WorkOrderHandler.handleRecordUpdatedEvent`,
319
+ errorHandler: SubscriberErrorHandler.handleError,
320
+ })
321
+ private async handleRecordUpdatedEvent(msg: IDbRecordUpdated) {
322
+ try {
323
+ // Check for stage changes
324
+ if (msg.event === LogsConstants.DB_RECORD_STAGE_UPDATED) {
325
+ const { id, entity, properties, stage } = msg;
326
+ // Handle stage transition
327
+ }
328
+ } catch (e) {
329
+ // Error handling
330
+ }
331
+ }
258
332
 
259
- // Entity events
260
- await channel.consume(ROUTING_KEY_ORDER_CREATED, (msg) => {
261
- const event: OrderCreatedEvent = JSON.parse(msg.content.toString());
262
- // Handle event
263
- });
333
+ // Subscribe to record created events
334
+ @RabbitSubscribe({
335
+ exchange: process.env.ODIN_ORG_ID,
336
+ routingKey: `FieldServiceModule.${WORK_ORDER}.${SUB_DB_RECORD_CREATED}`,
337
+ queue: `${process.env.DOMAIN}.WorkOrderHandler.handleRecordCreatedEvent`,
338
+ errorHandler: SubscriberErrorHandler.handleError,
339
+ })
340
+ private async handleRecordCreatedEvent(msg: IDbRecordCreated) {
341
+ try {
342
+ const { id, entity, properties } = msg;
343
+ // Handle new record
344
+ } catch (e) {
345
+ // Error handling
346
+ }
347
+ }
348
+ ```
264
349
 
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
- });
350
+ ### Common Event Constants
351
+
352
+ ```typescript
353
+ // Record lifecycle events
354
+ SUB_DB_RECORD_CREATED // New record created
355
+ SUB_DB_RECORD_UPDATED // Record updated (includes stage changes)
356
+ SUB_DB_RECORD_DELETED // Record deleted
357
+
358
+ // Check event type in handler
359
+ if (msg.event === LogsConstants.DB_RECORD_STAGE_UPDATED) { ... }
360
+ if (msg.event === LogsConstants.DB_RECORD_UPDATED) { ... }
270
361
  ```
271
362
 
272
363
  ## Migration Guide
package/dist/runner.js CHANGED
@@ -63,6 +63,73 @@ const odin_sdk_generator_1 = require("@d19n/odin-sdk-generator");
63
63
  const dotenv = __importStar(require("dotenv"));
64
64
  const fs = __importStar(require("fs"));
65
65
  const path = __importStar(require("path"));
66
+ /**
67
+ * Custom error class for SDK generation failures
68
+ */
69
+ class SdkGenerationError extends Error {
70
+ constructor(step, reason, missingPaths) {
71
+ super(`SDK Generation failed at step "${step}": ${reason}`);
72
+ this.step = step;
73
+ this.reason = reason;
74
+ this.missingPaths = missingPaths;
75
+ this.name = 'SdkGenerationError';
76
+ }
77
+ }
78
+ /**
79
+ * Verify that a directory exists and contains at least one file
80
+ */
81
+ function verifyDirectoryOutput(dir, stepName, minFiles = 1) {
82
+ if (!fs.existsSync(dir)) {
83
+ throw new SdkGenerationError(stepName, `Output directory does not exist`, [dir]);
84
+ }
85
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.ts'));
86
+ if (files.length < minFiles) {
87
+ throw new SdkGenerationError(stepName, `Output directory has insufficient files (expected at least ${minFiles}, found ${files.length})`, [dir]);
88
+ }
89
+ console.log(` ✓ Verified: ${dir} (${files.length} files)`);
90
+ }
91
+ /**
92
+ * Verify that specific files exist
93
+ */
94
+ function verifyFilesExist(files, stepName) {
95
+ const missing = [];
96
+ for (const file of files) {
97
+ if (!fs.existsSync(file)) {
98
+ missing.push(file);
99
+ }
100
+ }
101
+ if (missing.length > 0) {
102
+ throw new SdkGenerationError(stepName, `Expected output files are missing`, missing);
103
+ }
104
+ for (const file of files) {
105
+ console.log(` ✓ Verified: ${file}`);
106
+ }
107
+ }
108
+ /**
109
+ * Run a generation step with error handling and verification
110
+ */
111
+ function runStep(stepNumber, totalSteps, stepName, generatorFn, verifyFn) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ console.log(`\n[${stepNumber}/${totalSteps}] ${stepName}...`);
114
+ try {
115
+ const result = yield generatorFn();
116
+ // Run verification if provided
117
+ if (verifyFn) {
118
+ verifyFn();
119
+ }
120
+ return result;
121
+ }
122
+ catch (error) {
123
+ // If it's already our error type, rethrow
124
+ if (error instanceof SdkGenerationError) {
125
+ throw error;
126
+ }
127
+ // Wrap other errors
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ throw new SdkGenerationError(stepName, message);
130
+ }
131
+ });
132
+ }
66
133
  function cleanDirectory(dir) {
67
134
  if (fs.existsSync(dir)) {
68
135
  console.log(`Cleaning ${dir}...`);
@@ -77,6 +144,7 @@ const entitiesToExclude = ['Dashboard'];
77
144
  const packageName = '@d19n/youfibre-odin-sdk';
78
145
  function generate() {
79
146
  return __awaiter(this, void 0, void 0, function* () {
147
+ const totalSteps = 11;
80
148
  console.log('='.repeat(60));
81
149
  console.log('ODIN SDK Generation');
82
150
  console.log('='.repeat(60));
@@ -106,61 +174,47 @@ function generate() {
106
174
  // ============================================
107
175
  // STEP 1: Generate Actions (MUST be first)
108
176
  // ============================================
109
- console.log('\n[1/11] Generating actions...');
110
- yield (0, odin_sdk_generator_1.generateActions)();
177
+ yield runStep(1, totalSteps, 'Generating actions', () => (0, odin_sdk_generator_1.generateActions)(), () => verifyDirectoryOutput('./actions-v2', 'Generate Actions'));
111
178
  // ============================================
112
179
  // STEP 2: Generate Entities
113
180
  // ============================================
114
- console.log('\n[2/11] Generating entities...');
115
- yield (0, odin_sdk_generator_1.generateEntities)({ entitiesToExclude });
181
+ yield runStep(2, totalSteps, 'Generating entities', () => (0, odin_sdk_generator_1.generateEntities)({ entitiesToExclude }), () => verifyDirectoryOutput('./entities-v2', 'Generate Entities'));
116
182
  // ============================================
117
183
  // STEP 3: Generate Legacy API SDK (deprecated)
118
184
  // ============================================
119
- console.log('\n[3/11] Generating legacy API SDK (deprecated)...');
120
- yield (0, odin_sdk_generator_1.generateApiSdk)({ entitiesToExclude });
185
+ yield runStep(3, totalSteps, 'Generating legacy API SDK (deprecated)', () => (0, odin_sdk_generator_1.generateApiSdk)({ entitiesToExclude }), () => verifyDirectoryOutput('./api-sdk-v2', 'Generate API SDK'));
121
186
  // ============================================
122
187
  // STEP 4: Generate Legacy DB SDK (deprecated)
123
188
  // ============================================
124
- console.log('\n[4/11] Generating legacy DB SDK (deprecated)...');
125
- yield (0, odin_sdk_generator_1.generateDbSdk)({ entitiesToExclude });
189
+ yield runStep(4, totalSteps, 'Generating legacy DB SDK (deprecated)', () => (0, odin_sdk_generator_1.generateDbSdk)({ entitiesToExclude }), () => verifyDirectoryOutput('./db-sdk-v2', 'Generate DB SDK'));
126
190
  // ============================================
127
191
  // STEP 5: Generate Record Wrappers
128
192
  // ============================================
129
- console.log('\n[5/11] Generating record wrappers...');
130
- yield (0, odin_sdk_generator_1.generateRecordWrappers)({ entitiesToExclude });
193
+ yield runStep(5, totalSteps, 'Generating record wrappers', () => (0, odin_sdk_generator_1.generateRecordWrappers)({ entitiesToExclude }), () => verifyDirectoryOutput('./records-v2', 'Generate Record Wrappers'));
131
194
  // ============================================
132
195
  // STEP 6: Generate API Accessors
133
196
  // ============================================
134
- console.log('\n[6/11] Generating API accessors...');
135
- yield (0, odin_sdk_generator_1.generateEntityAccessors)({ entitiesToExclude });
197
+ yield runStep(6, totalSteps, 'Generating API accessors', () => (0, odin_sdk_generator_1.generateEntityAccessors)({ entitiesToExclude }), () => verifyDirectoryOutput('./accessors-v2', 'Generate API Accessors'));
136
198
  // ============================================
137
199
  // STEP 7: Generate DB Accessors
138
200
  // ============================================
139
- console.log('\n[7/11] Generating DB accessors...');
140
- yield (0, odin_sdk_generator_1.generateDbAccessors)({ entitiesToExclude });
201
+ yield runStep(7, totalSteps, 'Generating DB accessors', () => (0, odin_sdk_generator_1.generateDbAccessors)({ entitiesToExclude }), () => verifyDirectoryOutput('./db-accessors-v2', 'Generate DB Accessors'));
141
202
  // ============================================
142
203
  // STEP 8: Generate Unified Clients
143
204
  // ============================================
144
- console.log('\n[8/11] Generating unified clients...');
145
- yield (0, odin_sdk_generator_1.generateTypedClient)();
205
+ yield runStep(8, totalSteps, 'Generating unified clients', () => (0, odin_sdk_generator_1.generateTypedClient)(), () => verifyFilesExist(['./OdinApiClient.ts', './OdinDbClient.ts'], 'Generate Unified Clients'));
146
206
  // ============================================
147
207
  // STEP 9: Generate Entity Registry
148
208
  // ============================================
149
- console.log('\n[9/11] Generating entity registry...');
150
- yield (0, odin_sdk_generator_1.generateEntityRegistry)();
209
+ yield runStep(9, totalSteps, 'Generating entity registry', () => (0, odin_sdk_generator_1.generateEntityRegistry)(), () => verifyFilesExist(['./EntityRegistry.ts'], 'Generate Entity Registry'));
151
210
  // ============================================
152
211
  // STEP 10: Generate Root Barrel Exports
153
212
  // ============================================
154
- console.log('\n[10/11] Generating root barrel exports...');
155
- yield (0, odin_sdk_generator_1.generateBarrelExports)();
213
+ yield runStep(10, totalSteps, 'Generating root barrel exports', () => (0, odin_sdk_generator_1.generateBarrelExports)(), () => verifyFilesExist(['./index.ts'], 'Generate Barrel Exports'));
156
214
  // ============================================
157
215
  // STEP 11: Generate README Documentation
158
216
  // ============================================
159
- console.log('\n[11/11] Generating README documentation...');
160
- yield (0, odin_sdk_generator_1.generateReadme)({
161
- packageName,
162
- entitiesToExclude,
163
- });
217
+ yield runStep(11, totalSteps, 'Generating README documentation', () => (0, odin_sdk_generator_1.generateReadme)({ packageName, entitiesToExclude }), () => verifyFilesExist(['./README.md'], 'Generate README'));
164
218
  // NOTE: entities-v2 and actions-v2 barrel exports are handled by
165
219
  // steps 1 and 2 (generateActions, generateEntities). They are NOT
166
220
  // re-exported from the root index.ts due to export name conflicts.
@@ -169,7 +223,7 @@ function generate() {
169
223
  // DONE
170
224
  // ============================================
171
225
  console.log('\n' + '='.repeat(60));
172
- console.log('Generation complete!');
226
+ console.log('Generation complete!');
173
227
  console.log('='.repeat(60));
174
228
  console.log(`
175
229
  Generated directories:
@@ -190,5 +244,28 @@ Generated files:
190
244
  `);
191
245
  });
192
246
  }
193
- // Run generation
194
- generate().catch(console.error);
247
+ // Run generation with proper error handling
248
+ generate().catch((error) => {
249
+ var _a;
250
+ console.error('\n' + '='.repeat(60));
251
+ console.error('✗ SDK GENERATION FAILED');
252
+ console.error('='.repeat(60));
253
+ if (error instanceof SdkGenerationError) {
254
+ console.error(`\nStep: ${error.step}`);
255
+ console.error(`Reason: ${error.reason}`);
256
+ if ((_a = error.missingPaths) === null || _a === void 0 ? void 0 : _a.length) {
257
+ console.error('\nMissing paths:');
258
+ for (const p of error.missingPaths) {
259
+ console.error(` - ${p}`);
260
+ }
261
+ }
262
+ }
263
+ else {
264
+ console.error(`\nError: ${error.message || error}`);
265
+ }
266
+ console.error('\n' + '='.repeat(60));
267
+ console.error('Build aborted. No SDK version will be published.');
268
+ console.error('='.repeat(60));
269
+ // Exit with error code to fail CI/CD pipeline
270
+ process.exit(1);
271
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d19n/youfibre-odin-sdk",
3
- "version": "1.0.292",
3
+ "version": "1.0.294",
4
4
  "description": "",
5
5
  "author": "@d19n",
6
6
  "license": "UNLICENSED",
@@ -11,7 +11,7 @@
11
11
  "typescript": "4.8.4"
12
12
  },
13
13
  "dependencies": {
14
- "@d19n/odin-sdk-generator": "^3.0.186",
14
+ "@d19n/odin-sdk-generator": "^3.0.188",
15
15
  "@d19n/odin-types": "^3.0.18",
16
16
  "@d19n/youfibre-odin-sdk": "^1.0.219"
17
17
  },