@google-cloud/kcmd 0.1.0 → 0.2.1

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 (67) hide show
  1. package/README.md +18 -20
  2. package/build/ts/kcmd/cli.js +6164 -5631
  3. package/build/ts/kcmd/gcp/biglake.d.ts +2 -0
  4. package/build/ts/kcmd/gcp/biglake.js +13 -4
  5. package/build/ts/kcmd/gcp/spanner.d.ts +10 -0
  6. package/build/ts/kcmd/gcp/spanner.js +6 -3
  7. package/build/ts/kcmd/layouts/okf.js +49 -7
  8. package/build/ts/kcmd/layouts/semantic-model.d.ts +13 -0
  9. package/build/ts/kcmd/layouts/semantic-model.js +129 -50
  10. package/build/ts/kcmd/okf/concept.d.ts +1 -1
  11. package/build/ts/kcmd/okf/concept.js +6 -0
  12. package/build/ts/kcmd/okf/concept_to_entry.d.ts +2 -0
  13. package/build/ts/kcmd/okf/concept_to_entry.js +29 -2
  14. package/build/ts/kcmd/okf/init_okf_workspace.js +7 -9
  15. package/build/ts/kcmd/okf/okf_metadata.js +17 -0
  16. package/build/ts/kcmd/semantic/bigquery.js +139 -195
  17. package/build/ts/kcmd/semantic/binding.js +14 -19
  18. package/build/ts/kcmd/semantic/deploy_bigquery.js +6 -13
  19. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.d.ts +10 -35
  20. package/build/ts/kcmd/semantic/deploy_knowledge_catalog.js +105 -162
  21. package/build/ts/kcmd/semantic/deploy_spanner.js +7 -14
  22. package/build/ts/kcmd/semantic/deployment_target.d.ts +1 -4
  23. package/build/ts/kcmd/semantic/deployment_target.js +6 -62
  24. package/build/ts/kcmd/semantic/graph_ddl.d.ts +10 -0
  25. package/build/ts/kcmd/semantic/graph_ddl.js +152 -59
  26. package/build/ts/kcmd/semantic/import_model.d.ts +26 -4
  27. package/build/ts/kcmd/semantic/import_model.js +531 -111
  28. package/build/ts/kcmd/semantic/ir.d.ts +2 -4
  29. package/build/ts/kcmd/semantic/kc_converter.d.ts +7 -1
  30. package/build/ts/kcmd/semantic/kc_converter.js +32 -27
  31. package/build/ts/kcmd/semantic/kc_custom_types.d.ts +5 -12
  32. package/build/ts/kcmd/semantic/kc_custom_types.js +43 -114
  33. package/build/ts/kcmd/semantic/kc_ids.d.ts +4 -5
  34. package/build/ts/kcmd/semantic/kc_ids.js +4 -7
  35. package/build/ts/kcmd/semantic/knowledge_catalog.d.ts +0 -3
  36. package/build/ts/kcmd/semantic/knowledge_catalog.js +3 -86
  37. package/build/ts/kcmd/semantic/loader.d.ts +7 -11
  38. package/build/ts/kcmd/semantic/loader.js +282 -171
  39. package/build/ts/kcmd/semantic/osi_converter.js +14 -65
  40. package/build/ts/kcmd/semantic/parameters.d.ts +1 -0
  41. package/build/ts/kcmd/semantic/parameters.js +22 -3
  42. package/build/ts/kcmd/semantic/pull_kc.d.ts +1 -5
  43. package/build/ts/kcmd/semantic/pull_kc.js +38 -112
  44. package/build/ts/kcmd/semantic/resolve_inheritance.d.ts +7 -2
  45. package/build/ts/kcmd/semantic/resolve_inheritance.js +37 -21
  46. package/build/ts/kcmd/semantic/resolve_profiles.d.ts +51 -10
  47. package/build/ts/kcmd/semantic/resolve_profiles.js +213 -32
  48. package/build/ts/kcmd/semantic/runtime/runtime.d.ts +4 -12
  49. package/build/ts/kcmd/semantic/runtime/runtime.js +4 -21
  50. package/build/ts/kcmd/semantic/runtime/store.d.ts +14 -19
  51. package/build/ts/kcmd/semantic/runtime/store.js +21 -37
  52. package/build/ts/kcmd/semantic/source_dialect.d.ts +9 -2
  53. package/build/ts/kcmd/semantic/source_dialect.js +83 -33
  54. package/build/ts/kcmd/semantic/source_entries.d.ts +1 -1
  55. package/build/ts/kcmd/semantic/source_entries.js +95 -36
  56. package/build/ts/kcmd/semantic/sql_expr_utils.d.ts +13 -7
  57. package/build/ts/kcmd/semantic/sql_expr_utils.js +240 -67
  58. package/build/ts/kcmd/semantic/sql_parser.d.ts +5 -2
  59. package/build/ts/kcmd/semantic/sql_parser.js +22 -0
  60. package/build/ts/kcmd/semantic/transpile.d.ts +12 -10
  61. package/build/ts/kcmd/semantic/transpile.js +79 -61
  62. package/build/ts/kcmd/semantic/validate.d.ts +37 -2
  63. package/build/ts/kcmd/semantic/validate.js +467 -126
  64. package/build/ts/kcmd/snapshot.d.ts +4 -0
  65. package/build/ts/kcmd/snapshot.js +7 -0
  66. package/build/ts/kcmd/sync.js +44 -7
  67. package/package.json +1 -1
