@23blocks/angular 6.5.17 → 6.5.18

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,6 +1,6 @@
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)
@@ -15,10 +15,12 @@ npm install @23blocks/angular
15
15
 
16
16
  This package provides Angular-specific bindings for the 23blocks SDK:
17
17
 
18
- - **Injectable Services** - All blocks exposed as Angular services
19
- - **RxJS Observables** - All methods return Observables
20
- - **Token Management** - Automatic token storage and refresh
18
+ - **Injectable Services** - All 18 blocks exposed as Angular services
19
+ - **Typed Delegation** - Services expose block sub-services via typed getters
20
+ - **Promise-based** - All methods return Promises (use `from()` for Observables)
21
+ - **Token Management** - Automatic token storage and refresh for auth flows
21
22
  - **Dependency Injection** - Full DI support with providers
23
+ - **Zero Maintenance** - Getters auto-sync with block API types
22
24
 
23
25
  ## Quick Start
24
26
 
@@ -82,6 +84,7 @@ export class LoginComponent {
82
84
 
83
85
  login() {
84
86
  this.loading = true;
87
+ // Auth-flow methods return Observables with automatic token management
85
88
  this.auth.signIn({ email: this.email, password: this.password })
86
89
  .subscribe({
87
90
  next: (response) => {
@@ -98,6 +101,53 @@ export class LoginComponent {
98
101
  }
99
102
  ```
100
103
 
104
+ ## Service Architecture
105
+
106
+ Services expose block sub-services through typed getters. Each getter returns the block's native service, which provides Promise-based methods.
107
+
108
+ ```typescript
109
+ // All non-auth services use this pattern:
110
+ const products = inject(ProductsService);
111
+
112
+ // Access sub-services via getters
113
+ const list = await products.products.list({ page: 1, perPage: 20 });
114
+ const cart = await products.cart.get(cartId);
115
+ const categories = await products.categories.list();
116
+
117
+ // Or convert to Observables with from()
118
+ from(products.products.list()).subscribe(list => { ... });
119
+
120
+ // Full block access for advanced use cases
121
+ const block = products.productsBlock;
122
+ ```
123
+
124
+ ### AuthenticationService (Hybrid)
125
+
126
+ 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).
127
+
128
+ ```typescript
129
+ const auth = inject(AuthenticationService);
130
+
131
+ // Observable methods (with token management):
132
+ auth.signIn({ email, password }).subscribe(...)
133
+ auth.signUp({ email, password, passwordConfirmation }).subscribe(...)
134
+ auth.signOut().subscribe(...)
135
+ auth.refreshToken({ refreshToken }).subscribe(...)
136
+ auth.facebookLogin({ accessToken }).subscribe(...)
137
+ auth.googleLogin({ accessToken }).subscribe(...)
138
+
139
+ // Promise-based sub-services (via getters):
140
+ const user = await auth.users.get(userId);
141
+ const roles = await auth.roles.list();
142
+ const keys = await auth.apiKeys.list();
143
+ from(auth.mfa.enable(userId)).subscribe(...)
144
+
145
+ // Token management:
146
+ auth.isAuthenticated() // boolean | null
147
+ auth.getAccessToken() // string | null
148
+ auth.clearTokens() // void
149
+ ```
150
+
101
151
  ## Configuration Options
102
152
 
103
153
  ### provideBlocks23 Options
@@ -129,18 +179,7 @@ provideBlocks23({
129
179
  })
130
180
  ```
131
181
 
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)
182
+ ### Cookie Mode
144
183
 
145
184
  ```typescript
146
185
  provideBlocks23({
@@ -180,162 +219,32 @@ export class AppModule {}
180
219
 
181
220
  ## Available Services
182
221
 
183
- | Service | Description |
222
+ | Service | Sub-services |
184
223
  |---------|-------------|
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
224
+ | `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 |
225
+ | `SearchService` | search, history, favorites, entities, identities, jarvis |
226
+ | `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 |
227
+ | `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 |
228
+ | `ContentService` | posts, postVersions, postTemplates, comments, categories, tags, users, moderation, activity, series |
229
+ | `GeolocationService` | locations, addresses, areas, regions, routes, bookings, premises, premiseEvents, routeTracker, locationHours, locationImages, locationSlots, locationTaxes, locationGroups, identities, locationIdentities, geoCountries, geoStates, geoCities |
230
+ | `ConversationsService` | messages, draftMessages, groups, groupInvites, notifications, conversations, websocketTokens, contexts, notificationSettings, availabilities, messageFiles, sources, users, meetings, webNotifications |
231
+ | `FilesService` | storageFiles, entityFiles, fileSchemas, userFiles, fileCategories, fileTags, delegations, fileAccess, fileAccessRequests |
232
+ | `FormsService` | forms, schemas, schemaVersions, instances, sets, landings, subscriptions, appointments, surveys, referrals, mailTemplates, applicationForms, crmSync |
233
+ | `AssetsService` | assets, events, audits, categories, tags, vendors, warehouses, entities, operations, alerts, users, images |
234
+ | `CampaignsService` | campaigns, campaignMedia, landingPages, audiences, landingTemplates, targets, results, markets, locations, templates, mediaResults, media |
235
+ | `CompanyService` | companies, departments, teams, teamMembers, quarters, positions, employeeAssignments |
236
+ | `RewardsService` | rewards, coupons, loyalty, badges, couponConfigurations, offerCodes, expirationRules, customers, badgeCategories, moneyRules, productRules, eventRules |
237
+ | `SalesService` | orders, orderDetails, orderTaxes, payments, subscriptions, subscriptionModels, entities, users, customers, flexibleOrders, stripe, mercadopago, vendorPayments |
238
+ | `WalletService` | wallets, transactions, authorizationCodes, webhooks |
239
+ | `JarvisService` | agents, prompts, workflows, executions, conversations, aiModels, entities, clusters, users, workflowParticipants, workflowSteps, workflowInstances, agentRuntime, mailTemplates, marvinChat, promptComments, executionComments |
240
+ | `OnboardingService` | onboardings, flows, userJourneys, userIdentities, onboard, mailTemplates, remarketing |
241
+ | `UniversityService` | courses, lessons, enrollments, assignments, submissions, subjects, teachers, students, courseGroups, coachingSessions, tests, registrationTokens, placements, calendars, matches, attendance, notes |
242
+
243
+ Each service also exposes a `{serviceName}Block` getter for full block access.
244
+
245
+ ## Usage Examples
246
+
247
+ ### Authentication
339
248
 
340
249
  ```typescript
341
250
  import { Component, inject } from '@angular/core';
@@ -345,14 +254,14 @@ import { AuthenticationService } from '@23blocks/angular';
345
254
  export class AuthComponent {
346
255
  private auth = inject(AuthenticationService);
347
256
 
348
- // Sign in
257
+ // Sign in (Observable with token management)
349
258
  signIn() {
350
- this.auth.signIn({ email, password }).subscribe({
259
+ this.auth.signIn({ email: this.email, password: this.password }).subscribe({
351
260
  next: ({ user, accessToken }) => console.log('Welcome', user.email),
352
261
  });
353
262
  }
354
263
 
355
- // Sign up
264
+ // Sign up (Observable with token management)
356
265
  signUp() {
357
266
  this.auth.signUp({
358
267
  email: 'new@example.com',
@@ -361,7 +270,7 @@ export class AuthComponent {
361
270
  }).subscribe();
362
271
  }
363
272
 
364
- // Sign out
273
+ // Sign out (Observable with token management)
365
274
  signOut() {
366
275
  this.auth.signOut().subscribe();
367
276
  }
@@ -371,19 +280,23 @@ export class AuthComponent {
371
280
  return this.auth.isAuthenticated();
372
281
  }
373
282
 
374
- // Get current user
375
- getCurrentUser() {
376
- return this.auth.getCurrentUser();
283
+ // Access sub-services (Promise-based)
284
+ async loadUser(id: string) {
285
+ return await this.auth.users.get(id);
286
+ }
287
+
288
+ async listRoles() {
289
+ return await this.auth.roles.list();
377
290
  }
378
291
  }
379
292
  ```
380
293
 
381
- ## SearchService Example
294
+ ### Search
382
295
 
383
296
  ```typescript
384
297
  import { Component, inject } from '@angular/core';
385
298
  import { SearchService } from '@23blocks/angular';
386
- import { Subject, debounceTime, switchMap } from 'rxjs';
299
+ import { Subject, debounceTime, switchMap, from } from 'rxjs';
387
300
 
388
301
  @Component({
389
302
  selector: 'app-search',
@@ -395,14 +308,14 @@ import { Subject, debounceTime, switchMap } from 'rxjs';
395
308
  `,
396
309
  })
397
310
  export class SearchComponent {
398
- private search = inject(SearchService);
311
+ private searchSvc = inject(SearchService);
399
312
  private searchSubject = new Subject<string>();
400
313
  results: any[] = [];
401
314
 
402
315
  constructor() {
403
316
  this.searchSubject.pipe(
404
317
  debounceTime(300),
405
- switchMap((query) => this.search.search({ query, limit: 10 }))
318
+ switchMap((query) => from(this.searchSvc.search.search({ query, limit: 10 })))
406
319
  ).subscribe({
407
320
  next: (response) => this.results = response.results,
408
321
  });
@@ -415,28 +328,32 @@ export class SearchComponent {
415
328
  }
416
329
  ```
417
330
 
418
- ## ProductsService Example
331
+ ### Products
419
332
 
420
333
  ```typescript
421
- import { Component, inject, OnInit } from '@angular/core';
334
+ import { Component, inject } from '@angular/core';
422
335
  import { ProductsService } from '@23blocks/angular';
336
+ import { from } from 'rxjs';
423
337
 
424
338
  @Component({ ... })
425
- export class ProductsComponent implements OnInit {
339
+ export class ProductsComponent {
426
340
  private products = inject(ProductsService);
427
- productList$ = this.products.list({ limit: 20 });
428
341
 
429
- addToCart(productId: string) {
430
- this.products.addToCart({ productId, quantity: 1 }).subscribe();
342
+ // Promise-based
343
+ async loadProducts() {
344
+ return await this.products.products.list({ page: 1, perPage: 20 });
431
345
  }
432
- }
433
- ```
434
346
 
435
- ## ContentService Example
347
+ // Observable-based
348
+ products$ = from(this.products.products.list({ page: 1, perPage: 20 }));
436
349
 
437
- The ContentService provides access to posts, comments, categories, tags, and **series** (collections of posts).
350
+ async addToCart(productId: string) {
351
+ await this.products.cart.addItem({ productId, quantity: 1 });
352
+ }
353
+ }
354
+ ```
438
355
 
439
- ### Basic Content Operations
356
+ ### Content
440
357
 
441
358
  ```typescript
442
359
  import { Component, inject } from '@angular/core';
@@ -446,201 +363,85 @@ import { ContentService } from '@23blocks/angular';
446
363
  export class BlogComponent {
447
364
  private content = inject(ContentService);
448
365
 
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
- });
366
+ async loadPosts() {
367
+ return await this.content.posts.list({ page: 1, perPage: 10 });
457
368
  }
458
369
 
459
- // Create a new post
460
- createPost() {
461
- this.content.createPost({
370
+ async createPost() {
371
+ return await this.content.posts.create({
462
372
  title: 'My New Post',
463
373
  body: 'Post content here...',
464
374
  status: 'published',
465
- }).subscribe();
375
+ });
376
+ }
377
+
378
+ async loadSeries() {
379
+ return await this.content.series.list({ page: 1, perPage: 10 });
466
380
  }
467
381
  }
468
382
  ```
469
383
 
470
- ### Series Operations
471
-
472
- Series allow you to group posts into ordered collections (e.g., tutorials, courses, article series).
384
+ ### CRM
473
385
 
474
386
  ```typescript
475
387
  import { Component, inject } from '@angular/core';
476
- import { ContentService } from '@23blocks/angular';
388
+ import { CrmService } from '@23blocks/angular';
477
389
 
478
390
  @Component({ ... })
479
- export class SeriesComponent {
480
- private content = inject(ContentService);
391
+ export class CrmComponent {
392
+ private crm = inject(CrmService);
481
393
 
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
- });
394
+ async loadContacts() {
395
+ return await this.crm.contacts.list({ page: 1, perPage: 20 });
495
396
  }
496
397
 
497
- // Get a single series
498
- loadSeries(uniqueId: string) {
499
- this.content.getSeries(uniqueId).subscribe({
500
- next: (series) => console.log('Series:', series.title),
398
+ async createLead() {
399
+ return await this.crm.leads.create({
400
+ firstName: 'John',
401
+ lastName: 'Doe',
402
+ email: 'john@example.com',
501
403
  });
502
404
  }
503
-
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),
513
- });
514
- }
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
- }
528
- ```
529
-
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
405
  }
595
406
  ```
596
407
 
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
408
  ## RxJS Patterns
613
409
 
614
- ### Combining Multiple Services
410
+ Since sub-services return Promises, use `from()` to work with Observables:
615
411
 
616
412
  ```typescript
617
- import { forkJoin } from 'rxjs';
413
+ import { from, forkJoin } from 'rxjs';
414
+
415
+ // Convert a single Promise
416
+ from(this.products.products.list()).subscribe(list => { ... });
618
417
 
619
- // Fetch user and favorites in parallel
418
+ // Combine multiple calls
620
419
  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
- });
420
+ products: from(this.products.products.list()),
421
+ categories: from(this.products.categories.list()),
422
+ }).subscribe(({ products, categories }) => { ... });
423
+
424
+ // Use with RxJS operators
425
+ from(this.search.search.search({ query })).pipe(
426
+ map(result => result.data),
427
+ catchError(err => of([]))
428
+ ).subscribe(results => { ... });
628
429
  ```
629
430
 
630
431
  ### Caching with shareReplay
631
432
 
632
433
  ```typescript
633
- import { shareReplay } from 'rxjs';
434
+ import { from, shareReplay } from 'rxjs';
634
435
 
635
- // Cache the current user
636
- currentUser$ = this.auth.getCurrentUser().pipe(
436
+ // Cache categories
437
+ categories$ = from(this.products.categories.list()).pipe(
637
438
  shareReplay(1)
638
439
  );
639
440
  ```
640
441
 
641
- ### Error Handling
442
+ ## Error Handling
642
443
 
643
- Every error includes a unique request ID for easy debugging and support:
444
+ Every error includes a unique request ID for debugging:
644
445
 
645
446
  ```typescript
646
447
  import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
@@ -648,7 +449,6 @@ import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
648
449
  this.auth.signIn({ email, password }).subscribe({
649
450
  error: (err) => {
650
451
  if (isBlockErrorException(err)) {
651
- // Request tracing for debugging
652
452
  console.log('Request ID:', err.requestId); // "req_m5abc_xyz123"
653
453
  console.log('Duration:', err.duration); // 145 (ms)
654
454
 
@@ -659,15 +459,9 @@ this.auth.signIn({ email, password }).subscribe({
659
459
  case ErrorCodes.UNAUTHORIZED:
660
460
  this.error = 'Session expired';
661
461
  break;
662
- case ErrorCodes.VALIDATION_ERROR:
663
- this.error = err.message;
664
- break;
665
462
  default:
666
463
  this.error = err.message;
667
464
  }
668
-
669
- // Send request ID to support for debugging
670
- // "Please check request req_m5abc_xyz123"
671
465
  }
672
466
  },
673
467
  });
@@ -716,10 +510,16 @@ import { of } from 'rxjs';
716
510
 
717
511
  describe('LoginComponent', () => {
718
512
  const mockAuth = {
513
+ // Auth-flow methods return Observables
719
514
  signIn: jest.fn().mockReturnValue(of({
720
515
  user: { email: 'test@test.com' },
721
516
  accessToken: 'token',
722
517
  })),
518
+ // Sub-service getters return objects with Promise methods
519
+ users: {
520
+ get: jest.fn().mockResolvedValue({ email: 'test@test.com' }),
521
+ list: jest.fn().mockResolvedValue({ data: [] }),
522
+ },
723
523
  };
724
524
 
725
525
  beforeEach(() => {
@@ -732,6 +532,17 @@ describe('LoginComponent', () => {
732
532
  });
733
533
  ```
734
534
 
535
+ ## TypeScript
536
+
537
+ All services are fully typed. Import types from block packages:
538
+
539
+ ```typescript
540
+ import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
541
+ import type { Product } from '@23blocks/block-products';
542
+ import type { Contact, Lead } from '@23blocks/block-crm';
543
+ import type { Post, Series } from '@23blocks/block-content';
544
+ ```
545
+
735
546
  ## Environment Variables
736
547
 
737
548
  ```typescript
@@ -758,30 +569,6 @@ export const appConfig: ApplicationConfig = {
758
569
  };
759
570
  ```
760
571
 
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
572
  ## Related Packages
786
573
 
787
574
  - [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package