@reqquest/ui 1.10.0 → 1.12.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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/keys.d.ts +47 -0
- package/dist/keys.js +1 -0
- package/dist/registry.d.ts +84 -13
- package/dist/registry.js +61 -11
- package/dist/typed-client/schema.d.ts +195 -1
- package/dist/typed-client/schema.graphql +165 -0
- package/dist/typed-client/schema.js +18 -1
- package/dist/typed-client/types.d.ts +72 -0
- package/dist/typed-client/types.js +797 -593
- package/dist/types.d.ts +13 -1
- package/dist/types.js +10 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Downstream projects augment this interface to teach the UI which keys their API registers. Once
|
|
3
|
+
* augmented, `UIConfig` rejects a component registered under an unknown key, and requires one for
|
|
4
|
+
* every prompt the API defines.
|
|
5
|
+
*
|
|
6
|
+
* This is a separate interface from the one in '@reqquest/api' by necessity: `@reqquest/ui` cannot
|
|
7
|
+
* depend on the API package without pulling fastify, mysql2 and type-graphql into a browser build.
|
|
8
|
+
* The generated declaration is plain string literals with no dependencies, so the same analyzer can
|
|
9
|
+
* emit one file augmenting each package:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* node node_modules/@reqquest/api/dist/analysis/cli.js <api-project> \
|
|
13
|
+
* --emit-keys src/keys.generated.d.ts --module '@reqquest/ui'
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* // src/keys.generated.d.ts - GENERATED, commit it
|
|
18
|
+
* export {}
|
|
19
|
+
* declare module '@reqquest/ui' {
|
|
20
|
+
* interface ReqQuestKeys {
|
|
21
|
+
* prompts: 'state_residence_prompt' | 'ssn_value_prompt'
|
|
22
|
+
* requirements: 'state_residence_req'
|
|
23
|
+
* programs: 'adopt_a_pet_program'
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The `export {}` is load-bearing - without a top-level import or export the file is a global script
|
|
29
|
+
* and `declare module` REPLACES '@reqquest/ui' rather than augmenting it - and the file has to fall
|
|
30
|
+
* inside your tsconfig `include`, or it augments nothing and keys silently stay `string`.
|
|
31
|
+
*
|
|
32
|
+
* Regenerate when definitions change and gate it with `--check-keys`; a stale declaration offers keys
|
|
33
|
+
* the API no longer has.
|
|
34
|
+
*
|
|
35
|
+
* Leaving a slot unaugmented degrades its key type to `string`, so augmenting is entirely optional.
|
|
36
|
+
*/
|
|
37
|
+
export interface ReqQuestKeys {
|
|
38
|
+
}
|
|
39
|
+
type Slot<S extends keyof any> = ReqQuestKeys extends Record<S, infer T> ? T : never;
|
|
40
|
+
type SlotUnion<S extends keyof any> = [Slot<S>] extends [never] ? string : Extract<Slot<S>, string>;
|
|
41
|
+
/** Every prompt key in this application, or `string` when the `prompts` slot is not augmented. */
|
|
42
|
+
export type PromptKey = SlotUnion<'prompts'>;
|
|
43
|
+
/** Every requirement key in this application, or `string` when the `requirements` slot is not augmented. */
|
|
44
|
+
export type RequirementKey = SlotUnion<'requirements'>;
|
|
45
|
+
/** Every program key in this application, or `string` when the `programs` slot is not augmented. */
|
|
46
|
+
export type ProgramKey = SlotUnion<'programs'>;
|
|
47
|
+
export {};
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LayoutStructureNode, LayoutStructureNodeRoot, UserProfile } from '@txstate-mws/carbon-svelte';
|
|
2
2
|
import type { Component } from 'svelte';
|
|
3
|
+
import type { ProgramKey, PromptKey, RequirementKey } from './keys.js';
|
|
3
4
|
export interface PromptLoader {
|
|
4
5
|
skeletonComponent: Component<any>;
|
|
5
6
|
/**
|
|
@@ -11,10 +12,10 @@ export interface PromptLoader {
|
|
|
11
12
|
export type Loader = PromptLoader | boolean;
|
|
12
13
|
export interface ProgramDefinition {
|
|
13
14
|
/**
|
|
14
|
-
* The key
|
|
15
|
-
*
|
|
15
|
+
* The key of the program this decorates. Autocompletes and rejects unknown keys once the project
|
|
16
|
+
* generates a key declaration; plain `string` until then.
|
|
16
17
|
*/
|
|
17
|
-
key:
|
|
18
|
+
key: ProgramKey;
|
|
18
19
|
/**
|
|
19
20
|
* An icon to represent this program in the navigation.
|
|
20
21
|
*/
|
|
@@ -22,10 +23,10 @@ export interface ProgramDefinition {
|
|
|
22
23
|
}
|
|
23
24
|
export interface RequirementDefinition {
|
|
24
25
|
/**
|
|
25
|
-
* The key
|
|
26
|
-
*
|
|
26
|
+
* The key of the requirement this decorates. Autocompletes and rejects unknown keys once the
|
|
27
|
+
* project generates a key declaration; plain `string` until then.
|
|
27
28
|
*/
|
|
28
|
-
key:
|
|
29
|
+
key: RequirementKey;
|
|
29
30
|
/**
|
|
30
31
|
* An icon to represent this requirement in the navigation. Requirements do not appear
|
|
31
32
|
* in the navigation in the applicant's view.
|
|
@@ -41,7 +42,11 @@ export interface RequirementDefinition {
|
|
|
41
42
|
configureDisplayComponent?: Component;
|
|
42
43
|
}
|
|
43
44
|
export interface PromptDefinition {
|
|
44
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The key of the prompt this renders. Autocompletes and rejects unknown keys once the project
|
|
47
|
+
* generates a key declaration; plain `string` until then.
|
|
48
|
+
*/
|
|
49
|
+
key: PromptKey;
|
|
45
50
|
/**
|
|
46
51
|
* The component that will be used to render the form for this prompt.
|
|
47
52
|
*
|
|
@@ -160,11 +165,37 @@ export interface Terminologies {
|
|
|
160
165
|
/**
|
|
161
166
|
* A type for the config object that should be exported from a CMS instance's admin/local/index.js
|
|
162
167
|
* to configure how that instance should work.
|
|
168
|
+
*
|
|
169
|
+
* Each of the three definition lists accepts either an array or a keyed object. **Prefer the keyed
|
|
170
|
+
* object.** An array can only reject a key the API does not have; it can never require the keys the
|
|
171
|
+
* API *does* have, so a prompt added on the API side and forgotten here compiles cleanly and fails
|
|
172
|
+
* in front of an applicant. The keyed form turns that into a build error, and also rejects
|
|
173
|
+
* duplicate keys, which an array silently resolves last-wins.
|
|
174
|
+
*
|
|
175
|
+
* `prompts` is the only list whose coverage is *required*, because it is the only one that is load
|
|
176
|
+
* bearing: a `PromptDefinition` supplies the component that renders the screen. `requirements` and
|
|
177
|
+
* `programs` carry optional decoration - an icon, a configuration component - and leaving one out
|
|
178
|
+
* is a supported choice, so their keyed form is partial. Both still reject unknown keys.
|
|
179
|
+
*
|
|
180
|
+
* This degrades on its own: `PromptKey` is `string` until a project generates a key declaration
|
|
181
|
+
* (see keys.ts), and `{ [K in string]: ... }` requires nothing - so an unaugmented project keeps
|
|
182
|
+
* working exactly as before.
|
|
183
|
+
*
|
|
184
|
+
* The type parameters exist for repos holding several applications, where the generated union spans
|
|
185
|
+
* all of them and no single config could satisfy it. Generate per-application aliases with the
|
|
186
|
+
* analyzer's `--groups` flag and instantiate against one, e.g.
|
|
187
|
+
* `UIConfig<SimplePromptKey, SimpleRequirementKey, SimpleProgramKey>`.
|
|
163
188
|
*/
|
|
164
|
-
export interface UIConfig {
|
|
165
|
-
programs: ProgramDefinition[]
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
export interface UIConfig<PK extends string = PromptKey, RK extends string = RequirementKey, GK extends string = ProgramKey> {
|
|
190
|
+
programs: ProgramDefinition[] | {
|
|
191
|
+
[K in GK]?: Omit<ProgramDefinition, 'key'>;
|
|
192
|
+
};
|
|
193
|
+
requirements: RequirementDefinition[] | {
|
|
194
|
+
[K in RK]?: Omit<RequirementDefinition, 'key'>;
|
|
195
|
+
};
|
|
196
|
+
prompts: PromptDefinition[] | {
|
|
197
|
+
[K in PK]: Omit<PromptDefinition, 'key'>;
|
|
198
|
+
};
|
|
168
199
|
/**
|
|
169
200
|
*
|
|
170
201
|
* @param login
|
|
@@ -273,18 +304,58 @@ export interface UIConfigWithDefaults extends UIConfig {
|
|
|
273
304
|
terminology: Required<Terminologies>;
|
|
274
305
|
plural: Required<Terminologies>;
|
|
275
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Any `UIConfig`, whichever key unions it was written against. `UIRegistry` accepts this rather
|
|
309
|
+
* than `UIConfig` itself: a `UIConfig<SimplePromptKey>` is not assignable to `UIConfig<PromptKey>`
|
|
310
|
+
* when the unions differ, and TypeScript does not allow a generic constructor to bridge them.
|
|
311
|
+
*
|
|
312
|
+
* Nothing is lost by widening here. Exhaustiveness is enforced where the config literal is written
|
|
313
|
+
* (`const config: UIConfig<SimplePromptKey, ...> = { ... }`), which is the only place that knows
|
|
314
|
+
* which application it belongs to.
|
|
315
|
+
*/
|
|
316
|
+
export type AnyUIConfig = Omit<UIConfig, 'programs' | 'requirements' | 'prompts'> & {
|
|
317
|
+
programs: ProgramDefinition[] | Record<string, Omit<ProgramDefinition, 'key'> | undefined>;
|
|
318
|
+
requirements: RequirementDefinition[] | Record<string, Omit<RequirementDefinition, 'key'> | undefined>;
|
|
319
|
+
prompts: PromptDefinition[] | Record<string, Omit<PromptDefinition, 'key'>>;
|
|
320
|
+
};
|
|
276
321
|
export declare class UIRegistry {
|
|
277
|
-
config: UIConfig;
|
|
278
322
|
protected promptMap: Record<string, PromptDefinition>;
|
|
279
323
|
protected requirementMap: Record<string, RequirementDefinition>;
|
|
280
324
|
protected programMap: Record<string, ProgramDefinition>;
|
|
281
325
|
protected userLookup?: (login: string) => Promise<UserProfile | undefined>;
|
|
282
326
|
protected lang: Required<Terminologies>;
|
|
283
327
|
protected plural: Required<Terminologies>;
|
|
284
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Widened to `AnyUIConfig` rather than `UIConfig`: a narrow `UIConfig<SimplePromptKey>` is not
|
|
330
|
+
* assignable to `UIConfig<PromptKey>` when the unions differ. Keeping the three definition lists
|
|
331
|
+
* on the type (instead of `Omit`ing them away) matters for consumers that read
|
|
332
|
+
* `uiRegistry.config.prompts` - dropping them would be a breaking change.
|
|
333
|
+
*/
|
|
334
|
+
config: AnyUIConfig;
|
|
335
|
+
constructor(config: AnyUIConfig);
|
|
285
336
|
getWord(key: keyof Terminologies, count?: number, inclusive?: boolean): string;
|
|
286
337
|
getPlural(key: keyof Terminologies): string;
|
|
287
338
|
getPrompt(key: string): PromptDefinition | undefined;
|
|
288
339
|
getRequirement(key: string): RequirementDefinition | undefined;
|
|
289
340
|
getProgram(key: string): ProgramDefinition | undefined;
|
|
341
|
+
/**
|
|
342
|
+
* `UIConfig` keeps this UI in step with the API it was built against.
|
|
343
|
+
* Keys arrive here from the API at runtime, so these stay `string` rather than the key unions.
|
|
344
|
+
*/
|
|
345
|
+
protected warnedMissing: Set<string>;
|
|
346
|
+
/**
|
|
347
|
+
* This is the ONLY runtime signal that the UI is missing a definition, so it is deliberately NOT
|
|
348
|
+
* gated on a dev-only build flag. Coverage is enforced at build time (see UIConfig's keyed form),
|
|
349
|
+
* but that gate lives in this repo's tooling and does not ship to downstream projects - a project
|
|
350
|
+
* that never wires its own type check has nothing but this line. Staying silent in production is
|
|
351
|
+
* how a missing prompt turns into an unexplained blank spot on an applicant's form.
|
|
352
|
+
* The `warnedMissing` set keeps it to one line per key, so a list of 200 prompts cannot spam.
|
|
353
|
+
*
|
|
354
|
+
* Severity splits by kind because the kinds are not equally load-bearing. A `PromptDefinition`
|
|
355
|
+
* supplies the component that actually renders the screen, so a missing one breaks a page.
|
|
356
|
+
* `RequirementDefinition` and `ProgramDefinition` carry only optional decoration - an icon, a
|
|
357
|
+
* configuration component - and every consumer optional-chains them. Leaving one unregistered is
|
|
358
|
+
* a supported choice, so it warns rather than errors.
|
|
359
|
+
*/
|
|
360
|
+
protected warnIfMissing<T>(kind: string, key: string, definition: T | undefined): T | undefined;
|
|
290
361
|
}
|
package/dist/registry.js
CHANGED
|
@@ -1,23 +1,46 @@
|
|
|
1
1
|
import { plural } from 'txstate-utils';
|
|
2
|
+
/**
|
|
3
|
+
* Accepts either shape of a definition list and returns a key -> definition map. The keyed form
|
|
4
|
+
* carries its key in the object key, so it is grafted back on to keep every consumer's
|
|
5
|
+
* `definition.key` working.
|
|
6
|
+
*/
|
|
7
|
+
function toDefinitionMap(definitions) {
|
|
8
|
+
const map = {};
|
|
9
|
+
if (Array.isArray(definitions)) {
|
|
10
|
+
for (const definition of definitions)
|
|
11
|
+
map[definition.key] = definition;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// an explicit `undefined` value is a hole in a partial record, not a registration
|
|
15
|
+
for (const [key, definition] of Object.entries(definitions)) {
|
|
16
|
+
if (definition != null)
|
|
17
|
+
map[key] = { ...definition, key };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return map;
|
|
21
|
+
}
|
|
2
22
|
export class UIRegistry {
|
|
3
|
-
config;
|
|
4
23
|
promptMap = {};
|
|
5
24
|
requirementMap = {};
|
|
6
25
|
programMap = {};
|
|
7
26
|
userLookup;
|
|
8
27
|
lang;
|
|
9
28
|
plural;
|
|
29
|
+
/**
|
|
30
|
+
* Widened to `AnyUIConfig` rather than `UIConfig`: a narrow `UIConfig<SimplePromptKey>` is not
|
|
31
|
+
* assignable to `UIConfig<PromptKey>` when the unions differ. Keeping the three definition lists
|
|
32
|
+
* on the type (instead of `Omit`ing them away) matters for consumers that read
|
|
33
|
+
* `uiRegistry.config.prompts` - dropping them would be a breaking change.
|
|
34
|
+
*/
|
|
35
|
+
config;
|
|
10
36
|
constructor(config) {
|
|
11
37
|
this.config = config;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.requirementMap[requirement.key] = requirement;
|
|
16
|
-
for (const program of config.programs)
|
|
17
|
-
this.programMap[program.key] = program;
|
|
38
|
+
this.promptMap = toDefinitionMap(config.prompts);
|
|
39
|
+
this.requirementMap = toDefinitionMap(config.requirements);
|
|
40
|
+
this.programMap = toDefinitionMap(config.programs);
|
|
18
41
|
this.userLookup = config.userLookup;
|
|
19
42
|
this.lang = {
|
|
20
|
-
appRequest: config.terminology?.appRequest ?? (
|
|
43
|
+
appRequest: config.terminology?.appRequest ?? (Object.keys(this.programMap).length > 1 ? 'App Request' : 'Application'),
|
|
21
44
|
login: config.terminology?.login ?? 'Login',
|
|
22
45
|
period: config.terminology?.period ?? 'Period'
|
|
23
46
|
};
|
|
@@ -33,12 +56,39 @@ export class UIRegistry {
|
|
|
33
56
|
return this.plural[key];
|
|
34
57
|
}
|
|
35
58
|
getPrompt(key) {
|
|
36
|
-
return this.promptMap[key];
|
|
59
|
+
return this.warnIfMissing('prompt', key, this.promptMap[key]);
|
|
37
60
|
}
|
|
38
61
|
getRequirement(key) {
|
|
39
|
-
return this.requirementMap[key];
|
|
62
|
+
return this.warnIfMissing('requirement', key, this.requirementMap[key]);
|
|
40
63
|
}
|
|
41
64
|
getProgram(key) {
|
|
42
|
-
return this.programMap[key];
|
|
65
|
+
return this.warnIfMissing('program', key, this.programMap[key]);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `UIConfig` keeps this UI in step with the API it was built against.
|
|
69
|
+
* Keys arrive here from the API at runtime, so these stay `string` rather than the key unions.
|
|
70
|
+
*/
|
|
71
|
+
warnedMissing = new Set();
|
|
72
|
+
/**
|
|
73
|
+
* This is the ONLY runtime signal that the UI is missing a definition, so it is deliberately NOT
|
|
74
|
+
* gated on a dev-only build flag. Coverage is enforced at build time (see UIConfig's keyed form),
|
|
75
|
+
* but that gate lives in this repo's tooling and does not ship to downstream projects - a project
|
|
76
|
+
* that never wires its own type check has nothing but this line. Staying silent in production is
|
|
77
|
+
* how a missing prompt turns into an unexplained blank spot on an applicant's form.
|
|
78
|
+
* The `warnedMissing` set keeps it to one line per key, so a list of 200 prompts cannot spam.
|
|
79
|
+
*
|
|
80
|
+
* Severity splits by kind because the kinds are not equally load-bearing. A `PromptDefinition`
|
|
81
|
+
* supplies the component that actually renders the screen, so a missing one breaks a page.
|
|
82
|
+
* `RequirementDefinition` and `ProgramDefinition` carry only optional decoration - an icon, a
|
|
83
|
+
* configuration component - and every consumer optional-chains them. Leaving one unregistered is
|
|
84
|
+
* a supported choice, so it warns rather than errors.
|
|
85
|
+
*/
|
|
86
|
+
warnIfMissing(kind, key, definition) {
|
|
87
|
+
if (definition == null && !this.warnedMissing.has(kind + ':' + key)) {
|
|
88
|
+
this.warnedMissing.add(kind + ':' + key);
|
|
89
|
+
const log = kind === 'prompt' ? console.error : console.warn;
|
|
90
|
+
log(`[reqquest] The API returned a ${kind} \`${key}\` that this UI has no definition for. Add it to your UIConfig, or regenerate your key declaration if the API has changed.`);
|
|
91
|
+
}
|
|
92
|
+
return definition;
|
|
43
93
|
}
|
|
44
94
|
}
|