@revoengine/cli 1.0.9 → 1.0.11

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 (81) hide show
  1. package/README.md +464 -8
  2. package/dist/src/cli.js +65 -6
  3. package/dist/src/client.d.ts +366 -5
  4. package/dist/src/client.js +954 -13
  5. package/dist/src/commands/auth.js +4 -2
  6. package/dist/src/commands/component.js +839 -412
  7. package/dist/src/commands/database-schemas.d.ts +2 -0
  8. package/dist/src/commands/database-schemas.js +188 -0
  9. package/dist/src/commands/database-views.d.ts +2 -0
  10. package/dist/src/commands/database-views.js +123 -0
  11. package/dist/src/commands/endpoints.js +114 -0
  12. package/dist/src/commands/env.d.ts +2 -0
  13. package/dist/src/commands/env.js +380 -0
  14. package/dist/src/commands/events.d.ts +2 -0
  15. package/dist/src/commands/events.js +146 -0
  16. package/dist/src/commands/groups.d.ts +2 -0
  17. package/dist/src/commands/groups.js +169 -0
  18. package/dist/src/commands/index.d.ts +10 -0
  19. package/dist/src/commands/index.js +10 -0
  20. package/dist/src/commands/job-templates.d.ts +2 -0
  21. package/dist/src/commands/job-templates.js +101 -0
  22. package/dist/src/commands/metadata.d.ts +2 -0
  23. package/dist/src/commands/metadata.js +159 -0
  24. package/dist/src/commands/project.js +82 -1
  25. package/dist/src/commands/role-groups.d.ts +2 -0
  26. package/dist/src/commands/role-groups.js +152 -0
  27. package/dist/src/commands/schedules.d.ts +2 -0
  28. package/dist/src/commands/schedules.js +141 -0
  29. package/dist/src/commands/terminal-service.d.ts +38 -0
  30. package/dist/src/commands/terminal-service.js +210 -0
  31. package/dist/src/commands/terminal.d.ts +22 -0
  32. package/dist/src/commands/terminal.js +511 -0
  33. package/dist/src/component-lock.d.ts +126 -2
  34. package/dist/src/component-lock.js +378 -15
  35. package/dist/src/config.d.ts +20 -0
  36. package/dist/src/config.js +121 -6
  37. package/dist/src/database-schema-artifacts.d.ts +7 -0
  38. package/dist/src/database-schema-artifacts.js +8 -0
  39. package/dist/src/env-sync.d.ts +83 -0
  40. package/dist/src/env-sync.js +315 -0
  41. package/dist/src/metadata-backfill.d.ts +56 -0
  42. package/dist/src/metadata-backfill.js +1176 -0
  43. package/dist/src/project.d.ts +17 -9
  44. package/dist/src/project.js +87 -11
  45. package/dist/src/prompt.js +10 -18
  46. package/dist/src/resource-metadata.d.ts +25 -0
  47. package/dist/src/resource-metadata.js +132 -0
  48. package/dist/src/resource-syncs/database-schema-sync.d.ts +117 -0
  49. package/dist/src/resource-syncs/database-schema-sync.js +2289 -0
  50. package/dist/src/resource-syncs/database-view-sync.d.ts +124 -0
  51. package/dist/src/resource-syncs/database-view-sync.js +1317 -0
  52. package/dist/src/resource-syncs/endpoint-sync.d.ts +96 -0
  53. package/dist/src/resource-syncs/endpoint-sync.js +1283 -0
  54. package/dist/src/resource-syncs/event-sync.d.ts +99 -0
  55. package/dist/src/resource-syncs/event-sync.js +949 -0
  56. package/dist/src/resource-syncs/group-sync.d.ts +86 -0
  57. package/dist/src/resource-syncs/group-sync.js +882 -0
  58. package/dist/src/resource-syncs/job-template-sync.d.ts +85 -0
  59. package/dist/src/resource-syncs/job-template-sync.js +782 -0
  60. package/dist/src/resource-syncs/role-group-sync.d.ts +83 -0
  61. package/dist/src/resource-syncs/role-group-sync.js +597 -0
  62. package/dist/src/resource-syncs/schedule-sync.d.ts +111 -0
  63. package/dist/src/resource-syncs/schedule-sync.js +1302 -0
  64. package/dist/src/resource-syncs/util.d.ts +19 -0
  65. package/dist/src/resource-syncs/util.js +116 -0
  66. package/dist/src/runtime-view.d.ts +1 -0
  67. package/dist/src/runtime-view.js +6 -1
  68. package/dist/src/sync-output.d.ts +38 -0
  69. package/dist/src/sync-output.js +131 -0
  70. package/dist/src/tracked-resources.d.ts +7 -0
  71. package/dist/src/tracked-resources.js +61 -0
  72. package/dist/src/types.d.ts +227 -0
  73. package/dist/src/ui.d.ts +3 -0
  74. package/dist/src/ui.js +68 -10
  75. package/dist/src/utils.d.ts +2 -0
  76. package/dist/src/utils.js +64 -0
  77. package/dist/src/workspace-component.d.ts +2 -0
  78. package/dist/src/workspace-component.js +34 -0
  79. package/dist/src/workspace-resource.d.ts +2 -0
  80. package/dist/src/workspace-resource.js +52 -0
  81. package/package.json +8 -3
