@ankhorage/contracts 7.1.0 → 7.2.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/appManifest/bindings.d.ts +4 -0
  3. package/dist/appManifest/bindings.d.ts.map +1 -0
  4. package/dist/appManifest/bindings.js +96 -0
  5. package/dist/appManifest/bindings.js.map +1 -0
  6. package/dist/appManifest/dataSources.d.ts +2 -0
  7. package/dist/appManifest/dataSources.d.ts.map +1 -0
  8. package/dist/appManifest/dataSources.js +133 -0
  9. package/dist/appManifest/dataSources.js.map +1 -0
  10. package/dist/appManifest/generatedApis.d.ts +2 -0
  11. package/dist/appManifest/generatedApis.d.ts.map +1 -0
  12. package/dist/appManifest/generatedApis.js +87 -0
  13. package/dist/appManifest/generatedApis.js.map +1 -0
  14. package/dist/appManifest/infra.d.ts +2 -0
  15. package/dist/appManifest/infra.d.ts.map +1 -0
  16. package/dist/appManifest/infra.js +136 -0
  17. package/dist/appManifest/infra.js.map +1 -0
  18. package/dist/appManifest/screens.d.ts +6 -0
  19. package/dist/appManifest/screens.d.ts.map +1 -0
  20. package/dist/appManifest/screens.js +119 -0
  21. package/dist/appManifest/screens.js.map +1 -0
  22. package/dist/appManifest/shared.d.ts +8 -0
  23. package/dist/appManifest/shared.d.ts.map +1 -0
  24. package/dist/appManifest/shared.js +31 -0
  25. package/dist/appManifest/shared.js.map +1 -0
  26. package/dist/appManifest.d.ts +19 -0
  27. package/dist/appManifest.d.ts.map +1 -0
  28. package/dist/appManifest.js +64 -0
  29. package/dist/appManifest.js.map +1 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -0
  33. package/dist/index.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/appManifest/bindings.ts +135 -0
  36. package/src/appManifest/dataSources.ts +175 -0
  37. package/src/appManifest/generatedApis.ts +122 -0
  38. package/src/appManifest/infra.ts +199 -0
  39. package/src/appManifest/screens.ts +171 -0
  40. package/src/appManifest/shared.ts +40 -0
  41. package/src/appManifest.test.ts +185 -0
  42. package/src/appManifest.ts +86 -0
  43. package/src/index.ts +1 -0
