@ti-engine/web-framework 1.19.0 → 1.20.0
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/.env +4 -4
- package/CHANGELOG.md +384 -353
- package/README.md +73 -73
- package/bin/build/post-install.js +18 -18
- package/bin/localization/web-server-labels.json +27 -27
- package/bin/static/.well-known/appspecific/com.chrome.devtools.json +5 -5
- package/bin/static/fragments/components/component-notification-bar.html +21 -21
- package/bin/static/fragments/components/component-sidebar.html +33 -33
- package/bin/static/fragments/components/component-tooltip.html +10 -10
- package/bin/static/fragments/components/component-topbar.html +5 -5
- package/bin/static/fragments/frame-administration.html +2 -2
- package/bin/static/fragments/frame-application.html +18 -18
- package/bin/static/fragments/frame-dashboard.html +2 -2
- package/bin/static/fragments/frame-login.html +119 -119
- package/bin/static/fragments/frame-not-found.html +2 -2
- package/bin/static/fragments/frame-profile.html +2 -2
- package/bin/static/index.html +22 -22
- package/bin/static/scripts/ti-charts.js +1591 -1591
- package/bin/static/scripts/ti-framework.css +3194 -3194
- package/bin/static/scripts/ti-framework.js +1427 -1427
- package/bin/static/scripts/ti-theme-black-glass.css +216 -216
- package/bin/static/scripts/ti-theme-daylight.css +87 -87
- package/bin/web-app-manager.js +660 -663
- package/bin/web-server.js +936 -937
- package/bin/web-server.json +48 -48
- package/components/admin-config-handlers.js +95 -92
- package/components/auth-manager.js +438 -442
- package/components/authorization.js +135 -135
- package/components/config-change-notifier.js +98 -98
- package/components/config-registry.js +257 -260
- package/components/config-service.js +363 -360
- package/components/config-store.js +244 -246
- package/components/definitions.types.js +28 -26
- package/components/session-store.js +113 -110
- package/components/user.js +134 -132
- package/components/web-config-env.js +85 -85
- package/components/web-handlers.js +803 -800
- package/package.json +139 -67
- package/types/bin/web-app-manager.d.ts +194 -0
- package/types/bin/web-server.d.ts +373 -0
- package/types/components/admin-config-handlers.d.ts +11 -0
- package/types/components/auth-manager.d.ts +125 -0
- package/types/components/authorization.d.ts +54 -0
- package/types/components/config-change-notifier.d.ts +73 -0
- package/types/components/config-registry.d.ts +149 -0
- package/types/components/config-service.d.ts +218 -0
- package/types/components/config-store.d.ts +128 -0
- package/types/components/definitions.types.d.ts +31 -0
- package/types/components/session-store.d.ts +56 -0
- package/types/components/user.d.ts +83 -0
- package/types/components/web-config-env.d.ts +17 -0
- package/types/components/web-handlers.d.ts +23 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export = ConfigRegistry;
|
|
2
|
+
export type ConfigValidationIssue = {
|
|
3
|
+
/**
|
|
4
|
+
* A JSON pointer / data path to the offending value (e.g. ".competencies.E1-1.name"), or "".
|
|
5
|
+
*/
|
|
6
|
+
path: string;
|
|
7
|
+
/**
|
|
8
|
+
* Human-readable problem description.
|
|
9
|
+
*/
|
|
10
|
+
message: string;
|
|
11
|
+
/**
|
|
12
|
+
* "schema" for JSON-Schema failures, or a validator-supplied code (default "semantic").
|
|
13
|
+
*/
|
|
14
|
+
code: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional structured details (e.g. ajv params).
|
|
17
|
+
*/
|
|
18
|
+
params?: Object;
|
|
19
|
+
};
|
|
20
|
+
export type ValidatorContext = {
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the *pending* value of `key` when it is part of the
|
|
23
|
+
* current edit batch, otherwise its current committed value. Lets a validator check a sibling document's
|
|
24
|
+
* post-edit state — but calling this for the document being validated itself just returns the same incoming
|
|
25
|
+
* value already passed as the validator's first argument, not its prior state.
|
|
26
|
+
*/
|
|
27
|
+
getConfig: (key: string) => Promise<any>;
|
|
28
|
+
/**
|
|
29
|
+
* Always resolves the current committed value of `key`,
|
|
30
|
+
* even when `key` is the document currently under validation. Use this to compare a document against its own
|
|
31
|
+
* previous state (e.g. detecting an edit that should have bumped a version marker).
|
|
32
|
+
*/
|
|
33
|
+
getStoredConfig: (key: string) => Promise<any>;
|
|
34
|
+
};
|
|
35
|
+
export type SemanticValidator = (value: Object, context: ValidatorContext) => ConfigValidationIssue[] | Promise<ConfigValidationIssue[]>;
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} ConfigValidationIssue
|
|
38
|
+
* @property {string} path A JSON pointer / data path to the offending value (e.g. ".competencies.E1-1.name"), or "".
|
|
39
|
+
* @property {string} message Human-readable problem description.
|
|
40
|
+
* @property {string} code "schema" for JSON-Schema failures, or a validator-supplied code (default "semantic").
|
|
41
|
+
* @property {Object} [params] Optional structured details (e.g. ajv params).
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* The cross-document read context passed to every {@link SemanticValidator}, built fresh for each
|
|
45
|
+
* {@link ConfigService#applyEdits} call.
|
|
46
|
+
*
|
|
47
|
+
* @typedef {Object} ValidatorContext
|
|
48
|
+
* @property {(key: string) => Promise<*>} getConfig Resolves the *pending* value of `key` when it is part of the
|
|
49
|
+
* current edit batch, otherwise its current committed value. Lets a validator check a sibling document's
|
|
50
|
+
* post-edit state — but calling this for the document being validated itself just returns the same incoming
|
|
51
|
+
* value already passed as the validator's first argument, not its prior state.
|
|
52
|
+
* @property {(key: string) => Promise<*>} getStoredConfig Always resolves the current committed value of `key`,
|
|
53
|
+
* even when `key` is the document currently under validation. Use this to compare a document against its own
|
|
54
|
+
* previous state (e.g. detecting an edit that should have bumped a version marker).
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {(value: Object, context: ValidatorContext) => ConfigValidationIssue[]|Promise<ConfigValidationIssue[]>} SemanticValidator
|
|
58
|
+
* A semantic validator receives the candidate value and a {@link ValidatorContext} and returns the issues it found
|
|
59
|
+
* (empty array = OK). May be async.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Registry of editable configuration *documents* and the gate that validates a candidate value against a
|
|
63
|
+
* document's JSON Schema (ajv) plus its semantic validators. The framework stays domain-agnostic: an application
|
|
64
|
+
* registers its config documents (schemas, validators, defaults, editor metadata) at startup; this component knows
|
|
65
|
+
* only "validated, versioned JSON documents". Validation must pass *before* {@link ConfigStore#saveChangeSet}.
|
|
66
|
+
*
|
|
67
|
+
* @class ConfigRegistry
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
declare class ConfigRegistry {
|
|
71
|
+
#private;
|
|
72
|
+
constructor();
|
|
73
|
+
/**
|
|
74
|
+
* Registers an editable configuration document.
|
|
75
|
+
*
|
|
76
|
+
* @method
|
|
77
|
+
* @param {string} configKey Stable identifier for the document (also the ConfigStore key).
|
|
78
|
+
* @param {Object} definition
|
|
79
|
+
* @param {Object} definition.schema A JSON Schema for the document.
|
|
80
|
+
* @param {SemanticValidator[]} [definition.validators] Cross-cutting/semantic checks beyond the schema.
|
|
81
|
+
* @param {Object} [definition.defaultValue] The bootstrap default (seeds an empty store).
|
|
82
|
+
* @param {Object} [definition.metadata] Editor metadata (label, editor type, group, risk class, …).
|
|
83
|
+
* @returns {ConfigRegistry} this (chainable)
|
|
84
|
+
* @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} On missing key/schema.
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
register(configKey: string, definition: {
|
|
88
|
+
schema: Object;
|
|
89
|
+
validators?: SemanticValidator[];
|
|
90
|
+
defaultValue?: Object;
|
|
91
|
+
metadata?: Object;
|
|
92
|
+
}): ConfigRegistry;
|
|
93
|
+
/**
|
|
94
|
+
* Adds a schema that is referenced (via `$ref`/`$id`) by document schemas but is not itself a document.
|
|
95
|
+
*
|
|
96
|
+
* @method
|
|
97
|
+
* @param {Object} schema
|
|
98
|
+
* @returns {ConfigRegistry} this (chainable)
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
addSchema(schema: Object): ConfigRegistry;
|
|
102
|
+
/**
|
|
103
|
+
* @method
|
|
104
|
+
* @param {string} configKey
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
has(configKey: string): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* @method
|
|
111
|
+
* @returns {string[]} All registered configuration keys.
|
|
112
|
+
* @public
|
|
113
|
+
*/
|
|
114
|
+
list(): string[];
|
|
115
|
+
/**
|
|
116
|
+
* @method
|
|
117
|
+
* @param {string} configKey
|
|
118
|
+
* @returns {Object|undefined} The editor metadata registered for the document.
|
|
119
|
+
* @public
|
|
120
|
+
*/
|
|
121
|
+
metadataFor(configKey: string): Object | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* @method
|
|
124
|
+
* @param {string} configKey
|
|
125
|
+
* @returns {Object|undefined} The bootstrap default value registered for the document.
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
getDefault(configKey: string): Object | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Validates a candidate value for a registered document: JSON Schema first, then the semantic validators.
|
|
131
|
+
* Resolves with `{ valid, errors }` (errors is an array of {@link ConfigValidationIssue}).
|
|
132
|
+
*
|
|
133
|
+
* @method
|
|
134
|
+
* @param {string} configKey
|
|
135
|
+
* @param {Object} value
|
|
136
|
+
* @param {ValidatorContext} [context] Passed to each semantic validator.
|
|
137
|
+
* @returns {Promise<{valid: boolean, errors: ConfigValidationIssue[]}>}
|
|
138
|
+
* @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} If the document is not registered.
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
validate(configKey: string, value: Object, context?: ValidatorContext): Promise<{
|
|
142
|
+
valid: boolean;
|
|
143
|
+
errors: ConfigValidationIssue[];
|
|
144
|
+
}>;
|
|
145
|
+
}
|
|
146
|
+
declare namespace ConfigRegistry {
|
|
147
|
+
export { instance };
|
|
148
|
+
}
|
|
149
|
+
declare const instance: ConfigRegistry;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export = ConfigService;
|
|
2
|
+
import type ConfigChangeNotifier from "#config-change-notifier";
|
|
3
|
+
import type ConfigRegistry from "#config-registry";
|
|
4
|
+
import type ConfigStore from "#config-store";
|
|
5
|
+
/** @import ConfigChangeNotifier from "#config-change-notifier" */
|
|
6
|
+
/** @import ConfigRegistry from "#config-registry" */
|
|
7
|
+
/** @import ConfigStore from "#config-store" */
|
|
8
|
+
/**
|
|
9
|
+
* Orchestrates validated, versioned configuration edits on top of {@link ConfigStore} and {@link ConfigRegistry}.
|
|
10
|
+
*
|
|
11
|
+
* Two layers:
|
|
12
|
+
* - **Document level** — {@link ConfigService#applyEdits}: validate every affected document (schema + semantic,
|
|
13
|
+
* with a cross-document {@link ValidatorContext} whose `getConfig` sees the *pending* values of the same edit —
|
|
14
|
+
* letting a validator check a sibling document's post-edit state — while `getStoredConfig` always returns the
|
|
15
|
+
* committed value, even for the document currently under validation) and, only if all pass, commit them as one
|
|
16
|
+
* change-set. Validation failures return `{ ok:false, errors }` and write nothing; a version conflict from the
|
|
17
|
+
* store surfaces as a rejection.
|
|
18
|
+
* - **Entity level** — composite editors registered with `compose(docs)→view` / `decompose(edited, docs)→{key:value}`,
|
|
19
|
+
* so the UI edits a domain entity (e.g. a "competency") that is projected from, and scattered back into, several
|
|
20
|
+
* documents. {@link ConfigService#saveEditorEdit} decomposes the edit and routes it through `applyEdits`.
|
|
21
|
+
*
|
|
22
|
+
* @class ConfigService
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
declare class ConfigService {
|
|
26
|
+
#private;
|
|
27
|
+
/**
|
|
28
|
+
* @constructor
|
|
29
|
+
* @param {Object} [options]
|
|
30
|
+
* @param {ConfigStore} [options.store] Defaults to the ConfigStore singleton.
|
|
31
|
+
* @param {ConfigRegistry} [options.registry] Defaults to the ConfigRegistry singleton.
|
|
32
|
+
* @param {ConfigChangeNotifier} [options.notifier] Defaults to the ConfigChangeNotifier singleton.
|
|
33
|
+
*/
|
|
34
|
+
constructor(options?: {
|
|
35
|
+
store?: ConfigStore;
|
|
36
|
+
registry?: ConfigRegistry;
|
|
37
|
+
notifier?: ConfigChangeNotifier;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Validates and commits a set of document edits atomically. Each edit: `{ configKey, value, expectedVersion }`.
|
|
41
|
+
*
|
|
42
|
+
* @method
|
|
43
|
+
* @param {Array<{configKey: string, value: Object, expectedVersion: number}>} edits
|
|
44
|
+
* @param {Object} meta
|
|
45
|
+
* @param {string} meta.adminID
|
|
46
|
+
* @param {string} [meta.note]
|
|
47
|
+
* @returns {Promise<{ok: true, changeSetID: string, versions: Object<string, number>} | {ok: false, errors: Object<string, Array>}>}
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
applyEdits(edits: Array<{
|
|
51
|
+
configKey: string;
|
|
52
|
+
value: Object;
|
|
53
|
+
expectedVersion: number;
|
|
54
|
+
}>, meta: {
|
|
55
|
+
adminID: string;
|
|
56
|
+
note?: string;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
ok: true;
|
|
59
|
+
changeSetID: string;
|
|
60
|
+
versions: Record<string, number>;
|
|
61
|
+
} | {
|
|
62
|
+
ok: false;
|
|
63
|
+
errors: Record<string, any[]>;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Registers a composite editor over one or more documents.
|
|
67
|
+
*
|
|
68
|
+
* @method
|
|
69
|
+
* @param {string} editorKey
|
|
70
|
+
* @param {Object} definition
|
|
71
|
+
* @param {string[]} definition.documents The configKeys this editor spans.
|
|
72
|
+
* @param {(configs: Object) => *} definition.compose Maps `{ [configKey]: value }` → a view for the UI.
|
|
73
|
+
* @param {(editedView: *, currentDocs: Object) => Object.<string, Object>} definition.decompose Maps `(editedView, currentDocs)` → the
|
|
74
|
+
* full new values for the documents that changed (`{ [configKey]: newValue }`).
|
|
75
|
+
* @param {Object} [definition.metadata]
|
|
76
|
+
* @returns {ConfigService} this (chainable)
|
|
77
|
+
* @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS}
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
registerEditor(editorKey: string, definition: {
|
|
81
|
+
documents: string[];
|
|
82
|
+
compose: (configs: Object) => any;
|
|
83
|
+
decompose: (editedView: any, currentDocs: Object) => Record<string, Object>;
|
|
84
|
+
metadata?: Object;
|
|
85
|
+
}): ConfigService;
|
|
86
|
+
/**
|
|
87
|
+
* @method
|
|
88
|
+
* @param {string} editorKey
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
hasEditor(editorKey: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* @method
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
listEditors(): string[];
|
|
99
|
+
/**
|
|
100
|
+
* Loads the editor's documents and composes them into a view. Returns the view plus the current per-document
|
|
101
|
+
* versions, which the client must echo back on save for optimistic locking.
|
|
102
|
+
*
|
|
103
|
+
* @method
|
|
104
|
+
* @param {string} editorKey
|
|
105
|
+
* @returns {Promise<{rows: *, versions: Object<string, number>}>}
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
composeView(editorKey: string): Promise<{
|
|
109
|
+
rows: any;
|
|
110
|
+
versions: Record<string, number>;
|
|
111
|
+
}>;
|
|
112
|
+
/**
|
|
113
|
+
* Applies an edit made against a composite editor: decompose the edited view into per-document new values, then
|
|
114
|
+
* route through {@link ConfigService#applyEdits} (validate-all → atomic change-set). `expectedVersions` should be
|
|
115
|
+
* the versions returned by {@link ConfigService#composeView} when the edit started.
|
|
116
|
+
*
|
|
117
|
+
* @method
|
|
118
|
+
* @param {string} editorKey
|
|
119
|
+
* @param {*} editedView
|
|
120
|
+
* @param {Object} meta
|
|
121
|
+
* @param {Object<string, number>} [expectedVersions]
|
|
122
|
+
* @returns {Promise<Object>} The {@link ConfigService#applyEdits} result (or `{ ok:true, changeSetID:null }` if nothing changed).
|
|
123
|
+
* @public
|
|
124
|
+
*/
|
|
125
|
+
saveEditorEdit(editorKey: string, editedView: any, meta: Object, expectedVersions?: Record<string, number>): Promise<Object>;
|
|
126
|
+
/**
|
|
127
|
+
* Restores a prior change-set through the validated path: rebuild edits from the change-set's historic snapshots
|
|
128
|
+
* and route them through {@link ConfigService#applyEdits} — so the restore is **re-validated against the current
|
|
129
|
+
* schemas/validators** (a snapshot valid when written may be invalid now) and emits `config:changed`. Returns the
|
|
130
|
+
* `applyEdits` result (`{ ok:false, errors }` if a snapshot no longer validates; nothing is written then).
|
|
131
|
+
*
|
|
132
|
+
* @method
|
|
133
|
+
* @param {string} changeSetID
|
|
134
|
+
* @param {Object} meta
|
|
135
|
+
* @param {string} meta.adminID
|
|
136
|
+
* @param {string} [meta.note]
|
|
137
|
+
* @returns {Promise<Object>}
|
|
138
|
+
* @public
|
|
139
|
+
*/
|
|
140
|
+
restoreChangeSet(changeSetID: string, meta: {
|
|
141
|
+
adminID: string;
|
|
142
|
+
note?: string;
|
|
143
|
+
}): Promise<Object>;
|
|
144
|
+
/**
|
|
145
|
+
* @method
|
|
146
|
+
* @param {string} configKey
|
|
147
|
+
* @returns {Promise<Object|null>} The current envelope for a configuration document.
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
getCurrent(configKey: string): Promise<Object | null>;
|
|
151
|
+
/**
|
|
152
|
+
* @method
|
|
153
|
+
* @param {string} configKey
|
|
154
|
+
* @returns {Promise<Array<Object>>} The document's version history (ascending), each a full snapshot entry.
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
getHistory(configKey: string): Promise<Array<Object>>;
|
|
158
|
+
/**
|
|
159
|
+
* @method
|
|
160
|
+
* @param {string} changeSetID
|
|
161
|
+
* @returns {Promise<Object|null>} A single change-set record.
|
|
162
|
+
* @public
|
|
163
|
+
*/
|
|
164
|
+
getChange(changeSetID: string): Promise<Object | null>;
|
|
165
|
+
/**
|
|
166
|
+
* @method
|
|
167
|
+
* @returns {Promise<Array<Object>>} The cross-document audit feed (change-sets, most-recent first).
|
|
168
|
+
* @public
|
|
169
|
+
*/
|
|
170
|
+
listChanges(): Promise<Array<Object>>;
|
|
171
|
+
/**
|
|
172
|
+
* Builds a downloadable snapshot of the current live configuration — for every registered document, its repo file
|
|
173
|
+
* `path` (from registration metadata, if provided), current `version`, and `value`. This is the one-way export
|
|
174
|
+
* *out* of the store; an admin downloads it and commits the files to git. The store remains the live truth.
|
|
175
|
+
*
|
|
176
|
+
* @method
|
|
177
|
+
* @param {Object} [meta]
|
|
178
|
+
* @param {string} [meta.adminID]
|
|
179
|
+
* @returns {Promise<{exportedAt: string, exportedBy: (string|null), documents: Array<{configKey: string, path: (string|null), version: number, value: Object}>}>}
|
|
180
|
+
* @public
|
|
181
|
+
*/
|
|
182
|
+
exportBundle(meta?: {
|
|
183
|
+
adminID?: string;
|
|
184
|
+
}): Promise<{
|
|
185
|
+
exportedAt: string;
|
|
186
|
+
exportedBy: (string | null);
|
|
187
|
+
documents: Array<{
|
|
188
|
+
configKey: string;
|
|
189
|
+
path: (string | null);
|
|
190
|
+
version: number;
|
|
191
|
+
value: Object;
|
|
192
|
+
}>;
|
|
193
|
+
}>;
|
|
194
|
+
/**
|
|
195
|
+
* Seeds a document's default value into the store only if it has never been written (idempotent bootstrap).
|
|
196
|
+
* Used by an application to bring its file defaults into the store at startup before serving live config.
|
|
197
|
+
*
|
|
198
|
+
* @method
|
|
199
|
+
* @param {string} configKey
|
|
200
|
+
* @param {Object} defaultValue
|
|
201
|
+
* @returns {Promise<Object>} The current envelope.
|
|
202
|
+
* @public
|
|
203
|
+
*/
|
|
204
|
+
seedDefault(configKey: string, defaultValue: Object): Promise<Object>;
|
|
205
|
+
/**
|
|
206
|
+
* Subscribes a listener to `config:changed` events (delegates to the change notifier). Returns an unsubscribe fn.
|
|
207
|
+
*
|
|
208
|
+
* @method
|
|
209
|
+
* @param {(configs: Object) => void} listener
|
|
210
|
+
* @returns {() => void}
|
|
211
|
+
* @public
|
|
212
|
+
*/
|
|
213
|
+
onConfigChanged(listener: (configs: Object) => void): () => void;
|
|
214
|
+
}
|
|
215
|
+
declare namespace ConfigService {
|
|
216
|
+
export { instance };
|
|
217
|
+
}
|
|
218
|
+
declare const instance: ConfigService;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export = ConfigStore;
|
|
2
|
+
/**
|
|
3
|
+
* A versioned, change-set-aware configuration store backed by the common memory cache (RedisJSON).
|
|
4
|
+
* <br/>
|
|
5
|
+
* Each editable configuration is a *document* identified by a `configKey`. Every committed edit:
|
|
6
|
+
* - bumps the document's monotonic `version`,
|
|
7
|
+
* - writes a full **snapshot** to history (enabling restore),
|
|
8
|
+
* - and is correlated with the other documents written in the same logical edit via a shared **change-set** id,
|
|
9
|
+
* so a multi-document edit (and its restore) is treated as one unit even though storage is per-document.
|
|
10
|
+
* <br/>
|
|
11
|
+
* Optimistic locking: callers pass the `expectedVersion` they edited from; the save is rejected if any document
|
|
12
|
+
* moved on in the meantime. This component is storage-only — schema/semantic validation is a separate pipeline
|
|
13
|
+
* that must run *before* {@link ConfigStore#saveChangeSet}.
|
|
14
|
+
* <br/>
|
|
15
|
+
* NOTE: true cross-document atomicity is not provided (the cache exposes per-key commands only). All locks are
|
|
16
|
+
* checked *before* any write, so the common conflict case is safe; a mid-write process failure can leave a
|
|
17
|
+
* partially-applied change-set, detectable via the change-set record. Hardening (a Lua/MULTI write) is deferred.
|
|
18
|
+
*
|
|
19
|
+
* @class ConfigStore
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
declare class ConfigStore {
|
|
23
|
+
#private;
|
|
24
|
+
/**
|
|
25
|
+
* Returns the current envelope `{ value, version, updatedAt, updatedBy, changeSetID }` for a configuration
|
|
26
|
+
* document, or `null` if it has never been written.
|
|
27
|
+
*
|
|
28
|
+
* @method
|
|
29
|
+
* @param {string} configKey
|
|
30
|
+
* @returns {Promise<Object|null>}
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
getCurrent(configKey: string): Promise<Object | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Writes the default value as version 1 only if the document does not yet exist (idempotent bootstrap).
|
|
36
|
+
* Resolves with the current envelope either way.
|
|
37
|
+
*
|
|
38
|
+
* @method
|
|
39
|
+
* @param {string} configKey
|
|
40
|
+
* @param {Object} defaultValue
|
|
41
|
+
* @returns {Promise<Object>}
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
seedIfEmpty(configKey: string, defaultValue: Object): Promise<Object>;
|
|
45
|
+
/**
|
|
46
|
+
* Commits an edit spanning one or more documents as a single change-set. All optimistic-lock checks run before
|
|
47
|
+
* any write. Each edit: `{ configKey, value, expectedVersion }`.
|
|
48
|
+
*
|
|
49
|
+
* @method
|
|
50
|
+
* @param {Array<{configKey: string, value: Object, expectedVersion: number}>} edits
|
|
51
|
+
* @param {Object} meta
|
|
52
|
+
* @param {string} meta.adminID
|
|
53
|
+
* @param {string} [meta.note]
|
|
54
|
+
* @returns {Promise<{changeSetID: string, versions: Object<string, number>}>}
|
|
55
|
+
* @throws {TiException.E_WEB_INVALID_REQUEST_PARAMETERS} On bad input or a version conflict (see `details`).
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
saveChangeSet(edits: Array<{
|
|
59
|
+
configKey: string;
|
|
60
|
+
value: Object;
|
|
61
|
+
expectedVersion: number;
|
|
62
|
+
}>, meta: {
|
|
63
|
+
adminID: string;
|
|
64
|
+
note?: string;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
changeSetID: string;
|
|
67
|
+
versions: Record<string, number>;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Returns all history entries for a document, ascending by version.
|
|
71
|
+
*
|
|
72
|
+
* @method
|
|
73
|
+
* @param {string} configKey
|
|
74
|
+
* @returns {Promise<Array<Object>>}
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
listHistory(configKey: string): Promise<Array<Object>>;
|
|
78
|
+
/**
|
|
79
|
+
* Returns a single history snapshot entry for a document version, or `null`.
|
|
80
|
+
*
|
|
81
|
+
* @method
|
|
82
|
+
* @param {string} configKey
|
|
83
|
+
* @param {number} version
|
|
84
|
+
* @returns {Promise<Object|null>}
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
getVersion(configKey: string, version: number): Promise<Object | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Returns a change-set record by id, or `null`.
|
|
90
|
+
*
|
|
91
|
+
* @method
|
|
92
|
+
* @param {string} changeSetID
|
|
93
|
+
* @returns {Promise<Object|null>}
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
getChangeSet(changeSetID: string): Promise<Object | null>;
|
|
97
|
+
/**
|
|
98
|
+
* Returns every change-set record, most-recent first (the cross-document audit feed).
|
|
99
|
+
*
|
|
100
|
+
* @method
|
|
101
|
+
* @returns {Promise<Array<Object>>}
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
listChangeSets(): Promise<Array<Object>>;
|
|
105
|
+
/**
|
|
106
|
+
* Restores every document in a prior change-set to that change-set's snapshot, committing it as a *new*
|
|
107
|
+
* change-set (restore is never destructive — it moves forward to a past state).
|
|
108
|
+
*
|
|
109
|
+
* @method
|
|
110
|
+
* @param {string} changeSetID
|
|
111
|
+
* @param {Object} meta
|
|
112
|
+
* @param {string} meta.adminID
|
|
113
|
+
* @param {string} [meta.note]
|
|
114
|
+
* @returns {Promise<{changeSetID: string, versions: Object<string, number>}>}
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
117
|
+
restoreChangeSet(changeSetID: string, meta: {
|
|
118
|
+
adminID: string;
|
|
119
|
+
note?: string;
|
|
120
|
+
}): Promise<{
|
|
121
|
+
changeSetID: string;
|
|
122
|
+
versions: Record<string, number>;
|
|
123
|
+
}>;
|
|
124
|
+
}
|
|
125
|
+
declare namespace ConfigStore {
|
|
126
|
+
var _a: Readonly<ConfigStore>;
|
|
127
|
+
export { _a as instance };
|
|
128
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TiLocalizationLanguage } from "@ti-engine/core/localization";
|
|
2
|
+
export type TiSessionCallback = (error?: Error | null) => void;
|
|
3
|
+
export type TiSession = {
|
|
4
|
+
id: string;
|
|
5
|
+
user?: Object;
|
|
6
|
+
language?: TiLocalizationLanguage;
|
|
7
|
+
cookie?: Object;
|
|
8
|
+
oidc?: Object;
|
|
9
|
+
csrfToken?: string;
|
|
10
|
+
regenerate: (callback: TiSessionCallback) => TiSession;
|
|
11
|
+
destroy: (callback: TiSessionCallback) => TiSession;
|
|
12
|
+
save: (callback?: TiSessionCallback) => TiSession;
|
|
13
|
+
};
|
|
14
|
+
/** @import { TiLocalizationLanguage } from "@ti-engine/core/localization" */
|
|
15
|
+
/**
|
|
16
|
+
* @callback TiSessionCallback
|
|
17
|
+
* @param {Error|null} [error]
|
|
18
|
+
* @returns {void}
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} TiSession
|
|
22
|
+
* @property {string} id
|
|
23
|
+
* @property {Object} [user]
|
|
24
|
+
* @property {TiLocalizationLanguage} [language]
|
|
25
|
+
* @property {Object} [cookie]
|
|
26
|
+
* @property {Object} [oidc]
|
|
27
|
+
* @property {string} [csrfToken]
|
|
28
|
+
* @property {(callback: TiSessionCallback) => TiSession} regenerate
|
|
29
|
+
* @property {(callback: TiSessionCallback) => TiSession} destroy
|
|
30
|
+
* @property {(callback?: TiSessionCallback) => TiSession} save
|
|
31
|
+
*/
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export = SessionStore;
|
|
2
|
+
import session = require("express-session");
|
|
3
|
+
import type { SessionData } from "express-session";
|
|
4
|
+
import type { TiException } from "@ti-engine/core/exceptions";
|
|
5
|
+
/**
|
|
6
|
+
* A session store for the web server using the standard 'cache' module of the ti-engine.
|
|
7
|
+
* <br/>
|
|
8
|
+
* NOTE: This implementation is compatible with the 'express-session' module.
|
|
9
|
+
*
|
|
10
|
+
* @class SessionStore
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
declare class SessionStore extends session.Store {
|
|
14
|
+
/**
|
|
15
|
+
* @constructor
|
|
16
|
+
*/
|
|
17
|
+
constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Used to store a user session in the cache.
|
|
20
|
+
*
|
|
21
|
+
* @method
|
|
22
|
+
* @param {string} sessionID
|
|
23
|
+
* @param {SessionData} session
|
|
24
|
+
* @param {(error?: Error|TiException|null) => void} callback
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
set(sessionID: string, session: SessionData, callback: (error?: Error | TiException | null) => void): void;
|
|
28
|
+
/**
|
|
29
|
+
* Used to retrieve a user session from the cache.
|
|
30
|
+
*
|
|
31
|
+
* @method
|
|
32
|
+
* @param {string} sessionID
|
|
33
|
+
* @param {(error?: Error|TiException|null, session?: SessionData|null) => void} callback
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
get(sessionID: string, callback: (error?: Error | TiException | null, session?: SessionData | null) => void): void;
|
|
37
|
+
/**
|
|
38
|
+
* Used to remove a user session from the cache.
|
|
39
|
+
*
|
|
40
|
+
* @method
|
|
41
|
+
* @param {string} sessionID
|
|
42
|
+
* @param {(error?: Error|TiException|null) => void} callback
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
destroy(sessionID: string, callback: (error?: Error | TiException | null) => void): void;
|
|
46
|
+
/**
|
|
47
|
+
* Used to update the expiration time of a user session in the cache.
|
|
48
|
+
*
|
|
49
|
+
* @method
|
|
50
|
+
* @param {string} sessionID
|
|
51
|
+
* @param {SessionData} session
|
|
52
|
+
* @param {(error?: Error|TiException|null) => void} callback
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
touch(sessionID: string, session: SessionData, callback: (error?: Error | TiException | null) => void): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export = User;
|
|
2
|
+
import type { TiLocalizationLanguage } from "@ti-engine/core/localization";
|
|
3
|
+
/** @import { TiLocalizationLanguage } from "@ti-engine/core/localization" */
|
|
4
|
+
/**
|
|
5
|
+
* Represents a user in the system.
|
|
6
|
+
*
|
|
7
|
+
* @class User
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
declare class User {
|
|
11
|
+
#private;
|
|
12
|
+
/**
|
|
13
|
+
* @constructor
|
|
14
|
+
* @param {Object} userData
|
|
15
|
+
* @param {string} userData.userID
|
|
16
|
+
* @param {string} [userData.username]
|
|
17
|
+
* @param {string} [userData.email]
|
|
18
|
+
* @param {string} [userData.name]
|
|
19
|
+
* @param {TiLocalizationLanguage} [userData.language]
|
|
20
|
+
* @param {string[]} [userData.roles]
|
|
21
|
+
* @param {string[]} [userData.permissions]
|
|
22
|
+
* @param {Object} [userData.details]
|
|
23
|
+
*/
|
|
24
|
+
constructor(userData?: {
|
|
25
|
+
userID: string;
|
|
26
|
+
username?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
language?: TiLocalizationLanguage;
|
|
30
|
+
roles?: string[];
|
|
31
|
+
permissions?: string[];
|
|
32
|
+
details?: Object;
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* @property
|
|
36
|
+
* @returns {string}
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
get userID(): string;
|
|
40
|
+
/**
|
|
41
|
+
* @property
|
|
42
|
+
* @returns {string}
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
get username(): string;
|
|
46
|
+
/**
|
|
47
|
+
* @property
|
|
48
|
+
* @returns {string}
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
get email(): string;
|
|
52
|
+
/**
|
|
53
|
+
* @property
|
|
54
|
+
* @returns {string}
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
get name(): string;
|
|
58
|
+
/**
|
|
59
|
+
* @property
|
|
60
|
+
* @returns {TiLocalizationLanguage}
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
get language(): TiLocalizationLanguage;
|
|
64
|
+
/**
|
|
65
|
+
* @method
|
|
66
|
+
* @returns {*}
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
getDetail(key: any): any;
|
|
70
|
+
/**
|
|
71
|
+
* @method
|
|
72
|
+
* @param {string} key
|
|
73
|
+
* @param {*} value
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
setDetail(key: string, value: any): void;
|
|
77
|
+
/**
|
|
78
|
+
* @method
|
|
79
|
+
* @returns {Object}
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
asJSON(): Object;
|
|
83
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export = applyWebConfigEnvOverrides;
|
|
2
|
+
/**
|
|
3
|
+
* Applies TI_WEB_* environment-variable overrides onto an (already-merged) web server configuration object.
|
|
4
|
+
* Each override is applied ONLY when its environment variable is defined, so an absent variable leaves the
|
|
5
|
+
* configured/default value untouched (fully backward compatible). This gives ti-engine web servers 12-factor,
|
|
6
|
+
* container-friendly control over network binding, TLS, the session cookie secret, the enabled authentication
|
|
7
|
+
* methods, the admin allowlist, the trusted request origins, and the `/static` cache policy without editing config files. Note `TI_WEB_AUTH_METHODS`,
|
|
8
|
+
* `TI_WEB_AUTH_ADMINS`, `TI_WEB_TRUSTED_ORIGINS`, and `TI_WEB_STATIC_IMMUTABLE_PATHS` fully REPLACE their config arrays (`auth.enabledMethods` / `auth.admins` / `trustedOrigins` / `staticCache.immutablePaths`) rather than
|
|
9
|
+
* merging — the config-file merge is by-index and cannot cleanly override an array.
|
|
10
|
+
*
|
|
11
|
+
* @method
|
|
12
|
+
* @param {Object} config The web server configuration to augment (mutated in place and returned).
|
|
13
|
+
* @param {Object} [env=process.env] The environment source (injectable for testing).
|
|
14
|
+
* @returns {Object} The same config object, with any present overrides applied.
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
declare function applyWebConfigEnvOverrides(config: Object, env?: Object): Object;
|