@@ -1,4 +1,6 @@
1
1
  import type { ParsedArgs } from './types.ts';
2
+ import { type ComponentIdentityConfig, type ComponentIdentityMode } from './resource-metadata.ts';
3
+ import { type TrackedResource } from './tracked-resources.ts';
2
4
  export declare const REVO_PROJECT_DIR = ".revoengine";
3
5
  export declare const REVO_TYPES_DIR: string;
4
6
  export declare const REVO_TYPES_FILE: string;
@@ -15,6 +17,9 @@ export type ProjectInvocation = {
15
17
  action: 'init' | 'update';
16
18
  targetArg?: string;
17
19
  extraArgs: string[];
20
+ identityMode?: ComponentIdentityMode;
21
+ stableKeyName?: string;
22
+ trackedResources?: TrackedResource[];
18
23
  };
19
24
  export type EditorTypesBundle = {
20
25
  code: string;
@@ -34,6 +39,10 @@ export type RevoProjectMetadata = {
34
39
  hash: string;
35
40
  lastSyncAt: string;
36
41
  workspace?: string;
42
+ componentIdentity?: ComponentIdentityConfig;
43
+ /** Legacy project metadata field. Read for migration, but no longer written. */
44
+ stableKeyName?: string;
45
+ trackedResources?: TrackedResource[];
37
46
  };
38
47
  export type ProjectSyncInput = {
39
48
  endpoint: string;
@@ -43,6 +52,9 @@ export type ProjectSyncInput = {
43
52
  libVersion: string;
44
53
  hash: string;
45
54
  lastSyncAt: string;
55
+ authProfile?: string;
56
+ componentIdentity?: ComponentIdentityConfig;
57
+ trackedResources?: TrackedResource[];
46
58
  };
47
59
  export type ProjectConfigResult = {
48
60
  filePath: string;
@@ -69,6 +81,7 @@ export declare function buildProjectMetadata(input: {
69
81
  baseUrl: string;
70
82
  bundle: EditorTypesBundle;
71
83
  now?: Date;
84
+ authProfile?: string;
72
85
  }): {
73
86
  schemaVersion: 1;
74
87
  endpoint: string;
@@ -80,21 +93,16 @@ export declare function buildProjectMetadata(input: {
80
93
  };
81
94
  export declare function findProjectMetadataFile(startDir?: string): string;
82
95
  export declare function readProjectMetadata(startDir?: string): RevoProjectMetadata | null;
96
+ export declare function readProjectStableKeyName(startDir?: string): string;
97
+ export declare function readProjectComponentIdentity(startDir?: string): ComponentIdentityConfig;
98
+ export declare function readProjectTrackedResources(startDir?: string): TrackedResource[];
83
99
  export declare function resolveProjectRoot(startDir?: string): string;
84
100
  export declare function resolveProjectWorkspace(startDir?: string): string;
85
101
  export declare function buildProjectSyncState(input: {
86
102
  fallbackEndpoint: string;
87
103
  bundle: EditorTypesBundle;
88
104
  now?: Date;
89
- }): {
90
- endpoint: string;
91
- code: string;
92
- definitions: unknown[];
93
- apiVersion: string;
94
- libVersion: string;
95
- hash: string;
96
- lastSyncAt: string;
97
- };
105
+ }): ProjectSyncInput;
98
106
  export declare function syncProjectFiles(layoutOrTargetDir: ProjectTargetLayout | string, input: ProjectSyncInput): {
99
107
  projectDir: string;
100
108
  typesFile: string;
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { readJsonFile } from "./utils.js";
4
+ import { COMPONENT_IDENTITY_BY_ID, DEFAULT_COMPONENT_IDENTITY, DEFAULT_STABLE_KEY_NAME, normalizeComponentIdentityConfig, normalizeComponentIdentityMode, normalizeStableKeyName, } from "./resource-metadata.js";
5
+ import { TRACK_ALL_RESOURCES, normalizeTrackedResources, } from "./tracked-resources.js";
4
6
  export const REVO_PROJECT_DIR = '.revoengine';
5
7
  export const REVO_TYPES_DIR = path.join(REVO_PROJECT_DIR, 'types');
6
8
  export const REVO_TYPES_FILE = path.join(REVO_TYPES_DIR, 'revo.editor.d.ts');
@@ -180,6 +182,22 @@ function appendUnique(list, value) {
180
182
  }
181
183
  return list;
182
184
  }
185
+ function readResourceSelectionArgs(args) {
186
+ const values = [];
187
+ for (const name of ['resources', 'resource', 'tracked-resources', 'trackedResources']) {
188
+ const value = args[name];
189
+ if (typeof value === 'string') {
190
+ values.push(value);
191
+ }
192
+ else if (Array.isArray(value)) {
193
+ values.push(...value);
194
+ }
195
+ else if (value === true) {
196
+ throw new Error(`--${name} requires a value such as "all" or "component".`);
197
+ }
198
+ }
199
+ return values.length > 0 ? values : undefined;
200
+ }
183
201
  function writeJsonFile(filePath, value) {
184
202
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
185
203
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
@@ -187,27 +205,37 @@ function writeJsonFile(filePath, value) {
187
205
  export function resolveProjectInvocation(args) {
188
206
  const projectArgs = args._.slice(1);
189
207
  const [first, second, ...rest] = projectArgs;
208
+ const explicitStableKeyName = readString(args.stableKeyName)
209
+ || readString(args['stable-key-name']);
210
+ const stableKeyName = explicitStableKeyName
211
+ ? normalizeStableKeyName(explicitStableKeyName)
212
+ : undefined;
213
+ const explicitIdentityMode = readString(args.identityMode)
214
+ || readString(args['identity-mode']);
215
+ const identityMode = explicitIdentityMode
216
+ ? normalizeComponentIdentityMode(explicitIdentityMode)
217
+ : stableKeyName
218
+ ? 'stableKey'
219
+ : undefined;
220
+ const trackedResourceArgs = readResourceSelectionArgs(args);
221
+ const trackedResources = trackedResourceArgs ? normalizeTrackedResources(trackedResourceArgs) : undefined;
190
222
  if (!first) {
191
- return {
192
- action: 'init',
193
- extraArgs: [],
194
- };
223
+ throw new Error('Missing project action. Use `revo project init [path]` or `revo project update [path]`.');
195
224
  }
196
225
  if (first === 'init' || first === 'update') {
197
226
  return {
198
227
  action: first,
199
228
  targetArg: second,
200
229
  extraArgs: rest,
230
+ identityMode,
231
+ stableKeyName,
232
+ ...(trackedResources ? { trackedResources } : {}),
201
233
  };
202
234
  }
203
235
  if (first === 'switch') {
204
236
  throw new Error('`revo project switch` was removed because credentials are no longer instance-scoped.');
205
237
  }
206
- return {
207
- action: 'init',
208
- targetArg: first,
209
- extraArgs: second ? [second, ...rest] : rest,
210
- };
238
+ throw new Error(`Unknown project action "${first}". Use \`revo project init [path]\` or \`revo project update [path]\`.`);
211
239
  }
212
240
  function normalizeWorkspaceDirectory(projectRoot, workspaceRoot) {
213
241
  const relative = path.relative(projectRoot, workspaceRoot).split(path.sep).join('/');
@@ -320,7 +348,7 @@ export function buildProjectMetadata(input) {
320
348
  return {
321
349
  schemaVersion: 1,
322
350
  endpoint: bundle.endpoint || buildEditorEndpoint(baseUrl),
323
- authProfile: 'default',
351
+ authProfile: input.authProfile || 'default',
324
352
  apiVersion: getRequiredMetadataValue(bundle, 'apiVersion'),
325
353
  libVersion: getRequiredMetadataValue(bundle, 'libVersion'),
326
354
  hash: getRequiredMetadataValue(bundle, 'hash'),
@@ -363,6 +391,40 @@ export function readProjectMetadata(startDir = process.cwd()) {
363
391
  }
364
392
  return raw;
365
393
  }
394
+ export function readProjectStableKeyName(startDir = process.cwd()) {
395
+ const metadata = readProjectMetadata(startDir);
396
+ const identity = readProjectComponentIdentity(startDir);
397
+ return normalizeStableKeyName(identity.mode === 'stableKey'
398
+ ? identity.metadataProperty
399
+ : metadata?.stableKeyName || DEFAULT_STABLE_KEY_NAME);
400
+ }
401
+ export function readProjectComponentIdentity(startDir = process.cwd()) {
402
+ const metadata = readProjectMetadata(startDir);
403
+ if (!metadata) {
404
+ return COMPONENT_IDENTITY_BY_ID;
405
+ }
406
+ if (metadata.componentIdentity && isRecord(metadata.componentIdentity)) {
407
+ const mode = readString(metadata.componentIdentity.mode);
408
+ const metadataProperty = readString(metadata.componentIdentity.metadataProperty);
409
+ if (mode) {
410
+ return normalizeComponentIdentityConfig({
411
+ mode: normalizeComponentIdentityMode(mode),
412
+ ...(metadataProperty ? { metadataProperty } : {}),
413
+ });
414
+ }
415
+ }
416
+ if (metadata.stableKeyName) {
417
+ return normalizeComponentIdentityConfig({
418
+ mode: 'stableKey',
419
+ metadataProperty: metadata.stableKeyName,
420
+ }, DEFAULT_COMPONENT_IDENTITY);
421
+ }
422
+ return COMPONENT_IDENTITY_BY_ID;
423
+ }
424
+ export function readProjectTrackedResources(startDir = process.cwd()) {
425
+ const metadata = readProjectMetadata(startDir);
426
+ return normalizeTrackedResources(metadata?.trackedResources || TRACK_ALL_RESOURCES);
427
+ }
366
428
  export function resolveProjectRoot(startDir = process.cwd()) {
367
429
  const metadataFile = findProjectMetadataFile(startDir);
368
430
  return metadataFile ? path.dirname(path.dirname(metadataFile)) : '';
@@ -435,11 +497,13 @@ export function syncProjectFiles(layoutOrTargetDir, input) {
435
497
  writeJsonFile(metadataFile, {
436
498
  schemaVersion: 1,
437
499
  endpoint: input.endpoint,
438
- authProfile: 'default',
500
+ authProfile: input.authProfile || 'default',
439
501
  apiVersion: input.apiVersion,
440
502
  libVersion: input.libVersion,
441
503
  hash: input.hash,
442
504
  lastSyncAt: input.lastSyncAt,
505
+ componentIdentity: normalizeComponentIdentityConfig(input.componentIdentity, DEFAULT_COMPONENT_IDENTITY),
506
+ trackedResources: normalizeTrackedResources(input.trackedResources || TRACK_ALL_RESOURCES),
443
507
  ...(workspaceDirectory !== '.' ? { workspace: workspaceDirectory } : {}),
444
508
  });
445
509
  const configResult = ensureProjectConfig(workspaceRoot, toPosixRelative(workspaceRoot, typesFile));
@@ -480,6 +544,9 @@ export function ensureProjectConfig(rootDir, typesInclude = REVO_TYPES_FILE) {
480
544
  if (!fs.existsSync(tsconfigPath) && !fs.existsSync(jsconfigPath)) {
481
545
  writeJsonFile(tsconfigPath, {
482
546
  compilerOptions: {
547
+ target: 'ES2022',
548
+ module: 'ESNext',
549
+ moduleDetection: 'force',
483
550
  allowJs: true,
484
551
  checkJs: false,
485
552
  noEmit: true,
@@ -499,6 +566,15 @@ export function ensureProjectConfig(rootDir, typesInclude = REVO_TYPES_FILE) {
499
566
  const filePath = fs.existsSync(tsconfigPath) ? tsconfigPath : jsconfigPath;
500
567
  const parsed = parseConfigFile(filePath);
501
568
  const compilerOptions = ensureObjectField(parsed, 'compilerOptions', filePath);
569
+ if (compilerOptions.target === undefined) {
570
+ compilerOptions.target = 'ES2022';
571
+ }
572
+ if (compilerOptions.module === undefined) {
573
+ compilerOptions.module = 'ESNext';
574
+ }
575
+ if (compilerOptions.moduleDetection === undefined) {
576
+ compilerOptions.moduleDetection = 'force';
577
+ }
502
578
  if (compilerOptions.allowJs === undefined) {
503
579
  compilerOptions.allowJs = true;
504
580
  }
@@ -1,4 +1,5 @@
1
1
  import readline from 'node:readline/promises';
2
+ import { Writable } from 'node:stream';
2
3
  export function isInteractiveTerminal() {
3
4
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
4
5
  }
@@ -23,32 +24,23 @@ export async function promptSecret(message) {
23
24
  if (!isInteractiveTerminal()) {
24
25
  return '';
25
26
  }
27
+ const mutedOutput = new Writable({
28
+ write(_chunk, _encoding, callback) {
29
+ callback();
30
+ },
31
+ });
26
32
  const rl = readline.createInterface({
27
33
  input: process.stdin,
28
- output: process.stdout,
34
+ output: mutedOutput,
29
35
  terminal: true,
30
36
  });
31
- let muted = true;
32
- const originalWriteToOutput = rl._writeToOutput.bind(rl);
33
- rl._writeToOutput = (value) => {
34
- if (!muted) {
35
- originalWriteToOutput(value);
36
- return;
37
- }
38
- if (value.startsWith(`${message}: `)) {
39
- originalWriteToOutput(value);
40
- return;
41
- }
42
- // Preserve terminal editing/paste behavior while masking visible content.
43
- originalWriteToOutput('*');
44
- };
45
37
  try {
46
- const answer = await rl.question(`${message}: `);
47
- return answer;
38
+ process.stdout.write(`${message}: `);
39
+ return await rl.question('');
48
40
  }
49
41
  finally {
50
- muted = false;
51
42
  rl.close();
43
+ process.stdout.write('\n');
52
44
  }
53
45
  }
54
46
  export async function promptConfirm(message, defaultValue = false) {
@@ -0,0 +1,25 @@
1
+ import type { ComponentRecord } from './types.ts';
2
+ export declare const DEFAULT_STABLE_KEY_NAME = "stableKey";
3
+ export declare const NULL_CATEGORY_KEY = "__no_category__";
4
+ export type ComponentIdentityMode = 'componentId' | 'stableKey';
5
+ export type ComponentIdentityConfig = {
6
+ mode: ComponentIdentityMode;
7
+ metadataProperty?: string;
8
+ };
9
+ export declare const COMPONENT_IDENTITY_BY_ID: ComponentIdentityConfig;
10
+ export declare const DEFAULT_COMPONENT_IDENTITY: ComponentIdentityConfig;
11
+ export declare function normalizeStableKeyValue(value: string): string;
12
+ export declare function normalizeStableKeyName(value: string | null | undefined): string;
13
+ export declare function normalizeComponentIdentityMode(value: string | null | undefined): ComponentIdentityMode;
14
+ export declare function normalizeComponentIdentityConfig(value: Partial<ComponentIdentityConfig> | null | undefined, fallback?: ComponentIdentityConfig): ComponentIdentityConfig;
15
+ export declare function sameComponentIdentityConfig(left: ComponentIdentityConfig, right: ComponentIdentityConfig): boolean;
16
+ export declare function readConfiguredResourceMetadataStableKey(component: ComponentRecord, stableKeyName?: string): string | null;
17
+ export declare function readComponentIdentityValue(component: ComponentRecord, identity: ComponentIdentityConfig): string | null;
18
+ export declare function readResourceMetadataStableKey(component: ComponentRecord, stableKeyName?: string): string | null;
19
+ export declare function buildStableKey(component: ComponentRecord, stableKeyName?: string): string;
20
+ export declare function buildStableKeyValue(component: ComponentRecord, stableKeyName?: string): string;
21
+ export declare function buildResourceIdentityStableKeyValue(component: ComponentRecord): string;
22
+ export declare function buildResourceMetadataWithStableKey(component: ComponentRecord, stableKeyName?: string): {
23
+ [x: string]: unknown;
24
+ };
25
+ export declare function sanitizeUserMetadata(value: unknown): Record<string, unknown>;
@@ -0,0 +1,132 @@
1
+ export const DEFAULT_STABLE_KEY_NAME = 'stableKey';
2
+ export const NULL_CATEGORY_KEY = '__no_category__';
3
+ export const COMPONENT_IDENTITY_BY_ID = {
4
+ mode: 'componentId',
5
+ };
6
+ export const DEFAULT_COMPONENT_IDENTITY = {
7
+ mode: 'stableKey',
8
+ metadataProperty: DEFAULT_STABLE_KEY_NAME,
9
+ };
10
+ function isRecord(value) {
11
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
12
+ }
13
+ function readString(value) {
14
+ return typeof value === 'string' && value.trim() ? value : null;
15
+ }
16
+ function normalizeKeyPart(value) {
17
+ return (value ?? '').trim().replace(/\s+/g, ' ').toLowerCase();
18
+ }
19
+ export function normalizeStableKeyValue(value) {
20
+ return normalizeKeyPart(value);
21
+ }
22
+ export function normalizeStableKeyName(value) {
23
+ const normalized = (value || DEFAULT_STABLE_KEY_NAME).trim();
24
+ if (!normalized) {
25
+ return DEFAULT_STABLE_KEY_NAME;
26
+ }
27
+ if (!/^[A-Za-z_$][A-Za-z0-9_$-]*$/.test(normalized)) {
28
+ throw new Error('Stable key metadata name must start with a letter, "_", or "$" and contain only letters, digits, "_", "$", or "-".');
29
+ }
30
+ return normalized;
31
+ }
32
+ export function normalizeComponentIdentityMode(value) {
33
+ const normalized = (value || '').trim().toLowerCase().replace(/[-_\s]/g, '');
34
+ if (normalized === 'stablekey' || normalized === 'metadata') {
35
+ return 'stableKey';
36
+ }
37
+ if (normalized === 'componentid' || normalized === 'id' || normalized === 'uuid') {
38
+ return 'componentId';
39
+ }
40
+ throw new Error('Component identity mode must be "stableKey" or "componentId".');
41
+ }
42
+ export function normalizeComponentIdentityConfig(value, fallback = COMPONENT_IDENTITY_BY_ID) {
43
+ const mode = value?.mode
44
+ ? normalizeComponentIdentityMode(value.mode)
45
+ : fallback.mode;
46
+ if (mode === 'componentId') {
47
+ return { mode };
48
+ }
49
+ return {
50
+ mode,
51
+ metadataProperty: normalizeStableKeyName(value?.metadataProperty || fallback.metadataProperty),
52
+ };
53
+ }
54
+ export function sameComponentIdentityConfig(left, right) {
55
+ const normalizedLeft = normalizeComponentIdentityConfig(left);
56
+ const normalizedRight = normalizeComponentIdentityConfig(right);
57
+ return normalizedLeft.mode === normalizedRight.mode
58
+ && normalizedLeft.metadataProperty === normalizedRight.metadataProperty;
59
+ }
60
+ function readConfiguredStableKeyFromRecord(value, stableKeyName) {
61
+ if (!isRecord(value)) {
62
+ return null;
63
+ }
64
+ return readString(value[stableKeyName])
65
+ ?? (isRecord(value.details) ? readString(value.details[stableKeyName]) : null);
66
+ }
67
+ function readAnyStableKeyFromRecord(value, stableKeyName) {
68
+ return readConfiguredStableKeyFromRecord(value, stableKeyName)
69
+ ?? (stableKeyName === DEFAULT_STABLE_KEY_NAME ? null : readConfiguredStableKeyFromRecord(value, DEFAULT_STABLE_KEY_NAME));
70
+ }
71
+ export function readConfiguredResourceMetadataStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
72
+ const fieldName = normalizeStableKeyName(stableKeyName);
73
+ return readConfiguredStableKeyFromRecord(component.metadata, fieldName)
74
+ ?? readConfiguredStableKeyFromRecord(component.metaData, fieldName)
75
+ ?? readConfiguredStableKeyFromRecord(component.resourceMetadata, fieldName);
76
+ }
77
+ export function readComponentIdentityValue(component, identity) {
78
+ const normalized = normalizeComponentIdentityConfig(identity);
79
+ if (normalized.mode === 'componentId') {
80
+ return readString(component.componentId) ?? readString(component.id);
81
+ }
82
+ return readConfiguredResourceMetadataStableKey(component, normalized.metadataProperty);
83
+ }
84
+ export function readResourceMetadataStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
85
+ const fieldName = normalizeStableKeyName(stableKeyName);
86
+ return readAnyStableKeyFromRecord(component.metadata, fieldName)
87
+ ?? readAnyStableKeyFromRecord(component.metaData, fieldName)
88
+ ?? readAnyStableKeyFromRecord(component.resourceMetadata, fieldName);
89
+ }
90
+ export function buildStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
91
+ const metadataStableKey = readResourceMetadataStableKey(component, stableKeyName);
92
+ if (metadataStableKey) {
93
+ return normalizeKeyPart(metadataStableKey);
94
+ }
95
+ const category = normalizeKeyPart(component.category) || NULL_CATEGORY_KEY;
96
+ const name = normalizeKeyPart(component.name);
97
+ return `${category}/${name}`;
98
+ }
99
+ export function buildStableKeyValue(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
100
+ const metadataStableKey = readResourceMetadataStableKey(component, stableKeyName);
101
+ if (metadataStableKey) {
102
+ return metadataStableKey;
103
+ }
104
+ return buildResourceIdentityStableKeyValue(component);
105
+ }
106
+ export function buildResourceIdentityStableKeyValue(component) {
107
+ const category = typeof component.category === 'string' && component.category.trim()
108
+ ? component.category.trim()
109
+ : NULL_CATEGORY_KEY;
110
+ const name = typeof component.name === 'string' ? component.name.trim() : '';
111
+ return `${category}/${name}`;
112
+ }
113
+ export function buildResourceMetadataWithStableKey(component, stableKeyName = DEFAULT_STABLE_KEY_NAME) {
114
+ const fieldName = normalizeStableKeyName(stableKeyName);
115
+ return {
116
+ ...sanitizeUserMetadata(component.metadata),
117
+ [fieldName]: buildStableKeyValue(component, fieldName),
118
+ };
119
+ }
120
+ export function sanitizeUserMetadata(value) {
121
+ if (!isRecord(value)) {
122
+ return {};
123
+ }
124
+ const sanitized = {};
125
+ for (const [key, entry] of Object.entries(value)) {
126
+ if (key.startsWith('__')) {
127
+ continue;
128
+ }
129
+ sanitized[key] = entry;
130
+ }
131
+ return sanitized;
132
+ }
@@ -0,0 +1,117 @@
1
+ import type { DatabaseSchemaStatsRecord, RevoClient } from '../client.ts';
2
+ export declare const DATABASE_SCHEMAS_DIRECTORY = "DatabaseSchemas";
3
+ export declare const DATABASE_SCHEMA_MANIFEST_FILE = "database-schema.json";
4
+ type PortableDefinition = Record<string, unknown>;
5
+ export type DatabaseSchemaManifest = {
6
+ kind: 'database-schema' | 'database-partition';
7
+ stableKey: string;
8
+ name: string;
9
+ category?: string | null;
10
+ description?: string;
11
+ metadata: Record<string, unknown>;
12
+ type?: string;
13
+ definition?: PortableDefinition[];
14
+ partition?: Record<string, unknown>;
15
+ parentStableKey?: string;
16
+ migration?: {
17
+ foreignReferences: 'manual';
18
+ };
19
+ };
20
+ export type DatabaseSchemaPullResult = {
21
+ pulled: Array<{
22
+ manifest: DatabaseSchemaManifest;
23
+ targetPath: string;
24
+ }>;
25
+ pruned: Array<{
26
+ stableKey: string;
27
+ targetPath: string;
28
+ }>;
29
+ skipped: Array<{
30
+ targetPath: string;
31
+ reason: string;
32
+ }>;
33
+ diagnostics: Array<{
34
+ stableKey: string;
35
+ audit?: unknown;
36
+ acl?: unknown;
37
+ tags?: unknown;
38
+ }>;
39
+ };
40
+ export type DatabaseSchemaPlanStatus = 'clean' | 'create' | 'safe-update' | 'remote-changed' | 'conflict' | 'missing-lock' | 'orphan-root' | 'orphan-partition' | 'delete-partition' | 'previously-deleted-partition' | 'replacement-requires-two-phases' | 'unmanaged' | 'collision' | 'reconciled' | 'manual-schema-migration-required' | 'access-policy-required';
41
+ export type DatabaseSchemaChange = {
42
+ kind: 'field-changed' | 'immutable-field' | 'column-added' | 'column-removed' | 'column-changed' | 'column-renamed' | 'column-stable-key-missing' | 'database-view-dependency' | 'column-order' | 'foreign-reference' | 'audit-trigger-rebuild';
43
+ field: string;
44
+ risk: 'safe' | 'manual' | 'operational';
45
+ before?: unknown;
46
+ after?: unknown;
47
+ column?: string;
48
+ hint: string;
49
+ };
50
+ export type DatabaseSchemaPlanItem = {
51
+ status: DatabaseSchemaPlanStatus;
52
+ stableKey?: string;
53
+ name: string;
54
+ kind?: DatabaseSchemaManifest['kind'];
55
+ parentStableKey?: string;
56
+ reason?: string;
57
+ changedFields?: string[];
58
+ changes?: DatabaseSchemaChange[];
59
+ hints: string[];
60
+ desiredHash?: string;
61
+ currentHash?: string;
62
+ baselineHash?: string;
63
+ stats?: DatabaseSchemaStatsRecord['size'];
64
+ };
65
+ export type DatabaseSchemasPlan = {
66
+ resourceType: 'database-schema';
67
+ env: string;
68
+ withStats: boolean;
69
+ skipPartitions: boolean;
70
+ counts: Record<DatabaseSchemaPlanStatus, number>;
71
+ blockers: string[];
72
+ warnings: string[];
73
+ exclusions: string[];
74
+ items: DatabaseSchemaPlanItem[];
75
+ };
76
+ export type DatabaseSchemaPushResult = {
77
+ stableKey: string;
78
+ action: 'create' | 'update' | 'reconcile' | 'delete' | 'skipped';
79
+ status: 'deployed' | 'skipped' | 'failed' | 'write-unverified';
80
+ targetPath: string;
81
+ reason?: string;
82
+ error?: string;
83
+ };
84
+ export declare function hashPortableDatabaseSchema(manifest: DatabaseSchemaManifest): string;
85
+ export declare function buildDatabaseSchemasPlan(input: {
86
+ client: RevoClient;
87
+ cwd: string;
88
+ envName: string;
89
+ stableKeyName: string;
90
+ withStats?: boolean;
91
+ skipPartitions?: boolean;
92
+ }): Promise<DatabaseSchemasPlan>;
93
+ export declare function pullDatabaseSchemasToWorkspace(input: {
94
+ client: RevoClient;
95
+ cwd: string;
96
+ envName: string;
97
+ stableKeyName: string;
98
+ force?: boolean;
99
+ strict?: boolean;
100
+ pruneOrphans?: boolean;
101
+ }): Promise<DatabaseSchemaPullResult>;
102
+ export declare function pushDatabaseSchemas(input: {
103
+ client: RevoClient;
104
+ cwd: string;
105
+ envName: string;
106
+ stableKeyName: string;
107
+ stableKey?: string;
108
+ force?: boolean;
109
+ prunePartitions?: boolean;
110
+ prunePartitionsConfirmed?: boolean;
111
+ pruneOrphans?: boolean;
112
+ pruneOrphansConfirmed?: boolean;
113
+ }): Promise<{
114
+ plan: DatabaseSchemasPlan;
115
+ results: DatabaseSchemaPushResult[];
116
+ }>;
117
+ export {};