@nauticana/sail 0.9.0 → 0.9.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nauticana/sail",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Metadata-driven Angular component library for CRUD admin frontends backed by the keel Go backend. Tables, forms, navigation, OTP/2FA/social auth, billing, payout onboarding.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"files": [
|
|
@@ -46,7 +46,7 @@ export class TableLookup extends BaseView implements OnInit {
|
|
|
46
46
|
for (const api in data.Apis) {
|
|
47
47
|
const entry = data.Apis[api];
|
|
48
48
|
if (entry.Table.TableName === tableName) {
|
|
49
|
-
this.apiName.set(
|
|
49
|
+
this.apiName.set(api); // bare RestUri; BackendService.crudUrl resolves the version
|
|
50
50
|
break;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -557,7 +557,7 @@ export abstract class BaseAuthService extends BaseRestService {
|
|
|
557
557
|
if (!page.ItemId) continue;
|
|
558
558
|
const restUri = page.RestUri ?? '';
|
|
559
559
|
const api = apis?.[restUri];
|
|
560
|
-
const apiName =
|
|
560
|
+
const apiName = restUri; // bare RestUri; BackendService.crudUrl resolves the version
|
|
561
561
|
const tableName = api?.Table?.TableName ?? restUri;
|
|
562
562
|
|
|
563
563
|
const overrideComponent = this.getRouteOverride(restUri);
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { HttpErrorResponse, HttpParams } from "@angular/common/http";
|
|
2
|
-
import { Injectable } from "@angular/core";
|
|
3
|
-
import { catchError, map, Observable, throwError } from "rxjs";
|
|
4
|
-
import { PaginatedList } from "../model/appdata";
|
|
2
|
+
import { inject, Injectable } from "@angular/core";
|
|
3
|
+
import { catchError, map, Observable, switchMap, take, throwError } from "rxjs";
|
|
4
|
+
import { ApplicationData, PaginatedList } from "../model/appdata";
|
|
5
5
|
import { RestURL } from "./rest_url";
|
|
6
|
+
import { BaseAuthService } from "./auth.service";
|
|
6
7
|
import { BaseRestService } from "./base_rest.service";
|
|
7
8
|
|
|
8
9
|
@Injectable({providedIn: 'root'})
|
|
9
10
|
export class BackendService extends BaseRestService {
|
|
11
|
+
private readonly auth = inject(BaseAuthService);
|
|
10
12
|
protected readonly prefix = this.url(RestURL.api_prefix);
|
|
11
13
|
|
|
12
14
|
protected handleError(error: HttpErrorResponse) {
|
|
@@ -18,33 +20,25 @@ export class BackendService extends BaseRestService {
|
|
|
18
20
|
return throwError(() => 'error occurred, see console log and try again');
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
/**
|
|
22
|
-
* Build a fully-qualified URL under the configured API base. Use for
|
|
23
|
-
* endpoints that don't fit the metadata-driven `/api/{name}/{op}`
|
|
24
|
-
* shape — e.g. domain-specific actions like `rides/start`. Strips any
|
|
25
|
-
* leading slash on `path` so callers can pass either form.
|
|
26
|
-
*
|
|
27
|
-
* For metadata-driven CRUD use list/get/post/delete instead — they
|
|
28
|
-
* already build the URL.
|
|
29
|
-
*/
|
|
23
|
+
/** Free-form path under the API base — for custom endpoints (e.g. `rides/start`). CRUD methods use `crudUrl` instead. */
|
|
30
24
|
protected apiUrl(path: string): string {
|
|
31
25
|
const trimmed = path.startsWith('/') ? path.slice(1) : path;
|
|
32
26
|
return this.prefix + trimmed;
|
|
33
27
|
}
|
|
34
28
|
|
|
29
|
+
/** CRUD URL versioned from the backend (Apis[apiName].Version); throws rather than assume a version. */
|
|
30
|
+
private crudUrl(apiName: string, action: string): string {
|
|
31
|
+
const version = this.auth.getApiDictionary(apiName)?.Version;
|
|
32
|
+
if (!version) {
|
|
33
|
+
throw new Error(`sail: no backend version for API "${apiName}"; refusing to assume one.`);
|
|
34
|
+
}
|
|
35
|
+
return `${this.prefix}${version}/${apiName}/${action}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
35
38
|
/**
|
|
36
39
|
* Generic typed HTTP request for endpoints that don't fit the
|
|
37
40
|
* list/get/post/delete pattern. Subclasses use this for
|
|
38
|
-
* domain-specific actions
|
|
39
|
-
*
|
|
40
|
-
* class RideService extends BackendService {
|
|
41
|
-
* startRide(req: StartRideReq) {
|
|
42
|
-
* return this.request<RideResponse>('POST', 'rides/start', req);
|
|
43
|
-
* }
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* Errors are routed through handleError so callers see the same
|
|
47
|
-
* "see console log and try again" envelope as CRUD methods.
|
|
41
|
+
* domain-specific actions.
|
|
48
42
|
*/
|
|
49
43
|
protected request<T>(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: unknown): Observable<T> {
|
|
50
44
|
return this.http.request<T>(method, this.apiUrl(path), { body }).pipe(catchError(this.handleError));
|
|
@@ -62,34 +56,45 @@ export class BackendService extends BaseRestService {
|
|
|
62
56
|
return params;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
/** Gates a CRUD call on appdata being loaded — `appData$` is a ReplaySubject(1) so this resolves synchronously once the cache is populated, and blocks the call (instead of throwing) when a component fires CRUD before loadAppData() finishes. */
|
|
60
|
+
private appDataReady(): Observable<ApplicationData> {
|
|
61
|
+
return this.auth.getAppData().pipe(take(1));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Unwraps `{items, limit, offset, total}` to the items array. Pass `_limit` / `_offset` in `filter` to control paging (defaults: 100 / 0). */
|
|
71
65
|
list<T>(apiName: string, filter?: {[key: string]: string}) : Observable<T[]> {
|
|
72
66
|
return this.listPaginated<T>(apiName, filter).pipe(map((page) => page.items));
|
|
73
67
|
}
|
|
74
68
|
|
|
75
|
-
/** Paginated list — returns `{items, limit, offset, total}`. */
|
|
76
69
|
listPaginated<T>(apiName: string, filter?: {[key: string]: string}) : Observable<PaginatedList<T>> {
|
|
77
|
-
return this.
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
return this.appDataReady().pipe(
|
|
71
|
+
switchMap(() => this.http.get<PaginatedList<T>>(
|
|
72
|
+
this.crudUrl(apiName, 'list'),
|
|
73
|
+
{params: this.toParams(filter)},
|
|
74
|
+
)),
|
|
75
|
+
catchError(this.handleError),
|
|
76
|
+
);
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
get<T>(apiName: string, filter?: {[key: string]: string}) : Observable<T> {
|
|
84
|
-
return this.
|
|
80
|
+
return this.appDataReady().pipe(
|
|
81
|
+
switchMap(() => this.http.get<T>(this.crudUrl(apiName, 'get'), {params: this.toParams(filter)})),
|
|
82
|
+
catchError(this.handleError),
|
|
83
|
+
);
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
post<T>(apiName: string, items: T | T[]) : Observable<{message: string}> {
|
|
88
|
-
return this.
|
|
87
|
+
return this.appDataReady().pipe(
|
|
88
|
+
switchMap(() => this.http.post<{message: string}>(this.crudUrl(apiName, 'post'), items)),
|
|
89
|
+
catchError(this.handleError),
|
|
90
|
+
);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
delete(apiName: string, filter?: {[key: string]: string}): Observable<{message: string}> {
|
|
92
|
-
return this.
|
|
94
|
+
return this.appDataReady().pipe(
|
|
95
|
+
switchMap(() => this.http.delete<{message: string}>(this.crudUrl(apiName, 'delete'), {params: this.toParams(filter)})),
|
|
96
|
+
catchError(this.handleError),
|
|
97
|
+
);
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
/**
|