@@ -27,6 +27,10 @@ export declare class CatalogSnapshot {
27
27
  private _buildTypes;
28
28
  _loadEntryType(entryType: string): Promise<void>;
29
29
  _storeEntry(entry: dataplex.Entry): Promise<void>;
30
+ _previewStoreEntry(entry: dataplex.Entry): {
31
+ localName: string;
32
+ exists: boolean;
33
+ };
30
34
  _fetchEntry(name: string): Promise<dataplex.Entry | undefined>;
31
35
  }
32
36
  export declare function toLocalEntry(entry: dataplex.Entry, localName: string): md.Entry;
@@ -233,6 +233,13 @@ class CatalogSnapshot {
233
233
  }
234
234
  await this._layout.saveEntry(localName, local);
235
235
  }
236
+ // Reports what _storeEntry would do with a Dataplex entry, without writing:
237
+ // the local name it maps to and whether a local copy already exists. Used by
238
+ // `pull --dry-run`.
239
+ _previewStoreEntry(entry) {
240
+ const localName = this.manifest.source.localName(entry);
241
+ return { localName, exists: this._layout.entryExists(localName) };
242
+ }
236
243
  // Fetches a Dataplex entry from its local metadata representation.
237
244
  // This is only meant to be used within the syncing process (as part of push operations).
238
245
  async _fetchEntry(name) {
@@ -290,6 +290,20 @@ async function fetchEntryLinks(catalog, target, entries) {
290
290
  }
291
291
  return [...byKey.values()];
292
292
  }
293
+ async function withLinkRateLimitRetry(fn) {
294
+ const baseDelay = process.env.GCP_RETRY_DELAY_MS !== undefined
295
+ ? Math.max(0, Number(process.env.GCP_RETRY_DELAY_MS) || 0)
296
+ : 500;
297
+ let delay = baseDelay;
298
+ let res = await fn();
299
+ for (let attempt = 1; attempt < 4 && res.status === 429; attempt++) {
300
+ if (delay > 0)
301
+ await new Promise((r) => setTimeout(r, delay));
302
+ delay = Math.min(delay * 2, 5000);
303
+ res = await fn();
304
+ }
305
+ return res;
306
+ }
293
307
  /** Reconciles `bundle/_links.yaml` against the entry group's EntryLinks. */
