oip-common 0.0.2 → 0.0.4

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/ng-package.json CHANGED
@@ -4,5 +4,16 @@
4
4
  "lib": {
5
5
  "entryFile": "src/public-api.ts"
6
6
  },
7
- "assets": [{ "input": "src/assets", "glob": "**/*", "output": "assets" }]
7
+ "assets": [
8
+ {
9
+ "input": "./src/assets",
10
+ "glob": "**/*",
11
+ "output": "assets"
12
+ },
13
+ {
14
+ "input": "./templates",
15
+ "glob": "**/*",
16
+ "output": "templates"
17
+ }
18
+ ]
8
19
  }
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
- {
2
- "name": "oip-common",
3
- "version": "0.0.2",
4
- "description": "A template for cross-platform web applications based on sakai-ng and primeNG",
5
- "main": "index.js",
6
- "scripts": {
7
- "pub": "npm publish"
8
- },
9
- "keywords": ["oip", "template"],
10
- "author": "Igor Tyulyakov aka g101k",
11
- "license": "MIT",
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/g10101k/Oip"
15
- }
16
- }
1
+ {
2
+ "name": "oip-common",
3
+ "version": "0.0.4",
4
+ "description": "A template for cross-platform web applications based on sakai-ng and primeNG",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "pub": "npm publish"
8
+ },
9
+ "keywords": [
10
+ "oip",
11
+ "template"
12
+ ],
13
+ "author": "Igor Tyulyakov aka g101k",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/g10101k/Oip.git"
18
+ }
19
+ }
@@ -11,8 +11,8 @@
11
11
  */
12
12
 
13
13
  import { inject, Injectable } from "@angular/core";
14
- import { LayoutService, SecurityService } from "oip-common";
15
-
14
+ import { LayoutService, } from "../services/app.layout.service"
15
+ import { SecurityService } from "../services/security.service";
16
16
  export type QueryParamsType = Record<string | number, any>;
17
17
  export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
18
18
 
@@ -64,7 +64,7 @@ export enum ContentType {
64
64
  Text = "text/plain",
65
65
  }
66
66
 
