@sdcorejs/angular 19.2.0 → 19.2.1
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/components/api-contract-builder/index.d.ts +8 -0
- package/components/api-contract-builder/src/api-contract-builder.component.d.ts +123 -0
- package/components/api-contract-builder/src/api-contract.configuration.d.ts +41 -0
- package/components/api-contract-builder/src/api-contract.expression.d.ts +43 -0
- package/components/api-contract-builder/src/api-contract.model.d.ts +167 -0
- package/components/api-contract-builder/src/api-contract.samples.d.ts +25 -0
- package/components/api-contract-builder/src/api-contract.schema.d.ts +126 -0
- package/components/api-contract-builder/src/api-contract.serializer.d.ts +17 -0
- package/components/api-contract-builder/src/api-contract.validation.d.ts +12 -0
- package/components/api-contract-builder/src/components/api-contract-diagnostic-list.component.d.ts +18 -0
- package/components/api-contract-builder/src/components/api-contract-node-drawer.component.d.ts +130 -0
- package/components/api-contract-builder/src/components/api-contract-node-editor.component.d.ts +35 -0
- package/components/api-contract-builder/src/components/api-contract-node-summary.component.d.ts +41 -0
- package/components/api-contract-builder/src/components/api-contract-record-editor.component.d.ts +36 -0
- package/components/api-contract-builder/src/components/api-contract-source-editor.component.d.ts +86 -0
- package/components/api-contract-builder/src/components/api-contract-suggestion.model.d.ts +17 -0
- package/components/badge/src/badge.component.d.ts +1 -0
- package/components/index.d.ts +1 -0
- package/components/section/src/section.component.d.ts +2 -0
- package/fesm2022/sdcorejs-angular-components-api-contract-builder.mjs +3411 -0
- package/fesm2022/sdcorejs-angular-components-api-contract-builder.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs +14 -2
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +22 -5
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +14 -13
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components.mjs +1 -0
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +405 -0
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +15 -5
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/i18n/src/en.d.ts +80 -0
- package/modules/layout/components/sidebar-v1/components/sidebar/sidebar.component.d.ts +8 -0
- package/package.json +13 -9
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './src/api-contract.model';
|
|
2
|
+
export * from './src/api-contract.configuration';
|
|
3
|
+
export * from './src/api-contract.expression';
|
|
4
|
+
export * from './src/api-contract.schema';
|
|
5
|
+
export * from './src/api-contract.serializer';
|
|
6
|
+
export * from './src/api-contract.validation';
|
|
7
|
+
export * from './src/api-contract.samples';
|
|
8
|
+
export * from './src/api-contract-builder.component';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type SdApiContract, type SdApiContractDataType, type SdApiContractDiagnostic } from './api-contract.model';
|
|
2
|
+
import { type SdApiContractSchemaField, type SdApiContractStructuralNode } from './api-contract.schema';
|
|
3
|
+
import { SdApiContractNodeDrawer, type SdApiContractNodeCommit, type SdApiContractNodeEditRequest } from './components/api-contract-node-drawer.component';
|
|
4
|
+
import type { SdApiContractSuggestion } from './components/api-contract-suggestion.model';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
interface SdApiContractOption {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
interface SdApiContractStep {
|
|
11
|
+
index: number;
|
|
12
|
+
key: string;
|
|
13
|
+
label: string;
|
|
14
|
+
}
|
|
15
|
+
type SdApiContractNodeRecord = Record<string, SdApiContractStructuralNode>;
|
|
16
|
+
/** Every place a node can live. The builder needs it to route a commit back to its owner. */
|
|
17
|
+
type SdApiContractDrawerSection = 'input.schema' | 'req.path' | 'req.query' | 'req.headers' | 'req.body' | 'res.headers' | 'res.body' | 'output.schema';
|
|
18
|
+
/**
|
|
19
|
+
* Visual builder for an `SdApiContract`.
|
|
20
|
+
*
|
|
21
|
+
* The component is a **design-time** tool: it never performs a request, never resolves an
|
|
22
|
+
* expression and never holds a secret. It edits, validates and serializes the contract; executing
|
|
23
|
+
* it is a separate concern for a future `form-builder` / `form-render` integration.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```html
|
|
27
|
+
* <sd-api-contract-builder [(model)]="contract" autoId="product-search"></sd-api-contract-builder>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class SdApiContractBuilder {
|
|
31
|
+
#private;
|
|
32
|
+
model: import("@angular/core").ModelSignal<SdApiContract | null>;
|
|
33
|
+
mode: import("@angular/core").InputSignal<"edit" | "view">;
|
|
34
|
+
disabled: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
|
|
35
|
+
autoId: import("@angular/core").InputSignal<string | null | undefined>;
|
|
36
|
+
/** Fires whenever the diagnostics change, including once for the initially seeded contract. */
|
|
37
|
+
diagnosticsChange: import("@angular/core").OutputEmitterRef<readonly SdApiContractDiagnostic[]>;
|
|
38
|
+
/** Fires only when validity flips, so a consumer can gate a Save button without debouncing. */
|
|
39
|
+
validChange: import("@angular/core").OutputEmitterRef<boolean>;
|
|
40
|
+
protected readonly activeStep: import("@angular/core").WritableSignal<number>;
|
|
41
|
+
protected readonly contractVersion = 1;
|
|
42
|
+
protected readonly responseFieldToken: import("@angular/core").WritableSignal<string | null>;
|
|
43
|
+
protected readonly urlPlaceholder = "${env.baseUrl}/products/{id}";
|
|
44
|
+
protected readonly draft: import("@angular/core").Signal<SdApiContract | null>;
|
|
45
|
+
protected readonly readonly: import("@angular/core").Signal<boolean>;
|
|
46
|
+
protected readonly isView: import("@angular/core").Signal<boolean>;
|
|
47
|
+
protected readonly steps: SdApiContractStep[];
|
|
48
|
+
protected readonly methodOptions: SdApiContractOption[];
|
|
49
|
+
protected readonly allTypes: readonly SdApiContractDataType[];
|
|
50
|
+
protected readonly scalarTypes: readonly SdApiContractDataType[];
|
|
51
|
+
protected readonly queryTypes: readonly SdApiContractDataType[];
|
|
52
|
+
protected readonly nodeDrawer: import("@angular/core").Signal<SdApiContractNodeDrawer | undefined>;
|
|
53
|
+
protected readonly drawerLayer: import("@angular/core").Signal<"schema" | "mapping">;
|
|
54
|
+
protected readonly drawerAllowsTransform: import("@angular/core").Signal<boolean>;
|
|
55
|
+
protected readonly drawerSuggestions: import("@angular/core").Signal<readonly SdApiContractSuggestion[]>;
|
|
56
|
+
protected readonly diagnostics: import("@angular/core").Signal<readonly SdApiContractDiagnostic[]>;
|
|
57
|
+
protected readonly json: import("@angular/core").Signal<string>;
|
|
58
|
+
protected readonly requestSuggestions: import("@angular/core").Signal<SdApiContractSuggestion[]>;
|
|
59
|
+
protected readonly outputSuggestions: import("@angular/core").Signal<SdApiContractSuggestion[]>;
|
|
60
|
+
/** Response paths offered by the "use a response field as the output" action. */
|
|
61
|
+
protected readonly responseFieldOptions: import("@angular/core").Signal<SdApiContractOption[]>;
|
|
62
|
+
/** Leaf fields a dropdown / table consumer will see once this contract runs. */
|
|
63
|
+
protected readonly outputFields: import("@angular/core").Signal<SdApiContractSchemaField[]>;
|
|
64
|
+
protected readonly statusText: import("@angular/core").Signal<string>;
|
|
65
|
+
get autoIdAttr(): string | null;
|
|
66
|
+
constructor();
|
|
67
|
+
protected goToStep(index: number): void;
|
|
68
|
+
protected goToDiagnostic(diagnostic: SdApiContractDiagnostic): void;
|
|
69
|
+
protected createContract(): void;
|
|
70
|
+
protected setText(key: 'code' | 'name' | 'description', value: unknown): void;
|
|
71
|
+
protected setInputSchema(node: SdApiContractStructuralNode): void;
|
|
72
|
+
protected setMethod(value: unknown): void;
|
|
73
|
+
protected setUrl(value: unknown): void;
|
|
74
|
+
/**
|
|
75
|
+
* Opens the one drawer for whichever list asked.
|
|
76
|
+
*
|
|
77
|
+
* why gác `readonly` ở đây nữa: hàng thu gọn đã tự chặn khi read-only, nhưng builder là nơi duy nhất
|
|
78
|
+
* biết `mode`/`disabled` thật. Hai lớp gác rẻ hơn một đường mở drawer lọt trong chế độ xem.
|
|
79
|
+
*/
|
|
80
|
+
protected openNodeDrawer(section: SdApiContractDrawerSection, request: SdApiContractNodeEditRequest): void;
|
|
81
|
+
/**
|
|
82
|
+
* Writes a committed node back where it came from — one `modelChange`, at Save time.
|
|
83
|
+
*
|
|
84
|
+
* A rename goes through `sdApiContractRecordRename` first so the entry keeps its position in the
|
|
85
|
+
* JSON instead of jumping to the end. The drawer has already refused a duplicate name, so the
|
|
86
|
+
* rename cannot collide by the time it reaches here.
|
|
87
|
+
*/
|
|
88
|
+
protected applyNodeCommit(commit: SdApiContractNodeCommit): void;
|
|
89
|
+
protected setRequestRecord(section: 'path' | 'query' | 'headers', record: SdApiContractNodeRecord): void;
|
|
90
|
+
protected setRequestBody(node: SdApiContractStructuralNode): void;
|
|
91
|
+
protected addRequestBody(): void;
|
|
92
|
+
protected removeRequestBody(): void;
|
|
93
|
+
protected setStatus(value: unknown): void;
|
|
94
|
+
protected setResponseHeaders(record: SdApiContractNodeRecord): void;
|
|
95
|
+
protected setResponseBody(node: SdApiContractStructuralNode): void;
|
|
96
|
+
protected addResponseBody(): void;
|
|
97
|
+
protected removeResponseBody(): void;
|
|
98
|
+
protected setOutputSchema(node: SdApiContractStructuralNode): void;
|
|
99
|
+
/**
|
|
100
|
+
* Adopts a response subtree as the output schema.
|
|
101
|
+
*
|
|
102
|
+
* The subtree is **deep-copied**, never referenced: editing the output afterwards must not reach
|
|
103
|
+
* back into the response declaration. An array or scalar target takes a whole-node `source`; an
|
|
104
|
+
* object target keeps its shape and each branch gets its own `source`, because an object with both
|
|
105
|
+
* a whole-node source and child mappings is invalid by design.
|
|
106
|
+
*/
|
|
107
|
+
protected useResponseFieldAsOutput(value: unknown): void;
|
|
108
|
+
/**
|
|
109
|
+
* Adopts a contract pasted into the review editor.
|
|
110
|
+
*
|
|
111
|
+
* `<sd-code-editor language="json">` emits the PARSED value when the text is valid JSON and the
|
|
112
|
+
* raw STRING while it is still half-typed. A string therefore means "not parseable yet": keep the
|
|
113
|
+
* current draft and report it, because replacing a contract with a fragment of text would destroy
|
|
114
|
+
* the author's work on a keystroke.
|
|
115
|
+
*
|
|
116
|
+
* A parseable object is adopted VERBATIM — no field is added, removed or repaired. Whatever is
|
|
117
|
+
* wrong with it surfaces through `validateSdApiContract`, which is the whole point of pasting.
|
|
118
|
+
*/
|
|
119
|
+
protected applyPastedJson(value: unknown): void;
|
|
120
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SdApiContractBuilder, never>;
|
|
121
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SdApiContractBuilder, "sd-api-contract-builder", never, { "model": { "alias": "model"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "autoId": { "alias": "autoId"; "required": false; "isSignal": true; }; }, { "model": "modelChange"; "diagnosticsChange": "diagnosticsChange"; "validChange": "validChange"; }, never, never, true, never>;
|
|
122
|
+
}
|
|
123
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InjectionToken, type EnvironmentProviders } from '@angular/core';
|
|
2
|
+
import type { SdApiContractScalarDataType } from './api-contract.model';
|
|
3
|
+
/**
|
|
4
|
+
* Declaration of one global variable a contract may reference as `${env.<key>}`.
|
|
5
|
+
*
|
|
6
|
+
* Definition only — **never a value**. The builder is a design-time tool: it must be able to
|
|
7
|
+
* autocomplete and validate `${env.token}` without the token itself ever entering the app, the
|
|
8
|
+
* component, or the persisted JSON.
|
|
9
|
+
*/
|
|
10
|
+
export interface SdApiContractEnvironmentVariable {
|
|
11
|
+
/** Composite env variables are not supported — a global is always a single scalar. */
|
|
12
|
+
type: SdApiContractScalarDataType;
|
|
13
|
+
label?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Marks a secret (token, api key). The UI badges it and never previews a value — there is no
|
|
17
|
+
* value to preview, so this is purely an authoring signal.
|
|
18
|
+
*/
|
|
19
|
+
sensitive?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SdApiContractConfiguration {
|
|
22
|
+
env: Record<string, SdApiContractEnvironmentVariable>;
|
|
23
|
+
}
|
|
24
|
+
/** What the builder falls back to when the host application provides no configuration. */
|
|
25
|
+
export declare const SD_API_CONTRACT_EMPTY_CONFIGURATION: SdApiContractConfiguration;
|
|
26
|
+
export declare const SD_API_CONTRACT_CONFIGURATION: InjectionToken<SdApiContractConfiguration>;
|
|
27
|
+
/**
|
|
28
|
+
* Registers the env catalog available to every `<sd-api-contract-builder>` in the injector.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* provideSdApiContract({
|
|
32
|
+
* env: {
|
|
33
|
+
* baseUrl: { type: 'string', label: 'Backend base URL' },
|
|
34
|
+
* token: { type: 'string', label: 'Access token', sensitive: true },
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function provideSdApiContract(configuration: SdApiContractConfiguration): EnvironmentProviders;
|
|
40
|
+
/** Normalizes an optionally-injected configuration into one that is always safe to read. */
|
|
41
|
+
export declare function resolveSdApiContractConfiguration(configuration: SdApiContractConfiguration | null | undefined): SdApiContractConfiguration;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type SdApiContractExpressionRoot } from './api-contract.model';
|
|
2
|
+
/**
|
|
3
|
+
* How a `source` string relates to the expression grammar.
|
|
4
|
+
*
|
|
5
|
+
* - `literal` — no `${…}` at all.
|
|
6
|
+
* - `exact` — the whole string is one expression, so the referenced value keeps its own type.
|
|
7
|
+
* - `interpolated` — expressions embedded in surrounding text, so the result is always a string.
|
|
8
|
+
*/
|
|
9
|
+
export type SdApiContractTemplateKind = 'literal' | 'exact' | 'interpolated';
|
|
10
|
+
export type SdApiContractTemplateErrorCode = 'template.unterminated' | 'template.nested' | 'template.empty' | 'template.invalid-path' | 'template.unknown-root' | 'template.forbidden-segment';
|
|
11
|
+
export interface SdApiContractTemplateError {
|
|
12
|
+
code: SdApiContractTemplateErrorCode;
|
|
13
|
+
message: string;
|
|
14
|
+
/** Offset of the `${` that opened the offending expression. */
|
|
15
|
+
index: number;
|
|
16
|
+
raw: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SdApiContractExpressionReference {
|
|
19
|
+
root: SdApiContractExpressionRoot;
|
|
20
|
+
/** Segments *after* the root. `${input.customer.id}` → `['customer', 'id']`. */
|
|
21
|
+
path: readonly string[];
|
|
22
|
+
/** The inner text, e.g. `input.customer.id`. */
|
|
23
|
+
expression: string;
|
|
24
|
+
/** The full match including delimiters, e.g. `${input.customer.id}`. */
|
|
25
|
+
raw: string;
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
}
|
|
29
|
+
export interface SdApiContractTemplate {
|
|
30
|
+
kind: SdApiContractTemplateKind;
|
|
31
|
+
valid: boolean;
|
|
32
|
+
references: readonly SdApiContractExpressionReference[];
|
|
33
|
+
errors: readonly SdApiContractTemplateError[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parses a `source` / URL template into references and errors.
|
|
37
|
+
*
|
|
38
|
+
* Pure string scanning — no `eval`, no `new Function`, no expression evaluation of any kind. The
|
|
39
|
+
* grammar accepts exactly `${<root>.<identifier>(.<identifier>)*}` and nothing else.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseSdApiContractTemplate(source: unknown): SdApiContractTemplate;
|
|
42
|
+
/** The well-formed references of a template. Malformed expressions are dropped, not thrown. */
|
|
43
|
+
export declare function extractSdApiContractReferences(source: unknown): readonly SdApiContractExpressionReference[];
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { SdTemporalValueTransform } from '@sdcorejs/angular/forms/models';
|
|
2
|
+
/**
|
|
3
|
+
* Data-type vocabulary of an API contract.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately NOT reusing `SdQueryBuilderFieldType`: that union describes *filterable* fields in a
|
|
6
|
+
* query UI, this one describes the *shape of transported data*. They drift for different reasons.
|
|
7
|
+
*
|
|
8
|
+
* `date` / `datetime` are **logical** types — in the persisted JSON they are transported as strings,
|
|
9
|
+
* never as a JavaScript `Date`.
|
|
10
|
+
*/
|
|
11
|
+
export type SdApiContractDataType = 'string' | 'number' | 'boolean' | 'date' | 'datetime' | 'object' | 'array';
|
|
12
|
+
/** Every type that carries a single value (no `properties`, no `items`). */
|
|
13
|
+
export type SdApiContractScalarDataType = Exclude<SdApiContractDataType, 'object' | 'array'>;
|
|
14
|
+
/** Types that may appear as a temporal node and therefore accept `transform`. */
|
|
15
|
+
export type SdApiContractTemporalDataType = Extract<SdApiContractDataType, 'date' | 'datetime'>;
|
|
16
|
+
export declare const SD_API_CONTRACT_DATA_TYPES: readonly SdApiContractDataType[];
|
|
17
|
+
export declare const SD_API_CONTRACT_SCALAR_DATA_TYPES: readonly SdApiContractScalarDataType[];
|
|
18
|
+
export declare function sdIsApiContractDataType(value: unknown): value is SdApiContractDataType;
|
|
19
|
+
export declare function sdIsApiContractScalarDataType(value: unknown): value is SdApiContractScalarDataType;
|
|
20
|
+
export declare function sdIsApiContractTemporalDataType(value: unknown): value is SdApiContractTemporalDataType;
|
|
21
|
+
/**
|
|
22
|
+
* Anything a static literal may be. Mirrors what `JSON.parse` can produce, so a contract always
|
|
23
|
+
* round-trips through `JSON.stringify` without losing information.
|
|
24
|
+
*/
|
|
25
|
+
export type SdApiContractJsonValue = string | number | boolean | null | SdApiContractJsonValue[] | {
|
|
26
|
+
[key: string]: SdApiContractJsonValue;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Shared by every node in every layer.
|
|
30
|
+
*
|
|
31
|
+
* `required` is a **tri-state** and lives next to `type` (never a `required: string[]` array on the
|
|
32
|
+
* parent object, the way JSON Schema does it): `undefined` = not declared, `true` = mandatory,
|
|
33
|
+
* `false` = explicitly optional. The serializer omits `undefined` and keeps `false`.
|
|
34
|
+
*/
|
|
35
|
+
export interface SdApiContractNodeBase {
|
|
36
|
+
required?: boolean;
|
|
37
|
+
label?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The two mutually exclusive ways a mapped node receives a value.
|
|
42
|
+
*
|
|
43
|
+
* - `source` — a template referencing `${input.*}` / `${env.*}` / `${res.*}`.
|
|
44
|
+
* - `value` — a static JSON literal.
|
|
45
|
+
*
|
|
46
|
+
* A node carrying both is invalid (`mapping.source-and-value`).
|
|
47
|
+
*/
|
|
48
|
+
export interface SdApiContractMapping {
|
|
49
|
+
source?: string;
|
|
50
|
+
value?: SdApiContractJsonValue;
|
|
51
|
+
}
|
|
52
|
+
export interface SdApiContractFeScalarNode extends SdApiContractNodeBase {
|
|
53
|
+
type: SdApiContractScalarDataType;
|
|
54
|
+
/** Only meaningful on `date` / `datetime`; reused verbatim from the temporal form controls. */
|
|
55
|
+
transform?: SdTemporalValueTransform;
|
|
56
|
+
}
|
|
57
|
+
export interface SdApiContractFeObjectNode extends SdApiContractNodeBase {
|
|
58
|
+
type: 'object';
|
|
59
|
+
properties: Record<string, SdApiContractFeSchemaNode>;
|
|
60
|
+
}
|
|
61
|
+
export interface SdApiContractFeArrayNode extends SdApiContractNodeBase {
|
|
62
|
+
type: 'array';
|
|
63
|
+
items: SdApiContractFeSchemaNode;
|
|
64
|
+
}
|
|
65
|
+
/** A pure declaration — no `source` / `value`, because `input` is what the caller hands in. */
|
|
66
|
+
export type SdApiContractFeSchemaNode = SdApiContractFeScalarNode | SdApiContractFeObjectNode | SdApiContractFeArrayNode;
|
|
67
|
+
export interface SdApiContractRestScalarNode extends SdApiContractNodeBase {
|
|
68
|
+
type: SdApiContractScalarDataType;
|
|
69
|
+
}
|
|
70
|
+
export interface SdApiContractRestObjectNode extends SdApiContractNodeBase {
|
|
71
|
+
type: 'object';
|
|
72
|
+
properties: Record<string, SdApiContractRestNode>;
|
|
73
|
+
}
|
|
74
|
+
export interface SdApiContractRestArrayNode extends SdApiContractNodeBase {
|
|
75
|
+
type: 'array';
|
|
76
|
+
items: SdApiContractRestNode;
|
|
77
|
+
}
|
|
78
|
+
/** Describes what the backend returns. Never carries a mapping — nothing maps *into* a response. */
|
|
79
|
+
export type SdApiContractRestNode = SdApiContractRestScalarNode | SdApiContractRestObjectNode | SdApiContractRestArrayNode;
|
|
80
|
+
export interface SdApiContractMappedRestScalarNode extends SdApiContractNodeBase, SdApiContractMapping {
|
|
81
|
+
type: SdApiContractScalarDataType;
|
|
82
|
+
}
|
|
83
|
+
export interface SdApiContractMappedRestObjectNode extends SdApiContractNodeBase, SdApiContractMapping {
|
|
84
|
+
type: 'object';
|
|
85
|
+
/** Omitted when the whole object is mapped through `source` / `value`. */
|
|
86
|
+
properties?: Record<string, SdApiContractMappedRestNode>;
|
|
87
|
+
}
|
|
88
|
+
export interface SdApiContractMappedRestArrayNode extends SdApiContractNodeBase, SdApiContractMapping {
|
|
89
|
+
type: 'array';
|
|
90
|
+
/** Describes the element type. Per-item projection is intentionally out of scope. */
|
|
91
|
+
items: SdApiContractMappedRestNode;
|
|
92
|
+
}
|
|
93
|
+
export type SdApiContractMappedRestNode = SdApiContractMappedRestScalarNode | SdApiContractMappedRestObjectNode | SdApiContractMappedRestArrayNode;
|
|
94
|
+
export interface SdApiContractMappedFeScalarNode extends SdApiContractNodeBase, SdApiContractMapping {
|
|
95
|
+
type: SdApiContractScalarDataType;
|
|
96
|
+
transform?: SdTemporalValueTransform;
|
|
97
|
+
}
|
|
98
|
+
export interface SdApiContractMappedFeObjectNode extends SdApiContractNodeBase, SdApiContractMapping {
|
|
99
|
+
type: 'object';
|
|
100
|
+
properties?: Record<string, SdApiContractMappedFeSchemaNode>;
|
|
101
|
+
}
|
|
102
|
+
export interface SdApiContractMappedFeArrayNode extends SdApiContractNodeBase, SdApiContractMapping {
|
|
103
|
+
type: 'array';
|
|
104
|
+
items: SdApiContractMappedFeSchemaNode;
|
|
105
|
+
}
|
|
106
|
+
export type SdApiContractMappedFeSchemaNode = SdApiContractMappedFeScalarNode | SdApiContractMappedFeObjectNode | SdApiContractMappedFeArrayNode;
|
|
107
|
+
/** Structural union of every node shape, for utilities that traverse any layer. */
|
|
108
|
+
export type SdApiContractAnyNode = SdApiContractFeSchemaNode | SdApiContractRestNode | SdApiContractMappedRestNode | SdApiContractMappedFeSchemaNode;
|
|
109
|
+
export type SdApiContractHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
110
|
+
export declare const SD_API_CONTRACT_HTTP_METHODS: readonly SdApiContractHttpMethod[];
|
|
111
|
+
export declare function sdIsApiContractHttpMethod(value: unknown): value is SdApiContractHttpMethod;
|
|
112
|
+
/**
|
|
113
|
+
* The real HTTP request. No `.schema` wrapper on purpose — `req` *is* REST, so it exposes REST
|
|
114
|
+
* structure directly, while `input` / `output` are frontend contracts and keep their `.schema`.
|
|
115
|
+
*/
|
|
116
|
+
export interface SdApiContractRequest {
|
|
117
|
+
method: SdApiContractHttpMethod;
|
|
118
|
+
/** May interpolate `${env.*}` and carry REST placeholders such as `{id}`. */
|
|
119
|
+
url: string;
|
|
120
|
+
path?: Record<string, SdApiContractMappedRestNode>;
|
|
121
|
+
query?: Record<string, SdApiContractMappedRestNode>;
|
|
122
|
+
headers?: Record<string, SdApiContractMappedRestNode>;
|
|
123
|
+
body?: SdApiContractMappedRestNode;
|
|
124
|
+
}
|
|
125
|
+
export interface SdApiContractResponse {
|
|
126
|
+
/** One success status, or several. Each must be an integer in `100..599`. */
|
|
127
|
+
status: number | number[];
|
|
128
|
+
headers?: Record<string, SdApiContractRestNode>;
|
|
129
|
+
body?: SdApiContractRestNode;
|
|
130
|
+
}
|
|
131
|
+
/** The only `contractVersion` this release understands. */
|
|
132
|
+
export declare const SD_API_CONTRACT_VERSION = 1;
|
|
133
|
+
export interface SdApiContract {
|
|
134
|
+
contractVersion: 1;
|
|
135
|
+
code: string;
|
|
136
|
+
name: string;
|
|
137
|
+
description?: string;
|
|
138
|
+
input: {
|
|
139
|
+
schema: SdApiContractFeSchemaNode;
|
|
140
|
+
};
|
|
141
|
+
req: SdApiContractRequest;
|
|
142
|
+
res: SdApiContractResponse;
|
|
143
|
+
output: {
|
|
144
|
+
schema: SdApiContractMappedFeSchemaNode;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export type SdApiContractDiagnosticSeverity = 'error' | 'warning';
|
|
148
|
+
export interface SdApiContractDiagnostic {
|
|
149
|
+
/** Stable machine-readable identifier, e.g. `mapping.env.unknown`. Safe to switch on. */
|
|
150
|
+
code: string;
|
|
151
|
+
severity: SdApiContractDiagnosticSeverity;
|
|
152
|
+
/** Structural path into the contract, e.g. `req.body.properties.x`. Never localized. */
|
|
153
|
+
path: string;
|
|
154
|
+
/** Human-readable, English. The UI localizes by `code` when it wants a translated string. */
|
|
155
|
+
message: string;
|
|
156
|
+
}
|
|
157
|
+
/** Roots an expression may address. `output` is never a root — nothing reads from the output. */
|
|
158
|
+
export type SdApiContractExpressionRoot = 'input' | 'env' | 'res';
|
|
159
|
+
export declare const SD_API_CONTRACT_EXPRESSION_ROOTS: readonly SdApiContractExpressionRoot[];
|
|
160
|
+
/**
|
|
161
|
+
* Where a mapping lives, which decides the roots it may read.
|
|
162
|
+
*
|
|
163
|
+
* - `request` (`req.url` / `path` / `query` / `headers` / `body`) → `input`, `env`.
|
|
164
|
+
* - `output` (`output.schema`) → `res`, `input`, `env`.
|
|
165
|
+
*/
|
|
166
|
+
export type SdApiContractMappingContext = 'request' | 'output';
|
|
167
|
+
export declare const SD_API_CONTRACT_ALLOWED_ROOTS: Readonly<Record<SdApiContractMappingContext, readonly SdApiContractExpressionRoot[]>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SdApiContractConfiguration } from './api-contract.configuration';
|
|
2
|
+
import type { SdApiContract } from './api-contract.model';
|
|
3
|
+
/**
|
|
4
|
+
* Reference contracts, shared by the docs, the showcase and the test-suite so the canonical example
|
|
5
|
+
* can never drift between them.
|
|
6
|
+
*
|
|
7
|
+
* Each is a **factory**, not a constant: the builder takes a two-way `[(model)]`, and handing two
|
|
8
|
+
* demos the same object would let one seed the other.
|
|
9
|
+
*/
|
|
10
|
+
/** The env catalog the samples reference. Definitions only — no secret ever has a value here. */
|
|
11
|
+
export declare const SD_API_CONTRACT_SAMPLE_ENVIRONMENT: SdApiContractConfiguration;
|
|
12
|
+
/** `GET` list endpoint whose output is a root array — the dropdown / table shape. */
|
|
13
|
+
export declare function sdApiContractSearchSample(): SdApiContract;
|
|
14
|
+
/**
|
|
15
|
+
* `POST` endpoint showing every mapping flavour at once:
|
|
16
|
+
* `input.a → req.body.x`, `input.b → req.body.y`, `input.c → req.body.z`,
|
|
17
|
+
* `env.userId → req.body.u`, and a static literal in `req.body.v`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sdApiContractCreateSample(): SdApiContract;
|
|
20
|
+
/**
|
|
21
|
+
* Deliberately broken contract used to demonstrate the diagnostics: an undeclared env variable, a
|
|
22
|
+
* `{id}` placeholder with no `req.path` entry, a `${input.page}` that does not exist, and an output
|
|
23
|
+
* source pointing at a scalar while the output declares an array.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sdApiContractInvalidSample(): SdApiContract;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { SdTemporalValueTransform } from '@sdcorejs/angular/forms/models';
|
|
2
|
+
import { type SdApiContractDataType, type SdApiContractExpressionRoot, type SdApiContractJsonValue, type SdApiContractResponse } from './api-contract.model';
|
|
3
|
+
/**
|
|
4
|
+
* Structural view of a node from any layer (`input.schema`, `req.*`, `res.*`, `output.schema`).
|
|
5
|
+
*
|
|
6
|
+
* The traversal utilities are deliberately layer-agnostic: the four public node unions differ only
|
|
7
|
+
* in which members they *allow*, and every one of them is structurally assignable to this shape.
|
|
8
|
+
* Whether a member is legal where it appears is the validator's job, not the traversal's.
|
|
9
|
+
*/
|
|
10
|
+
export interface SdApiContractStructuralNode {
|
|
11
|
+
type: SdApiContractDataType;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
label?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
transform?: SdTemporalValueTransform;
|
|
16
|
+
source?: string;
|
|
17
|
+
value?: SdApiContractJsonValue;
|
|
18
|
+
properties?: Record<string, SdApiContractStructuralNode>;
|
|
19
|
+
items?: SdApiContractStructuralNode;
|
|
20
|
+
}
|
|
21
|
+
/** A node narrowed to the object shape, so `properties` is safe to read. */
|
|
22
|
+
export interface SdApiContractObjectShape extends SdApiContractStructuralNode {
|
|
23
|
+
type: 'object';
|
|
24
|
+
properties: Record<string, SdApiContractStructuralNode>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Structural pointer into a node tree: alternating `'properties', <key>` and `'items'` segments.
|
|
28
|
+
* The same shape the diagnostics use, so a diagnostic path can drive navigation in the UI.
|
|
29
|
+
*/
|
|
30
|
+
export type SdApiContractNodePointer = readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* One addressable field discovered by traversal.
|
|
33
|
+
*
|
|
34
|
+
* **Path convention:** dot-joined property names. Array items are *flattened under the array's own
|
|
35
|
+
* path* (`items.id`, and for a root array simply `id`), which is what a dropdown / table consumer
|
|
36
|
+
* wants. Set `arrays: 'stop'` to instead get exactly the set of paths an `${…}` expression can
|
|
37
|
+
* address — expressions never index into an array.
|
|
38
|
+
*/
|
|
39
|
+
export interface SdApiContractSchemaField {
|
|
40
|
+
path: string;
|
|
41
|
+
segments: readonly string[];
|
|
42
|
+
type: SdApiContractDataType;
|
|
43
|
+
required?: boolean;
|
|
44
|
+
label?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
/** `true` when traversal did not descend any further from this field. */
|
|
47
|
+
leaf: boolean;
|
|
48
|
+
/** `true` when the field was reached by descending through an array's `items`. */
|
|
49
|
+
arrayItem: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface SdApiContractFieldListOptions {
|
|
52
|
+
/** `'flatten'` (default) descends into array items; `'stop'` treats an array as a leaf. */
|
|
53
|
+
arrays?: 'flatten' | 'stop';
|
|
54
|
+
/** Dotted prefix prepended to every emitted path, e.g. `'body'`. */
|
|
55
|
+
basePath?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface SdApiContractResolvedReference {
|
|
58
|
+
type: SdApiContractDataType;
|
|
59
|
+
required?: boolean;
|
|
60
|
+
label?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
/** `null` for synthetic references such as `res.status`, which have no declared node. */
|
|
63
|
+
node: SdApiContractStructuralNode | null;
|
|
64
|
+
}
|
|
65
|
+
export interface SdApiContractUrlPlaceholders {
|
|
66
|
+
/** Unique placeholder names, in first-appearance order. */
|
|
67
|
+
names: readonly string[];
|
|
68
|
+
duplicates: readonly string[];
|
|
69
|
+
/** Raw fragments that look like a placeholder but are not one, e.g. `{}` or `{first name}`. */
|
|
70
|
+
malformed: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
/** Flattens a node tree into addressable fields. See `SdApiContractSchemaField` for the convention. */
|
|
73
|
+
export declare function listSdApiContractSchemaFields(node: SdApiContractStructuralNode, options?: SdApiContractFieldListOptions): readonly SdApiContractSchemaField[];
|
|
74
|
+
/** Every `${res.…}` path the output layer may address, in a stable order. */
|
|
75
|
+
export declare function listSdApiContractResponseFields(response: SdApiContractResponse): readonly SdApiContractSchemaField[];
|
|
76
|
+
/**
|
|
77
|
+
* Resolves a *logical* reference path (`customer.id`) against a schema.
|
|
78
|
+
*
|
|
79
|
+
* Arrays are terminal: `${res.body.items}` addresses the whole array, `${res.body.items.id}` does
|
|
80
|
+
* not exist because there is no element to address. Per-item projection is out of scope.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveSdApiContractSchemaPath(root: SdApiContractStructuralNode, path: readonly string[]): SdApiContractStructuralNode | null;
|
|
83
|
+
/** Resolves `status` / `headers.<name>` / `body.<path>` against a response declaration. */
|
|
84
|
+
export declare function resolveSdApiContractResponsePath(response: SdApiContractResponse, path: readonly string[]): SdApiContractResolvedReference | null;
|
|
85
|
+
/** Reads the node a structural pointer addresses, or `null` when the pointer does not resolve. */
|
|
86
|
+
export declare function getSdApiContractNodeAt(root: SdApiContractStructuralNode, pointer: SdApiContractNodePointer): SdApiContractStructuralNode | null;
|
|
87
|
+
/** Replaces the node a pointer addresses, rebuilding only the spine. Never mutates `root`. */
|
|
88
|
+
export declare function setSdApiContractNodeAt<T extends SdApiContractStructuralNode>(root: T, pointer: SdApiContractNodePointer, node: SdApiContractStructuralNode): T;
|
|
89
|
+
/** Appends a property. A key that already exists is left untouched — the caller must dedupe first. */
|
|
90
|
+
export declare function addSdApiContractProperty(node: SdApiContractStructuralNode, key: string, child: SdApiContractStructuralNode): SdApiContractObjectShape;
|
|
91
|
+
/** Renames a property **in place in the key order**, so the JSON diff stays readable. */
|
|
92
|
+
export declare function renameSdApiContractProperty(node: SdApiContractStructuralNode, from: string, to: string): SdApiContractObjectShape;
|
|
93
|
+
export declare function removeSdApiContractProperty(node: SdApiContractStructuralNode, key: string): SdApiContractObjectShape;
|
|
94
|
+
/** A minimal well-formed node of the given type. */
|
|
95
|
+
export declare function createSdApiContractNode(type: SdApiContractDataType): SdApiContractStructuralNode;
|
|
96
|
+
/**
|
|
97
|
+
* Retypes a node, dropping the members the new type cannot carry.
|
|
98
|
+
*
|
|
99
|
+
* Returns the same reference when the type is unchanged, so an idempotent UI write never produces a
|
|
100
|
+
* spurious `modelChange`.
|
|
101
|
+
*/
|
|
102
|
+
export declare function changeSdApiContractNodeType(node: SdApiContractStructuralNode, type: SdApiContractDataType): SdApiContractStructuralNode;
|
|
103
|
+
/** Deep copy of a node subtree. Used when a response subtree is adopted as the output schema. */
|
|
104
|
+
export declare function cloneSdApiContractNode<T extends SdApiContractStructuralNode>(node: T): T;
|
|
105
|
+
/**
|
|
106
|
+
* Deep copy of a whole contract.
|
|
107
|
+
*
|
|
108
|
+
* The builder clones on the way in so the object a parent owns is never reachable from an edit, and
|
|
109
|
+
* a consumer can do the same before handing a contract to anything that might mutate it.
|
|
110
|
+
*/
|
|
111
|
+
export declare function cloneSdApiContract<T>(contract: T): T;
|
|
112
|
+
export declare function sdApiContractRecordSet<T>(record: Record<string, T> | undefined, key: string, value: T): Record<string, T>;
|
|
113
|
+
export declare function sdApiContractRecordRemove<T>(record: Record<string, T>, key: string): Record<string, T>;
|
|
114
|
+
/** Renames a key in place. A collision or an empty target is a no-op — the caller reports it. */
|
|
115
|
+
export declare function sdApiContractRecordRename<T>(record: Record<string, T>, from: string, to: string): Record<string, T>;
|
|
116
|
+
/** Builds the canonical expression text — the inverse of `parseSdApiContractTemplate`. */
|
|
117
|
+
export declare function formatSdApiContractExpression(root: SdApiContractExpressionRoot, path: readonly string[]): string;
|
|
118
|
+
/** Joins a diagnostic base path with a structural pointer, e.g. `req.body` + `properties.x`. */
|
|
119
|
+
export declare function formatSdApiContractPointer(base: string, pointer: SdApiContractNodePointer): string;
|
|
120
|
+
/**
|
|
121
|
+
* Reads REST placeholders out of a URL template.
|
|
122
|
+
*
|
|
123
|
+
* `${…}` interpolation is masked out first, so `${env.baseUrl}` is never mistaken for a `{…}`
|
|
124
|
+
* path placeholder.
|
|
125
|
+
*/
|
|
126
|
+
export declare function parseSdApiContractUrlPlaceholders(url: string): SdApiContractUrlPlaceholders;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SdApiContract } from './api-contract.model';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic JSON for an API contract.
|
|
4
|
+
*
|
|
5
|
+
* Three guarantees the persisted file depends on:
|
|
6
|
+
*
|
|
7
|
+
* 1. **System keys are ordered**, so two authors editing the same contract produce the same bytes
|
|
8
|
+
* and a `git diff` shows the semantic change instead of a reshuffle.
|
|
9
|
+
* 2. **User-declared keys keep their order** (`properties`, `query`, `headers`, …) — that order is
|
|
10
|
+
* authored information, and sorting it would churn every diff.
|
|
11
|
+
* 3. **Only contract vocabulary survives.** The builder's transient UI state (expansion, selection,
|
|
12
|
+
* internal ids) is dropped by construction: the serializer copies a fixed key whitelist rather
|
|
13
|
+
* than the object it was handed, so a new piece of UI state can never leak into the file.
|
|
14
|
+
*
|
|
15
|
+
* `undefined` members are omitted; declared `false`, `0`, `null` and `""` are kept.
|
|
16
|
+
*/
|
|
17
|
+
export declare function serializeSdApiContract(contract: SdApiContract | null | undefined): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type SdApiContractConfiguration } from './api-contract.configuration';
|
|
2
|
+
import { type SdApiContractDiagnostic } from './api-contract.model';
|
|
3
|
+
/**
|
|
4
|
+
* Validates a contract against the grammar, the schema rules, the REST rules and the injected env
|
|
5
|
+
* catalog.
|
|
6
|
+
*
|
|
7
|
+
* Pure and UI-free: it takes `unknown` because an externally supplied contract may be malformed,
|
|
8
|
+
* and it **never repairs anything** — a silent fix would hide the very mistake the author needs to
|
|
9
|
+
* see. Diagnostics come back in a fixed traversal order (metadata → `input` → `req` → `res` →
|
|
10
|
+
* `output`, declaration order within each), so the same contract always yields the same list.
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateSdApiContract(contract: unknown, configuration?: SdApiContractConfiguration): SdApiContractDiagnostic[];
|
package/components/api-contract-builder/src/components/api-contract-diagnostic-list.component.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SdApiContractDiagnostic } from '../api-contract.model';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Validation summary + the diagnostic list.
|
|
5
|
+
*
|
|
6
|
+
* Severity is never signalled by colour alone: every row carries an icon, the severity word, the
|
|
7
|
+
* stable `code` and the structural `path`, so the list stays readable for a colour-blind reader and
|
|
8
|
+
* usable from a screen reader.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SdApiContractDiagnosticList {
|
|
11
|
+
diagnostics: import("@angular/core").InputSignal<readonly SdApiContractDiagnostic[]>;
|
|
12
|
+
autoId: import("@angular/core").InputSignal<string | null | undefined>;
|
|
13
|
+
navigate: import("@angular/core").OutputEmitterRef<SdApiContractDiagnostic>;
|
|
14
|
+
protected readonly errorCount: import("@angular/core").Signal<number>;
|
|
15
|
+
protected readonly warningCount: import("@angular/core").Signal<number>;
|
|
16
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SdApiContractDiagnosticList, never>;
|
|
17
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SdApiContractDiagnosticList, "sd-api-contract-diagnostic-list", never, { "diagnostics": { "alias": "diagnostics"; "required": false; "isSignal": true; }; "autoId": { "alias": "autoId"; "required": false; "isSignal": true; }; }, { "navigate": "navigate"; }, never, never, true, never>;
|
|
18
|
+
}
|