294
308
  async function reconcileLinks(catalog, target, desiredLinks, entries, existingEntries) {
295
309
  const existingLinks = new Map();
@@ -304,8 +318,14 @@ async function reconcileLinks(catalog, target, desiredLinks, entries, existingEn
304
318
  const res = await catalog.lookupEntryLinks(target.project, target.location, {
305
319
  entry: entryName,
306
320
  });
307
- if (res.status === 404 || res.status === 501)
321
+ // A brand-new entry created milliseconds earlier in batchChangeMetadata
322
+ // can return 403 ("or it may not exist") during regional read-after-create
323
+ // IAM propagation and has no pre-existing links to delete.
324
+ if (res.status === 404 ||
325
+ res.status === 501 ||
326
+ (res.status === 403 && !existingEntries.has(entryName))) {
308
327
  return [];
328
+ }
309
329
  if (res.status !== 200) {
310
330
  console.error(` lookupEntryLinks(${entryName}): HTTP ${res.status} ${res.message ?? ''}`);
311
331
  lookupFailed = true;
@@ -345,7 +365,7 @@ async function reconcileLinks(catalog, target, desiredLinks, entries, existingEn
345
365
  for (const [id, prev] of existingLinks) {
346
366
  const desired = desiredById.get(id);
347
367
  if (!desired || (0, links_1.isStaleEntryLink)(prev, desired)) {
348
- const del = await catalog.deleteEntryLink(target.project, target.location, target.groupId, id);
368
+ const del = await withLinkRateLimitRetry(() => catalog.deleteEntryLink(target.project, target.location, target.groupId, id));
349
369
  if (del.status !== 200 && del.status !== 404) {
350
370
  console.error(` ${id}: HTTP ${del.status} ${del.message ?? ''}`);
351
371
  failed++;
@@ -359,11 +379,11 @@ async function reconcileLinks(catalog, target, desiredLinks, entries, existingEn
359
379
  const id = (0, links_1.entryLinkId)(link);
360
380
  const prev = existingLinks.get(id);
361
381
  if (!prev) {
362
- const res = await catalog.createEntryLink(target.project, target.location, target.groupId, id, link);
382
+ const res = await withLinkRateLimitRetry(() => catalog.createEntryLink(target.project, target.location, target.groupId, id, link));
363
383
  if (res.status === 409) {
364
- const del = await catalog.deleteEntryLink(target.project, target.location, target.groupId, id);
384
+ const del = await withLinkRateLimitRetry(() => catalog.deleteEntryLink(target.project, target.location, target.groupId, id));
365
385
  if (del.status === 200 || del.status === 404) {
366
- const retry = await catalog.createEntryLink(target.project, target.location, target.groupId, id, link);
386
+ const retry = await withLinkRateLimitRetry(() => catalog.createEntryLink(target.project, target.location, target.groupId, id, link));
367
387
  if (retry.status !== 200) {
368
388
  console.error(` ${id}: HTTP ${retry.status} ${retry.message ?? ''}`);
369
389
  failed++;
@@ -380,7 +400,7 @@ async function reconcileLinks(catalog, target, desiredLinks, entries, existingEn
380
400
  }
381
401
  }
382
402
  else if (JSON.stringify(prev.aspects ?? {}) !== JSON.stringify(link.aspects ?? {})) {
383
- const upd = await catalog.updateEntryLink(link, Object.keys(link.aspects ?? {}));
403
+ const upd = await withLinkRateLimitRetry(() => catalog.updateEntryLink(link, Object.keys(link.aspects ?? {})));
384
404
  if (upd.status !== 200) {
385
405
  console.error(` ${id}: HTTP ${upd.status} ${upd.message ?? ''}`);
386
406
  failed++;
@@ -436,6 +456,8 @@ class CatalogSync {
436
456
  const requested = [...aspectTypes.keys()];
437
457
  const system = new Set(this._snapshot.manifest.source.systemAspects);
438
458
  const leftOut = new Map();
459
+ let wouldCreate = 0;
460
+ let wouldUpdate = 0;
439
461
  for (const entry of entries) {
440
462
  // TODO: Handle local modification conflicts.
441
463
  // TODO: Handle config changes or service deletions that require removing local entries.
@@ -450,7 +472,18 @@ class CatalogSync {
450
472
  // lookupEntry sets every returned aspect's type.
451
473
  const stored = new Set(Object.values(res.result.aspects ?? {})
452
474
  .map(a => gcp._nameToTypeRef(a.aspectType)));
453
- await this._snapshot._storeEntry(res.result);
475
+ if (options?.dryRun) {
476
+ // Reconstruct and report only; never touch the local snapshot.
477
+ const { localName, exists } = this._snapshot._previewStoreEntry(res.result);
478
+ console.log(`would ${exists ? 'update' : 'create'} ${localName}`);
479
+ if (exists)
480
+ wouldUpdate++;
481
+ else
482
+ wouldCreate++;
483
+ }
484
+ else {
485
+ await this._snapshot._storeEntry(res.result);
486
+ }
454
487
  const onEntry = await this._catalog.lookupEntryAspectTypes(nameParts[1], nameParts[3], entry.name);
455
488
  for (const type of onEntry.result ?? []) {
456
489
  if (!stored.has(type) && !aspectTypes.has(type) && !system.has(type)) {
@@ -463,6 +496,10 @@ class CatalogSync {
463
496
  `${count === 1 ? 'entry' : 'entries'}; list it under ` +
464
497
  `snapshot.aspects in catalog.yaml to pull it.`);
465
498
  }
499
+ if (options?.dryRun) {
500
+ console.log(`Dry run: would write ${wouldCreate} new and ${wouldUpdate} updated ` +
501
+ `entr${wouldCreate + wouldUpdate === 1 ? 'y' : 'ies'}; no files written (--dry-run).`);
502
+ }
466
503
  return { success: true };
467
504
  }
468
505
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@google-cloud/kcmd",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Google Knowledge Catalog Metadata as Code Library, CLI and MCP Server",
5
5
  "repository": {
6
6
  "type": "git",