@23blocks/angular 6.5.17 → 7.0.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.
package/dist/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @23blocks/angular
2
2
 
3
- Angular bindings for the 23blocks SDK - Injectable services with RxJS Observables.
3
+ Angular bindings for the 23blocks SDK - Injectable services with typed delegation to block APIs.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@23blocks/angular.svg)](https://www.npmjs.com/package/@23blocks/angular)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
+ > **v7.0.0** - Complete rewrite from Observable wrappers to typed getter delegation. See [CHANGELOG.md](./CHANGELOG.md) for migration guide.
9
+
8
10
  ## Installation
9
11
 
10
12
  ```bash
@@ -15,10 +17,12 @@ npm install @23blocks/angular
15
17
 
16
18
  This package provides Angular-specific bindings for the 23blocks SDK:
17
19
 
18
- - **Injectable Services** - All blocks exposed as Angular services
19
- - **RxJS Observables** - All methods return Observables
20
- - **Token Management** - Automatic token storage and refresh
20
+ - **Injectable Services** - All 18 blocks exposed as Angular services
21
+ - **Typed Delegation** - Services expose block sub-services via typed getters
22
+ - **Promise-based** - All methods return Promises (use `from()` for Observables)
23
+ - **Token Management** - Automatic token storage and refresh for auth flows
21
24
  - **Dependency Injection** - Full DI support with providers
25
+ - **Zero Maintenance** - Getters auto-sync with block API types
22
26
 
23
27
  ## Quick Start
24
28
 
@@ -82,6 +86,7 @@ export class LoginComponent {
82
86
 
83
87
  login() {
84
88
  this.loading = true;
89
+ // Auth-flow methods return Observables with automatic token management
85
90
  this.auth.signIn({ email: this.email, password: this.password })
86
91
  .subscribe({
87
92
  next: (response) => {
@@ -98,6 +103,53 @@ export class LoginComponent {
98
103
  }
99
104
  ```
100
105
 
106
+ ## Service Architecture
107
+
108
+ Services expose block sub-services through typed getters. Each getter returns the block's native service, which provides Promise-based methods.
109
+
110
+ ```typescript
111
+ // All non-auth services use this pattern:
112
+ const products = inject(ProductsService);
113
+
114
+ // Access sub-services via getters
115
+ const list = await products.products.list({ page: 1, perPage: 20 });
116
+ const cart = await products.cart.get(cartId);
117
+ const categories = await products.categories.list();
118
+
119
+ // Or convert to Observables with from()
120
+ from(products.products.list()).subscribe(list => { ... });
121
+
122
+ // Full block access for advanced use cases
123
+ const block = products.productsBlock;
124
+ ```
125
+
126
+ ### AuthenticationService (Hybrid)
127
+
128
+ AuthenticationService is special: auth-flow methods that manage tokens return **Observables** with automatic `tap()` for token storage. All other sub-services are delegated via getters (Promise-based).
129
+
130
+ ```typescript
131
+ const auth = inject(AuthenticationService);
132
+
133
+ // Observable methods (with token management):
134
+ auth.signIn({ email, password }).subscribe(...)
135
+ auth.signUp({ email, password, passwordConfirmation }).subscribe(...)
136
+ auth.signOut().subscribe(...)
137
+ auth.refreshToken({ refreshToken }).subscribe(...)
138
+ auth.facebookLogin({ accessToken }).subscribe(...)
139
+ auth.googleLogin({ accessToken }).subscribe(...)
140
+
141
+ // Promise-based sub-services (via getters):
142
+ const user = await auth.users.get(userId);
143
+ const roles = await auth.roles.list();
144
+ const keys = await auth.apiKeys.list();
145
+ from(auth.mfa.enable(userId)).subscribe(...)
146
+
147
+ // Token management:
148
+ auth.isAuthenticated() // boolean | null
149
+ auth.getAccessToken() // string | null
150
+ auth.clearTokens() // void
151
+ ```
152
+
101
153
  ## Configuration Options
102
154
 
103
155
  ### provideBlocks23 Options
@@ -129,18 +181,7 @@ provideBlocks23({
129
181
  })
130
182
  ```
131
183
 
132
- ### Token Mode (Default)
133
-
134
- ```typescript
135
- provideBlocks23({
136
- apiKey: 'your-api-key',
137
- urls: { authentication: 'https://auth.yourapp.com' },
138
- authMode: 'token', // default
139
- storage: 'localStorage', // default
140
- })
141
- ```
142
-
143
- ### Cookie Mode (Recommended for Security)
184
+ ### Cookie Mode
144
185
 
145
186
  ```typescript
146
187
  provideBlocks23({
@@ -180,162 +221,32 @@ export class AppModule {}
180
221
 
181
222
  ## Available Services
182
223
 
183
- | Service | Description |
224
+ | Service | Sub-services |
184
225
  |---------|-------------|
185
- | `AuthenticationService` | Sign in, sign up, password reset, MFA |
186
- | `SearchService` | Full-text search, favorites |
187
- | `ProductsService` | Products, categories, variants, cart |
188
- | `CrmService` | Contacts, accounts, leads, opportunities |
189
- | `ContentService` | Posts, comments, categories, tags |
190
- | `GeolocationService` | Addresses, locations, areas |
191
- | `ConversationsService` | Messages, groups, notifications |
192
- | `FilesService` | File uploads, storage |
193
- | `FormsService` | Form builder, submissions |
194
- | `AssetsService` | Asset management, tracking |
195
- | `CampaignsService` | Marketing campaigns, audiences |
196
- | `CompanyService` | Company settings, departments, teams |
197
- | `RewardsService` | Rewards, coupons, loyalty, badges |
198
- | `SalesService` | Orders, payments, subscriptions |
199
- | `WalletService` | Digital wallet, transactions |
200
- | `JarvisService` | AI assistant, prompts, workflows |
201
- | `OnboardingService` | User onboarding flows |
202
- | `UniversityService` | Courses, lessons, enrollments |
203
-
204
- ## Authentication Examples
205
-
206
- ### Sign In
207
-
208
- ```typescript
209
- import { Component, inject } from '@angular/core';
210
- import { AuthenticationService } from '@23blocks/angular';
211
-
212
- @Component({ ... })
213
- export class LoginComponent {
214
- private auth = inject(AuthenticationService);
215
-
216
- email = '';
217
- password = '';
218
-
219
- signIn() {
220
- // Required: email, password
221
- this.auth.signIn({ email: this.email, password: this.password }).subscribe({
222
- next: ({ user, accessToken, refreshToken, expiresIn }) => {
223
- console.log('Welcome', user.email);
224
- // In token mode, tokens are automatically stored
225
- },
226
- error: (err) => {
227
- console.error('Login failed:', err.message);
228
- },
229
- });
230
- }
231
- }
232
- ```
233
-
234
- ### Sign Up (Registration)
235
-
236
- ```typescript
237
- @Component({ ... })
238
- export class RegisterComponent {
239
- private auth = inject(AuthenticationService);
240
-
241
- // Sign up with required fields only
242
- signUp() {
243
- this.auth.signUp({
244
- email: 'new@example.com', // Required
245
- password: 'password', // Required
246
- passwordConfirmation: 'password', // Required - must match password
247
- }).subscribe({
248
- next: ({ user, accessToken, message }) => {
249
- // accessToken may be undefined if email confirmation is enabled
250
- if (accessToken) {
251
- console.log('Logged in as', user.email);
252
- } else {
253
- console.log(message); // "Confirmation email sent"
254
- }
255
- },
256
- });
257
- }
258
-
259
- // Sign up with all optional fields
260
- signUpFull() {
261
- this.auth.signUp({
262
- // Required
263
- email: 'new@example.com',
264
- password: 'securePassword123',
265
- passwordConfirmation: 'securePassword123',
266
-
267
- // Optional
268
- name: 'John Doe',
269
- username: 'johndoe',
270
- roleId: 'role-uuid',
271
- confirmSuccessUrl: 'https://yourapp.com/confirmed', // Redirect after email confirmation
272
- timeZone: 'America/New_York',
273
- preferredLanguage: 'en',
274
- payload: { referralCode: 'ABC123' },
275
- subscription: 'premium-plan',
276
- }).subscribe();
277
- }
278
- }
279
- ```
280
-
281
- ### Sign Out
282
-
283
- ```typescript
284
- signOut() {
285
- this.auth.signOut().subscribe({
286
- next: () => {
287
- console.log('Signed out');
288
- // Tokens are automatically cleared
289
- },
290
- });
291
- }
292
- ```
293
-
294
- ### Email Confirmation
295
-
296
- ```typescript
297
- // Confirm email with token from URL
298
- confirmEmail(token: string) {
299
- this.auth.confirmEmail(token).subscribe({
300
- next: (user) => {
301
- console.log('Email confirmed for', user.email);
302
- },
303
- });
304
- }
305
-
306
- // Resend confirmation email
307
- resendConfirmation(email: string) {
308
- this.auth.resendConfirmation({
309
- email,
310
- confirmSuccessUrl: 'https://yourapp.com/confirmed', // Optional
311
- }).subscribe({
312
- next: () => {
313
- console.log('Confirmation email sent');
314
- },
315
- });
316
- }
317
- ```
318
-
319
- ### Get Current User
320
-
321
- ```typescript
322
- // Returns user with role, avatar, and profile included
323
- getCurrentUser() {
324
- return this.auth.getCurrentUser();
325
- }
326
- ```
327
-
328
- ### Check Authentication
329
-
330
- ```typescript
331
- // Token mode: returns true/false
332
- // Cookie mode: returns null (use validateToken instead)
333
- isAuthenticated(): boolean | null {
334
- return this.auth.isAuthenticated();
335
- }
336
- ```
337
-
338
- ### Full AuthenticationService Example
226
+ | `AuthenticationService` | auth, users, roles, permissions, apiKeys, mfa, oauth, avatars, tenants, apps, blocks, services, subscriptionModels, userSubscriptions, companySubscriptions, countries, states, counties, cities, currencies, guests, magicLinks, refreshTokens, userDevices, tenantUsers, mailTemplates, jwks, adminRsaKeys, oidc |
227
+ | `SearchService` | search, history, favorites, entities, identities, jarvis |
228
+ | `ProductsService` | products, cart, cartDetails, categories, brands, vendors, warehouses, channels, collections, productSets, shoppingLists, promotions, prices, filters, images, variations, reviews, variationReviews, stock, suggestions, addons, myCarts, remarketing, visitors, productVendors |
229
+ | `CrmService` | accounts, contacts, contactEvents, leads, leadFollows, opportunities, meetings, meetingParticipants, meetingBillings, quotes, subscribers, referrals, touches, categories, calendarAccounts, busyBlocks, icsTokens, zoomMeetings, zoomHosts, mailTemplates, communications, users, billingReports, calendarSync |
230
+ | `ContentService` | posts, postVersions, postTemplates, comments, categories, tags, users, moderation, activity, series |
231
+ | `GeolocationService` | locations, addresses, areas, regions, routes, bookings, premises, premiseEvents, routeTracker, locationHours, locationImages, locationSlots, locationTaxes, locationGroups, identities, locationIdentities, geoCountries, geoStates, geoCities |
232
+ | `ConversationsService` | messages, draftMessages, groups, groupInvites, notifications, conversations, websocketTokens, contexts, notificationSettings, availabilities, messageFiles, sources, users, meetings, webNotifications |
233
+ | `FilesService` | storageFiles, entityFiles, fileSchemas, userFiles, fileCategories, fileTags, delegations, fileAccess, fileAccessRequests |
234
+ | `FormsService` | forms, schemas, schemaVersions, instances, sets, landings, subscriptions, appointments, surveys, referrals, mailTemplates, applicationForms, crmSync |
235
+ | `AssetsService` | assets, events, audits, categories, tags, vendors, warehouses, entities, operations, alerts, users, images |
236
+ | `CampaignsService` | campaigns, campaignMedia, landingPages, audiences, landingTemplates, targets, results, markets, locations, templates, mediaResults, media |
237
+ | `CompanyService` | companies, departments, teams, teamMembers, quarters, positions, employeeAssignments |
238
+ | `RewardsService` | rewards, coupons, loyalty, badges, couponConfigurations, offerCodes, expirationRules, customers, badgeCategories, moneyRules, productRules, eventRules |
239
+ | `SalesService` | orders, orderDetails, orderTaxes, payments, subscriptions, subscriptionModels, entities, users, customers, flexibleOrders, stripe, mercadopago, vendorPayments |
240
+ | `WalletService` | wallets, transactions, authorizationCodes, webhooks |
241
+ | `JarvisService` | agents, prompts, workflows, executions, conversations, aiModels, entities, clusters, users, workflowParticipants, workflowSteps, workflowInstances, agentRuntime, mailTemplates, marvinChat, promptComments, executionComments |
242
+ | `OnboardingService` | onboardings, flows, userJourneys, userIdentities, onboard, mailTemplates, remarketing |
243
+ | `UniversityService` | courses, lessons, enrollments, assignments, submissions, subjects, teachers, students, courseGroups, coachingSessions, tests, registrationTokens, placements, calendars, matches, attendance, notes |
244
+
245
+ Each service also exposes a `{serviceName}Block` getter for full block access.
246
+
247
+ ## Usage Examples
248
+
249
+ ### Authentication
339
250
 
340
251
  ```typescript
341
252
  import { Component, inject } from '@angular/core';
@@ -345,14 +256,14 @@ import { AuthenticationService } from '@23blocks/angular';
345
256
  export class AuthComponent {
346
257
  private auth = inject(AuthenticationService);
347
258
 
348
- // Sign in
259
+ // Sign in (Observable with token management)
349
260
  signIn() {
350
- this.auth.signIn({ email, password }).subscribe({
261
+ this.auth.signIn({ email: this.email, password: this.password }).subscribe({
351
262
  next: ({ user, accessToken }) => console.log('Welcome', user.email),
352
263
  });
353
264
  }
354
265
 
355
- // Sign up
266
+ // Sign up (Observable with token management)
356
267
  signUp() {
357
268
  this.auth.signUp({
358
269
  email: 'new@example.com',
@@ -361,7 +272,7 @@ export class AuthComponent {
361
272
  }).subscribe();
362
273
  }
363
274
 
364
- // Sign out
275
+ // Sign out (Observable with token management)
365
276
  signOut() {
366
277
  this.auth.signOut().subscribe();
367
278
  }
@@ -371,19 +282,23 @@ export class AuthComponent {
371
282
  return this.auth.isAuthenticated();
372
283
  }
373
284
 
374
- // Get current user
375
- getCurrentUser() {
376
- return this.auth.getCurrentUser();
285
+ // Access sub-services (Promise-based)
286
+ async loadUser(id: string) {
287
+ return await this.auth.users.get(id);
288
+ }
289
+
290
+ async listRoles() {
291
+ return await this.auth.roles.list();
377
292
  }
378
293
  }
379
294
  ```
380
295
 
381
- ## SearchService Example
296
+ ### Search
382
297
 
383
298
  ```typescript
384
299
  import { Component, inject } from '@angular/core';
385
300
  import { SearchService } from '@23blocks/angular';
386
- import { Subject, debounceTime, switchMap } from 'rxjs';
301
+ import { Subject, debounceTime, switchMap, from } from 'rxjs';
387
302
 
388
303
  @Component({
389
304
  selector: 'app-search',
@@ -395,14 +310,14 @@ import { Subject, debounceTime, switchMap } from 'rxjs';
395
310
  `,
396
311
  })
397
312
  export class SearchComponent {
398
- private search = inject(SearchService);
313
+ private searchSvc = inject(SearchService);
399
314
  private searchSubject = new Subject<string>();
400
315
  results: any[] = [];
401
316
 
402
317
  constructor() {
403
318
  this.searchSubject.pipe(
404
319
  debounceTime(300),
405
- switchMap((query) => this.search.search({ query, limit: 10 }))
320
+ switchMap((query) => from(this.searchSvc.search.search({ query, limit: 10 })))
406
321
  ).subscribe({
407
322
  next: (response) => this.results = response.results,
408
323
  });
@@ -415,28 +330,32 @@ export class SearchComponent {
415
330
  }
416
331
  ```
417
332
 
418
- ## ProductsService Example
333
+ ### Products
419
334
 
420
335
  ```typescript
421
- import { Component, inject, OnInit } from '@angular/core';
336
+ import { Component, inject } from '@angular/core';
422
337
  import { ProductsService } from '@23blocks/angular';
338
+ import { from } from 'rxjs';
423
339
 
424
340
  @Component({ ... })
425
- export class ProductsComponent implements OnInit {
341
+ export class ProductsComponent {
426
342
  private products = inject(ProductsService);
427
- productList$ = this.products.list({ limit: 20 });
428
343
 
429
- addToCart(productId: string) {
430
- this.products.addToCart({ productId, quantity: 1 }).subscribe();
344
+ // Promise-based
345
+ async loadProducts() {
346
+ return await this.products.products.list({ page: 1, perPage: 20 });
431
347
  }
432
- }
433
- ```
434
348
 
435
- ## ContentService Example
349
+ // Observable-based
350
+ products$ = from(this.products.products.list({ page: 1, perPage: 20 }));
436
351
 
437
- The ContentService provides access to posts, comments, categories, tags, and **series** (collections of posts).
352
+ async addToCart(productId: string) {
353
+ await this.products.cart.addItem({ productId, quantity: 1 });
354
+ }
355
+ }
356
+ ```
438
357
 
439
- ### Basic Content Operations
358
+ ### Content
440
359
 
441
360
  ```typescript
442
361
  import { Component, inject } from '@angular/core';
@@ -446,201 +365,85 @@ import { ContentService } from '@23blocks/angular';
446
365
  export class BlogComponent {
447
366
  private content = inject(ContentService);
448
367
 
449
- // List posts with pagination
450
- posts$ = this.content.listPosts({ page: 1, perPage: 10 });
451
-
452
- // Get a single post
453
- loadPost(uniqueId: string) {
454
- this.content.getPost(uniqueId).subscribe({
455
- next: (post) => console.log('Post:', post.title),
456
- });
368
+ async loadPosts() {
369
+ return await this.content.posts.list({ page: 1, perPage: 10 });
457
370
  }
458
371
 
459
- // Create a new post
460
- createPost() {
461
- this.content.createPost({
372
+ async createPost() {
373
+ return await this.content.posts.create({
462
374
  title: 'My New Post',
463
375
  body: 'Post content here...',
464
376
  status: 'published',
465
- }).subscribe();
377
+ });
378
+ }
379
+
380
+ async loadSeries() {
381
+ return await this.content.series.list({ page: 1, perPage: 10 });
466
382
  }
467
383
  }
468
384
  ```
469
385
 
470
- ### Series Operations
471
-
472
- Series allow you to group posts into ordered collections (e.g., tutorials, courses, article series).
386
+ ### CRM
473
387
 
474
388
  ```typescript
475
389
  import { Component, inject } from '@angular/core';
476
- import { ContentService } from '@23blocks/angular';
390
+ import { CrmService } from '@23blocks/angular';
477
391
 
478
392
  @Component({ ... })
479
- export class SeriesComponent {
480
- private content = inject(ContentService);
481
-
482
- // List all series
483
- series$ = this.content.listSeries({ page: 1, perPage: 10 });
484
-
485
- // Query series with filters
486
- loadPublicSeries() {
487
- this.content.querySeries({
488
- visibility: 'public',
489
- completionStatus: 'ongoing',
490
- page: 1,
491
- perPage: 20,
492
- }).subscribe({
493
- next: (result) => console.log('Series:', result.data),
494
- });
495
- }
393
+ export class CrmComponent {
394
+ private crm = inject(CrmService);
496
395
 
497
- // Get a single series
498
- loadSeries(uniqueId: string) {
499
- this.content.getSeries(uniqueId).subscribe({
500
- next: (series) => console.log('Series:', series.title),
501
- });
396
+ async loadContacts() {
397
+ return await this.crm.contacts.list({ page: 1, perPage: 20 });
502
398
  }
503
399
 
504
- // Create a new series
505
- createSeries() {
506
- this.content.createSeries({
507
- title: 'TypeScript Fundamentals',
508
- description: 'A complete guide to TypeScript',
509
- visibility: 'public',
510
- completionStatus: 'ongoing',
511
- }).subscribe({
512
- next: (series) => console.log('Created:', series.uniqueId),
400
+ async createLead() {
401
+ return await this.crm.leads.create({
402
+ firstName: 'John',
403
+ lastName: 'Doe',
404
+ email: 'john@example.com',
513
405
  });
514
406
  }
515
-
516
- // Update a series
517
- updateSeries(uniqueId: string) {
518
- this.content.updateSeries(uniqueId, {
519
- completionStatus: 'completed',
520
- }).subscribe();
521
- }
522
-
523
- // Delete a series
524
- deleteSeries(uniqueId: string) {
525
- this.content.deleteSeries(uniqueId).subscribe();
526
- }
527
407
  }
528
408
  ```
529
409
 
530
- ### Series Social Actions
531
-
532
- ```typescript
533
- // Like/dislike a series
534
- likeSeries(uniqueId: string) {
535
- this.content.likeSeries(uniqueId).subscribe({
536
- next: (series) => console.log('Likes:', series.likes),
537
- });
538
- }
539
-
540
- dislikeSeries(uniqueId: string) {
541
- this.content.dislikeSeries(uniqueId).subscribe();
542
- }
543
-
544
- // Follow/unfollow a series
545
- followSeries(uniqueId: string) {
546
- this.content.followSeries(uniqueId).subscribe({
547
- next: (series) => console.log('Followers:', series.followers),
548
- });
549
- }
550
-
551
- unfollowSeries(uniqueId: string) {
552
- this.content.unfollowSeries(uniqueId).subscribe();
553
- }
554
-
555
- // Save/unsave a series (bookmarking)
556
- saveSeries(uniqueId: string) {
557
- this.content.saveSeries(uniqueId).subscribe();
558
- }
559
-
560
- unsaveSeries(uniqueId: string) {
561
- this.content.unsaveSeries(uniqueId).subscribe();
562
- }
563
- ```
564
-
565
- ### Series Post Management
566
-
567
- ```typescript
568
- // Get posts in a series (ordered)
569
- loadSeriesPosts(seriesUniqueId: string) {
570
- this.content.getSeriesPosts(seriesUniqueId).subscribe({
571
- next: (posts) => console.log('Posts in series:', posts.length),
572
- });
573
- }
574
-
575
- // Add a post to a series with optional sequence
576
- addPostToSeries(seriesUniqueId: string, postUniqueId: string) {
577
- this.content.addSeriesPost(seriesUniqueId, postUniqueId, 1).subscribe();
578
- }
579
-
580
- // Remove a post from a series
581
- removePostFromSeries(seriesUniqueId: string, postUniqueId: string) {
582
- this.content.removeSeriesPost(seriesUniqueId, postUniqueId).subscribe();
583
- }
584
-
585
- // Reorder posts in a series
586
- reorderPosts(seriesUniqueId: string) {
587
- this.content.reorderSeriesPosts(seriesUniqueId, {
588
- posts: [
589
- { postUniqueId: 'post-1', sequence: 1 },
590
- { postUniqueId: 'post-2', sequence: 2 },
591
- { postUniqueId: 'post-3', sequence: 3 },
592
- ],
593
- }).subscribe();
594
- }
595
- ```
596
-
597
- ### Series Types
598
-
599
- ```typescript
600
- import type {
601
- Series,
602
- CreateSeriesRequest,
603
- UpdateSeriesRequest,
604
- ListSeriesParams,
605
- QuerySeriesParams,
606
- ReorderPostsRequest,
607
- SeriesVisibility, // 'public' | 'private' | 'unlisted'
608
- SeriesCompletionStatus // 'ongoing' | 'completed' | 'hiatus' | 'cancelled'
609
- } from '@23blocks/block-content';
610
- ```
611
-
612
410
  ## RxJS Patterns
613
411
 
614
- ### Combining Multiple Services
412
+ Since sub-services return Promises, use `from()` to work with Observables:
615
413
 
616
414
  ```typescript
617
- import { forkJoin } from 'rxjs';
415
+ import { from, forkJoin } from 'rxjs';
416
+
417
+ // Convert a single Promise
418
+ from(this.products.products.list()).subscribe(list => { ... });
618
419
 
619
- // Fetch user and favorites in parallel
420
+ // Combine multiple calls
620
421
  forkJoin({
621
- user: this.auth.getCurrentUser(),
622
- favorites: this.search.listFavorites({ limit: 10 }),
623
- }).subscribe({
624
- next: ({ user, favorites }) => {
625
- console.log(user, favorites);
626
- },
627
- });
422
+ products: from(this.products.products.list()),
423
+ categories: from(this.products.categories.list()),
424
+ }).subscribe(({ products, categories }) => { ... });
425
+
426
+ // Use with RxJS operators
427
+ from(this.search.search.search({ query })).pipe(
428
+ map(result => result.data),
429
+ catchError(err => of([]))
430
+ ).subscribe(results => { ... });
628
431
  ```
629
432
 
630
433
  ### Caching with shareReplay
631
434
 
632
435
  ```typescript
633
- import { shareReplay } from 'rxjs';
436
+ import { from, shareReplay } from 'rxjs';
634
437
 
635
- // Cache the current user
636
- currentUser$ = this.auth.getCurrentUser().pipe(
438
+ // Cache categories
439
+ categories$ = from(this.products.categories.list()).pipe(
637
440
  shareReplay(1)
638
441
  );
639
442
  ```
640
443
 
641
- ### Error Handling
444
+ ## Error Handling
642
445
 
643
- Every error includes a unique request ID for easy debugging and support:
446
+ Every error includes a unique request ID for debugging:
644
447
 
645
448
  ```typescript
646
449
  import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
@@ -648,7 +451,6 @@ import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
648
451
  this.auth.signIn({ email, password }).subscribe({
649
452
  error: (err) => {
650
453
  if (isBlockErrorException(err)) {
651
- // Request tracing for debugging
652
454
  console.log('Request ID:', err.requestId); // "req_m5abc_xyz123"
653
455
  console.log('Duration:', err.duration); // 145 (ms)
654
456
 
@@ -659,15 +461,9 @@ this.auth.signIn({ email, password }).subscribe({
659
461
  case ErrorCodes.UNAUTHORIZED:
660
462
  this.error = 'Session expired';
661
463
  break;
662
- case ErrorCodes.VALIDATION_ERROR:
663
- this.error = err.message;
664
- break;
665
464
  default:
666
465
  this.error = err.message;
667
466
  }
668
-
669
- // Send request ID to support for debugging
670
- // "Please check request req_m5abc_xyz123"
671
467
  }
672
468
  },
673
469
  });
@@ -716,10 +512,16 @@ import { of } from 'rxjs';
716
512
 
717
513
  describe('LoginComponent', () => {
718
514
  const mockAuth = {
515
+ // Auth-flow methods return Observables
719
516
  signIn: jest.fn().mockReturnValue(of({
720
517
  user: { email: 'test@test.com' },
721
518
  accessToken: 'token',
722
519
  })),
520
+ // Sub-service getters return objects with Promise methods
521
+ users: {
522
+ get: jest.fn().mockResolvedValue({ email: 'test@test.com' }),
523
+ list: jest.fn().mockResolvedValue({ data: [] }),
524
+ },
723
525
  };
724
526
 
725
527
  beforeEach(() => {
@@ -732,6 +534,17 @@ describe('LoginComponent', () => {
732
534
  });
733
535
  ```
734
536
 
537
+ ## TypeScript
538
+
539
+ All services are fully typed. Import types from block packages:
540
+
541
+ ```typescript
542
+ import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
543
+ import type { Product } from '@23blocks/block-products';
544
+ import type { Contact, Lead } from '@23blocks/block-crm';
545
+ import type { Post, Series } from '@23blocks/block-content';
546
+ ```
547
+
735
548
  ## Environment Variables
736
549
 
737
550
  ```typescript
@@ -758,30 +571,6 @@ export const appConfig: ApplicationConfig = {
758
571
  };
759
572
  ```
760
573
 
761
- ## TypeScript
762
-
763
- All services are fully typed:
764
-
765
- ```typescript
766
- import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
767
-
768
- // SignInResponse
769
- interface SignInResponse {
770
- user: User;
771
- accessToken: string;
772
- refreshToken?: string;
773
- tokenType: string;
774
- expiresIn?: number;
775
- }
776
-
777
- // SignUpResponse - accessToken is optional (email confirmation)
778
- interface SignUpResponse {
779
- user: User;
780
- accessToken?: string; // undefined if email confirmation enabled
781
- message?: string;
782
- }
783
- ```
784
-
785
574
  ## Related Packages
786
575
 
787
576
  - [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package