@@ -0,0 +1,122 @@
1
+ import {
2
+ isManifestValue,
3
+ isOptionalBoolean,
4
+ isOptionalString,
5
+ isRecord,
6
+ isStringArray,
7
+ } from './shared';
8
+
9
+ export function isGeneratedApiRegistry(value: unknown): boolean {
10
+ return isRecord(value) && Object.values(value).every(isGeneratedApiDefinition);
11
+ }
12
+
13
+ function isGeneratedApiDefinition(value: unknown): boolean {
14
+ return (
15
+ isRecord(value) &&
16
+ typeof value.id === 'string' &&
17
+ value.protocol === 'rest' &&
18
+ isOptionalString(value.name) &&
19
+ isOptionalString(value.description) &&
20
+ typeof value.basePath === 'string' &&
21
+ isDatabaseAdapterRef(value.database) &&
22
+ Array.isArray(value.resources) &&
23
+ value.resources.every(isGeneratedApiResourceDefinition) &&
24
+ (value.auth === undefined || isGeneratedApiAuthRequirement(value.auth)) &&
25
+ (value.metadata === undefined || isManifestValue(value.metadata))
26
+ );
27
+ }
28
+
29
+ function isDatabaseAdapterRef(value: unknown): boolean {
30
+ return isRecord(value) && value.kind === 'database' && isAdapterRef(value);
31
+ }
32
+
33
+ function isGeneratedApiAuthRequirement(value: unknown): boolean {
34
+ return (
35
+ isRecord(value) &&
36
+ isOptionalBoolean(value.required) &&
37
+ (value.roles === undefined || isStringArray(value.roles)) &&
38
+ (value.permissions === undefined || isStringArray(value.permissions)) &&
39
+ isOptionalString(value.policy) &&
40
+ (value.metadata === undefined || isManifestValue(value.metadata))
41
+ );
42
+ }
43
+
44
+ function isGeneratedApiResourceDefinition(value: unknown): boolean {
45
+ return (
46
+ isRecord(value) &&
47
+ typeof value.id === 'string' &&
48
+ isOptionalString(value.name) &&
49
+ isOptionalString(value.description) &&
50
+ typeof value.path === 'string' &&
51
+ isDbCollectionDefinition(value.collection) &&
52
+ Array.isArray(value.operations) &&
53
+ value.operations.every(isGeneratedApiCrudOperation) &&
54
+ (value.seed === undefined || isSeedRecords(value.seed)) &&
55
+ (value.policies === undefined ||
56
+ (Array.isArray(value.policies) && value.policies.every(isGeneratedApiPolicyRef))) &&
57
+ (value.metadata === undefined || isManifestValue(value.metadata))
58
+ );
59
+ }
60
+
61
+ function isSeedRecords(value: unknown): boolean {
62
+ return (
63
+ Array.isArray(value) &&
64
+ value.every((record) => isRecord(record) && Object.values(record).every(isManifestValue))
65
+ );
66
+ }
67
+
68
+ function isGeneratedApiCrudOperation(value: unknown): boolean {
69
+ return ['create', 'delete', 'list', 'read', 'update'].includes(String(value));
70
+ }
71
+
72
+ function isGeneratedApiPolicyRef(value: unknown): boolean {
73
+ return (
74
+ isRecord(value) &&
75
+ typeof value.id === 'string' &&
76
+ (value.operation === undefined || isGeneratedApiCrudOperation(value.operation))
77
+ );
78
+ }
79
+
80
+ function isDbCollectionDefinition(value: unknown): boolean {
81
+ return (
82
+ isRecord(value) &&
83
+ typeof value.name === 'string' &&
84
+ isOptionalString(value.schema) &&
85
+ Array.isArray(value.fields) &&
86
+ value.fields.every(isDbFieldDefinition) &&
87
+ isOptionalString(value.primaryKey)
88
+ );
89
+ }
90
+
91
+ function isDbFieldDefinition(value: unknown): boolean {
92
+ return (
93
+ isRecord(value) &&
94
+ typeof value.name === 'string' &&
95
+ typeof value.type === 'string' &&
96
+ ['text', 'number', 'boolean', 'datetime', 'json', 'uuid'].includes(value.type) &&
97
+ isOptionalBoolean(value.required) &&
98
+ isOptionalBoolean(value.unique) &&
99
+ isDbDefaultValue(value.defaultValue)
100
+ );
101
+ }
102
+
103
+ function isDbDefaultValue(value: unknown): boolean {
104
+ return (
105
+ value === undefined ||
106
+ value === null ||
107
+ typeof value === 'boolean' ||
108
+ typeof value === 'number' ||
109
+ typeof value === 'string'
110
+ );
111
+ }
112
+
113
+ function isAdapterRef(value: unknown): boolean {
114
+ return (
115
+ isRecord(value) &&
116
+ typeof value.id === 'string' &&
117
+ typeof value.kind === 'string' &&
118
+ isOptionalString(value.packageName) &&
119
+ isOptionalString(value.exportName) &&
120
+ (value.config === undefined || isManifestValue(value.config))
121
+ );
122
+ }
@@ -0,0 +1,199 @@
1
+ import { AUTH_OAUTH_PROVIDER_IDS } from '../auth';
2
+ import {
3
+ AUTH_PROFILE_CREATE_STRATEGIES,
4
+ AUTH_PROFILE_PRIMARY_KEY_STRATEGIES,
5
+ AUTH_PROFILE_UPDATE_STRATEGIES,
6
+ AUTH_SCOPES,
7
+ AUTH_SIGN_IN_IDENTIFIERS,
8
+ AUTH_SIGN_UP_POLICIES,
9
+ AUTHZ_ENGINES,
10
+ AUTHZ_KINDS,
11
+ DATABASE_TIERS,
12
+ STATE_PERSISTENCE_MODES,
13
+ STORAGE_PROVIDERS,
14
+ } from '../types';
15
+ import {
16
+ isOptionalBoolean,
17
+ isOptionalString,
18
+ isRecord,
19
+ isStringArray,
20
+ isStringRecord,
21
+ } from './shared';
22
+
23
+ const AUTH_OAUTH_PROVIDER_SET = new Set<string>(AUTH_OAUTH_PROVIDER_IDS);
24
+ const AUTH_PROFILE_CREATE_STRATEGY_SET = new Set<string>(AUTH_PROFILE_CREATE_STRATEGIES);
25
+ const AUTH_PROFILE_PRIMARY_KEY_STRATEGY_SET = new Set<string>(AUTH_PROFILE_PRIMARY_KEY_STRATEGIES);
26
+ const AUTH_PROFILE_UPDATE_STRATEGY_SET = new Set<string>(AUTH_PROFILE_UPDATE_STRATEGIES);
27
+ const AUTH_SCOPE_SET = new Set<string>(AUTH_SCOPES);
28
+ const AUTH_SIGN_IN_IDENTIFIER_SET = new Set<string>(AUTH_SIGN_IN_IDENTIFIERS);
29
+ const AUTH_SIGN_UP_POLICY_SET = new Set<string>(AUTH_SIGN_UP_POLICIES);
30
+ const AUTHZ_ENGINE_SET = new Set<string>(AUTHZ_ENGINES);
31
+ const AUTHZ_KIND_SET = new Set<string>(AUTHZ_KINDS);
32
+ const DATABASE_TIER_SET = new Set<string>(DATABASE_TIERS);
33
+ const STATE_PERSISTENCE_MODE_SET = new Set<string>(STATE_PERSISTENCE_MODES);
34
+ const STORAGE_PROVIDER_SET = new Set<string>(STORAGE_PROVIDERS);
35
+
36
+ export function isInfraManifest(value: unknown): boolean {
37
+ return (
38
+ isRecord(value) &&
39
+ (value.deployment === undefined || isDeploymentSpec(value.deployment)) &&
40
+ (value.auth === undefined || isAuthSpec(value.auth)) &&
41
+ (value.database === undefined || isDatabaseSpec(value.database)) &&
42
+ (value.storage === undefined || isStorageSpec(value.storage)) &&
43
+ (value.state === undefined || isStateSpec(value.state)) &&
44
+ (value.networking === undefined || isNetworkingSpec(value.networking)) &&
45
+ Array.isArray(value.modules) &&
46
+ value.modules.every((moduleId) => typeof moduleId === 'string') &&
47
+ (value.modulesConfig === undefined || isRecord(value.modulesConfig)) &&
48
+ !('plugins' in value) &&
49
+ !('pluginsConfig' in value)
50
+ );
51
+ }
52
+
53
+ function isDeploymentSpec(value: unknown): boolean {
54
+ return (
55
+ isRecord(value) && typeof value.target === 'string' && typeof value.monitoring === 'boolean'
56
+ );
57
+ }
58
+
59
+ function isDatabaseSpec(value: unknown): boolean {
60
+ return (
61
+ isRecord(value) &&
62
+ typeof value.provider === 'string' &&
63
+ typeof value.tier === 'string' &&
64
+ DATABASE_TIER_SET.has(value.tier)
65
+ );
66
+ }
67
+
68
+ function isStorageSpec(value: unknown): boolean {
69
+ return (
70
+ isRecord(value) &&
71
+ typeof value.provider === 'string' &&
72
+ STORAGE_PROVIDER_SET.has(value.provider) &&
73
+ isStringArray(value.buckets)
74
+ );
75
+ }
76
+
77
+ function isStateSpec(value: unknown): boolean {
78
+ return (
79
+ isRecord(value) &&
80
+ typeof value.provider === 'string' &&
81
+ (value.persistence === undefined ||
82
+ (typeof value.persistence === 'string' && STATE_PERSISTENCE_MODE_SET.has(value.persistence)))
83
+ );
84
+ }
85
+
86
+ function isNetworkingSpec(value: unknown): boolean {
87
+ return isRecord(value) && isOptionalString(value.domain) && typeof value.cdn === 'boolean';
88
+ }
89
+
90
+ function isAuthSpec(value: unknown): boolean {
91
+ return (
92
+ isRecord(value) &&
93
+ typeof value.scope === 'string' &&
94
+ AUTH_SCOPE_SET.has(value.scope) &&
95
+ typeof value.provider === 'string' &&
96
+ (value.authorization === undefined || isAuthzSpec(value.authorization)) &&
97
+ (value.flow === undefined || isAuthFlow(value.flow)) &&
98
+ (value.signIn === undefined || isAuthSignInSpec(value.signIn)) &&
99
+ (value.signUp === undefined || isAuthSignUpSpec(value.signUp)) &&
100
+ (value.oauth === undefined || isAuthOAuthConfig(value.oauth)) &&
101
+ (value.profile === undefined || isAuthProfileSpec(value.profile))
102
+ );
103
+ }
104
+
105
+ function isAuthzSpec(value: unknown): boolean {
106
+ return (
107
+ isRecord(value) &&
108
+ typeof value.kind === 'string' &&
109
+ AUTHZ_KIND_SET.has(value.kind) &&
110
+ typeof value.engine === 'string' &&
111
+ AUTHZ_ENGINE_SET.has(value.engine)
112
+ );
113
+ }
114
+
115
+ function isAuthFlow(value: unknown): boolean {
116
+ return (
117
+ isRecord(value) &&
118
+ typeof value.signInRoute === 'string' &&
119
+ isOptionalString(value.signUpRoute) &&
120
+ isOptionalString(value.signOutRoute) &&
121
+ isOptionalString(value.forgotPasswordRoute) &&
122
+ isOptionalString(value.otpRoute) &&
123
+ typeof value.postSignInRoute === 'string' &&
124
+ isOptionalString(value.unauthorizedRoute)
125
+ );
126
+ }
127
+
128
+ function isAuthSignInSpec(value: unknown): boolean {
129
+ return (
130
+ isRecord(value) &&
131
+ Array.isArray(value.identifiers) &&
132
+ value.identifiers.every(
133
+ (identifier) => typeof identifier === 'string' && AUTH_SIGN_IN_IDENTIFIER_SET.has(identifier),
134
+ )
135
+ );
136
+ }
137
+
138
+ function isAuthSignUpSpec(value: unknown): boolean {
139
+ return (
140
+ isRecord(value) &&
141
+ isStringArray(value.requiredFields) &&
142
+ (value.optionalFields === undefined || isStringArray(value.optionalFields)) &&
143
+ (value.signUpPolicy === undefined ||
144
+ (typeof value.signUpPolicy === 'string' && AUTH_SIGN_UP_POLICY_SET.has(value.signUpPolicy)))
145
+ );
146
+ }
147
+
148
+ function isAuthOAuthConfig(value: unknown): boolean {
149
+ return (
150
+ isRecord(value) &&
151
+ typeof value.enabled === 'boolean' &&
152
+ typeof value.callbackRoute === 'string' &&
153
+ Array.isArray(value.providers) &&
154
+ value.providers.every(isAuthOAuthProviderConfig)
155
+ );
156
+ }
157
+
158
+ function isAuthOAuthProviderConfig(value: unknown): boolean {
159
+ return (
160
+ isRecord(value) &&
161
+ typeof value.id === 'string' &&
162
+ (AUTH_OAUTH_PROVIDER_SET.has(value.id) || value.id.length > 0) &&
163
+ isOptionalString(value.label) &&
164
+ isOptionalBoolean(value.enabled) &&
165
+ (value.scopes === undefined || isStringArray(value.scopes)) &&
166
+ (value.queryParams === undefined || isStringRecord(value.queryParams)) &&
167
+ (value.icon === undefined || isIconSpec(value.icon)) &&
168
+ (value.credentialsRef === undefined || typeof value.credentialsRef === 'string')
169
+ );
170
+ }
171
+
172
+ function isAuthProfileSpec(value: unknown): boolean {
173
+ return (
174
+ isRecord(value) &&
175
+ isStringArray(value.fields) &&
176
+ isOptionalString(value.table) &&
177
+ (value.primaryKey === undefined ||
178
+ (typeof value.primaryKey === 'string' &&
179
+ AUTH_PROFILE_PRIMARY_KEY_STRATEGY_SET.has(value.primaryKey))) &&
180
+ (value.createStrategy === undefined ||
181
+ (typeof value.createStrategy === 'string' &&
182
+ AUTH_PROFILE_CREATE_STRATEGY_SET.has(value.createStrategy))) &&
183
+ (value.updateStrategy === undefined ||
184
+ (typeof value.updateStrategy === 'string' &&
185
+ AUTH_PROFILE_UPDATE_STRATEGY_SET.has(value.updateStrategy)))
186
+ );
187
+ }
188
+
189
+ function isIconSpec(value: unknown): boolean {
190
+ return (
191
+ isRecord(value) &&
192
+ typeof value.name === 'string' &&
193
+ isOptionalString(value.provider) &&
194
+ (value.size === undefined ||
195
+ typeof value.size === 'string' ||
196
+ typeof value.size === 'number') &&
197
+ isOptionalString(value.color)
198
+ );
199
+ }
@@ -0,0 +1,171 @@
1
+ import { COLOR_HARMONIES } from '@ankhorage/color-theory';
2
+
3
+ import { APP_CATEGORIES, NAVIGATOR_TYPES } from '../types';
4
+ import { isBindingValueSource, isScreenDataLoaderDefinition } from './bindings';
5
+ import {
6
+ isOptionalBoolean,
7
+ isOptionalNumber,
8
+ isOptionalString,
9
+ isRecord,
10
+ isStringArray,
11
+ } from './shared';
12
+
13
+ const APP_CATEGORY_SET = new Set<string>(APP_CATEGORIES);
14
+ const COLOR_HARMONY_SET = new Set<string>(COLOR_HARMONIES);
15
+ const NAVIGATOR_TYPE_SET = new Set<string>(NAVIGATOR_TYPES);
16
+ const SPLASH_SCREEN_RESIZE_MODE_SET = new Set<string>(['contain', 'cover', 'native']);
17
+
18
+ export function isManifestMetadata(value: unknown): boolean {
19
+ return (
20
+ isRecord(value) &&
21
+ typeof value.name === 'string' &&
22
+ typeof value.slug === 'string' &&
23
+ typeof value.version === 'string' &&
24
+ typeof value.category === 'string' &&
25
+ APP_CATEGORY_SET.has(value.category) &&
26
+ typeof value.themeId === 'string' &&
27
+ isOptionalString(value.created) &&
28
+ isOptionalString(value.updated)
29
+ );
30
+ }
31
+
32
+ export function isThemeConfig(value: unknown): boolean {
33
+ return (
34
+ isRecord(value) &&
35
+ typeof value.id === 'string' &&
36
+ typeof value.name === 'string' &&
37
+ isThemeModeConfig(value.light) &&
38
+ isThemeModeConfig(value.dark)
39
+ );
40
+ }
41
+
42
+ export function isNavigatorSpec(value: unknown): boolean {
43
+ return (
44
+ isRecord(value) &&
45
+ typeof value.type === 'string' &&
46
+ NAVIGATOR_TYPE_SET.has(value.type) &&
47
+ isOptionalString(value.initialRouteName) &&
48
+ Array.isArray(value.routes) &&
49
+ value.routes.every(isRouteDefinition) &&
50
+ (value.options === undefined || isRecord(value.options))
51
+ );
52
+ }
53
+
54
+ export function isScreenRegistry(value: unknown): boolean {
55
+ return (
56
+ isRecord(value) &&
57
+ Object.entries(value).every(
58
+ ([registryKey, screen]) =>
59
+ isScreenSpec(screen) && isRecord(screen) && registryKey === screen.id,
60
+ )
61
+ );
62
+ }
63
+
64
+ export function isSplashScreenSpec(value: unknown): boolean {
65
+ return (
66
+ isSplashScreenModeSpec(value) &&
67
+ isRecord(value) &&
68
+ (value.dark === undefined || isSplashScreenModeSpec(value.dark))
69
+ );
70
+ }
71
+
72
+ function isThemeModeConfig(value: unknown): boolean {
73
+ return (
74
+ isRecord(value) &&
75
+ typeof value.primaryColor === 'string' &&
76
+ typeof value.harmony === 'string' &&
77
+ COLOR_HARMONY_SET.has(value.harmony)
78
+ );
79
+ }
80
+
81
+ function isUiNode(value: unknown): boolean {
82
+ return (
83
+ isRecord(value) &&
84
+ typeof value.id === 'string' &&
85
+ typeof value.type === 'string' &&
86
+ isOptionalString(value.alias) &&
87
+ (value.props === undefined || isRecord(value.props)) &&
88
+ (value.style === undefined || isRecord(value.style)) &&
89
+ (value.repeat === undefined || isUiNodeRepeatSpec(value.repeat)) &&
90
+ (value.children === undefined ||
91
+ (Array.isArray(value.children) && value.children.every(isUiNode)))
92
+ );
93
+ }
94
+
95
+ function isUiNodeRepeatSpec(value: unknown): boolean {
96
+ return (
97
+ isRecord(value) &&
98
+ isBindingValueSource(value.source) &&
99
+ isOptionalString(value.itemAlias) &&
100
+ isOptionalString(value.keyPath) &&
101
+ (value.empty === undefined || (Array.isArray(value.empty) && value.empty.every(isUiNode)))
102
+ );
103
+ }
104
+
105
+ function isScreenSpec(value: unknown): boolean {
106
+ return (
107
+ isRecord(value) &&
108
+ typeof value.id === 'string' &&
109
+ typeof value.name === 'string' &&
110
+ isOptionalString(value.title) &&
111
+ isOptionalString(value.description) &&
112
+ (value.dataLoaders === undefined ||
113
+ (Array.isArray(value.dataLoaders) &&
114
+ value.dataLoaders.every(isScreenDataLoaderDefinition))) &&
115
+ (value.requires === undefined || isScreenRequirements(value.requires)) &&
116
+ isUiNode(value.root)
117
+ );
118
+ }
119
+
120
+ function isRouteDefinition(value: unknown): boolean {
121
+ return (
122
+ isRecord(value) &&
123
+ typeof value.name === 'string' &&
124
+ isOptionalString(value.path) &&
125
+ isOptionalString(value.label) &&
126
+ (value.icon === undefined || isIconSpec(value.icon)) &&
127
+ isOptionalBoolean(value.showInPrimaryNavigation) &&
128
+ (value.guards === undefined || isStringArray(value.guards)) &&
129
+ isOptionalString(value.screenId) &&
130
+ (value.navigator === undefined || isNavigatorSpec(value.navigator))
131
+ );
132
+ }
133
+
134
+ function isIconSpec(value: unknown): boolean {
135
+ return (
136
+ isRecord(value) &&
137
+ typeof value.name === 'string' &&
138
+ isOptionalString(value.provider) &&
139
+ (value.size === undefined ||
140
+ typeof value.size === 'string' ||
141
+ typeof value.size === 'number') &&
142
+ isOptionalString(value.color)
143
+ );
144
+ }
145
+
146
+ function isSplashScreenModeSpec(value: unknown): boolean {
147
+ return (
148
+ isRecord(value) &&
149
+ isOptionalString(value.image) &&
150
+ isOptionalNumber(value.imageWidth) &&
151
+ (value.resizeMode === undefined ||
152
+ (typeof value.resizeMode === 'string' &&
153
+ SPLASH_SCREEN_RESIZE_MODE_SET.has(value.resizeMode))) &&
154
+ isOptionalString(value.backgroundColor)
155
+ );
156
+ }
157
+
158
+ function isScreenRequirements(value: unknown): boolean {
159
+ return (
160
+ isRecord(value) &&
161
+ (value.permissions === undefined || isRequirementArray(value.permissions, 'permission')) &&
162
+ (value.capabilities === undefined || isRequirementArray(value.capabilities, 'capability'))
163
+ );
164
+ }
165
+
166
+ function isRequirementArray(value: unknown, key: 'capability' | 'permission'): boolean {
167
+ return (
168
+ Array.isArray(value) &&
169
+ value.every((entry) => isRecord(entry) && typeof entry[key] === 'string')
170
+ );
171
+ }
@@ -0,0 +1,40 @@
1
+ export function isRecord(value: unknown): value is Record<string, unknown> {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ }
4
+
5
+ export function isOptionalString(value: unknown): boolean {
6
+ return value === undefined || typeof value === 'string';
7
+ }
8
+
9
+ export function isOptionalNumber(value: unknown): boolean {
10
+ return value === undefined || typeof value === 'number';
11
+ }
12
+
13
+ export function isOptionalBoolean(value: unknown): boolean {
14
+ return value === undefined || typeof value === 'boolean';
15
+ }
16
+
17
+ export function isStringArray(value: unknown): value is string[] {
18
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
19
+ }
20
+
21
+ export function isStringRecord(value: unknown): boolean {
22
+ return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string');
23
+ }
24
+
25
+ export function isManifestValue(value: unknown): boolean {
26
+ if (
27
+ value === null ||
28
+ typeof value === 'string' ||
29
+ typeof value === 'number' ||
30
+ typeof value === 'boolean'
31
+ ) {
32
+ return true;
33
+ }
34
+
35
+ if (Array.isArray(value)) {
36
+ return value.every(isManifestValue);
37
+ }
38
+
39
+ return isRecord(value) && Object.values(value).every(isManifestValue);
40
+ }