@ankhorage/contracts 13.0.1 → 15.0.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 (256) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +10 -2
  3. package/dist/appManifest/apis.d.ts +1 -0
  4. package/dist/appManifest/apis.d.ts.map +1 -1
  5. package/dist/appManifest/apis.js +17 -14
  6. package/dist/appManifest/apis.js.map +1 -1
  7. package/dist/appManifest/bindings.d.ts +3 -0
  8. package/dist/appManifest/bindings.d.ts.map +1 -1
  9. package/dist/appManifest/bindings.js +17 -1
  10. package/dist/appManifest/bindings.js.map +1 -1
  11. package/dist/appManifest/constants.d.ts +18 -0
  12. package/dist/appManifest/constants.d.ts.map +1 -0
  13. package/dist/appManifest/constants.js +18 -0
  14. package/dist/appManifest/constants.js.map +1 -0
  15. package/dist/appManifest/data.d.ts +4 -0
  16. package/dist/appManifest/data.d.ts.map +1 -1
  17. package/dist/appManifest/data.js +22 -3
  18. package/dist/appManifest/data.js.map +1 -1
  19. package/dist/appManifest/dataSources.d.ts +1 -0
  20. package/dist/appManifest/dataSources.d.ts.map +1 -1
  21. package/dist/appManifest/dataSources.js +7 -6
  22. package/dist/appManifest/dataSources.js.map +1 -1
  23. package/dist/appManifest/deploy.d.ts +1 -0
  24. package/dist/appManifest/deploy.d.ts.map +1 -1
  25. package/dist/appManifest/deploy.js +16 -13
  26. package/dist/appManifest/deploy.js.map +1 -1
  27. package/dist/appManifest/icon.d.ts.map +1 -1
  28. package/dist/appManifest/icon.js +2 -1
  29. package/dist/appManifest/icon.js.map +1 -1
  30. package/dist/appManifest/isAppManifest.d.ts +4 -0
  31. package/dist/appManifest/isAppManifest.d.ts.map +1 -0
  32. package/dist/appManifest/isAppManifest.js +69 -0
  33. package/dist/appManifest/isAppManifest.js.map +1 -0
  34. package/dist/appManifest/isAppStateSpec.d.ts +4 -0
  35. package/dist/appManifest/isAppStateSpec.d.ts.map +1 -0
  36. package/dist/appManifest/isAppStateSpec.js +9 -0
  37. package/dist/appManifest/isAppStateSpec.js.map +1 -0
  38. package/dist/appManifest/isManifestValue.d.ts +3 -0
  39. package/dist/appManifest/isManifestValue.d.ts.map +1 -0
  40. package/dist/appManifest/isManifestValue.js +15 -0
  41. package/dist/appManifest/isManifestValue.js.map +1 -0
  42. package/dist/appManifest/media.d.ts +1 -0
  43. package/dist/appManifest/media.d.ts.map +1 -1
  44. package/dist/appManifest/media.js +10 -8
  45. package/dist/appManifest/media.js.map +1 -1
  46. package/dist/appManifest/navigator.d.ts.map +1 -1
  47. package/dist/appManifest/navigator.js +7 -6
  48. package/dist/appManifest/navigator.js.map +1 -1
  49. package/dist/appManifest/navigatorOptions.d.ts +0 -2
  50. package/dist/appManifest/navigatorOptions.d.ts.map +1 -1
  51. package/dist/appManifest/navigatorOptions.js +8 -12
  52. package/dist/appManifest/navigatorOptions.js.map +1 -1
  53. package/dist/appManifest/parseAppManifest.d.ts +10 -0
  54. package/dist/appManifest/parseAppManifest.d.ts.map +1 -0
  55. package/dist/appManifest/parseAppManifest.js +14 -0
  56. package/dist/appManifest/parseAppManifest.js.map +1 -0
  57. package/dist/appManifest/screens.d.ts +4 -0
  58. package/dist/appManifest/screens.d.ts.map +1 -1
  59. package/dist/appManifest/screens.js +14 -2
  60. package/dist/appManifest/screens.js.map +1 -1
  61. package/dist/appManifest/splashScreen.d.ts +15 -0
  62. package/dist/appManifest/splashScreen.d.ts.map +1 -0
  63. package/dist/appManifest/splashScreen.js +2 -0
  64. package/dist/appManifest/splashScreen.js.map +1 -0
  65. package/dist/appManifest.d.ts +4 -18
  66. package/dist/appManifest.d.ts.map +1 -1
  67. package/dist/appManifest.js +2 -80
  68. package/dist/appManifest.js.map +1 -1
  69. package/dist/auth.d.ts +3 -2
  70. package/dist/auth.d.ts.map +1 -1
  71. package/dist/auth.js.map +1 -1
  72. package/dist/deploy.d.ts +0 -9
  73. package/dist/deploy.d.ts.map +1 -1
  74. package/dist/deploy.js +0 -8
  75. package/dist/deploy.js.map +1 -1
  76. package/dist/environments.d.ts +4 -0
  77. package/dist/environments.d.ts.map +1 -0
  78. package/dist/environments.js +3 -0
  79. package/dist/environments.js.map +1 -0
  80. package/dist/index.d.ts +2 -1
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +2 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/infra/constants.d.ts +91 -0
  85. package/dist/infra/constants.d.ts.map +1 -0
  86. package/dist/infra/constants.js +91 -0
  87. package/dist/infra/constants.js.map +1 -0
  88. package/dist/infra/infraFields.d.ts +12 -0
  89. package/dist/infra/infraFields.d.ts.map +1 -0
  90. package/dist/infra/infraFields.js +14 -0
  91. package/dist/infra/infraFields.js.map +1 -0
  92. package/dist/infra/isInfraAdapterDescriptor.d.ts +4 -0
  93. package/dist/infra/isInfraAdapterDescriptor.d.ts.map +1 -0
  94. package/dist/infra/isInfraAdapterDescriptor.js +31 -0
  95. package/dist/infra/isInfraAdapterDescriptor.js.map +1 -0
  96. package/dist/infra/isInfraAuthSpec.d.ts +4 -0
  97. package/dist/infra/isInfraAuthSpec.d.ts.map +1 -0
  98. package/dist/infra/isInfraAuthSpec.js +75 -0
  99. package/dist/infra/isInfraAuthSpec.js.map +1 -0
  100. package/dist/infra/isInfraCredentialRef.d.ts +3 -0
  101. package/dist/infra/isInfraCredentialRef.d.ts.map +1 -0
  102. package/dist/infra/isInfraCredentialRef.js +10 -0
  103. package/dist/infra/isInfraCredentialRef.js.map +1 -0
  104. package/dist/infra/isInfraDeploymentSpec.d.ts +4 -0
  105. package/dist/infra/isInfraDeploymentSpec.d.ts.map +1 -0
  106. package/dist/infra/isInfraDeploymentSpec.js +66 -0
  107. package/dist/infra/isInfraDeploymentSpec.js.map +1 -0
  108. package/dist/infra/isInfraEnvironmentSpec.d.ts +4 -0
  109. package/dist/infra/isInfraEnvironmentSpec.d.ts.map +1 -0
  110. package/dist/infra/isInfraEnvironmentSpec.js +82 -0
  111. package/dist/infra/isInfraEnvironmentSpec.js.map +1 -0
  112. package/dist/infra/isInfraManifest.d.ts +4 -0
  113. package/dist/infra/isInfraManifest.d.ts.map +1 -0
  114. package/dist/infra/isInfraManifest.js +23 -0
  115. package/dist/infra/isInfraManifest.js.map +1 -0
  116. package/dist/infra/isInfraShape.d.ts +3 -0
  117. package/dist/infra/isInfraShape.d.ts.map +1 -0
  118. package/dist/infra/isInfraShape.js +10 -0
  119. package/dist/infra/isInfraShape.js.map +1 -0
  120. package/dist/infra/isInfraWorkloadHealth.d.ts +3 -0
  121. package/dist/infra/isInfraWorkloadHealth.d.ts.map +1 -0
  122. package/dist/infra/isInfraWorkloadHealth.js +37 -0
  123. package/dist/infra/isInfraWorkloadHealth.js.map +1 -0
  124. package/dist/infra/isInfraWorkloadSpec.d.ts +4 -0
  125. package/dist/infra/isInfraWorkloadSpec.d.ts.map +1 -0
  126. package/dist/infra/isInfraWorkloadSpec.js +70 -0
  127. package/dist/infra/isInfraWorkloadSpec.js.map +1 -0
  128. package/dist/infra/isInfraWorkloadValue.d.ts +3 -0
  129. package/dist/infra/isInfraWorkloadValue.d.ts.map +1 -0
  130. package/dist/infra/isInfraWorkloadValue.js +40 -0
  131. package/dist/infra/isInfraWorkloadValue.js.map +1 -0
  132. package/dist/infra/parseInfraManifest.d.ts +5 -0
  133. package/dist/infra/parseInfraManifest.d.ts.map +1 -0
  134. package/dist/infra/parseInfraManifest.js +17 -0
  135. package/dist/infra/parseInfraManifest.js.map +1 -0
  136. package/dist/infra/validateInfraAdapterSelection.d.ts +6 -0
  137. package/dist/infra/validateInfraAdapterSelection.d.ts.map +1 -0
  138. package/dist/infra/validateInfraAdapterSelection.js +36 -0
  139. package/dist/infra/validateInfraAdapterSelection.js.map +1 -0
  140. package/dist/infra.d.ts +16 -0
  141. package/dist/infra.d.ts.map +1 -0
  142. package/dist/infra.js +11 -0
  143. package/dist/infra.js.map +1 -0
  144. package/dist/media.d.ts +7 -6
  145. package/dist/media.d.ts.map +1 -1
  146. package/dist/media.js +5 -5
  147. package/dist/media.js.map +1 -1
  148. package/dist/state.d.ts +5 -0
  149. package/dist/state.d.ts.map +1 -1
  150. package/dist/state.js.map +1 -1
  151. package/dist/types/appManifest.d.ts +9 -0
  152. package/dist/types/appManifest.d.ts.map +1 -0
  153. package/dist/types/appManifest.js +2 -0
  154. package/dist/types/appManifest.js.map +1 -0
  155. package/dist/types/infraAdapters.d.ts +85 -0
  156. package/dist/types/infraAdapters.d.ts.map +1 -0
  157. package/dist/types/infraAdapters.js +2 -0
  158. package/dist/types/infraAdapters.js.map +1 -0
  159. package/dist/types/infraLifecycle.d.ts +107 -0
  160. package/dist/types/infraLifecycle.d.ts.map +1 -0
  161. package/dist/types/infraLifecycle.js +2 -0
  162. package/dist/types/infraLifecycle.js.map +1 -0
  163. package/dist/types/infraManifest.d.ts +142 -0
  164. package/dist/types/infraManifest.d.ts.map +1 -0
  165. package/dist/types/infraManifest.js +2 -0
  166. package/dist/types/infraManifest.js.map +1 -0
  167. package/dist/types/infraSecrets.d.ts +15 -0
  168. package/dist/types/infraSecrets.d.ts.map +1 -0
  169. package/dist/types/infraSecrets.js +2 -0
  170. package/dist/types/infraSecrets.js.map +1 -0
  171. package/dist/types/infraValidation.d.ts +5 -0
  172. package/dist/types/infraValidation.d.ts.map +1 -0
  173. package/dist/types/infraValidation.js +2 -0
  174. package/dist/types/infraValidation.js.map +1 -0
  175. package/dist/types/infraWorkload.d.ts +70 -0
  176. package/dist/types/infraWorkload.d.ts.map +1 -0
  177. package/dist/types/infraWorkload.js +2 -0
  178. package/dist/types/infraWorkload.js.map +1 -0
  179. package/dist/types.d.ts +8 -82
  180. package/dist/types.d.ts.map +1 -1
  181. package/dist/types.js +0 -9
  182. package/dist/types.js.map +1 -1
  183. package/package.json +13 -3
  184. package/src/appManifest/apis.ts +18 -16
  185. package/src/appManifest/bindings.ts +18 -1
  186. package/src/appManifest/constants.ts +19 -0
  187. package/src/appManifest/data.ts +23 -9
  188. package/src/appManifest/dataSources.ts +8 -7
  189. package/src/appManifest/deploy.ts +17 -15
  190. package/src/appManifest/icon.ts +3 -1
  191. package/src/appManifest/isAppManifest.ts +95 -0
  192. package/src/appManifest/isAppStateSpec.ts +13 -0
  193. package/src/appManifest/isManifestValue.ts +19 -0
  194. package/src/appManifest/media.ts +11 -10
  195. package/src/appManifest/navigator.ts +7 -6
  196. package/src/appManifest/navigatorOptions.ts +9 -16
  197. package/src/appManifest/parseAppManifest.ts +15 -0
  198. package/src/appManifest/screens.ts +14 -2
  199. package/src/appManifest/splashScreen.test.ts +40 -0
  200. package/src/appManifest/splashScreen.ts +24 -0
  201. package/src/appManifest.test.ts +9 -5
  202. package/src/appManifest.ts +8 -106
  203. package/src/auth-flow-contract.test.ts +6 -10
  204. package/src/auth-oauth.test.ts +2 -2
  205. package/src/auth.ts +3 -2
  206. package/src/bindings.test.ts +5 -0
  207. package/src/contracts.test.ts +21 -39
  208. package/src/deploy.test.ts +13 -5
  209. package/src/deploy.ts +0 -11
  210. package/src/environments.ts +4 -0
  211. package/src/index.ts +2 -1
  212. package/src/infra/constants.ts +91 -0
  213. package/src/infra/infraFields.ts +20 -0
  214. package/src/infra/isInfraAdapterDescriptor.ts +37 -0
  215. package/src/infra/isInfraAuthSpec.ts +98 -0
  216. package/src/infra/isInfraCredentialRef.ts +11 -0
  217. package/src/infra/isInfraDeploymentSpec.ts +75 -0
  218. package/src/infra/isInfraEnvironmentSpec.ts +95 -0
  219. package/src/infra/isInfraManifest.ts +29 -0
  220. package/src/infra/isInfraShape.ts +16 -0
  221. package/src/infra/isInfraWorkloadHealth.ts +38 -0
  222. package/src/infra/isInfraWorkloadSpec.ts +87 -0
  223. package/src/infra/isInfraWorkloadValue.ts +41 -0
  224. package/src/infra/parseInfraManifest.ts +20 -0
  225. package/src/infra/validateInfraAdapterSelection.ts +42 -0
  226. package/src/infra-types.test.ts +78 -0
  227. package/src/infra-workload.test.ts +186 -0
  228. package/src/infra.test.ts +268 -0
  229. package/src/infra.ts +15 -0
  230. package/src/media.ts +12 -11
  231. package/src/profile-contract.test.ts +2 -3
  232. package/src/secrets.test.ts +19 -13
  233. package/src/state.ts +6 -0
  234. package/src/types/appManifest.ts +5 -0
  235. package/src/types/infraAdapters.ts +148 -0
  236. package/src/types/infraLifecycle.ts +105 -0
  237. package/src/types/infraManifest.ts +130 -0
  238. package/src/types/infraSecrets.ts +16 -0
  239. package/src/types/infraValidation.ts +2 -0
  240. package/src/types/infraWorkload.ts +64 -0
  241. package/src/types.ts +8 -103
  242. package/dist/appManifest/infra.d.ts +0 -2
  243. package/dist/appManifest/infra.d.ts.map +0 -1
  244. package/dist/appManifest/infra.js +0 -130
  245. package/dist/appManifest/infra.js.map +0 -1
  246. package/dist/appManifest/shared.d.ts +0 -8
  247. package/dist/appManifest/shared.d.ts.map +0 -1
  248. package/dist/appManifest/shared.js +0 -31
  249. package/dist/appManifest/shared.js.map +0 -1
  250. package/dist/secretManifest.d.ts +0 -11
  251. package/dist/secretManifest.d.ts.map +0 -1
  252. package/dist/secretManifest.js +0 -2
  253. package/dist/secretManifest.js.map +0 -1
  254. package/src/appManifest/infra.ts +0 -190
  255. package/src/appManifest/shared.ts +0 -40
  256. package/src/secretManifest.ts +0 -12
