Skip to content

Commit 88cebc8

Browse files
committed
RestProvider initial
1 parent 1509c92 commit 88cebc8

File tree

16 files changed

+836
-0
lines changed

16 files changed

+836
-0
lines changed

@types/core/di/ng-module/ng-module.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export class NgModule {
5050
runBlocks: Array<ng.Injectable<any>>;
5151
services: any[];
5252
wasmModules: any[];
53+
restDefinitions: any[];
5354
/**
5455
* @param {string} name
5556
* @param {any} object
@@ -200,4 +201,19 @@ export class NgModule {
200201
ctor: Function,
201202
backendOrConfig: ng.StorageBackend,
202203
): NgModule;
204+
/**
205+
* @template T, ID
206+
* Register a REST resource during module configuration.
207+
* @param {string} name - Service name
208+
* @param {string} url - Base URL or URI template
209+
* @param {ng.EntityClass<T>} entityClass - Optional constructor for mapping JSON
210+
* @param {Object=} options - Optional RestService options (interceptors, etc)
211+
* @returns {NgModule}
212+
*/
213+
rest<T, ID>(
214+
name: string,
215+
url: string,
216+
entityClass: ng.EntityClass<T>,
217+
options?: any | undefined,
218+
): NgModule;
203219
}

@types/namespace.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ import {
7171
CookieStoreOptions as TCookieStoreOptions,
7272
CookieOptions as TCookieOptions,
7373
} from "./services/cookie/interface.ts";
74+
import {
75+
RestDefinition as TRestDefinition,
76+
EntityClass as TEntityClass,
77+
} from "./services/rest/interface.ts";
78+
import { RestService as TRestService } from "./services/rest/rest.js";
7479
declare global {
7580
interface Function {
7681
$inject?: readonly string[] | undefined;
@@ -140,5 +145,8 @@ declare global {
140145
type StreamConnectionConfig = TStreamConnectionConfig;
141146
type CookieOptions = TCookieOptions;
142147
type CookieStoreOptions = TCookieStoreOptions;
148+
type RestService<T, ID> = TRestService<T, ID>;
149+
type RestDefinition<T> = TRestDefinition<T>;
150+
type EntityClass<T> = TEntityClass<T>;
143151
}
144152
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export interface RestDefinition<T = any> {
2+
name: string;
3+
url: string;
4+
/** Constructor for mapping JSON to class instance */
5+
entityClass?: EntityClass<T>;
6+
options?: Record<string, any>;
7+
}
8+
/**
9+
* A constructor type for mapping JSON objects to entity instances
10+
*/
11+
export interface EntityClass<T = any> {
12+
/**
13+
* Creates a new instance of the entity from a raw object
14+
* @param data - Raw data (typically JSON) to map
15+
*/
16+
new (data: any): T;
17+
}

@types/services/rest/rest.d.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @template T, ID
3+
*/
4+
export class RestService<T, ID> {
5+
/**
6+
* @param {ng.HttpService} $http Angular-like $http service
7+
* @param {string} baseUrl Base URL template, e.g. "/users/:id{/subId}{?page,limit}"
8+
* @param {{new(data: any): T}=} entityClass Optional constructor for mapping JSON to class instances
9+
* @param {Object=} providerDefaults Optional defaults from RestProvider
10+
*/
11+
constructor(
12+
$http: ng.HttpService,
13+
baseUrl: string,
14+
entityClass?:
15+
| {
16+
new (data: any): T;
17+
}
18+
| undefined,
19+
providerDefaults?: any | undefined,
20+
);
21+
/** @private @type {ng.HttpService} */
22+
private $http;
23+
/** @private @type {string} */
24+
private baseUrl;
25+
/** @private @type {{new(data: any): T}=} */
26+
private entityClass;
27+
/** @type {Object} global defaults from provider */
28+
providerDefaults: any;
29+
/** @type {Array<(config: any) => any | Promise<any>>} */
30+
requestInterceptors: Array<(config: any) => any | Promise<any>>;
31+
/** @type {Array<(response: any) => any | Promise<any>>} */
32+
responseInterceptors: Array<(response: any) => any | Promise<any>>;
33+
/**
34+
* Apply all request interceptors sequentially
35+
* @private
36+
*/
37+
private _applyRequestInterceptors;
38+
/**
39+
* Apply all response interceptors sequentially
40+
* @private
41+
*/
42+
private _applyResponseInterceptors;
43+
/**
44+
* @private
45+
*/
46+
private _request;
47+
/** @private map raw data to entity class */
48+
private mapEntity;
49+
/**
50+
* @private
51+
* Build URL by replacing colon-style params first, then expanding RFC 6570 template
52+
* @param {string} template
53+
* @param {Record<string, any>} [params]
54+
* @returns {string}
55+
*/
56+
private buildUrl;
57+
/** List entities (optional query params) */
58+
list(params?: {}): Promise<any>;
59+
/** Read entity by ID (ID can be in colon or RFC template) */
60+
read(id: any, params?: {}): Promise<any>;
61+
create(item: any, params?: {}): Promise<any>;
62+
update(id: any, item: any, params?: {}): Promise<any>;
63+
delete(id: any, params?: {}): Promise<boolean>;
64+
}
65+
/**
66+
* RestProvider - register named rest stores at config time.
67+
*
68+
* Usage (in config):
69+
* restProvider.rest('user', '/api/users', User);
70+
*
71+
* Then at runtime you can inject `rest` factory and do:
72+
* const userApi = rest('/api/users', User);
73+
* or use the pre-registered named services:
74+
* const userApi = rest.get('user');
75+
*/
76+
export class RestProvider {
77+
/** @private @type {import('./interface.ts').RestDefinition[]} */
78+
private definitions;
79+
/** provider-level defaults (optional) */
80+
defaults: {};
81+
/**
82+
* Register a named rest definition during configtime
83+
* @template T
84+
* @param {string} name
85+
* @param {string} url
86+
* @param {{new(data:any):T}=} entityClass
87+
*/
88+
rest<T>(
89+
name: string,
90+
url: string,
91+
entityClass?:
92+
| {
93+
new (data: any): T;
94+
}
95+
| undefined,
96+
): void;
97+
/**
98+
* $get factory: returns a `rest` factory function and allows access
99+
* to pre-registered services via rest.get(name).
100+
*
101+
* @returns {(baseUrl:string, entityClass?:Function, options?:object) => RestService}
102+
*/
103+
$get: (
104+
| string
105+
| (($http: any) => {
106+
(baseUrl: any, entityClass: any, options: any): RestService<any, any>;
107+
get(name: any): any;
108+
listNames(): any[];
109+
})
110+
)[];
111+
}

@types/services/rest/rfc.d.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* RFC 6570 Level 4 URI Template expander
3+
*
4+
* Supports operators: (none), +, #, ., /, ;, ?, &
5+
* Supports varspec modifiers: explode (*) and prefix (:len)
6+
*
7+
* Usage:
8+
* expandUriTemplate("/users/{id}", { id: 10 }) === "/users/10"
9+
* expandUriTemplate("/search{?q,lang}", { q: "a b", lang: "en" }) === "/search?q=a%20b&lang=en"
10+
* expandUriTemplate("/repos/{owner}/{repo}/issues{?labels*}", { labels: ["bug","ui"] }) === "/repos/x/y/issues?labels=bug&labels=ui"
11+
*
12+
* @param {string} template
13+
* @param {Object<string, any>} vars
14+
* @returns {string}
15+
*/
16+
export function expandUriTemplate(
17+
template: string,
18+
vars?: {
19+
[x: string]: any;
20+
},
21+
): string;
22+
/**
23+
* Helper: percent-encode a string. If allowReserved true, reserved chars are NOT encoded.
24+
* @param {string} str
25+
* @param {boolean} allowReserved
26+
* @returns {string}
27+
*/
28+
export function pctEncode(str: string, allowReserved: boolean): string;
29+
/**
30+
* Parse and expand a single expression (content between { and }).
31+
* @param {string} expression
32+
* @param {Object<string, any>} vars
33+
* @returns {string}
34+
*/
35+
export function expandExpression(
36+
expression: string,
37+
vars: {
38+
[x: string]: any;
39+
},
40+
): string;

src/core/di/ng-module/ng-module.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export class NgModule {
6868
this.services = [];
6969

7070
this.wasmModules = [];
71+
72+
this.restDefinitions = [];
7173
}
7274

7375
/**
@@ -357,4 +359,35 @@ export class NgModule {
357359
]);
358360
return this;
359361
}
362+
363+
/**
364+
* @template T, ID
365+
* Register a REST resource during module configuration.
366+
* @param {string} name - Service name
367+
* @param {string} url - Base URL or URI template
368+
* @param {ng.EntityClass<T>} entityClass - Optional constructor for mapping JSON
369+
* @param {Object=} options - Optional RestService options (interceptors, etc)
370+
* @returns {NgModule}
371+
*/
372+
rest(name, url, entityClass, options = {}) {
373+
const def = { name, url, entityClass, options };
374+
this.restDefinitions.push(def);
375+
376+
// push provider/factory to invokeQueue
377+
this.invokeQueue.push([
378+
$t.$provide,
379+
"factory",
380+
[
381+
name,
382+
[
383+
$t.$rest,
384+
/** @param {(baseUrl:string, entityClass?:Function, options?:object) => ng.RestService<T, ID>} $rest */ (
385+
$rest,
386+
) => $rest(url, entityClass, options),
387+
],
388+
],
389+
]);
390+
391+
return this;
392+
}
360393
}

src/injection-tokens.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const $injectTokens = Object.freeze({
4545
$log: "$log",
4646
$viewScroll: "$viewScroll",
4747
$parse: "$parse",
48+
$rest: "$rest",
4849
$rootScope: "$rootScope",
4950
$rootElement: "$rootElement",
5051
$router: "$router",

src/namespace.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ import {
7575
CookieStoreOptions as TCookieStoreOptions,
7676
CookieOptions as TCookieOptions,
7777
} from "./services/cookie/interface.ts";
78+
import {
79+
RestDefinition as TRestDefinition,
80+
EntityClass as TEntityClass,
81+
} from "./services/rest/interface.ts";
82+
import { RestService as TRestService } from "./services/rest/rest.js";
7883

7984
/* ────────────────────────────────────────────────
8085
Runtime global initialization
@@ -162,5 +167,8 @@ declare global {
162167
export type StreamConnectionConfig = TStreamConnectionConfig;
163168
export type CookieOptions = TCookieOptions;
164169
export type CookieStoreOptions = TCookieStoreOptions;
170+
export type RestService<T, ID> = TRestService<T, ID>;
171+
export type RestDefinition<T> = TRestDefinition<T>;
172+
export type EntityClass<T> = TEntityClass<T>;
165173
}
166174
}

src/ng.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ import { ngWorkerDirective } from "./directive/worker/worker.js";
137137
import { ngWasmDirective } from "./directive/wasm/wasm.js";
138138
import { ngScopeDirective } from "./directive/scope/scope.js";
139139
import { CookieProvider } from "./services/cookie/cookie.js";
140+
import { RestProvider } from "./services/rest/rest.js";
140141

141142
/**
142143
* Initializes core `ng` module.
@@ -271,6 +272,7 @@ export function registerNgModule(angular) {
271272
$log: LogProvider,
272273
$parse: ParseProvider,
273274
$$rAFScheduler: RafSchedulerProvider,
275+
$rest: RestProvider,
274276
$rootScope: RootScopeProvider,
275277
$router: Router,
276278
$sce: SceProvider,

src/services/rest/interface.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export interface RestDefinition<T = any> {
2+
name: string;
3+
url: string;
4+
/** Constructor for mapping JSON to class instance */
5+
entityClass?: EntityClass<T>;
6+
options?: Record<string, any>;
7+
}
8+
9+
/**
10+
* A constructor type for mapping JSON objects to entity instances
11+
*/
12+
export interface EntityClass<T = any> {
13+
/**
14+
* Creates a new instance of the entity from a raw object
15+
* @param data - Raw data (typically JSON) to map
16+
*/
17+
new (data: any): T;
18+
}

0 commit comments

Comments
 (0)