67
- @Injectable({ providedIn: "root" })
67
+ @Injectable({providedIn: "root"})
68
68
  export class HttpClient<SecurityDataType = unknown> {
69
69
  protected securityService = inject(SecurityService);
70
70
  protected layoutService = inject(LayoutService);
@@ -202,16 +202,16 @@ export class HttpClient<SecurityDataType = unknown> {
202
202
  };
203
203
 
204
204
  public request = async <T = any, E = any>({
205
- body,
206
- secure,
207
- path,
208
- type,
209
- query,
210
- format,
211
- baseUrl,
212
- cancelToken,
213
- ...params
214
- }: FullRequestParams): Promise<T> => {
205
+ body,
206
+ secure,
207
+ path,
208
+ type,
209
+ query,
210
+ format,
211
+ baseUrl,
212
+ cancelToken,
213
+ ...params
214
+ }: FullRequestParams): Promise<T> => {
215
215
  const secureParams =
216
216
  ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
217
217
  this.securityWorker &&
@@ -229,7 +229,7 @@ export class HttpClient<SecurityDataType = unknown> {
229
229
  headers: {
230
230
  ...(requestParams.headers || {}),
231
231
  ...(type && type !== ContentType.FormData
232
- ? { "Content-Type": type }
232
+ ? {"Content-Type": type}
233
233
  : {}),
234
234
  },
235
235
  signal:
@@ -252,18 +252,18 @@ export class HttpClient<SecurityDataType = unknown> {
252
252
  const data = !responseFormat
253
253
  ? r
254
254
  : await response[responseFormat]()
255
- .then((data) => {
256
- if (r.ok) {
257
- r.data = data;
258
- } else {
259
- r.error = data;
260
- }
261
- return r;
262
- })
263
- .catch((e) => {
264
- r.error = e;
265
- return r;
266
- });
255
+ .then((data) => {
256
+ if (r.ok) {
257
+ r.data = data;
258
+ } else {
259
+ r.error = data;
260
+ }
261
+ return r;
262
+ })
263
+ .catch((e) => {
264
+ r.error = e;
265
+ return r;
266
+ });
267
267
 
268
268
  if (cancelToken) {
269
269
  this.abortControllers.delete(cancelToken);
@@ -1,5 +1,4 @@
1
1
  import { Component, inject, OnDestroy, OnInit } from '@angular/core';
2
- import { BaseModuleComponent, NoSettingsDto, SecurityComponent } from 'oip-common';
3
2
  import { TagModule } from 'primeng/tag';
4
3
  import { ConfirmationService, SharedModule } from 'primeng/api';
5
4
  import { TableModule } from 'primeng/table';
@@ -11,6 +10,9 @@ import { ConfirmDialog } from 'primeng/confirmdialog';
11
10
  import { NgIf } from '@angular/common';
12
11
  import { Tooltip } from 'primeng/tooltip';
13
12
  import { ActivatedRoute } from '@angular/router';
13
+ import { BaseModuleComponent } from "../base-module.component";
14
+ import { NoSettingsDto } from "../../dtos/no-settings.dto";
15
+ import { SecurityComponent } from "../security.component";
14
16
 
15
17
  export interface MigrationDto {
16
18
  name: string;
@@ -1,5 +1,4 @@
1
1
  import { Component, OnDestroy, OnInit } from '@angular/core';
2
- import { BaseModuleComponent, NoSettingsDto, SecurityComponent } from 'oip-common';
3
2
  import { TagModule } from 'primeng/tag';
4
3
  import { ConfirmationService, SharedModule } from 'primeng/api';
5
4
  import { TableModule } from 'primeng/table';
@@ -10,6 +9,9 @@ import { FormsModule } from '@angular/forms';
10
9
  import { ConfirmDialog } from 'primeng/confirmdialog';
11
10
  import { NgIf } from '@angular/common';
12
11
  import { Tooltip } from 'primeng/tooltip';
12
+ import { BaseModuleComponent } from "./base-module.component";
13
+ import { NoSettingsDto } from "../dtos/no-settings.dto";
14
+ import { SecurityComponent } from "./security.component";
13
15
 
14
16
  export interface MigrationDto {
15
17
  name: string;
@@ -39,7 +41,7 @@ export interface ApplyMigrationRequest {
39
41
  selector: 'db-migration',
40
42
  template: `
41
43
  <div *ngIf="isContent" class="card" style="height: 100%">
42
- <p-confirmDialog />
44
+ <p-confirmDialog/>
43
45
  <div>
44
46
  <h5>Migration manager</h5>
45
47
  <div class="flex flex-row gap-2">
@@ -49,14 +51,14 @@ export interface ApplyMigrationRequest {
49
51
  severity="secondary"
50
52
  tooltipPosition="bottom"
51
53
  [outlined]="true"
52
- (click)="refreshAction()" />
54
+ (click)="refreshAction()"/>
53
55
  <p-button
54
56
  icon="pi pi-filter-slash"
55
57
  pTooltip="Clean filter"
56
58
  severity="secondary"
57
59
  tooltipPosition="bottom"
58
60
  [outlined]="true"
59
- (click)="dt.clear()" />
61
+ (click)="dt.clear()"/>
60
62
  </div>
61
63
  <div>
62
64
  <p-table #dt dataKey="name" editMode="row" size="small" [scrollable]="true" [value]="data">
@@ -64,7 +66,7 @@ export interface ApplyMigrationRequest {
64
66
  <tr>
65
67
  <th pSortableColumn="name" scope="col">
66
68
  Migration name
67
- <p-columnFilter display="menu" field="name" type="text" />
69
+ <p-columnFilter display="menu" field="name" type="text"/>
68
70
  </th>
69
71
  <th scope="col">Applied</th>
70
72
  <th scope="col">Exist</th>
@@ -89,7 +91,7 @@ export interface ApplyMigrationRequest {
89
91
  </td>
90
92
  <td>
91
93
  @if (rowData.exist) {
92
- <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true" />
94
+ <p-button icon="pi pi-check" severity="success" [rounded]="true" [text]="true"/>
93
95
  }
94
96
  </td>
95
97
  <td>
@@ -116,15 +118,14 @@ export interface ApplyMigrationRequest {
116
118
  </div>
117
119
  </div>
118
120
  @if (isSecurity) {
119
- <security [controller]="controller" [id]="id" />
121
+ <security [controller]="controller" [id]="id"/>
120
122
  }
121
123
  `,
122
124
  providers: [ConfirmationService]
123
125
  })
124
126
  export class DbMigrationComponent
125
127
  extends BaseModuleComponent<NoSettingsDto, NoSettingsDto>
126
- implements OnInit, OnDestroy
127
- {
128
+ implements OnInit, OnDestroy {
128
129
  data: MigrationDto[];
129
130
 
130
131
  async ngOnInit() {
@@ -148,7 +149,7 @@ export class DbMigrationComponent
148
149
  }
149
150
 
150
151
  async applyMigration(rowData: MigrationDto) {
151
- const request = { name: rowData.name } as ApplyMigrationRequest;
152
+ const request = {name: rowData.name} as ApplyMigrationRequest;
152
153
  return this.baseDataService.sendRequest(`api/${this.controller}/apply-migration`, 'POST', request);
153
154
  }
154
155
  }
@@ -4,10 +4,10 @@ import { DialogModule } from 'primeng/dialog';
4
4
  import { InputTextModule } from 'primeng/inputtext';
5
5
  import { SelectModule } from 'primeng/select';
6
6
  import { FormsModule } from '@angular/forms';
7
- import { MenuService } from 'oip-common';
8
7
  import { TranslatePipe } from '@ngx-translate/core';
9
8
  import { AddModuleInstanceDto, IntKeyValueDto } from '../../api/data-contracts';
10
9
  import { Menu } from '../../api/Menu';
10
+ import { MenuService } from "../../services/app.menu.service";
11
11
 
12
12
  @Component({
13
13
  selector: 'menu-item-create-dialog',
@@ -30,14 +30,14 @@ import { Menu } from '../../api/Menu';
30
30
  id="oip-menu-item-create-dialog-parent-input"
31
31
  pInputText
32
32
  readonly
33
- [ngModel]="menuService.contextMenuItem?.label" />
33
+ [ngModel]="menuService.contextMenuItem?.label"/>
34
34
  </div>
35
35
  }
36
36
  <div class="flex items-center gap-4 mb-4">
37
37
  <label class="font-semibold w-1/3" for="oip-menu-item-create-label">
38
38
  {{ 'menuItemCreateDialogComponent.label' | translate }}
39
39
  </label>
40
- <input autocomplete="off" class="flex-auto" id="oip-menu-item-create-label" pInputText [(ngModel)]="label" />
40
+ <input autocomplete="off" class="flex-auto" id="oip-menu-item-create-label" pInputText [(ngModel)]="label"/>
41
41
  </div>
42
42
  <div class="flex items-center gap-4 mb-4">
43
43
  <label class="font-semibold w-1/3" for="oip-menu-item-create-module">
@@ -51,14 +51,14 @@ import { Menu } from '../../api/Menu';
51
51
  optionValue="key"
52
52
  placeholder="{{ 'menuItemCreateDialogComponent.selectModule' | translate }}"
53
53
  [options]="modules"
54
- [(ngModel)]="selectModule" />
54
+ [(ngModel)]="selectModule"/>
55
55
  </div>
56
56
  <div class="flex items-center gap-4 mb-4">
57
57
  <label class="font-semibold w-1/3" for="oip-menu-item-create-dialog-icon">
58
58
  {{ 'menuItemCreateDialogComponent.icon' | translate }}
59
59
  </label>
60
60
  <i class="{{ selectIcon }}"></i>
61
- <input class="flex-auto" id="oip-menu-item-create-dialog-icon" pInputText [(ngModel)]="selectIcon" />
61
+ <input class="flex-auto" id="oip-menu-item-create-dialog-icon" pInputText [(ngModel)]="selectIcon"/>
62
62
  </div>
63
63
  <div class="flex justify-end gap-2">
64
64
  <p-button
@@ -66,12 +66,12 @@ import { Menu } from '../../api/Menu';
66
66
  label="{{ 'menuItemCreateDialogComponent.cancel' | translate }}"
67
67
  severity="secondary"
68
68
  (click)="changeVisible()"
69
- (keydown)="changeVisible()" />
69
+ (keydown)="changeVisible()"/>
70
70
  <p-button
71
71
  id="oip-menu-item-create-save"
72
72
  label="{{ 'menuItemCreateDialogComponent.save' | translate }}"
73
73
  (click)="save()"
74
- (keydown)="save()" />
74
+ (keydown)="save()"/>
75
75
  </div>
76
76
  </p-dialog>
77
77
  `
@@ -3,7 +3,8 @@ import { ButtonModule } from 'primeng/button';
3
3
  import { DialogModule } from 'primeng/dialog';
4
4
  import { InputTextModule } from 'primeng/inputtext';
5
5
  import { FormsModule } from '@angular/forms';
6
- import { MenuService, SecurityDataService } from 'oip-common';
6
+ import { MenuService } from '../../services/app.menu.service'
7
+ import { SecurityDataService, } from '../../services/security-data.service'
7
8
  import { TranslatePipe } from '@ngx-translate/core';
8
9
  import { EditModuleInstanceDto } from '../../dtos/edit-module-instance.dto';
9
10
  import { MultiSelectModule } from 'primeng/multiselect';
@@ -27,7 +28,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
27
28
  class="flex-auto"
28
29
  id="oip-menu-item-edit-dialog-menu-input"
29
30
  pInputText
30
- [(ngModel)]="item.label" />
31
+ [(ngModel)]="item.label"/>
31
32
  </div>
32
33
 
33
34
  <div class="flex items-center gap-4 mb-4">
@@ -35,7 +36,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
35
36
  {{ 'menuItemEditDialogComponent.icon' | translate }}
36
37
  </label>
37
38
  <i class="{{ item.icon }}"></i>
38
- <input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon" />
39
+ <input class="flex-auto" id="oip-menu-item-edit-dialog-icon" pInputText [(ngModel)]="item.icon"/>
39
40
  </div>
40
41
 
41
42
  <div class="flex items-center gap-4 mb-4">
@@ -49,7 +50,7 @@ import { MultiSelectModule } from 'primeng/multiselect';
49
50
  placeholder="Select roles"
50
51
  [maxSelectedLabels]="10"
51
52
  [options]="roles"
52
- [(ngModel)]="item.viewRoles" />
53
+ [(ngModel)]="item.viewRoles"/>
53
54
  </div>
54
55
 
55
56
  <div class="flex justify-end gap-2">
@@ -58,12 +59,12 @@ import { MultiSelectModule } from 'primeng/multiselect';
58
59
  label="{{ 'menuItemEditDialogComponent.cancel' | translate }}"
59
60
  severity="secondary"
60
61
  (click)="changeVisible()"
61
- (keydown)="changeVisible()" />
62
+ (keydown)="changeVisible()"/>
62
63
  <p-button
63
64
  id="oip-menu-item-edit-dialog-save-edit-button"
64
65
  label="{{ 'menuItemEditDialogComponent.save' | translate }}"
65
66
  (click)="save()"
66
- (keydown)="save()" />
67
+ (keydown)="save()"/>
67
68
  </div>
68
69
  </p-dialog>
69
70
  `
@@ -2,7 +2,8 @@ import { Component, inject } from '@angular/core';
2
2
  import { FileUploadModule } from 'primeng/fileupload';
3
3
  import { ImageModule } from 'primeng/image';
4
4
  import { AvatarModule } from 'primeng/avatar';
5
- import { MsgService, UserService } from 'oip-common';
5
+ import { MsgService } from "../services/msg.service";
6
+ import { UserService } from "../services/user.service";
6
7
  import { TranslatePipe, TranslateService } from '@ngx-translate/core';
7
8
 
8
9
  @Component({
@@ -15,7 +16,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
15
16
  id="oip-user-profile-photo-avatar"
16
17
  shape="circle"
17
18
  size="xlarge"
18
- [image]="userService.photoLoaded ? userService.photo : null" />
19
+ [image]="userService.photoLoaded ? userService.photo : null"/>
19
20
  <div class="mt-2">
20
21
  <p-fileupload
21
22
  accept="image/*"
@@ -28,7 +29,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
28
29
  url="/api/user-profile/post-user-photo"
29
30
  withCredentials="true"
30
31
  [auto]="true"
31
- (onUpload)="onBasicUploadAuto($event)" />
32
+ (onUpload)="onBasicUploadAuto($event)"/>
32
33
  </div>
33
34
  `
34
35
  })
@@ -60,7 +60,7 @@ export function convertToPrimeNgDateFormat(dateformat: string): string {
60
60
  }
61
61
 
62
62
  /**
63
- * Преобразовать все свойства объекта со строковыми датами в даты
63
+ * Convert all properties of an object with string dates to Date objects
64
64
  * @param obj
65
65
  */
66
66
  export function restoreDates(obj: any) {
@@ -86,7 +86,7 @@ export function restoreDates(obj: any) {
86
86
  }
87
87
 
88
88
  /**
89
- * Проверить, является ли строка датой в ISO формате
89
+ * Check if a string is a date in ISO format
90
90
  * @param str
91
91
  */
92
92
  export function isIsoDate(str: string) {
@@ -11,7 +11,8 @@
11
11
  */
12
12
 
13
13
  import { inject, Injectable } from '@angular/core';
14
- import { LayoutService, SecurityService } from 'oip-common';
14
+ import { LayoutService } from '../services/app.layout.service';
15
+ import { SecurityService } from '../services/security.service';
15
16
 
16
17
  export type QueryParamsType = Record<string | number, any>;
17
18
  export type ResponseFormat = keyof Omit<Body, 'body' | 'bodyUsed'>;
@@ -58,7 +59,7 @@ export enum ContentType {
58
59
  Text = 'text/plain'
59
60
  }
60
61
 
61
- @Injectable({ providedIn: 'root' })
62
+ @Injectable({providedIn: 'root'})
62
63
  export class HttpClient<SecurityDataType = unknown> {
63
64
  protected securityService = inject(SecurityService);
64
65
  public baseUrl: string = '';
@@ -75,6 +76,7 @@ export class HttpClient<SecurityDataType = unknown> {
75
76
  const storageData = localStorage.getItem('0-oip-client');
76
77
  return JSON.parse(storageData)?.authnResult?.access_token;
77
78
  }
79
+
78
80
  private getLanguage(): string {
79
81
  // Получаем язык из localStorage или используем значение по умолчанию
80
82
  return localStorage.getItem('user-language') || 'en';
@@ -189,16 +191,16 @@ export class HttpClient<SecurityDataType = unknown> {
189
191
  };
190
192
 
191
193
  public request = async <T = any, E = any>({
192
- body,
193
- secure,
194
- path,
195
- type,
196
- query,
197
- format,
198
- baseUrl,
199
- cancelToken,
200
- ...params
201
- }: FullRequestParams): Promise<T> => {
194
+ body,
195
+ secure,
196
+ path,
197
+ type,
198
+ query,
199
+ format,
200
+ baseUrl,
201
+ cancelToken,
202
+ ...params
203
+ }: FullRequestParams): Promise<T> => {
202
204
  const secureParams =
203
205
  ((typeof secure === 'boolean' ? secure : this.baseApiParams.secure) &&
204
206
  this.securityWorker &&
@@ -213,7 +215,7 @@ export class HttpClient<SecurityDataType = unknown> {
213
215
  ...requestParams,
214
216
  headers: {
215
217
  ...(requestParams.headers || {}),
216
- ...(type && type !== ContentType.FormData ? { 'Content-Type': type } : {})
218
+ ...(type && type !== ContentType.FormData ? {'Content-Type': type} : {})
217
219
  },
218
220
  signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
219
221
  body: typeof body === 'undefined' || body === null ? null : payloadFormatter(body)
@@ -227,18 +229,18 @@ export class HttpClient<SecurityDataType = unknown> {
227
229
  const data = !responseFormat
228
230
  ? r
229
231
  : await response[responseFormat]()
230
- .then((data) => {
231
- if (r.ok) {
232
- r.data = data;
233
- } else {
234
- r.error = data;
235
- }
236
- return r;
237
- })
238
- .catch((e) => {
239
- r.error = e;
240
- return r;
241
- });
232
+ .then((data) => {
233
+ if (r.ok) {
234
+ r.data = data;
235
+ } else {
236
+ r.error = data;
237
+ }
238
+ return r;
239
+ })
240
+ .catch((e) => {
241
+ r.error = e;
242
+ return r;
243
+ });
242
244
 
243
245
  if (cancelToken) {
244
246
  this.abortControllers.delete(cancelToken);
@@ -0,0 +1,30 @@
1
+ <%
2
+ const { utils, route, config, modelTypes } = it;
3
+ const { _, pascalCase, require } = utils;
4
+ const apiClassName = pascalCase(route.moduleName);
5
+ const routes = route.routes;
6
+ const dataContracts = _.map(modelTypes, "name");
7
+ %>
8
+
9
+ <% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
10
+
11
+ import { HttpClient, RequestParams, ContentType, HttpResponse } from "./<%~ config.fileNames.httpClient %>";
12
+ <% if (dataContracts.length) { %>
13
+ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
14
+ <% } %>
15
+ import { Injectable } from "@angular/core";
16
+
17
+ @Injectable()
18
+ export class <%= apiClassName %><SecurityDataType = unknown><% if (!config.singleHttpClient) { %> extends HttpClient<SecurityDataType> <% } %> {
19
+ <% if(config.singleHttpClient) { %>
20
+ http: HttpClient<SecurityDataType>;
21
+
22
+ constructor (http: HttpClient<SecurityDataType>) {
23
+ this.http = http;
24
+ }
25
+ <% } %>
26
+
27
+ <% for (const route of routes) { %>
28
+ <%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
29
+ <% } %>
30
+ }
@@ -0,0 +1,37 @@
1
+ <%
2
+ const { data, utils } = it;
3
+ const { formatDescription, require, _ } = utils;
4
+
5
+ const stringify = (value) => _.isObject(value) ? JSON.stringify(value) : _.isString(value) ? `"${value}"` : value
6
+
7
+ const jsDocLines = _.compact([
8
+ data.title,
9
+ data.description && formatDescription(data.description),
10
+ !_.isUndefined(data.deprecated) && data.deprecated && '@deprecated',
11
+ !_.isUndefined(data.format) && `@format ${data.format}`,
12
+ !_.isUndefined(data.minimum) && `@min ${data.minimum}`,
13
+ !_.isUndefined(data.multipleOf) && `@multipleOf ${data.multipleOf}`,
14
+ !_.isUndefined(data.exclusiveMinimum) && `@exclusiveMin ${data.exclusiveMinimum}`,
15
+ !_.isUndefined(data.maximum) && `@max ${data.maximum}`,
16
+ !_.isUndefined(data.minLength) && `@minLength ${data.minLength}`,
17
+ !_.isUndefined(data.maxLength) && `@maxLength ${data.maxLength}`,
18
+ !_.isUndefined(data.exclusiveMaximum) && `@exclusiveMax ${data.exclusiveMaximum}`,
19
+ !_.isUndefined(data.maxItems) && `@maxItems ${data.maxItems}`,
20
+ !_.isUndefined(data.minItems) && `@minItems ${data.minItems}`,
21
+ !_.isUndefined(data.uniqueItems) && `@uniqueItems ${data.uniqueItems}`,
22
+ !_.isUndefined(data.default) && `@default ${stringify(data.default)}`,
23
+ !_.isUndefined(data.pattern) && `@pattern ${data.pattern}`,
24
+ !_.isUndefined(data.example) && `@example ${stringify(data.example)}`
25
+ ]).join('\n').split('\n');
26
+ %>
27
+ <% if (jsDocLines.every(_.isEmpty)) { %>
28
+ <% } else if (jsDocLines.length === 1) { %>
29
+ /** <%~ jsDocLines[0] %> */
30
+ <% } else if (jsDocLines.length) { %>
31
+ /**
32
+ <% for (jsDocLine of jsDocLines) { %>
33
+ * <%~ jsDocLine %>
34
+
35
+ <% } %>
36
+ */
37
+ <% } %>
@@ -0,0 +1,52 @@
1
+ <%
2
+ const { modelTypes, utils, config } = it;
3
+ const { formatDescription, require, _, Ts } = utils;
4
+
5
+
6
+ const buildGenerics = (contract) => {
7
+ if (!contract.genericArgs || !contract.genericArgs.length) return '';
8
+
9
+ return '<' + contract.genericArgs.map(({ name, default: defaultType, extends: extendsType }) => {
10
+ return [
11
+ name,
12
+ extendsType && `extends ${extendsType}`,
13
+ defaultType && `= ${defaultType}`,
14
+ ].join('')
15
+ }).join(',') + '>'
16
+ }
17
+
18
+ const dataContractTemplates = {
19
+ enum: (contract) => {
20
+ return `enum ${contract.name} {\r\n${contract.content} \r\n }`;
21
+ },
22
+ interface: (contract) => {
23
+ return `interface ${contract.name}${buildGenerics(contract)} {\r\n${contract.content}}\r\n\r\n`;
24
+ },
25
+ type: (contract) => {
26
+ return `type ${contract.name}${buildGenerics(contract)} = ${contract.content}`;
27
+ },
28
+ }
29
+ %>
30
+
31
+ <% if (config.internalTemplateOptions.addUtilRequiredKeysType) { %>
32
+ type <%~ config.Ts.CodeGenKeyword.UtilRequiredKeys %><T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
33
+ <% } %>
34
+
35
+ <% for (const contract of modelTypes) {
36
+ if (contract.typeIdentifier === 'interface' && contract.rawContent && Array.isArray(contract.rawContent)) {
37
+ contract.rawContent.forEach((prop) => {
38
+ if (prop.format ==='date-time') {
39
+ prop.field = prop.field.replace(': string', ': Date');
40
+ }
41
+ })
42
+ let customContent = '';
43
+ contract.rawContent.forEach((prop) => {
44
+ if (prop.description) customContent += `/** ${prop.description} */ \r\n`;
45
+ customContent += `${prop.field};\r\n`;
46
+ })
47
+ contract.content = customContent;
48
+ }
49
+ %>
50
+ <%~ includeFile('./data-contract-jsdoc.ejs', { ...it, data: { ...contract, ...contract.typeData } }) %>
51
+ <%~ contract.internal ? '' : 'export'%> <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
52
+ <% } %>
@@ -0,0 +1,12 @@
1
+ <%
2
+ const { contract, utils, config } = it;
3
+ const { formatDescription, require, _ } = utils;
4
+ const { name, $content } = contract;
5
+ %>
6
+ <% if (config.generateUnionEnums) { %>
7
+ export type <%~ name %> = <%~ _.map($content, ({ value }) => value).join(" | ") %>
8
+ <% } else { %>
9
+ export enum <%~ name %> {
10
+ <%~ _.map($content, ({ key, value }) => `${key} = ${value}`).join(",\n") %>
11
+ }
12
+ <% } %>
@@ -0,0 +1,245 @@
1
+ <%
2
+ const { apiConfig, generateResponses, config } = it;
3
+ %>
4
+
5
+ import { SecurityService } from "oip-common";
6
+ import { LayoutService } from "oip-common";
7
+ import { inject, Injectable } from "@angular/core";
8
+
9
+ export type QueryParamsType = Record<string | number, any>;
10
+ export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
11
+
12
+ export interface FullRequestParams extends Omit<RequestInit, "body"> {
13
+ /** set parameter to `true` for call `securityWorker` for this request */
14
+ secure?: boolean;
15
+ /** request path */
16
+ path: string;
17
+ /** content type of request body */
18
+ type?: ContentType;
19
+ /** query params */
20
+ query?: QueryParamsType;
21
+ /** format of response (i.e. response.json() -> format: "json") */
22
+ format?: ResponseFormat;
23
+ /** request body */
24
+ body?: unknown;
25
+ /** base url */
26
+ baseUrl?: string;
27
+ /** request cancellation token */
28
+ cancelToken?: CancelToken;
29
+ }
30
+
31
+ export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">
32
+
33
+
34
+ export interface ApiConfig<SecurityDataType = unknown> {
35
+ baseUrl?: string;
36
+ baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
37
+ securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
38
+ customFetch?: typeof fetch;
39
+ }
40
+
41
+ export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
42
+ data: D;
43
+ error: E;
44
+ }
45
+
46
+ type CancelToken = Symbol | string | number;
47
+
48
+ export enum ContentType {
49
+ Json = "application/json",
50
+ FormData = "multipart/form-data",
51
+ UrlEncoded = "application/x-www-form-urlencoded",
52
+ Text = "text/plain",
53
+ }
54
+
55
+ @Injectable({ providedIn: 'root' })
56
+ export class HttpClient<SecurityDataType = unknown> {
57
+ protected securityService = inject(SecurityService);
58
+ protected layoutService = inject(LayoutService);
59
+ public baseUrl: string = "<%~ apiConfig.baseUrl %>";
60
+ private securityData: SecurityDataType | null = null;
61
+ private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"] =
62
+ (securityData) => ({
63
+ headers: {
64
+ "Accept-language": this.layoutService.language()
65
+ ? this.layoutService.language()
66
+ : 'en',
67
+ "X-Timezone": this.layoutService.timeZone(),
68
+ Authorization: `Bearer ${securityData}`,
69
+ },
70
+ });
71
+ private abortControllers = new Map<CancelToken, AbortController>();
72
+ private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
73
+
74
+ private baseApiParams: RequestParams = {
75
+ credentials: 'same-origin',
76
+ headers: {},
77
+ redirect: 'follow',
78
+ referrerPolicy: 'no-referrer',
79
+ }
80
+
81
+ constructor() {
82
+ this.securityService.getAccessToken().subscribe((token) => {
83
+ this.securityData = token;
84
+ });
85
+ }
86
+
87
+ public setSecurityData = (data: SecurityDataType | null) => {
88
+ this.securityData = data;
89
+ }
90
+
91
+ protected encodeQueryParam(key: string, value: any) {
92
+ const encodedKey = encodeURIComponent(key);
93
+ return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
94
+ }
95
+
96
+ protected addQueryParam(query: QueryParamsType, key: string) {
97
+ return this.encodeQueryParam(key, query[key]);
98
+ }
99
+
100
+ protected addArrayQueryParam(query: QueryParamsType, key: string) {
101
+ const value = query[key];
102
+ return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
103
+ }
104
+
105
+ protected toQueryString(rawQuery?: QueryParamsType): string {
106
+ const query = rawQuery || {};
107
+ const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
108
+ return keys
109
+ .map((key) =>
110
+ Array.isArray(query[key])
111
+ ? this.addArrayQueryParam(query, key)
112
+ : this.addQueryParam(query, key),
113
+ )
114
+ .join("&");
115
+ }
116
+
117
+ protected addQueryParams(rawQuery?: QueryParamsType): string {
118
+ const queryString = this.toQueryString(rawQuery);
119
+ return queryString ? `?${queryString}` : "";
120
+ }
121
+
122
+ private contentFormatters: Record<ContentType, (input: any) => any> = {
123
+ [ContentType.Json]: (input:any) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
124
+ [ContentType.Text]: (input:any) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input,
125
+ [ContentType.FormData]: (input: any) =>
126
+ Object.keys(input || {}).reduce((formData, key) => {
127
+ const property = input[key];
128
+ formData.append(
129
+ key,
130
+ property instanceof Blob ?
131
+ property :
132
+ typeof property === "object" && property !== null ?
133
+ JSON.stringify(property) :
134
+ `${property}`
135
+ );
136
+ return formData;
137
+ }, new FormData()),
138
+ [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
139
+ }
140
+
141
+ protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
142
+ return {
143
+ ...this.baseApiParams,
144
+ ...params1,
145
+ ...(params2 || {}),
146
+ headers: {
147
+ ...(this.baseApiParams.headers || {}),
148
+ ...(params1.headers || {}),
149
+ ...((params2 && params2.headers) || {}),
150
+ },
151
+ };
152
+ }
153
+
154
+ protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
155
+ if (this.abortControllers.has(cancelToken)) {
156
+ const abortController = this.abortControllers.get(cancelToken);
157
+ if (abortController) {
158
+ return abortController.signal;
159
+ }
160
+ return void 0;
161
+ }
162
+
163
+ const abortController = new AbortController();
164
+ this.abortControllers.set(cancelToken, abortController);
165
+ return abortController.signal;
166
+ }
167
+
168
+ public abortRequest = (cancelToken: CancelToken) => {
169
+ const abortController = this.abortControllers.get(cancelToken)
170
+
171
+ if (abortController) {
172
+ abortController.abort();
173
+ this.abortControllers.delete(cancelToken);
174
+ }
175
+ }
176
+
177
+ public request = async <T = any, E = any>({
178
+ body,
179
+ secure,
180
+ path,
181
+ type,
182
+ query,
183
+ format,
184
+ baseUrl,
185
+ cancelToken,
186
+ ...params
187
+ <% if (config.unwrapResponseData) { %>
188
+ }: FullRequestParams): Promise<T> => {
189
+ <% } else { %>
190
+ }: FullRequestParams): Promise<HttpResponse<T, E>> => {
191
+ <% } %>
192
+ const secureParams = ((typeof secure === 'boolean' ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData)) || {};
193
+ const requestParams = this.mergeRequestParams(params, secureParams);
194
+ const queryString = query && this.toQueryString(query);
195
+ const payloadFormatter = this.contentFormatters[type || ContentType.Json];
196
+ let responseFormat = format || requestParams.format;
197
+
198
+ return this.customFetch(
199
+ `${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
200
+ {
201
+ ...requestParams,
202
+ headers: {
203
+ ...(requestParams.headers || {}),
204
+ ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
205
+ },
206
+ signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
207
+ body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
208
+ }
209
+ ).then(async (response) => {
210
+ const r = response.clone() as HttpResponse<T, E>;
211
+ r.data = (null as unknown) as T;
212
+ r.error = (null as unknown) as E;
213
+
214
+ if (typeof E !== undefined && responseFormat === undefined)
215
+ responseFormat = 'json';
216
+
217
+ const data = !responseFormat ? r : await response[responseFormat]()
218
+ .then((data) => {
219
+ if (r.ok) {
220
+ r.data = data;
221
+ } else {
222
+ r.error = data;
223
+ }
224
+ return r;
225
+ })
226
+ .catch((e) => {
227
+ r.error = e;
228
+ return r;
229
+ });
230
+
231
+ if (cancelToken) {
232
+ this.abortControllers.delete(cancelToken);
233
+ }
234
+
235
+ <% if (!config.disableThrowOnError) { %>
236
+ if (!response.ok) throw data;
237
+ <% } %>
238
+ <% if (config.unwrapResponseData) { %>
239
+ return data.data;
240
+ <% } else { %>
241
+ return data;
242
+ <% } %>
243
+ });
244
+ };
245
+ }
@@ -0,0 +1,10 @@
1
+ <%
2
+ const { contract, utils } = it;
3
+ const { formatDescription, require, _ } = utils;
4
+ %>
5
+ export interface <%~ contract.name %> {
6
+ <% for (const field of contract.$content) { %>
7
+ <%~ includeFile('./object-field-jsdoc.ejs', { ...it, field }) %>
8
+ <%~ field.name %><%~ field.isRequired ? '' : '?' %>: <%~ field.value %><%~ field.isNullable ? ' | null' : ''%>;
9
+ <% } %>
10
+ }
@@ -0,0 +1,28 @@
1
+ <%
2
+ const { field, utils } = it;
3
+ const { formatDescription, require, _ } = utils;
4
+
5
+ const comments = _.uniq(
6
+ _.compact([
7
+ field.title,
8
+ field.description,
9
+ field.deprecated && ` * @deprecated`,
10
+ !_.isUndefined(field.format) && `@format ${field.format}`,
11
+ !_.isUndefined(field.minimum) && `@min ${field.minimum}`,
12
+ !_.isUndefined(field.maximum) && `@max ${field.maximum}`,
13
+ !_.isUndefined(field.pattern) && `@pattern ${field.pattern}`,
14
+ !_.isUndefined(field.example) &&
15
+ `@example ${_.isObject(field.example) ? JSON.stringify(field.example) : field.example}`,
16
+ ]).reduce((acc, comment) => [...acc, ...comment.split(/\n/g)], []),
17
+ );
18
+ %>
19
+ <% if (comments.length === 1) { %>
20
+ /** <%~ comments[0] %> */
21
+ <% } else if (comments.length) { %>
22
+ /**
23
+ <% comments.forEach(comment => { %>
24
+ * <%~ comment %>
25
+
26
+ <% }) %>
27
+ */
28
+ <% } %>
@@ -0,0 +1,100 @@
1
+ <%
2
+ const { utils, route, config } = it;
3
+ const { requestBodyInfo, responseBodyInfo, specificArgNameResolver } = route;
4
+ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRef, require } = utils;
5
+ const { parameters, path, method, payload, query, formData, security, requestParams } = route.request;
6
+ const { type, errorType, contentTypes } = route.response;
7
+ const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
8
+ const routeDocs = includeFile("./route-docs", { config, route, utils });
9
+ const queryName = (query && query.name) || "query";
10
+ const pathParams = _.values(parameters);
11
+ const pathParamsNames = _.map(pathParams, "name");
12
+
13
+ const isFetchTemplate = config.httpClientType === HTTP_CLIENT.FETCH;
14
+
15
+ const requestConfigParam = {
16
+ name: specificArgNameResolver.resolve(RESERVED_REQ_PARAMS_ARG_NAMES),
17
+ optional: true,
18
+ type: "RequestParams",
19
+ defaultValue: "{}",
20
+ }
21
+
22
+ const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${type}${defaultValue ? ` = ${defaultValue}` : ''}`;
23
+
24
+ const rawWrapperArgs = config.extractRequestParams ?
25
+ _.compact([
26
+ requestParams && {
27
+ name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName,
28
+ optional: false,
29
+ type: getInlineParseContent(requestParams),
30
+ },
31
+ ...(!requestParams ? pathParams : []),
32
+ payload,
33
+ requestConfigParam,
34
+ ]) :
35
+ _.compact([
36
+ ...pathParams,
37
+ query,
38
+ payload,
39
+ requestConfigParam,
40
+ ])
41
+
42
+ const wrapperArgs = _
43
+ // Sort by optionality
44
+ .sortBy(rawWrapperArgs, [o => o.optional])
45
+ .map(argToTmpl)
46
+ .join(', ')
47
+
48
+ // RequestParams["type"]
49
+ const requestContentKind = {
50
+ "JSON": "ContentType.Json",
51
+ "URL_ENCODED": "ContentType.UrlEncoded",
52
+ "FORM_DATA": "ContentType.FormData",
53
+ "TEXT": "ContentType.Text",
54
+ }
55
+ // RequestParams["format"]
56
+ const responseContentKind = {
57
+ "JSON": '"json"',
58
+ "IMAGE": '"blob"',
59
+ "FORM_DATA": isFetchTemplate ? '"formData"' : '"document"'
60
+ }
61
+
62
+ const bodyTmpl = _.get(payload, "name") || null;
63
+ const queryTmpl = (query != null && queryName) || null;
64
+ const bodyContentKindTmpl = requestContentKind[requestBodyInfo.contentKind] || null;
65
+ const responseFormatTmpl = responseContentKind[responseBodyInfo.success && responseBodyInfo.success.schema && responseBodyInfo.success.schema.contentKind] || null;
66
+ const securityTmpl = security ? 'true' : null;
67
+
68
+ const describeReturnType = () => {
69
+ if (!config.toJS) return "";
70
+
71
+ switch(config.httpClientType) {
72
+ case HTTP_CLIENT.AXIOS: {
73
+ return `Promise<AxiosResponse<${type}>>`
74
+ }
75
+ default: {
76
+ return `Promise<HttpResponse<${type}, ${errorType}>`
77
+ }
78
+ }
79
+ }
80
+
81
+ %>
82
+ /**
83
+ <%~ routeDocs.description %>
84
+
85
+ *<% /* Here you can add some other JSDoc tags */ %>
86
+
87
+ <%~ routeDocs.lines %>
88
+
89
+ */
90
+ <%~ route.routeName.usage %> = (<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
91
+ <%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>, <%~ errorType %>>({
92
+ path: `<%~ path %>`,
93
+ method: '<%~ _.upperCase(method) %>',
94
+ <%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
95
+ <%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
96
+ <%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
97
+ <%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
98
+ <%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
99
+ ...<%~ _.get(requestConfigParam, "name") %>,
100
+ })
@@ -0,0 +1,29 @@
1
+ <%
2
+ const { config, route, utils } = it;
3
+ const { _, formatDescription, fmtToJSDocLine, pascalCase, require } = utils;
4
+ const { raw, request, routeName } = route;
5
+
6
+ const jsDocDescription = raw.summary ?
7
+ ` * @description ${formatDescription(raw.summary, true)}` :
8
+ fmtToJSDocLine('No description', { eol: false });
9
+ const jsDocLines = _.compact([
10
+ _.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`,
11
+ ` * @name ${_.camelCase(routeName.usage)}`,
12
+ ` * @request ${_.upperCase(request.method)}:${raw.route}`,
13
+ raw.deprecated && ` * @deprecated`,
14
+ routeName.duplicate && ` * @originalName ${routeName.original}`,
15
+ routeName.duplicate && ` * @duplicate`,
16
+ request.security && ` * @secure`,
17
+ ...(config.generateResponses && raw.responsesTypes.length
18
+ ? raw.responsesTypes.map(
19
+ ({ type, status, description, isSuccess }) =>
20
+ ` * @response \`${status}\` \`${_.replace(_.replace(type, /\/\*/g, "\\*"), /\*\//g, "*\\")}\` ${description}`,
21
+ )
22
+ : []),
23
+ ]).map(str => str.trimEnd()).join("\n");
24
+
25
+ return {
26
+ description: jsDocDescription,
27
+ lines: jsDocLines,
28
+ }
29
+ %>
@@ -0,0 +1,42 @@
1
+ <%
2
+ const { routeInfo, utils } = it;
3
+ const {
4
+ operationId,
5
+ method,
6
+ route,
7
+ moduleName,
8
+ responsesTypes,
9
+ description,
10
+ tags,
11
+ summary,
12
+ pathArgs,
13
+ } = routeInfo;
14
+ const { _, fmtToJSDocLine, require } = utils;
15
+
16
+ const methodAliases = {
17
+ get: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
18
+ post: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
19
+ put: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
20
+ patch: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
21
+ delete: (pathName, hasPathInserts) => _.camelCase(`${pathName}`),
22
+ };
23
+
24
+ const createCustomOperationId = (method, route, moduleName) => {
25
+ const hasPathInserts = /\{(\w){1,}\}$/g.test(route);
26
+ const splittedRouteBySlash = _.compact(_.replace(route, /\{(\w){1,}\}/g, "").split("/"));
27
+ const routeParts = (splittedRouteBySlash.length > 1
28
+ ? splittedRouteBySlash.splice(1)
29
+ : splittedRouteBySlash
30
+ ).join("_");
31
+ return routeParts.length > 3 && methodAliases[method]
32
+ ? methodAliases[method](routeParts, hasPathInserts)
33
+ : _.camelCase(_.lowerCase(method) + "_" + [moduleName].join("_")) || "index";
34
+ };
35
+
36
+ if (operationId)
37
+ return _.camelCase(operationId);
38
+ if (route === "/")
39
+ return _.camelCase(`${_.lowerCase(method)}Root`);
40
+
41
+ return createCustomOperationId(method, route, moduleName);
42
+ %>
@@ -0,0 +1,23 @@
1
+ <%
2
+ const { route, utils, config } = it;
3
+ const { _, pascalCase, require } = utils;
4
+ const { query, payload, pathParams, headers } = route.request;
5
+
6
+ const routeDocs = includeFile("./route-docs", { config, route, utils });
7
+ const routeNamespace = pascalCase(route.routeName.usage);
8
+
9
+ %>
10
+
11
+ /**
12
+ <%~ routeDocs.description %>
13
+
14
+ <%~ routeDocs.lines %>
15
+
16
+ */
17
+ export namespace <%~ routeNamespace %> {
18
+ export type RequestParams = <%~ (pathParams && pathParams.type) || '{}' %>;
19
+ export type RequestQuery = <%~ (query && query.type) || '{}' %>;
20
+ export type RequestBody = <%~ (payload && payload.type) || 'never' %>;
21
+ export type RequestHeaders = <%~ (headers && headers.type) || '{}' %>;
22
+ export type ResponseBody = <%~ route.response.type %>;
23
+ }
@@ -0,0 +1,18 @@
1
+ <%
2
+ const { utils, config, route, modelTypes } = it;
3
+ const { _, pascalCase } = utils;
4
+ const { routes, moduleName } = route;
5
+ const dataContracts = config.modular ? _.map(modelTypes, "name") : [];
6
+
7
+ %>
8
+ <% if (dataContracts.length) { %>
9
+ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
10
+ <% } %>
11
+
12
+ export namespace <%~ pascalCase(moduleName) %> {
13
+ <% for (const route of routes) { %>
14
+
15
+ <%~ includeFile('./route-type.ejs', { ...it, route }) %>
16
+
17
+ <% } %>
18
+ }
@@ -0,0 +1,15 @@
1
+ <%
2
+ const { contract, utils } = it;
3
+ const { formatDescription, require, _ } = utils;
4
+
5
+ %>
6
+ <% if (contract.$content.length) { %>
7
+ export type <%~ contract.name %> = {
8
+ <% for (const field of contract.$content) { %>
9
+ <%~ includeFile('./object-field-jsdoc.ejs', { ...it, field }) %>
10
+ <%~ field.field %>;
11
+ <% } %>
12
+ }<%~ utils.isNeedToAddNull(contract) ? ' | null' : ''%>
13
+ <% } else { %>
14
+ export type <%~ contract.name %> = Record<string, any>;
15
+ <% } %>