@@ -9,11 +9,10 @@ import {
9
9
  APP_CATEGORIES,
10
10
  type AppCategory,
11
11
  type AppManifest,
12
- AUTH_PROVIDERS,
12
+ type AppStateSpec,
13
13
  AUTH_SIGN_IN_IDENTIFIERS,
14
14
  AUTH_SIGN_UP_POLICIES,
15
15
  type AuthFlowConfig,
16
- type AuthSpec,
17
16
  type ButtonPressEventDto,
18
17
  type CollectionItemPressEventDto,
19
18
  type ComponentEventDto,
@@ -22,16 +21,12 @@ import {
22
21
  type DbAdminAdapter,
23
22
  type DbChangeEvent,
24
23
  type DbRealtimeAdapter,
25
- DEPLOYMENT_TARGETS,
26
24
  type FormSubmitEventDto,
27
25
  type ImageAssetSource,
26
+ type InfraAuthSpec,
28
27
  type InfraManifest,
29
28
  NAVIGATOR_TYPES,
30
29
  type RouteDefinition,
31
- type SplashScreenSpec,
32
- STATE_PERSISTENCE_MODES,
33
- STATE_PROVIDERS,
34
- type StateSpec,
35
30
  type StoragePublicUrlResult,
36
31
  type StorageResult,
37
32
  type StorageUploadResult,
@@ -106,10 +101,6 @@ describe('contracts', () => {
106
101
  'utilities_tools',
107
102
  'weather',
108
103
  ]);
109
- expect(DEPLOYMENT_TARGETS).toEqual(['minikube']);
110
- expect(AUTH_PROVIDERS).toEqual(['supabase']);
111
- expect(STATE_PROVIDERS).toEqual(['legend']);
112
- expect(STATE_PERSISTENCE_MODES).toEqual(['none', 'local', 'secure', 'database']);
113
104
  });
114
105
 
115
106
  it('exports the app category union for template packages', () => {
@@ -196,25 +187,6 @@ describe('contracts', () => {
196
187
  expect(theme.recipes?.patterns?.Panel?.padding).toBe('xl');
197
188
  });
198
189
 
199
- it('accepts serializable splash screen branding on app manifests', () => {
200
- const splashScreen: SplashScreenSpec = {
201
- backgroundColor: '#ffffff',
202
- image: { mediaId: 'splash-logo' },
203
- imageWidth: 160,
204
- resizeMode: 'contain',
205
- dark: {
206
- backgroundColor: '#000000',
207
- image: { mediaId: 'splash-logo-dark' },
208
- imageWidth: 160,
209
- resizeMode: 'contain',
210
- },
211
- };
212
-
213
- const manifest: Pick<AppManifest, 'splashScreen'> = { splashScreen };
214
-
215
- expect(JSON.parse(JSON.stringify(manifest))).toEqual({ splashScreen });
216
- });
217
-
218
190
  it('accepts screens with operation data loaders and repeat empty-state nodes', () => {
219
191
  const manifest: Pick<AppManifest, 'screens'> = {
220
192
  screens: {
@@ -275,14 +247,19 @@ describe('contracts', () => {
275
247
  expect(manifest.screens.products?.root.repeat?.empty?.[0]?.type).toBe('Notice');
276
248
  });
277
249
 
278
- it('accepts provider-neutral state infra selection on app manifests', () => {
279
- const state: StateSpec = {
250
+ it('selects in-memory application state independently from infrastructure', () => {
251
+ const state: AppStateSpec = {
280
252
  provider: 'legend',
281
- persistence: 'none',
253
+ persistence: false,
282
254
  };
283
- const manifest: Pick<AppManifest, 'infra'> = {
255
+ const manifest: Pick<AppManifest, 'infra' | 'state'> = {
256
+ state,
284
257
  infra: {
285
- state,
258
+ environments: {
259
+ local: {
260
+ deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
261
+ },
262
+ },
286
263
  modules: [],
287
264
  modulesConfig: {
288
265
  localization: {
@@ -293,8 +270,9 @@ describe('contracts', () => {
293
270
  };
294
271
 
295
272
  expect(JSON.parse(JSON.stringify(manifest))).toEqual({
273
+ state,
296
274
  infra: {
297
- state,
275
+ environments: manifest.infra.environments,
298
276
  modules: [],
299
277
  modulesConfig: {
300
278
  localization: {
@@ -312,6 +290,11 @@ describe('contracts', () => {
312
290
  const hasLegacyModules: HasLegacyModules = false;
313
291
  const hasLegacyModulesConfig: HasLegacyModulesConfig = false;
314
292
  const infra: InfraManifest = {
293
+ environments: {
294
+ local: {
295
+ deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
296
+ },
297
+ },
315
298
  modules: ['expo-localization'],
316
299
  modulesConfig: {
317
300
  'expo-localization': {
@@ -328,7 +311,7 @@ describe('contracts', () => {
328
311
 
329
312
  expect(hasLegacyModules).toBe(false);
330
313
  expect(hasLegacyModulesConfig).toBe(false);
331
- expect(Object.keys(infra).sort()).toEqual(['modules', 'modulesConfig']);
314
+ expect(Object.keys(infra).sort()).toEqual(['environments', 'modules', 'modulesConfig']);
332
315
  expect(expoConfig.plugins).toEqual(['expo-router']);
333
316
  });
334
317
 
@@ -387,10 +370,9 @@ describe('contracts', () => {
387
370
  unauthorizedRoute: '/sign-in',
388
371
  };
389
372
 
390
- const auth: AuthSpec = {
373
+ const auth: InfraAuthSpec = {
391
374
  scope: 'global',
392
375
  provider: 'supabase',
393
- authorization: { kind: 'RBAC', engine: 'cerbos' },
394
376
  flow: authFlow,
395
377
  signIn: { identifiers: ['email'] },
396
378
  signUp: {
@@ -4,8 +4,9 @@ import { join } from 'node:path';
4
4
  import { describe, expect, it } from 'bun:test';
5
5
 
6
6
  import { isAppManifest } from './appManifest';
7
- import { APP_DEPLOY_ENVIRONMENT_IDS, APP_DEPLOY_TARGET_IDS, isAppDeployManifest } from './deploy';
8
- import { DEPLOYMENT_TARGETS } from './types';
7
+ import { APP_DEPLOY_TARGET_IDS, isAppDeployManifest } from './deploy';
8
+ import { APP_ENVIRONMENT_IDS } from './environments';
9
+ import { INFRA_RUNTIME_COMPATIBILITY } from './infra';
9
10
 
10
11
  function createManifest(deploy?: unknown): Record<string, unknown> {
11
12
  return {
@@ -26,7 +27,14 @@ function createManifest(deploy?: unknown): Record<string, unknown> {
26
27
  ],
27
28
  activeThemeId: 'default',
28
29
  ...(deploy === undefined ? {} : { deploy }),
29
- infra: { deployment: { target: 'minikube', monitoring: true }, modules: [] },
30
+ infra: {
31
+ environments: {
32
+ local: {
33
+ deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
34
+ },
35
+ },
36
+ modules: [],
37
+ },
30
38
  navigator: { type: 'stack', routes: [] },
31
39
  screens: {},
32
40
  settings: { localization: { defaultLocale: 'en', locales: ['en'] } },
@@ -36,11 +44,11 @@ function createManifest(deploy?: unknown): Record<string, unknown> {
36
44
  describe('app deployment contracts', () => {
37
45
  it('keeps app distribution targets distinct from Infra deployment targets', () => {
38
46
  expect(APP_DEPLOY_TARGET_IDS).toEqual(['web', 'android', 'ios']);
39
- expect(DEPLOYMENT_TARGETS).toEqual(['minikube']);
47
+ expect(Object.keys(INFRA_RUNTIME_COMPATIBILITY)).toEqual(['minikube', 'k3s', 'docker-compose']);
40
48
  });
41
49
 
42
50
  it('defines one logical environment vocabulary across platform-specific execution', () => {
43
- expect(APP_DEPLOY_ENVIRONMENT_IDS).toEqual(['local', 'preview', 'production']);
51
+ expect(APP_ENVIRONMENT_IDS).toEqual(['local', 'preview', 'production']);
44
52
  });
45
53
 
46
54
  it('accepts canonical web, Android, and iOS desired state with stable native schemes', () => {
package/src/deploy.ts CHANGED
@@ -2,17 +2,6 @@ export const APP_DEPLOY_TARGET_IDS = ['web', 'android', 'ios'] as const;
2
2
 
3
3
  export type AppDeployTargetId = (typeof APP_DEPLOY_TARGET_IDS)[number];
4
4
 
5
- /**
6
- * Logical application environments shared across platform-specific planning.
7
- *
8
- * `local` maps to local Web, iOS development, and Android debug execution.
9
- * Environment-specific endpoints, signing material, and credentials are owned
10
- * by their dedicated deployment/infra configuration rather than this identity contract.
11
- */
12
- export const APP_DEPLOY_ENVIRONMENT_IDS = ['local', 'preview', 'production'] as const;
13
-
14
- export type AppDeployEnvironmentId = (typeof APP_DEPLOY_ENVIRONMENT_IDS)[number];
15
-
16
5
  export interface AppDeployProviderSelection {
17
6
  readonly build?: string;
18
7
  readonly publish?: string;
@@ -0,0 +1,4 @@
1
+ /** Logical application environments shared by infrastructure and application shipment. */
2
+ export const APP_ENVIRONMENT_IDS = ['local', 'preview', 'production'] as const;
3
+
4
+ export type AppEnvironmentId = (typeof APP_ENVIRONMENT_IDS)[number];
package/src/index.ts CHANGED
@@ -5,12 +5,13 @@ export * from './cli';
5
5
  export * from './data';
6
6
  export * from './db';
7
7
  export * from './deploy';
8
+ export * from './environments';
9
+ export * from './infra';
8
10
  export * from './media';
9
11
  export * from './navigator';
10
12
  export * from './repository';
11
13
  export * from './requirements';
12
14
  export * from './runtimeCallbacks';
13
- export * from './secretManifest';
14
15
  export * from './secrets';
15
16
  export * from './state';
16
17
  export * from './storage';
@@ -0,0 +1,91 @@
1
+ /** One compatibility source for manifest types, validation, discovery and configuration UIs. */
2
+ export const INFRA_RUNTIME_COMPATIBILITY = {
3
+ minikube: ['local'],
4
+ k3s: ['local', 'hetzner'],
5
+ 'docker-compose': ['local', 'hetzner'],
6
+ } as const;
7
+
8
+ /** Canonical package identities. Capabilities describe ownership, not eight mandatory methods. */
9
+ export const INFRA_ADAPTER_CATALOG = {
10
+ local: {
11
+ id: 'local',
12
+ package: '@ankhorage/local',
13
+ kind: 'compute',
14
+ configVersion: 1,
15
+ capabilities: ['compute'],
16
+ targets: ['local-host'],
17
+ dependencies: [],
18
+ },
19
+ hetzner: {
20
+ id: 'hetzner',
21
+ package: '@ankhorage/hetzner',
22
+ kind: 'compute',
23
+ configVersion: 1,
24
+ capabilities: ['compute'],
25
+ targets: ['ssh-host'],
26
+ dependencies: [],
27
+ },
28
+ minikube: {
29
+ id: 'minikube',
30
+ package: '@ankhorage/minikube',
31
+ kind: 'runtime',
32
+ configVersion: 1,
33
+ capabilities: ['runtime', 'workloads', 'networking'],
34
+ targets: ['local-host'],
35
+ dependencies: ['compute'],
36
+ },
37
+ k3s: {
38
+ id: 'k3s',
39
+ package: '@ankhorage/k3s',
40
+ kind: 'runtime',
41
+ configVersion: 1,
42
+ capabilities: ['runtime', 'workloads', 'networking'],
43
+ targets: ['local-host', 'ssh-host'],
44
+ dependencies: ['compute'],
45
+ },
46
+ 'docker-compose': {
47
+ id: 'docker-compose',
48
+ package: '@ankhorage/docker-compose',
49
+ kind: 'runtime',
50
+ configVersion: 1,
51
+ capabilities: ['runtime', 'workloads', 'networking'],
52
+ targets: ['local-host', 'ssh-host'],
53
+ dependencies: ['compute'],
54
+ },
55
+ supabase: {
56
+ id: 'supabase',
57
+ package: '@ankhorage/supabase',
58
+ kind: 'service',
59
+ configVersion: 1,
60
+ capabilities: ['database', 'auth', 'objectStorage'],
61
+ targets: [],
62
+ dependencies: ['workloads'],
63
+ },
64
+ cerbos: {
65
+ id: 'cerbos',
66
+ package: '@ankhorage/cerbos',
67
+ kind: 'service',
68
+ configVersion: 1,
69
+ capabilities: ['authz'],
70
+ targets: [],
71
+ dependencies: ['workloads'],
72
+ },
73
+ r2: {
74
+ id: 'r2',
75
+ package: '@ankhorage/r2',
76
+ kind: 'service',
77
+ configVersion: 1,
78
+ capabilities: ['objectStorage'],
79
+ targets: [],
80
+ dependencies: [],
81
+ },
82
+ 'supabase-vault': {
83
+ id: 'supabase-vault',
84
+ package: '@ankhorage/supabase-vault',
85
+ kind: 'service',
86
+ configVersion: 1,
87
+ capabilities: ['secretStore'],
88
+ targets: [],
89
+ dependencies: ['database'],
90
+ },
91
+ } as const;
@@ -0,0 +1,20 @@
1
+ import { isStringArray } from '@ankhorage/utility/array';
2
+ import { isNonEmptyString } from '@ankhorage/utility/string';
3
+
4
+ /*** Primitive field rules for portable Infra configuration, shared by its exact-shape validators. */
5
+ export const infraFields = {
6
+ optionalText: (value: unknown): boolean => value === undefined || isNonEmptyString(value),
7
+ strings: (value: unknown): boolean => isStringArray(value) && value.every(isNonEmptyString),
8
+ optionalStrings: (value: unknown): boolean =>
9
+ value === undefined || (isStringArray(value) && value.every(isNonEmptyString)),
10
+ positiveInteger: (value: unknown): boolean =>
11
+ typeof value === 'number' && Number.isSafeInteger(value) && value > 0,
12
+ nonnegativeInteger: (value: unknown): boolean =>
13
+ typeof value === 'number' && Number.isSafeInteger(value) && value >= 0,
14
+ optionalPositiveInteger: (value: unknown): boolean =>
15
+ value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value > 0),
16
+ optionalNonnegativeInteger: (value: unknown): boolean =>
17
+ value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0),
18
+ port: (value: unknown): boolean =>
19
+ typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535,
20
+ };
@@ -0,0 +1,37 @@
1
+ import { isRecord } from '@ankhorage/utility/object';
2
+
3
+ import type { InfraAdapterDescriptor } from '../types/infraAdapters';
4
+ import { INFRA_ADAPTER_CATALOG } from './constants';
5
+ import { isInfraShape } from './isInfraShape';
6
+
7
+ /*** Installed adapters must match the canonical identity, capabilities, targets and config version exactly. */
8
+ export function isInfraAdapterDescriptor(value: unknown): value is InfraAdapterDescriptor {
9
+ if (!isRecord(value)) return false;
10
+ const entry = Object.values(INFRA_ADAPTER_CATALOG).find((candidate) => candidate.id === value.id);
11
+ if (!entry) return false;
12
+ return isInfraShape(value, {
13
+ id: (id) => id === entry.id,
14
+ package: (name) => name === entry.package,
15
+ kind: (kind) => kind === entry.kind,
16
+ configVersion: (version) => version === entry.configVersion,
17
+ capabilities: (items) => matchesCatalogList(items, entry.capabilities),
18
+ targets: (items) => matchesCatalogList(items, entry.targets),
19
+ dependencies: (items) => matchesCatalogList(items, entry.dependencies),
20
+ operations: (items) =>
21
+ items === undefined ||
22
+ (Array.isArray(items) &&
23
+ new Set(items).size === items.length &&
24
+ items.every((item: unknown) =>
25
+ ['suspend', 'resume', 'generate', 'diagnostics'].some((operation) => operation === item),
26
+ )),
27
+ });
28
+ }
29
+
30
+ /*** Canonical descriptor lists are ordered so JSON discovery and conformance are deterministic. */
31
+ function matchesCatalogList(value: unknown, expected: readonly string[]): boolean {
32
+ return (
33
+ Array.isArray(value) &&
34
+ value.length === expected.length &&
35
+ value.every((item, index) => item === expected.at(index))
36
+ );
37
+ }
@@ -0,0 +1,98 @@
1
+ import { isStringArray } from '@ankhorage/utility/array';
2
+ import { isRecordOf } from '@ankhorage/utility/object';
3
+ import { isOptionalString } from '@ankhorage/utility/string';
4
+
5
+ import { isIconSpec } from '../appManifest/icon';
6
+ import { AUTH_OAUTH_PROVIDER_IDS } from '../auth';
7
+ import {
8
+ AUTH_PROFILE_CREATE_STRATEGIES,
9
+ AUTH_PROFILE_PRIMARY_KEY_STRATEGIES,
10
+ AUTH_PROFILE_UPDATE_STRATEGIES,
11
+ AUTH_SCOPES,
12
+ AUTH_SIGN_IN_IDENTIFIERS,
13
+ AUTH_SIGN_UP_POLICIES,
14
+ } from '../types';
15
+ import type { InfraAuthSpec } from '../types/infraManifest';
16
+ import type { InfraShape } from '../types/infraValidation';
17
+ import { isInfraShape } from './isInfraShape';
18
+
19
+ /*** Preserve application auth configuration while rejecting nested authorization and unknown providers. */
20
+ export function isInfraAuthSpec(value: unknown): value is InfraAuthSpec {
21
+ return isInfraShape(value, {
22
+ provider: (provider) => provider === 'supabase',
23
+ scope: (scope) => scope === undefined || AUTH_SCOPES.some((item) => item === scope),
24
+ flow: (flow) => flow === undefined || isAuthFlow(flow),
25
+ signIn: (signIn) =>
26
+ signIn === undefined ||
27
+ isInfraShape(signIn, {
28
+ identifiers: (ids) =>
29
+ Array.isArray(ids) &&
30
+ ids.every((id) => AUTH_SIGN_IN_IDENTIFIERS.some((item) => item === id)),
31
+ }),
32
+ signUp: (signUp) => signUp === undefined || isAuthSignUp(signUp),
33
+ oauth: (oauth) => oauth === undefined || isAuthOAuth(oauth),
34
+ profile: (profile) => profile === undefined || isAuthProfile(profile),
35
+ } satisfies InfraShape<InfraAuthSpec>);
36
+ }
37
+
38
+ /*** Auth routing intent stays separate from provider-runtime callbacks and Navigator topology. */
39
+ function isAuthFlow(value: unknown): boolean {
40
+ return isInfraShape(value, {
41
+ signInRoute: (route) => typeof route === 'string',
42
+ postSignInRoute: (route) => typeof route === 'string',
43
+ signUpRoute: isOptionalString,
44
+ signOutRoute: isOptionalString,
45
+ forgotPasswordRoute: isOptionalString,
46
+ otpRoute: isOptionalString,
47
+ unauthorizedRoute: isOptionalString,
48
+ });
49
+ }
50
+
51
+ /*** Validate sign-up field intent using the existing application auth contract. */
52
+ function isAuthSignUp(value: unknown): boolean {
53
+ return isInfraShape(value, {
54
+ requiredFields: isStringArray,
55
+ optionalFields: (fields) => fields === undefined || isStringArray(fields),
56
+ signUpPolicy: (policy) =>
57
+ policy === undefined || AUTH_SIGN_UP_POLICIES.some((item) => item === policy),
58
+ });
59
+ }
60
+
61
+ /*** OAuth references contain no credential payload. Custom OAuth identities remain application-owned. */
62
+ function isAuthOAuth(value: unknown): boolean {
63
+ return isInfraShape(value, {
64
+ enabled: (enabled) => typeof enabled === 'boolean',
65
+ callbackRoute: (route) => typeof route === 'string',
66
+ providers: (providers) => Array.isArray(providers) && providers.every(isAuthOAuthProvider),
67
+ });
68
+ }
69
+
70
+ /*** Validate OAuth display/config metadata, preserving explicitly extensible OAuth provider IDs. */
71
+ function isAuthOAuthProvider(value: unknown): boolean {
72
+ return isInfraShape(value, {
73
+ id: (id) =>
74
+ typeof id === 'string' &&
75
+ (AUTH_OAUTH_PROVIDER_IDS.some((item) => item === id) || id.length > 0),
76
+ label: isOptionalString,
77
+ enabled: (value) => value === undefined || typeof value === 'boolean',
78
+ scopes: (scopes) => scopes === undefined || isStringArray(scopes),
79
+ queryParams: (params) =>
80
+ params === undefined || isRecordOf(params, (entry) => typeof entry === 'string'),
81
+ icon: (icon) => icon === undefined || isIconSpec(icon),
82
+ credentialsRef: isOptionalString,
83
+ });
84
+ }
85
+
86
+ /*** Validate profile provisioning choices without inventing a new application profile model. */
87
+ function isAuthProfile(value: unknown): boolean {
88
+ return isInfraShape(value, {
89
+ fields: isStringArray,
90
+ table: isOptionalString,
91
+ primaryKey: (key) =>
92
+ key === undefined || AUTH_PROFILE_PRIMARY_KEY_STRATEGIES.some((item) => item === key),
93
+ createStrategy: (strategy) =>
94
+ strategy === undefined || AUTH_PROFILE_CREATE_STRATEGIES.some((item) => item === strategy),
95
+ updateStrategy: (strategy) =>
96
+ strategy === undefined || AUTH_PROFILE_UPDATE_STRATEGIES.some((item) => item === strategy),
97
+ });
98
+ }
@@ -0,0 +1,11 @@
1
+ import { isNonEmptyString } from '@ankhorage/utility/string';
2
+
3
+ import { isInfraShape } from './isInfraShape';
4
+
5
+ /*** Bootstrap credential references are explicitly separate from managed runtime secrets. */
6
+ export function isInfraCredentialRef(value: unknown): boolean {
7
+ return isInfraShape(value, {
8
+ source: (source) => source === 'control-plane',
9
+ name: isNonEmptyString,
10
+ });
11
+ }
@@ -0,0 +1,75 @@
1
+ import { isRecord } from '@ankhorage/utility/object';
2
+ import { isNonEmptyString } from '@ankhorage/utility/string';
3
+
4
+ import type {
5
+ InfraComputeSelection,
6
+ InfraDeploymentSpec,
7
+ InfraRuntimeSelection,
8
+ } from '../types/infraManifest';
9
+ import type { InfraShape } from '../types/infraValidation';
10
+ import { INFRA_RUNTIME_COMPATIBILITY } from './constants';
11
+ import { infraFields } from './infraFields';
12
+ import { isInfraCredentialRef } from './isInfraCredentialRef';
13
+ import { isInfraShape } from './isInfraShape';
14
+
15
+ /*** Validate provider-specific config and the exact same compatibility map used by TypeScript. */
16
+ export function isInfraDeploymentSpec(value: unknown): value is InfraDeploymentSpec {
17
+ if (!isInfraShape(value, { compute: isCompute, runtime: isRuntime }) || !isRecord(value))
18
+ return false;
19
+ if (!isRecord(value.compute) || !isRecord(value.runtime)) return false;
20
+ const compute = value.compute.provider;
21
+ const runtime = value.runtime.provider;
22
+ return Object.entries(INFRA_RUNTIME_COMPATIBILITY).some(
23
+ ([id, providers]) => id === runtime && providers.some((provider) => provider === compute),
24
+ );
25
+ }
26
+
27
+ /*** Compute configuration is closed per provider; control-plane refs cannot be managed secrets. */
28
+ function isCompute(value: unknown): boolean {
29
+ if (!isRecord(value)) return false;
30
+ if (value.provider === 'local')
31
+ return isInfraShape(value, {
32
+ provider: (provider) => provider === 'local',
33
+ workingDirectory: infraFields.optionalText,
34
+ } satisfies InfraShape<InfraComputeSelection<'local'>>);
35
+ return isInfraShape(value, {
36
+ provider: (provider) => provider === 'hetzner',
37
+ location: isNonEmptyString,
38
+ serverType: infraFields.optionalText,
39
+ image: infraFields.optionalText,
40
+ credentials: (credentials) => credentials === undefined || isInfraCredentialRef(credentials),
41
+ } satisfies InfraShape<InfraComputeSelection<'hetzner'>>);
42
+ }
43
+
44
+ /*** Runtime topology expresses host roles and never couples k3s to a compute vendor. */
45
+ function isRuntime(value: unknown): boolean {
46
+ if (!isRecord(value)) return false;
47
+ switch (value.provider) {
48
+ case 'minikube':
49
+ return isInfraShape(value, {
50
+ provider: (provider) => provider === 'minikube',
51
+ profile: infraFields.optionalText,
52
+ driver: (driver) => driver === undefined || driver === 'docker' || driver === 'podman',
53
+ cpus: infraFields.optionalPositiveInteger,
54
+ memoryMiB: infraFields.optionalPositiveInteger,
55
+ } satisfies InfraShape<InfraRuntimeSelection<'minikube'>>);
56
+ case 'k3s':
57
+ return isInfraShape(value, {
58
+ provider: (provider) => provider === 'k3s',
59
+ version: infraFields.optionalText,
60
+ topology: (topology) =>
61
+ topology === undefined ||
62
+ isInfraShape(topology, {
63
+ servers: infraFields.positiveInteger,
64
+ agents: infraFields.nonnegativeInteger,
65
+ }),
66
+ } satisfies InfraShape<InfraRuntimeSelection<'k3s'>>);
67
+ case 'docker-compose':
68
+ return isInfraShape(value, {
69
+ provider: (provider) => provider === 'docker-compose',
70
+ projectName: infraFields.optionalText,
71
+ } satisfies InfraShape<InfraRuntimeSelection<'docker-compose'>>);
72
+ default:
73
+ return false;
74
+ }
75
+ }
@@ -0,0 +1,95 @@
1
+ import { isRecord } from '@ankhorage/utility/object';
2
+
3
+ import type { InfraEnvironmentSpec, InfraObjectStorageSpec } from '../types/infraManifest';
4
+ import type { InfraShape } from '../types/infraValidation';
5
+ import { INFRA_ADAPTER_CATALOG } from './constants';
6
+ import { infraFields } from './infraFields';
7
+ import { isInfraAuthSpec } from './isInfraAuthSpec';
8
+ import { isInfraCredentialRef } from './isInfraCredentialRef';
9
+ import { isInfraDeploymentSpec } from './isInfraDeploymentSpec';
10
+ import { isInfraShape } from './isInfraShape';
11
+ import { isInfraWorkloadSpec } from './isInfraWorkloadSpec';
12
+
13
+ /*** Validate sibling capabilities and their required relationships within one environment. */
14
+ export function isInfraEnvironmentSpec(value: unknown): value is InfraEnvironmentSpec {
15
+ return isEnvironmentShape(value) && hasServiceDependencies(value);
16
+ }
17
+
18
+ /*** Check all environment field shapes before inspecting canonical dependency metadata. */
19
+ function isEnvironmentShape(value: unknown): value is InfraEnvironmentSpec {
20
+ return isInfraShape(value, {
21
+ deployment: isInfraDeploymentSpec,
22
+ database: (database) =>
23
+ database === undefined ||
24
+ isInfraShape(database, {
25
+ provider: (provider) => provider === 'supabase',
26
+ tier: (tier) => tier === undefined || tier === 'dev' || tier === 'prod',
27
+ }),
28
+ objectStorage: (storage) => storage === undefined || isObjectStorage(storage),
29
+ auth: (auth) => auth === undefined || isInfraAuthSpec(auth),
30
+ authz: (authz) =>
31
+ authz === undefined ||
32
+ isInfraShape(authz, {
33
+ provider: (provider) => provider === 'cerbos',
34
+ kind: (kind) => kind === 'ABAC' || kind === 'RBAC',
35
+ }),
36
+ secretStore: (store) =>
37
+ store === undefined ||
38
+ isInfraShape(store, {
39
+ provider: (provider) => provider === 'supabase-vault',
40
+ schema: infraFields.optionalText,
41
+ }),
42
+ networking: (networking) =>
43
+ networking === undefined || isInfraShape(networking, { domain: infraFields.optionalText }),
44
+ workloads: (workloads) => workloads === undefined || isWorkloads(workloads),
45
+ } satisfies InfraShape<InfraEnvironmentSpec>);
46
+ }
47
+
48
+ /*** Object storage can vary independently from database/auth; there is no implicit auto provider. */
49
+ function isObjectStorage(value: unknown): boolean {
50
+ if (!isRecord(value)) return false;
51
+ const common = {
52
+ provider: (provider: unknown) => provider === value.provider,
53
+ buckets: infraFields.optionalStrings,
54
+ };
55
+ if (value.provider === 'supabase') return isInfraShape(value, common);
56
+ return isInfraShape(value, {
57
+ ...common,
58
+ provider: (provider) => provider === 'r2',
59
+ accountId: infraFields.optionalText,
60
+ credentials: (credentials) => credentials === undefined || isInfraCredentialRef(credentials),
61
+ } satisfies InfraShape<Extract<InfraObjectStorageSpec, { readonly provider: 'r2' }>>);
62
+ }
63
+
64
+ /*** Duplicate desired workload identities cannot be reconciled safely. */
65
+ function isWorkloads(value: unknown): boolean {
66
+ return (
67
+ Array.isArray(value) &&
68
+ value.every(isInfraWorkloadSpec) &&
69
+ new Set(value.map((workload) => workload.id)).size === value.length
70
+ );
71
+ }
72
+
73
+ /*** Check catalog dependencies against selected sibling capabilities and runtime capabilities. */
74
+ function hasServiceDependencies(value: InfraEnvironmentSpec): boolean {
75
+ const selections = [
76
+ value.database,
77
+ value.auth,
78
+ value.authz,
79
+ value.objectStorage,
80
+ value.secretStore,
81
+ ];
82
+ const runtime = Object.values(INFRA_ADAPTER_CATALOG).find(
83
+ (entry) => entry.id === value.deployment.runtime.provider,
84
+ );
85
+ const capabilities = new Set<string>([
86
+ 'compute',
87
+ ...(runtime?.capabilities ?? []),
88
+ ...Object.entries(value)
89
+ .filter(([, selection]) => selection !== undefined)
90
+ .map(([capability]) => capability),
91
+ ]);
92
+ return Object.values(INFRA_ADAPTER_CATALOG)
93
+ .filter((entry) => selections.some((selection) => selection?.provider === entry.id))
94
+ .every((entry) => entry.dependencies.every((dependency) => capabilities.has(dependency)));
95
+ }