@documentation-robotics/cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -16
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +191 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/extract.js +138 -0
- package/dist/analyzers/cards/extract.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/routes.js +96 -0
- package/dist/analyzers/cards/routes.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +270 -174
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +342 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +49 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/extraction-plan.js +541 -0
- package/dist/analyzers/extraction-plan.js.map +7 -0
- package/dist/analyzers/index-job.js +229 -0
- package/dist/analyzers/index-job.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +23 -11
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +31 -1
- package/dist/coding-agents/default-permissions.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +269 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +7 -4
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +143 -40
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +63 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +80 -29
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +10 -4
- package/dist/commands/delete.js.map +2 -2
- package/dist/commands/farm.js +176 -60
- package/dist/commands/farm.js.map +2 -2
- package/dist/commands/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +40 -21
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +208 -37
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +52 -32
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -103
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +19 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +34 -15
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/annotation-store.js +15 -5
- package/dist/core/annotation-store.js.map +2 -2
- package/dist/core/catalog-migration.js +34 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +160 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +848 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.js.map +7 -0
- package/dist/core/farm-manifest.js +12 -6
- package/dist/core/farm-manifest.js.map +2 -2
- package/dist/core/farm-sync-engine.js +1 -1
- package/dist/core/farm-sync-engine.js.map +2 -2
- package/dist/core/farm-sync-state.js +10 -2
- package/dist/core/farm-sync-state.js.map +2 -2
- package/dist/core/migration-registry.js +88 -47
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +82 -30
- package/dist/core/model.js.map +2 -2
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/project-init.js +53 -0
- package/dist/core/project-init.js.map +7 -0
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +161 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +145 -18
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +166 -24
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/upgrade-plan.js +117 -0
- package/dist/core/upgrade-plan.js.map +7 -0
- package/dist/core/virtual-projection.js +13 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2468 -7913
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +30 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +8 -2
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +20 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +66 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +47 -26
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +68 -2
- package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
- package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +95 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +178 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +8 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +6 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +3 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +225 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +177 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +14 -1
- package/dist/mcp/api-key-manager.js.map +2 -2
- package/dist/mcp/changeset-only.js +45 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/project-root.js +85 -0
- package/dist/mcp/project-root.js.map +7 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/server-instructions.js.map +7 -0
- package/dist/mcp/strict-schema.js +52 -0
- package/dist/mcp/strict-schema.js.map +7 -0
- package/dist/mcp/tool-registry.js +55 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +98 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-commit.js +224 -0
- package/dist/mcp/tools/changeset-commit.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +260 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +17 -1
- package/dist/mcp/tools/changeset-show.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +49 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/codebase-extract.js +836 -0
- package/dist/mcp/tools/codebase-extract.js.map +7 -0
- package/dist/mcp/tools/codebase-index.js +125 -0
- package/dist/mcp/tools/codebase-index.js.map +7 -0
- package/dist/mcp/tools/codebase-shared.js +265 -0
- package/dist/mcp/tools/codebase-shared.js.map +7 -0
- package/dist/mcp/tools/codebase-verify.js +48 -0
- package/dist/mcp/tools/codebase-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +34 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +191 -74
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-delete.js +2 -2
- package/dist/mcp/tools/model-delete.js.map +2 -2
- package/dist/mcp/tools/model-search.js +14 -0
- package/dist/mcp/tools/model-search.js.map +2 -2
- package/dist/mcp/tools/model-show.js +29 -2
- package/dist/mcp/tools/model-show.js.map +2 -2
- package/dist/mcp/tools/model-trace.js +93 -30
- package/dist/mcp/tools/model-trace.js.map +2 -2
- package/dist/mcp/tools/model-update.js +46 -26
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/model-validate.js +46 -4
- package/dist/mcp/tools/model-validate.js.map +2 -2
- package/dist/mcp/tools/project-status-collectors.js +381 -0
- package/dist/mcp/tools/project-status-collectors.js.map +7 -0
- package/dist/mcp/tools/project-status.js +237 -0
- package/dist/mcp/tools/project-status.js.map +7 -0
- package/dist/mcp/tools/project-tools.js +505 -0
- package/dist/mcp/tools/project-tools.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +167 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/review-open.js +210 -0
- package/dist/mcp/tools/review-open.js.map +7 -0
- package/dist/mcp/tools/shared.js +35 -4
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/reports/model-report-orchestrator.js +31 -2
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/schemas/bundled/analyzers/cbm.json +1200 -70
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +735 -371
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +395 -411
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +865 -1646
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +686 -231
- package/dist/server/server.js.map +3 -3
- package/dist/server/viewer-registry.js +264 -0
- package/dist/server/viewer-registry.js.map +7 -0
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/ansi-suppressor.js +6 -8
- package/dist/utils/ansi-suppressor.js.map +2 -2
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +9 -3
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/id-generator.js +5 -2
- package/dist/utils/id-generator.js.map +2 -2
- package/dist/utils/project-paths.js +26 -1
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +64 -31
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/composed-reference-validator.js +5 -27
- package/dist/validators/composed-reference-validator.js.map +2 -2
- package/dist/validators/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- package/dist/validators/reference-validator.js +0 -7
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +3 -3
- package/dist/validators/relationship-schema-validator.js.map +2 -2
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
- package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +2 -1
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-brief-compact.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The compact brief \u2014 what `changeset_brief` returns by default.\n *\n * The full brief (`buildBrief`) lists every changed element with its attributes, every connection\n * with the other end's name and layer, every unwired connection, and the code each element touches.\n * On a real plan that is 55\u2013420 KB, more than an agent's client takes in one result. The compact\n * brief keeps what a planning pass reads: counts, reach, gaps (one line each), intent (ids), drift,\n * and each changed element with its connections as `predicate \u2192 id` lines. A large plan (more than\n * LARGE_PLAN changed elements, a bootstrap) has its elements summarised by type instead, and long id\n * lists cut, so the default stays well under 30 KB. `sections` returns only the sections it names (a\n * small header besides), and `detail: \"full\"` returns the full brief unchanged.\n *\n * See \"Iterating on a plan\" in docs/CHANGE_MODELING.md.\n */\n\nimport { getAllLayerIds } from \"../generated/layer-registry.js\";\nimport type { BriefDelta } from \"./changeset-brief-delta.js\";\nimport type { BriefDrift, BriefGap, ChangesetBrief, ElementUnwired } from \"./changeset-analysis.js\";\n\n/**\n * Sections available through `sections`: each full-brief section in full, and `changes`, the\n * compact element list of every changed element. Naming sections returns only those, with a small\n * header (revision, the changeset's id, name and status, summary counts).\n */\nexport const BRIEF_SECTIONS = [\n \"changes\",\n \"changeset\",\n \"drift\",\n \"layers\",\n \"relationshipChanges\",\n \"alreadyInBase\",\n \"gaps\",\n \"unwired\",\n \"intent\",\n \"codeTouched\",\n \"feedback\",\n \"delta\",\n] as const;\n\nexport type BriefSection = (typeof BRIEF_SECTIONS)[number];\n\n/** A changed element's description longer than this is cut (the changeset's own never is). */\nconst ELEMENT_DESCRIPTION_LIMIT = 160;\n\n/** A feedback comment longer than this is cut. */\nconst FEEDBACK_LIMIT = 200;\n\n/** A plan with more changed elements than this is summarised by type in the default brief. */\nexport const LARGE_PLAN = 60;\n\n/** Ids listed per type in a large plan's summary. */\nconst IDS_PER_TYPE = 5;\n\n/** An id list in the default brief (intent, the delta) longer than this is cut, with its count kept. */\nexport const LIST_LIMIT = 30;\n\n/**\n * A changed element as `sections: [\"changes\"]` returns it: the build view. What the build needs to\n * write the element (its name, description in full, attributes, the fields an update changes with\n * both values, its files) and its connections as `predicate \u2192 id` lines, without the full brief's\n * names and layers per connection.\n */\nexport interface BuildChange {\n id: string;\n op: \"added\" | \"updated\" | \"deleted\";\n name: string;\n description?: string;\n attributes?: Record<string, unknown>;\n /** Updates: each field that changes, before and after. */\n changedFields?: Array<{ field: string; before: unknown; after: unknown }>;\n sourceFiles?: string[];\n connections: string[];\n}\n\n/** Narrows `sections` output to some elements: those in these layers, or with these ids. */\nexport interface SectionFilter {\n layers?: string[];\n ids?: string[];\n}\n\n/** A large plan's changed elements, per `layer.type`. */\nexport interface ChangesByType {\n type: string;\n added: number;\n updated: number;\n deleted: number;\n /** The first ids of the type, in brief order. */\n ids: string[];\n /** How many more of the type there are. */\n more?: number;\n}\n\nexport interface CompactChange {\n id: string;\n // The type is the id's second segment (`layer.type.name`), so it is not repeated.\n op: \"added\" | \"updated\" | \"deleted\";\n /** The element's description, cut at 160 characters. */\n description?: string;\n /**\n * The files its source reference names: where the build edits. A file's name alone when no other\n * touched file has it; `codeTouched` has the path.\n */\n sourceFiles?: string[];\n /** Updates: the fields that change. */\n changedFields?: string[];\n /**\n * `predicate \u2192 id` for a relationship from this element, `predicate \u2190 id` for one into it from an\n * element the changeset does not change (one between two changed elements is listed once, on its\n * source). `(deleted)` marks a relationship the change removes, `(base)` one the base already has.\n */\n connections: string[];\n}\n\nexport interface CompactDrift {\n isDrifted: boolean;\n warnings: string[];\n baseIndex?: string;\n /** `id: reason` for elements, `source --predicate--> target: reason` for relationships. */\n overlapping?: string[];\n baseChanges?: {\n counts: NonNullable<BriefDrift[\"baseChanges\"]>[\"counts\"];\n elementsChanged: string[];\n elementsRemoved: string[];\n truncated: boolean;\n };\n}\n\nexport interface CompactDelta {\n since: number;\n revision: number;\n elements: { added: string[]; removed: string[]; updated: string[] };\n relationships: { added: string[]; removed: string[] };\n unstaged: string[];\n gaps: { opened: string[]; closed: string[] };\n unwired: { wired: number; new: number; newElements: number };\n reach: BriefDelta[\"reach\"];\n intent: BriefDelta[\"intent\"];\n /** Set when a list was cut at LIST_LIMIT: each list's full length. `sections: [\"delta\"]` has them all. */\n counts?: Record<string, number>;\n warnings?: string[];\n}\n\nexport interface CompactBrief {\n detail: \"compact\";\n revision: number;\n revisionToken: string;\n changeset: {\n id: string;\n name: string;\n status: string;\n description?: string;\n drift?: CompactDrift;\n };\n summary: ChangesetBrief[\"summary\"];\n /** Changed elements per layer, in layer order. */\n byLayer: Array<{ layer: string; added: number; updated: number; deleted: number }>;\n reach: ChangesetBrief[\"reach\"];\n /** `element: kind: detail`, one per gap (see `gapLines` for a long list). */\n gaps: string[];\n intent: {\n traced: string[];\n untraced: string[];\n unanchored?: string[];\n /** Set when a list was cut at LIST_LIMIT: each list's full length. */\n counts?: { traced: number; untraced: number; unanchored?: number };\n };\n /** Unwired connections of added and updated elements, counted per other-end layer (listed when asked for). */\n unwired?: { total: number; byLayer: Record<string, number> } | ChangesetBrief[\"unwired\"];\n /**\n * Every changed element. Left out of a brief with a delta, and of a large plan's (which has\n * `changesByType`); `sections: [\"changes\"]` lists them.\n */\n changes?: CompactChange[];\n /** A large plan's changed elements, counted per type with the first ids of each. */\n changesByType?: ChangesByType[];\n /** `status source --predicate--> target` for relationships between unchanged elements. */\n relationshipChanges: string[];\n alreadyInBase?: ChangesetBrief[\"alreadyInBase\"];\n /** Files of the changed elements, with how many changed elements each has. */\n codeTouched: string[];\n /** Files that hold only existing elements next to the change: the build must not break them. */\n neighborFiles: string[];\n /** `annotationId on elementId (author, n replies): content`. */\n feedback: string[];\n delta?: CompactDelta;\n /** Set when the call had arguments this form doesn't use (e.g. `layers` or `ids` without `sections`). */\n note?: string;\n /** How to get more. */\n more: string;\n}\n\nconst rel = (r: { source: string; predicate: string; target: string }) => `${r.source} --${r.predicate}--> ${r.target}`;\nconst gapLine = (g: BriefGap) => `${g.element}: ${g.kind}: ${g.detail}`;\n\n/**\n * Gaps one line each; past LIST_LIMIT, gaps that say the same thing (a bootstrap's unconnected\n * adds) share one line, `kind: detail (n elements: first ids, +m more)`.\n */\nexport function gapLines(gaps: BriefGap[]): string[] {\n if (gaps.length <= LIST_LIMIT) return gaps.map(gapLine);\n const groups = new Map<string, string[]>();\n for (const g of gaps) {\n const key = `${g.kind}: ${g.detail}`;\n groups.set(key, [...(groups.get(key) ?? []), g.element]);\n }\n return [...groups].map(([key, elements]) =>\n elements.length === 1\n ? `${elements[0]}: ${key}`\n : `${key} (${elements.length} elements: ${elements.slice(0, IDS_PER_TYPE).join(\", \")}` +\n `${elements.length > IDS_PER_TYPE ? `, +${elements.length - IDS_PER_TYPE} more` : \"\"})`\n );\n}\nconst cut = (text: string, limit: number) => (text.length > limit ? `${text.slice(0, limit - 1)}\u2026` : text);\n\n/** Unwired connections counted per other-end layer, across every added or updated element. */\nexport function unwiredCounts(unwired: ElementUnwired[]): { total: number; byLayer: Record<string, number> } {\n const counts = new Map<string, number>();\n let total = 0;\n for (const element of unwired) {\n for (const { layer, entries } of element.byLayer) {\n counts.set(layer, (counts.get(layer) ?? 0) + entries.length);\n total += entries.length;\n }\n }\n const order = getAllLayerIds();\n const rank = (layer: string) => (order.includes(layer) ? order.indexOf(layer) : order.length);\n const byLayer = Object.fromEntries([...counts].sort(([a], [b]) => rank(a) - rank(b)));\n return { total, byLayer };\n}\n\nfunction compactDrift(drift: BriefDrift): CompactDrift {\n const overlapping = drift.overlapping\n ? [\n ...drift.overlapping.elements.map((e) => `${e.id}: ${e.reason}`),\n ...drift.overlapping.relationships.map((r) => `${rel(r)}: ${r.reason}${r.endpoints ? ` (${r.endpoints.join(\", \")})` : \"\"}`),\n ]\n : undefined;\n return {\n isDrifted: drift.isDrifted,\n warnings: drift.warnings,\n ...(drift.baseIndex ? { baseIndex: drift.baseIndex } : {}),\n ...(overlapping ? { overlapping } : {}),\n ...(drift.baseChanges\n ? {\n baseChanges: {\n counts: drift.baseChanges.counts,\n elementsChanged: drift.baseChanges.elementsChanged,\n elementsRemoved: drift.baseChanges.elementsRemoved,\n truncated: drift.baseChanges.truncated,\n },\n }\n : {}),\n };\n}\n\n/** A list cut at `limit`, recording its full length in `counts` under `name` when it was cut. */\nfunction capped<T>(list: T[], name: string, counts: Record<string, number>, limit = LIST_LIMIT): T[] {\n if (list.length <= limit) return list;\n counts[name] = list.length;\n return list.slice(0, limit);\n}\n\nexport function compactDelta(delta: BriefDelta): CompactDelta {\n const counts: Record<string, number> = {};\n const out: CompactDelta = {\n since: delta.since,\n revision: delta.revision,\n elements: {\n added: capped(delta.elements.added, \"elements.added\", counts),\n removed: capped(delta.elements.removed, \"elements.removed\", counts),\n updated: capped(delta.elements.updated, \"elements.updated\", counts).map((u) => `${u.element} (${u.fields.join(\", \")})`),\n },\n relationships: {\n added: capped(delta.relationships.added, \"relationships.added\", counts).map(rel),\n removed: capped(delta.relationships.removed, \"relationships.removed\", counts).map(rel),\n },\n unstaged: delta.unstaged.map(\n (u) =>\n `#${u.sequence} ${u.operation} ${u.source ? rel({ source: u.source, predicate: u.predicate ?? \"?\", target: u.target ?? \"?\" }) : (u.elementId ?? \"\")} (unstaged at ${u.removedAt})`\n ),\n gaps: { opened: gapLines(delta.gaps.opened), closed: gapLines(delta.gaps.closed) },\n unwired: { wired: delta.unwired.wired.length, new: delta.unwired.new.length, newElements: delta.unwired.newElements.length },\n reach: delta.reach,\n intent: {\n newlyTraced: capped(delta.intent.newlyTraced, \"intent.newlyTraced\", counts),\n newlyUntraced: capped(delta.intent.newlyUntraced, \"intent.newlyUntraced\", counts),\n },\n ...(delta.warnings ? { warnings: delta.warnings } : {}),\n };\n if (Object.keys(counts).length > 0) out.counts = counts;\n return out;\n}\n\n/** Each changed element in the compact form, and the touched files (changed ones counted, neighbour-only ones apart). */\nfunction compactChanges(brief: ChangesetBrief): {\n changes: CompactChange[];\n build: BuildChange[];\n files: Map<string, number>;\n neighborFiles: string[];\n} {\n const changedIds = new Set(brief.layers.flatMap((l) => l.changes.map((c) => c.id)));\n\n // Files: per changed element, and those holding only neighbours.\n const filesOf = new Map<string, string[]>();\n const files = new Map<string, number>();\n const neighborFiles: string[] = [];\n // Per element, a file's name stands for its path when no other touched file has that name.\n const baseName = (file: string) => file.slice(file.lastIndexOf(\"/\") + 1);\n const nameCount = new Map<string, number>();\n for (const { file } of brief.codeTouched) nameCount.set(baseName(file), (nameCount.get(baseName(file)) ?? 0) + 1);\n const shortName = (file: string) => (nameCount.get(baseName(file)) === 1 ? baseName(file) : file);\n for (const { file, elements } of brief.codeTouched) {\n const changed = elements.filter((e) => e.role === \"changed\");\n if (changed.length === 0) neighborFiles.push(file);\n else files.set(file, changed.length);\n for (const e of changed) filesOf.set(e.id, [...(filesOf.get(e.id) ?? []), shortName(file)]);\n }\n\n const changes: CompactChange[] = [];\n const build: BuildChange[] = [];\n for (const c of brief.layers.flatMap((l) => l.changes)) {\n const connections: string[] = [];\n for (const n of c.connections) {\n if (n.direction === \"in\" && changedIds.has(n.other)) continue; // listed on its source\n const mark = n.status === \"deleted\" ? \" (deleted)\" : n.status === \"unchanged\" ? \" (base)\" : \"\";\n connections.push(n.direction === \"out\" ? `${n.predicate} \u2192 ${n.other}${mark}` : `${n.predicate} \u2190 ${n.other}${mark}`);\n }\n const sourceFiles = filesOf.has(c.id) ? { sourceFiles: filesOf.get(c.id) } : {};\n changes.push({\n id: c.id,\n op: c.op,\n ...(c.description ? { description: cut(c.description, ELEMENT_DESCRIPTION_LIMIT) } : {}),\n ...sourceFiles,\n ...(c.changedFields?.length ? { changedFields: c.changedFields.map((f) => f.field) } : {}),\n connections,\n });\n build.push({\n id: c.id,\n op: c.op,\n name: c.name,\n ...(c.description ? { description: c.description } : {}),\n ...(c.attributes && Object.keys(c.attributes).length > 0 ? { attributes: c.attributes } : {}),\n ...(c.changedFields?.length ? { changedFields: c.changedFields } : {}),\n ...sourceFiles,\n connections,\n });\n }\n return { changes, build, files, neighborFiles };\n}\n\n/** A large plan's changed elements per `layer.type`, in brief order, with the first ids of each. */\nexport function changesByType(brief: ChangesetBrief, idsPerType = IDS_PER_TYPE): ChangesByType[] {\n const groups = new Map<string, ChangesByType>();\n for (const layer of brief.layers) {\n for (const c of layer.changes) {\n const parts = c.id.split(\".\");\n const type = parts.length >= 3 ? `${parts[0]}.${parts[1]}` : layer.layer;\n const group = groups.get(type) ?? { type, added: 0, updated: 0, deleted: 0, ids: [] };\n group[c.op] += 1;\n if (group.ids.length < idsPerType) group.ids.push(c.id);\n else group.more = (group.more ?? 0) + 1;\n groups.set(type, group);\n }\n }\n return [...groups.values()];\n}\n\nfunction headerOf(brief: ChangesetBrief): { id: string; name: string; status: string } {\n return { id: brief.changeset.id, name: brief.changeset.name, status: brief.changeset.status };\n}\n\n/**\n * The compact form of a full brief. `unwired` is the per-element list, counted per layer (pass it\n * when the brief was built without its unwired listing). `briefSections` returns named sections only.\n */\nexport function compactBrief(brief: ChangesetBrief, options: { unwired?: ElementUnwired[]; delta?: BriefDelta } = {}): CompactBrief {\n const { changes, files, neighborFiles } = compactChanges(brief);\n const changedCount = changes.length;\n const large = changedCount > LARGE_PLAN;\n\n // The description in full: accepted gaps and decisions are recorded there.\n const description = brief.changeset.description;\n const header: CompactBrief[\"changeset\"] = { ...headerOf(brief), ...(description ? { description } : {}) };\n if (brief.changeset.drift) header.drift = compactDrift(brief.changeset.drift);\n\n // Intent as ids; a long list is cut (a bootstrap has every element untraced) with its count kept.\n const cutLists: Record<string, number> = {};\n const unanchored = brief.intent.unanchored?.map((u) => u.element);\n const intent: CompactBrief[\"intent\"] = {\n traced: capped(brief.intent.traced.map((t) => t.element), \"traced\", cutLists),\n untraced: capped(brief.intent.untraced, \"untraced\", cutLists),\n ...(unanchored ? { unanchored: capped(unanchored, \"unanchored\", cutLists) } : {}),\n };\n if (Object.keys(cutLists).length > 0) {\n intent.counts = {\n traced: brief.intent.traced.length,\n untraced: brief.intent.untraced.length,\n ...(unanchored ? { unanchored: unanchored.length } : {}),\n };\n }\n\n const withDelta = !!options.delta;\n const elementList = withDelta ? {} : large ? { changesByType: changesByType(brief) } : { changes };\n const sectionNames = `sections: [...] for only some sections, in full (${BRIEF_SECTIONS.join(\", \")})`;\n const more =\n (withDelta\n ? 'With since, the changed elements are left out: the delta says what changed (sections: [\"changes\"] lists them). '\n : large\n ? `This plan changes ${changedCount} elements, so changesByType counts them per type with the first ${IDS_PER_TYPE} ids ` +\n 'of each; sections: [\"changes\"] lists every one with its connections. '\n : \"Each changed element is listed with a short description, its files and its connections; attributes are in the full brief. \") +\n (brief.gaps.length > LIST_LIMIT ? 'Gaps that say the same thing share a line (sections: [\"gaps\"] lists each). ' : \"\") +\n (intent.counts ? `Intent lists are cut at ${LIST_LIMIT} ids (intent.counts has their lengths; sections: [\"intent\"] has them all). ` : \"\") +\n `Pass detail: \"full\" for the whole brief, or ${sectionNames}.`;\n\n const out: CompactBrief = {\n detail: \"compact\",\n revision: brief.revision,\n revisionToken: brief.revisionToken,\n changeset: header,\n summary: brief.summary,\n byLayer: brief.layers.map((l) => ({\n layer: l.layer,\n added: l.changes.filter((c) => c.op === \"added\").length,\n updated: l.changes.filter((c) => c.op === \"updated\").length,\n deleted: l.changes.filter((c) => c.op === \"deleted\").length,\n })),\n reach: brief.reach,\n gaps: gapLines(brief.gaps),\n intent,\n ...(brief.unwired && !options.unwired\n ? { unwired: brief.unwired }\n : options.unwired\n ? { unwired: unwiredCounts(options.unwired) }\n : {}),\n ...elementList,\n relationshipChanges: brief.relationshipChanges.map((r) => `${r.status} ${rel(r)}`),\n codeTouched: [...files].map(([file, n]) => `${file} (${n})`),\n neighborFiles,\n feedback: brief.feedback.map(feedbackLine),\n more,\n };\n if (brief.alreadyInBase.elements.length + brief.alreadyInBase.relationships.length > 0) {\n // Short, and commit skips them: listed whenever there are any.\n out.alreadyInBase = brief.alreadyInBase;\n }\n if (options.delta) out.delta = compactDelta(options.delta);\n return out;\n}\n\nconst feedbackLine = (f: ChangesetBrief[\"feedback\"][number]) =>\n `${f.annotationId} on ${f.elementId} (${f.author}, ${f.replies} ${f.replies === 1 ? \"reply\" : \"replies\"}): ${cut(f.content, FEEDBACK_LIMIT)}`;\n\n/** A brief of named sections only: a small header, then each section in full. */\nexport interface SectionsBrief {\n detail: \"compact\";\n revision: number;\n revisionToken: string;\n /** The changeset's id, name and status; its description and drift with the `changeset` section. */\n changeset: { id: string; name: string; status: string; description?: string; drift?: BriefDrift };\n summary: ChangesetBrief[\"summary\"];\n /** The sections returned, in the order asked. */\n sections: BriefSection[];\n /** The filter applied to the element sections, when one was given. */\n filter?: SectionFilter;\n /** The build view of each changed element (see BuildChange). */\n changes?: BuildChange[];\n drift?: BriefDrift;\n layers?: ChangesetBrief[\"layers\"];\n relationshipChanges?: ChangesetBrief[\"relationshipChanges\"];\n alreadyInBase?: ChangesetBrief[\"alreadyInBase\"];\n gaps?: BriefGap[];\n unwired?: ChangesetBrief[\"unwired\"];\n intent?: ChangesetBrief[\"intent\"];\n codeTouched?: ChangesetBrief[\"codeTouched\"];\n feedback?: ChangesetBrief[\"feedback\"];\n /** With since: the compact delta, or the full one when `delta` is named. */\n delta?: CompactDelta | BriefDelta;\n more: string;\n}\n\n/**\n * Only the named sections, each in full, under a small header. `changes` is the build view of every\n * changed element; with `since`, the delta comes too, compact unless `delta` is named. `filter`\n * narrows the element sections (changes, layers, gaps, unwired, intent, feedback) to the elements in\n * some layers or with some ids.\n */\nexport function briefSections(\n brief: ChangesetBrief,\n sections: readonly BriefSection[],\n options: { delta?: BriefDelta; filter?: SectionFilter } = {}\n): SectionsBrief {\n const names = [...new Set(sections)];\n const filter = options.filter && (options.filter.layers?.length || options.filter.ids?.length) ? options.filter : undefined;\n const ids = filter?.ids ? new Set(filter.ids) : undefined;\n const layers = filter?.layers ? new Set(filter.layers) : undefined;\n const keep = (id: string): boolean => {\n if (!filter) return true;\n return (ids?.has(id) ?? false) || (layers?.has(id.split(\".\")[0]) ?? false);\n };\n const out: SectionsBrief = {\n detail: \"compact\",\n revision: brief.revision,\n revisionToken: brief.revisionToken,\n changeset: headerOf(brief),\n summary: brief.summary,\n sections: names,\n ...(filter ? { filter } : {}),\n more: \"\",\n };\n const notes: string[] = [];\n for (const name of names) {\n switch (name) {\n case \"changes\":\n out.changes = compactChanges(brief).build.filter((c) => keep(c.id));\n break;\n case \"changeset\": {\n const description = brief.changeset.description;\n out.changeset = {\n ...out.changeset,\n ...(description ? { description } : {}),\n ...(brief.changeset.drift ? { drift: brief.changeset.drift } : {}),\n };\n break;\n }\n case \"drift\":\n if (brief.changeset.drift) out.drift = brief.changeset.drift;\n else notes.push(\"No drift: the changeset is not staged.\");\n break;\n case \"layers\":\n out.layers = filter\n ? brief.layers.map((l) => ({ ...l, changes: l.changes.filter((c) => keep(c.id)) })).filter((l) => l.changes.length > 0)\n : brief.layers;\n break;\n case \"relationshipChanges\":\n out.relationshipChanges = brief.relationshipChanges;\n break;\n case \"alreadyInBase\":\n out.alreadyInBase = brief.alreadyInBase;\n break;\n case \"gaps\":\n out.gaps = brief.gaps.filter((g) => keep(g.element));\n break;\n case \"unwired\":\n if (brief.unwired) out.unwired = filter ? { ...brief.unwired, elements: brief.unwired.elements.filter((e) => keep(e.element)) } : brief.unwired;\n break;\n case \"intent\":\n out.intent = filter\n ? {\n traced: brief.intent.traced.filter((t) => keep(t.element)),\n untraced: brief.intent.untraced.filter(keep),\n ...(brief.intent.unanchored ? { unanchored: brief.intent.unanchored.filter((u) => keep(u.element)) } : {}),\n }\n : brief.intent;\n break;\n case \"codeTouched\":\n out.codeTouched = brief.codeTouched;\n break;\n case \"feedback\":\n out.feedback = brief.feedback.filter((f) => keep(f.elementId));\n break;\n case \"delta\":\n if (!options.delta) notes.push(\"delta needs since: pass the revisionToken of an earlier brief.\");\n break;\n }\n }\n if (options.delta) out.delta = names.includes(\"delta\") ? options.delta : compactDelta(options.delta);\n if (filter && ids) {\n const known = new Set(brief.layers.flatMap((l) => l.changes.map((c) => c.id)));\n const unknown = [...ids].filter((id) => !known.has(id));\n if (unknown.length > 0) notes.push(`Not changed by this changeset: ${unknown.join(\", \")}.`);\n }\n out.more = [\n ...notes,\n `Only the sections asked for are returned (${names.join(\", \")})` +\n (filter ? \", for the elements the filter keeps\" : \"\") +\n `. Call without sections for the compact brief, or with detail: \"full\" for everything.`,\n ].join(\" \");\n return out;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { buildBrief, computeUnwired, unwiredKey } from "./changeset-analysis.js";
|
|
2
|
+
import { ChangesetGraph, changesetRevisionToken, parseRevisionToken, relationshipKey, unstagedSince } from "./changeset-graph.js";
|
|
3
|
+
import { summarizeChange } from "./changeset-summary.js";
|
|
4
|
+
const NOTE = "The earlier state is rebuilt from the changes that made up the plan at `since` (unstaged ones from the changeset's unstage record), applied to today's base model; base drift since then is not separated out. It is approximate when a change staged by then was rewritten in place afterwards: pass the brief's revisionToken as `since` to be warned when that happened.";
|
|
5
|
+
async function briefDelta(model, now, since) {
|
|
6
|
+
let revision;
|
|
7
|
+
const warnings = [], changeset = now.graph.changeset;
|
|
8
|
+
if (typeof since == "string") {
|
|
9
|
+
const parsed = parseRevisionToken(since);
|
|
10
|
+
if (!parsed)
|
|
11
|
+
throw new Error(`since "${since}" is neither a revision number nor a revision token (<revision>:<hash>) from a brief`);
|
|
12
|
+
revision = parsed.revision;
|
|
13
|
+
const today = changesetRevisionToken(changeset, revision);
|
|
14
|
+
revision <= now.brief.revision && today !== since && warnings.push(`The plan at revision ${revision} is not the one that revision token was taken from: a change up to it was rewritten in place since then (or the changeset was rewritten, e.g. by an upgrade). The earlier state below is rebuilt from what is recorded now, so this delta is approximate.`);
|
|
15
|
+
} else
|
|
16
|
+
revision = since;
|
|
17
|
+
const prunedThrough = changeset.unstagedPrunedThrough;
|
|
18
|
+
prunedThrough !== void 0 && revision < prunedThrough && warnings.push(`Unstage tombstones up to revision ${prunedThrough} were dropped to keep the record bounded, so changes unstaged between revision ${revision} and then may be missing from the rebuilt earlier state and from \`unstaged\`; this delta is approximate.`);
|
|
19
|
+
const earlier = await ChangesetGraph.build(model, changeset, { throughSequence: revision }), then = {
|
|
20
|
+
brief: buildBrief({ graph: earlier, unwired: "none" }),
|
|
21
|
+
unwired: computeUnwired(earlier),
|
|
22
|
+
graph: earlier
|
|
23
|
+
}, delta = diffBriefs(then, { brief: now.brief, unwired: computeUnwired(now.graph), graph: now.graph }, revision, warnings);
|
|
24
|
+
return delta.unstaged = unstagedSince(changeset, revision).map((t, index) => ({
|
|
25
|
+
...summarizeChange({ ...t.change, sequenceNumber: t.sequence }, index),
|
|
26
|
+
removedAt: t.removedAt
|
|
27
|
+
})), delta;
|
|
28
|
+
}
|
|
29
|
+
function diffBriefs(then, now, since, extraWarnings = []) {
|
|
30
|
+
const revision = now.brief.revision, warnings = [...extraWarnings];
|
|
31
|
+
since > revision && warnings.push(`since (${since}) is past the current revision (${revision}), so it is not a revision of this plan; the delta compares the changeset with itself.`);
|
|
32
|
+
const changedIds = (brief) => brief.layers.flatMap((l) => l.changes.map((c) => c.id)), thenIds = new Set(changedIds(then.brief)), nowIds = new Set(changedIds(now.brief)), gapKey = (g) => `${g.kind}|${g.element}|${g.kind === "peer-pattern" ? g.detail.replace(/^\d+ of \d+ /, "") : g.detail}`, thenGaps = new Map(then.brief.gaps.map((g) => [gapKey(g), g])), nowGaps = new Map(now.brief.gaps.map((g) => [gapKey(g), g])), flatten = (list) => {
|
|
33
|
+
const map = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const { element, byLayer } of list)
|
|
35
|
+
for (const { layer, entries } of byLayer)
|
|
36
|
+
for (const entry of entries)
|
|
37
|
+
map.set(unwiredKey(element, entry), { element, layer, ...entry });
|
|
38
|
+
return map;
|
|
39
|
+
}, thenUnwired = flatten(then.unwired), nowUnwired = flatten(now.unwired), thenScoped = new Set(then.unwired.map((u) => u.element)), nowScoped = new Set(now.unwired.map((u) => u.element)), traced = (brief) => new Set(brief.intent.traced.map((t) => t.element)), untraced = (brief) => new Set(brief.intent.untraced), thenTraced = traced(then.brief), thenUntraced = untraced(then.brief), thenReach = new Set(then.brief.reach.touched), nowReach = new Set(now.brief.reach.touched);
|
|
40
|
+
return {
|
|
41
|
+
since,
|
|
42
|
+
revision,
|
|
43
|
+
elements: {
|
|
44
|
+
added: [...nowIds].filter((id) => !thenIds.has(id)),
|
|
45
|
+
removed: [...thenIds].filter((id) => !nowIds.has(id)),
|
|
46
|
+
updated: then.graph && now.graph ? elementUpdates(then.graph, now.graph) : []
|
|
47
|
+
},
|
|
48
|
+
relationships: then.graph && now.graph ? relationshipChanges(then.graph, now.graph) : { added: [], removed: [] },
|
|
49
|
+
unstaged: [],
|
|
50
|
+
gaps: {
|
|
51
|
+
opened: [...nowGaps].filter(([k]) => !thenGaps.has(k)).map(([, g]) => g),
|
|
52
|
+
closed: [...thenGaps].filter(([k]) => !nowGaps.has(k)).map(([, g]) => g)
|
|
53
|
+
},
|
|
54
|
+
unwired: {
|
|
55
|
+
wired: [...thenUnwired].filter(([k, e]) => !nowUnwired.has(k) && nowScoped.has(e.element)).map(([, e]) => e),
|
|
56
|
+
new: [...nowUnwired].filter(([k, e]) => !thenUnwired.has(k) && thenScoped.has(e.element)).map(([, e]) => e),
|
|
57
|
+
newElements: now.unwired.filter((u) => !thenScoped.has(u.element)).map((u) => ({ element: u.element, count: u.byLayer.reduce((sum, l) => sum + l.entries.length, 0) }))
|
|
58
|
+
},
|
|
59
|
+
reach: {
|
|
60
|
+
newlyReached: now.brief.reach.touched.filter((l) => !thenReach.has(l)),
|
|
61
|
+
noLongerReached: then.brief.reach.touched.filter((l) => !nowReach.has(l))
|
|
62
|
+
},
|
|
63
|
+
intent: {
|
|
64
|
+
newlyTraced: now.brief.intent.traced.map((t) => t.element).filter((id) => !thenTraced.has(id)),
|
|
65
|
+
newlyUntraced: now.brief.intent.untraced.filter((id) => !thenUntraced.has(id))
|
|
66
|
+
},
|
|
67
|
+
note: NOTE,
|
|
68
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function elementUpdates(then, now) {
|
|
72
|
+
const updates = [];
|
|
73
|
+
for (const after of now.elements.values()) {
|
|
74
|
+
if (after.status === "deleted")
|
|
75
|
+
continue;
|
|
76
|
+
const before = then.getElement(after.id);
|
|
77
|
+
if (!before || before.status === "deleted" || before.status === "unchanged" && after.status === "unchanged")
|
|
78
|
+
continue;
|
|
79
|
+
const fields = changedFields(before, after);
|
|
80
|
+
fields.length > 0 && updates.push({ element: after.id, fields });
|
|
81
|
+
}
|
|
82
|
+
return updates;
|
|
83
|
+
}
|
|
84
|
+
function changedFields(before, after) {
|
|
85
|
+
const fields = [], same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
86
|
+
same(before.name, after.name) || fields.push("name"), same(before.description, after.description) || fields.push("description");
|
|
87
|
+
for (const key of [.../* @__PURE__ */ new Set([...Object.keys(before.attributes), ...Object.keys(after.attributes)])].sort())
|
|
88
|
+
same(before.attributes[key], after.attributes[key]) || fields.push(`attributes.${key}`);
|
|
89
|
+
return same(before.sourceReference, after.sourceReference) || fields.push("sourceReference"), fields;
|
|
90
|
+
}
|
|
91
|
+
function relationshipChanges(then, now) {
|
|
92
|
+
const live = (graph) => new Map(graph.relationships().filter((r) => r.status !== "deleted").map((r) => [relationshipKey(r), { source: r.source, predicate: r.predicate, target: r.target }])), before = live(then), after = live(now);
|
|
93
|
+
return {
|
|
94
|
+
added: [...after].filter(([k]) => !before.has(k)).map(([, r]) => r),
|
|
95
|
+
removed: [...before].filter(([k]) => !after.has(k)).map(([, r]) => r)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
briefDelta,
|
|
100
|
+
diffBriefs
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=changeset-brief-delta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-brief-delta.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Brief deltas \u2014 what changed in a changeset's brief since an earlier revision.\n *\n * The earlier brief is rebuilt from the changes that made up the plan at that revision (a\n * `ChangesetGraph` over them, against today's base) and compared with the brief now. Sequence\n * numbers are never reused and every unstage leaves a tombstone, so the plan at a revision can be\n * rebuilt after later unstages too. See \"Iterating on a plan\" in docs/CHANGE_MODELING.md.\n */\n\nimport {\n buildBrief,\n computeUnwired,\n unwiredKey,\n type BriefGap,\n type ChangesetBrief,\n type ElementUnwired,\n type UnwiredEntry,\n} from \"./changeset-analysis.js\";\nimport {\n ChangesetGraph,\n changesetRevisionToken,\n parseRevisionToken,\n relationshipKey,\n unstagedSince,\n type GraphElement,\n} from \"./changeset-graph.js\";\nimport { summarizeChange, type ChangeSummaryEntry } from \"./changeset-summary.js\";\nimport type { StagedChange } from \"./changeset.js\";\nimport type { Model } from \"./model.js\";\n\nexport type UnwiredDeltaEntry = { element: string; layer: string } & UnwiredEntry;\n\nexport type RelationshipTriple = { source: string; predicate: string; target: string };\n\nexport interface BriefDelta {\n /** The revision compared against, as asked (the number, when a revision token was passed). */\n since: number;\n /** The revision now. */\n revision: number;\n /**\n * Elements that entered or left the change, and elements whose content (name, description,\n * attributes, source reference) changed since, with the fields that did.\n */\n elements: { added: string[]; removed: string[]; updated: Array<{ element: string; fields: string[] }> };\n /** Relationships live now and not then (added), or then and not now (removed), new elements' included. */\n relationships: { added: RelationshipTriple[]; removed: RelationshipTriple[] };\n /** Changes staged at `since` and unstaged after it, with the revision each unstage made. */\n unstaged: Array<ChangeSummaryEntry & { removedAt: number }>;\n gaps: { opened: BriefGap[]; closed: BriefGap[] };\n unwired: {\n /** Unwired then, wired now (the element is still in the change). */\n wired: UnwiredDeltaEntry[];\n /** Unwired now, not then, on elements that were already in the change. */\n new: UnwiredDeltaEntry[];\n /** Elements new to the change, with how many unwired connections they bring (listed under `unwired`). */\n newElements: Array<{ element: string; count: number }>;\n };\n reach: { newlyReached: string[]; noLongerReached: string[] };\n intent: { newlyTraced: string[]; newlyUntraced: string[] };\n /** How the earlier state was rebuilt, and when it is approximate. */\n note: string;\n warnings?: string[];\n}\n\nexport interface BriefSide {\n brief: ChangesetBrief;\n unwired: ElementUnwired[];\n /** The projection the brief was built from; needed for element updates and relationship changes. */\n graph?: ChangesetGraph;\n}\n\nconst NOTE =\n \"The earlier state is rebuilt from the changes that made up the plan at `since` (unstaged ones from the \" +\n \"changeset's unstage record), applied to today's base model; base drift since then is not separated out. \" +\n \"It is approximate when a change staged by then was rewritten in place afterwards: pass the brief's \" +\n \"revisionToken as `since` to be warned when that happened.\";\n\n/**\n * Rebuilds the brief side for `since` (a revision number, or a brief's `revisionToken`) and compares\n * it with `now`. A token whose hash no longer matches the plan at its revision adds a warning: a\n * change up to it was rewritten in place, so the rebuilt earlier state is not the one the token was\n * taken from.\n */\nexport async function briefDelta(\n model: Model,\n now: { graph: ChangesetGraph; brief: ChangesetBrief },\n since: number | string\n): Promise<BriefDelta> {\n let revision: number;\n const warnings: string[] = [];\n const changeset = now.graph.changeset;\n if (typeof since === \"string\") {\n const parsed = parseRevisionToken(since);\n if (!parsed) {\n throw new Error(`since \"${since}\" is neither a revision number nor a revision token (<revision>:<hash>) from a brief`);\n }\n revision = parsed.revision;\n const today = changesetRevisionToken(changeset, revision);\n if (revision <= now.brief.revision && today !== since) {\n warnings.push(\n `The plan at revision ${revision} is not the one that revision token was taken from: a change up to it ` +\n \"was rewritten in place since then (or the changeset was rewritten, e.g. by an upgrade). The earlier \" +\n \"state below is rebuilt from what is recorded now, so this delta is approximate.\"\n );\n }\n } else {\n revision = since;\n }\n const prunedThrough = changeset.unstagedPrunedThrough;\n if (prunedThrough !== undefined && revision < prunedThrough) {\n warnings.push(\n `Unstage tombstones up to revision ${prunedThrough} were dropped to keep the record bounded, so changes ` +\n `unstaged between revision ${revision} and then may be missing from the rebuilt earlier state and from ` +\n \"`unstaged`; this delta is approximate.\"\n );\n }\n const earlier = await ChangesetGraph.build(model, changeset, { throughSequence: revision });\n const then: BriefSide = {\n brief: buildBrief({ graph: earlier, unwired: \"none\" }),\n unwired: computeUnwired(earlier),\n graph: earlier,\n };\n const delta = diffBriefs(then, { brief: now.brief, unwired: computeUnwired(now.graph), graph: now.graph }, revision, warnings);\n delta.unstaged = unstagedSince(changeset, revision).map((t, index) => ({\n ...summarizeChange({ ...t.change, sequenceNumber: t.sequence } as StagedChange, index),\n removedAt: t.removedAt,\n }));\n return delta;\n}\n\nexport function diffBriefs(then: BriefSide, now: BriefSide, since: number, extraWarnings: string[] = []): BriefDelta {\n const revision = now.brief.revision;\n const warnings: string[] = [...extraWarnings];\n if (since > revision) {\n warnings.push(\n `since (${since}) is past the current revision (${revision}), so it is not a revision of this plan; the ` +\n \"delta compares the changeset with itself.\"\n );\n }\n\n const changedIds = (brief: ChangesetBrief) => brief.layers.flatMap((l) => l.changes.map((c) => c.id));\n const thenIds = new Set(changedIds(then.brief));\n const nowIds = new Set(changedIds(now.brief));\n\n const gapKey = (g: BriefGap) => `${g.kind}|${g.element}|${g.kind === \"peer-pattern\" ? g.detail.replace(/^\\d+ of \\d+ /, \"\") : g.detail}`;\n const thenGaps = new Map(then.brief.gaps.map((g) => [gapKey(g), g]));\n const nowGaps = new Map(now.brief.gaps.map((g) => [gapKey(g), g]));\n\n const flatten = (list: ElementUnwired[]) => {\n const map = new Map<string, UnwiredDeltaEntry>();\n for (const { element, byLayer } of list) {\n for (const { layer, entries } of byLayer) {\n for (const entry of entries) map.set(unwiredKey(element, entry), { element, layer, ...entry });\n }\n }\n return map;\n };\n const thenUnwired = flatten(then.unwired);\n const nowUnwired = flatten(now.unwired);\n const thenScoped = new Set(then.unwired.map((u) => u.element));\n const nowScoped = new Set(now.unwired.map((u) => u.element));\n\n const traced = (brief: ChangesetBrief) => new Set(brief.intent.traced.map((t) => t.element));\n const untraced = (brief: ChangesetBrief) => new Set(brief.intent.untraced);\n const thenTraced = traced(then.brief);\n const thenUntraced = untraced(then.brief);\n const thenReach = new Set(then.brief.reach.touched);\n const nowReach = new Set(now.brief.reach.touched);\n\n return {\n since,\n revision,\n elements: {\n added: [...nowIds].filter((id) => !thenIds.has(id)),\n removed: [...thenIds].filter((id) => !nowIds.has(id)),\n updated: then.graph && now.graph ? elementUpdates(then.graph, now.graph) : [],\n },\n relationships: then.graph && now.graph ? relationshipChanges(then.graph, now.graph) : { added: [], removed: [] },\n unstaged: [],\n gaps: {\n opened: [...nowGaps].filter(([k]) => !thenGaps.has(k)).map(([, g]) => g),\n closed: [...thenGaps].filter(([k]) => !nowGaps.has(k)).map(([, g]) => g),\n },\n unwired: {\n wired: [...thenUnwired]\n .filter(([k, e]) => !nowUnwired.has(k) && nowScoped.has(e.element))\n .map(([, e]) => e),\n new: [...nowUnwired].filter(([k, e]) => !thenUnwired.has(k) && thenScoped.has(e.element)).map(([, e]) => e),\n newElements: now.unwired\n .filter((u) => !thenScoped.has(u.element))\n .map((u) => ({ element: u.element, count: u.byLayer.reduce((sum, l) => sum + l.entries.length, 0) })),\n },\n reach: {\n newlyReached: now.brief.reach.touched.filter((l) => !thenReach.has(l)),\n noLongerReached: then.brief.reach.touched.filter((l) => !nowReach.has(l)),\n },\n intent: {\n newlyTraced: now.brief.intent.traced.map((t) => t.element).filter((id) => !thenTraced.has(id)),\n newlyUntraced: now.brief.intent.untraced.filter((id) => !thenUntraced.has(id)),\n },\n note: NOTE,\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n}\n\n/** Elements present (not deleted) in both projections whose content differs, with the fields that do. */\nfunction elementUpdates(then: ChangesetGraph, now: ChangesetGraph): Array<{ element: string; fields: string[] }> {\n const updates: Array<{ element: string; fields: string[] }> = [];\n for (const after of now.elements.values()) {\n if (after.status === \"deleted\") continue;\n const before = then.getElement(after.id);\n if (!before || before.status === \"deleted\") continue;\n // Both from the base: same content. (One unchanged and one not still compares: an update\n // staged or unstaged since.)\n if (before.status === \"unchanged\" && after.status === \"unchanged\") continue;\n const fields = changedFields(before, after);\n if (fields.length > 0) updates.push({ element: after.id, fields });\n }\n return updates;\n}\n\nfunction changedFields(before: GraphElement, after: GraphElement): string[] {\n const fields: string[] = [];\n const same = (a: unknown, b: unknown) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);\n if (!same(before.name, after.name)) fields.push(\"name\");\n if (!same(before.description, after.description)) fields.push(\"description\");\n for (const key of [...new Set([...Object.keys(before.attributes), ...Object.keys(after.attributes)])].sort()) {\n if (!same(before.attributes[key], after.attributes[key])) fields.push(`attributes.${key}`);\n }\n if (!same(before.sourceReference, after.sourceReference)) fields.push(\"sourceReference\");\n return fields;\n}\n\n/** Relationships live in one projection and not the other. */\nfunction relationshipChanges(\n then: ChangesetGraph,\n now: ChangesetGraph\n): { added: RelationshipTriple[]; removed: RelationshipTriple[] } {\n const live = (graph: ChangesetGraph) =>\n new Map(\n graph\n .relationships()\n .filter((r) => r.status !== \"deleted\")\n .map((r) => [relationshipKey(r), { source: r.source, predicate: r.predicate, target: r.target }])\n );\n const before = live(then);\n const after = live(now);\n return {\n added: [...after].filter(([k]) => !before.has(k)).map(([, r]) => r),\n removed: [...before].filter(([k]) => !after.has(k)).map(([, r]) => r),\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { compactChange, nextSequenceNumber, sequenceOf } from "./changeset.js";
|
|
3
|
+
import { stagedSourceReference } from "./staged-source-reference.js";
|
|
4
|
+
function sequencedChanges(changeset) {
|
|
5
|
+
return changeset.changes.map((change, index) => ({ change, sequence: sequenceOf(change, index) })).sort((a, b) => a.sequence - b.sequence);
|
|
6
|
+
}
|
|
7
|
+
function changesAtRevision(changeset, revision) {
|
|
8
|
+
const live = sequencedChanges(changeset);
|
|
9
|
+
if (revision === void 0)
|
|
10
|
+
return live;
|
|
11
|
+
const bySequence = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const entry of live)
|
|
13
|
+
entry.sequence <= revision && bySequence.set(entry.sequence, entry);
|
|
14
|
+
for (const t of tombstonesLiveAt(changeset, revision))
|
|
15
|
+
bySequence.set(t.sequence, {
|
|
16
|
+
change: { ...t.change, sequenceNumber: t.sequence },
|
|
17
|
+
sequence: t.sequence,
|
|
18
|
+
...t.rewritten ? {} : { unstagedAt: t.removedAt }
|
|
19
|
+
});
|
|
20
|
+
return [...bySequence.values()].sort((a, b) => a.sequence - b.sequence);
|
|
21
|
+
}
|
|
22
|
+
function tombstonesLiveAt(changeset, revision) {
|
|
23
|
+
const earliest = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const t of changeset.unstaged ?? []) {
|
|
25
|
+
if (t.sequence > revision || t.removedAt <= revision)
|
|
26
|
+
continue;
|
|
27
|
+
const kept = earliest.get(t.sequence);
|
|
28
|
+
(!kept || t.removedAt < kept.removedAt) && earliest.set(t.sequence, t);
|
|
29
|
+
}
|
|
30
|
+
return [...earliest.values()].sort((a, b) => a.sequence - b.sequence);
|
|
31
|
+
}
|
|
32
|
+
function unstagedSince(changeset, revision) {
|
|
33
|
+
const live = new Set(sequencedChanges(changeset).map(({ sequence }) => sequence)), result = [];
|
|
34
|
+
for (const { sequence } of tombstonesLiveAt(changeset, revision)) {
|
|
35
|
+
if (live.has(sequence))
|
|
36
|
+
continue;
|
|
37
|
+
const unstage = (changeset.unstaged ?? []).filter((t) => t.sequence === sequence && !t.rewritten && t.removedAt > revision).sort((x, y) => y.removedAt - x.removedAt)[0];
|
|
38
|
+
unstage && result.push(unstage);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function changesetRevision(changeset) {
|
|
43
|
+
return nextSequenceNumber(changeset) - 1;
|
|
44
|
+
}
|
|
45
|
+
function changesetRevisionToken(changeset, throughSequence) {
|
|
46
|
+
const revision = throughSequence ?? changesetRevision(changeset), hash = createHash("sha256");
|
|
47
|
+
for (const { change, sequence } of changesAtRevision(changeset, revision)) {
|
|
48
|
+
const compact = compactChange(change);
|
|
49
|
+
hash.update(JSON.stringify([sequence, compact.type, compact.elementId, compact.timestamp ?? null, compact.before ?? null, compact.after ?? null])), hash.update(`
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
return `${revision}:${hash.digest("hex").slice(0, 12)}`;
|
|
53
|
+
}
|
|
54
|
+
function parseRevisionToken(token) {
|
|
55
|
+
const match = /^(-1|\d+):([0-9a-f]+)$/.exec(token);
|
|
56
|
+
return match ? { revision: Number(match[1]), hash: match[2] } : void 0;
|
|
57
|
+
}
|
|
58
|
+
function relationshipKey(rel) {
|
|
59
|
+
return `${rel.source}::${rel.predicate}::${rel.target}`;
|
|
60
|
+
}
|
|
61
|
+
class ChangesetGraph {
|
|
62
|
+
changeset;
|
|
63
|
+
elements = /* @__PURE__ */ new Map();
|
|
64
|
+
relationshipsByKey = /* @__PURE__ */ new Map();
|
|
65
|
+
/** UUID → dotted path, so relationship rows that name elements by UUID still resolve. */
|
|
66
|
+
uuidToPath = /* @__PURE__ */ new Map();
|
|
67
|
+
adjacency = null;
|
|
68
|
+
/** The staged changes this projection applied, in sequence order. */
|
|
69
|
+
appliedChanges = [];
|
|
70
|
+
_revision = -1;
|
|
71
|
+
constructor(changeset) {
|
|
72
|
+
this.changeset = changeset;
|
|
73
|
+
}
|
|
74
|
+
/** The revision this projection shows (see `changesetRevision`); -1 when nothing was staged by then. */
|
|
75
|
+
get revision() {
|
|
76
|
+
return this._revision;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Builds the projected graph of `changeset` over the base model. With `throughSequence`, the plan
|
|
80
|
+
* as it stood at that revision, over today's base: the changes numbered at most that, including
|
|
81
|
+
* ones unstaged since (rebuilt from their tombstones).
|
|
82
|
+
*/
|
|
83
|
+
static async build(model, changeset, options = {}) {
|
|
84
|
+
const graph = new ChangesetGraph(changeset);
|
|
85
|
+
for (const layerName of model.getLayerNames()) {
|
|
86
|
+
const layer = await model.getBaseLayer(layerName);
|
|
87
|
+
if (layer)
|
|
88
|
+
for (const element of layer.listElements()) {
|
|
89
|
+
const record = element.toJSON();
|
|
90
|
+
graph.putElement(fromRecord(record, layerName, "unchanged"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const baseRelationships = typeof model.getBaseRelationships == "function" ? model.getBaseRelationships() : model.relationships.getAll();
|
|
94
|
+
for (const rel of baseRelationships) {
|
|
95
|
+
const resolved = {
|
|
96
|
+
source: graph.resolve(rel.source),
|
|
97
|
+
predicate: rel.predicate,
|
|
98
|
+
target: graph.resolve(rel.target)
|
|
99
|
+
};
|
|
100
|
+
graph.relationshipsByKey.set(relationshipKey(resolved), {
|
|
101
|
+
...resolved,
|
|
102
|
+
layer: rel.layer,
|
|
103
|
+
...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
|
|
104
|
+
...rel.properties ? { properties: rel.properties } : {},
|
|
105
|
+
status: "unchanged",
|
|
106
|
+
...resolved.source !== rel.source || resolved.target !== rel.target ? { raw: { source: rel.source, target: rel.target } } : {}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const through = options.throughSequence, applied = changesAtRevision(changeset, through);
|
|
110
|
+
graph.appliedChanges = applied.map(({ change }) => change);
|
|
111
|
+
const current = changesetRevision(changeset);
|
|
112
|
+
graph._revision = through === void 0 ? current : Math.min(through, current);
|
|
113
|
+
for (const change of graph.appliedChanges)
|
|
114
|
+
graph.apply(change);
|
|
115
|
+
return graph;
|
|
116
|
+
}
|
|
117
|
+
/** Maps a UUID to its dotted path; returns any other identifier unchanged. */
|
|
118
|
+
resolve(id) {
|
|
119
|
+
return this.uuidToPath.get(id) ?? id;
|
|
120
|
+
}
|
|
121
|
+
getElement(id) {
|
|
122
|
+
return this.elements.get(this.resolve(id));
|
|
123
|
+
}
|
|
124
|
+
/** Every relationship in the projection, including the ones the changeset deletes. */
|
|
125
|
+
relationships() {
|
|
126
|
+
return [...this.relationshipsByKey.values()];
|
|
127
|
+
}
|
|
128
|
+
/** Whether the projection holds a relationship that is not deleted. */
|
|
129
|
+
hasLiveRelationship(source, predicate, target) {
|
|
130
|
+
const rel = this.relationshipsByKey.get(relationshipKey({ source: this.resolve(source), predicate, target: this.resolve(target) }));
|
|
131
|
+
return !!rel && rel.status !== "deleted";
|
|
132
|
+
}
|
|
133
|
+
getRelationship(source, predicate, target) {
|
|
134
|
+
return this.relationshipsByKey.get(relationshipKey({ source: this.resolve(source), predicate, target: this.resolve(target) }));
|
|
135
|
+
}
|
|
136
|
+
/** Elements the changeset adds, updates or deletes, in the order of their first change. */
|
|
137
|
+
changedElements() {
|
|
138
|
+
const seen = /* @__PURE__ */ new Set(), result = [];
|
|
139
|
+
for (const change of this.appliedChanges) {
|
|
140
|
+
if (change.type.startsWith("relationship-"))
|
|
141
|
+
continue;
|
|
142
|
+
const element = this.getElement(change.elementId);
|
|
143
|
+
!element || element.status === "unchanged" || seen.has(element.id) || (seen.add(element.id), result.push(element));
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
/** Relationships the changeset adds or deletes. */
|
|
148
|
+
changedRelationships() {
|
|
149
|
+
return this.relationships().filter((rel) => rel.status !== "unchanged");
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Staged adds the base model already has: elements and relationships the changeset adds whose id
|
|
153
|
+
* (or source, predicate and target) now exists in the base. They are not counted as adds, since
|
|
154
|
+
* commit skips them; a later staged update or delete of the same element still applies.
|
|
155
|
+
*/
|
|
156
|
+
alreadyInBase() {
|
|
157
|
+
return {
|
|
158
|
+
elements: [...this.elements.values()].filter((e) => e.alreadyInBase && e.status !== "deleted"),
|
|
159
|
+
relationships: this.relationships().filter((r) => r.alreadyInBase && r.status === "unchanged")
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** Relationships touching an element, with the element's side of each. */
|
|
163
|
+
neighbors(id, options = {}) {
|
|
164
|
+
const all = this.getAdjacency().get(this.resolve(id)) ?? [];
|
|
165
|
+
return options.includeDeleted ? all : all.filter((n) => n.relationship.status !== "deleted");
|
|
166
|
+
}
|
|
167
|
+
getAdjacency() {
|
|
168
|
+
if (this.adjacency)
|
|
169
|
+
return this.adjacency;
|
|
170
|
+
const adjacency = /* @__PURE__ */ new Map(), push = (id, neighbor) => {
|
|
171
|
+
const list = adjacency.get(id);
|
|
172
|
+
list ? list.push(neighbor) : adjacency.set(id, [neighbor]);
|
|
173
|
+
};
|
|
174
|
+
for (const rel of this.relationshipsByKey.values())
|
|
175
|
+
push(rel.source, { relationship: rel, direction: "out", other: rel.target }), rel.target !== rel.source && push(rel.target, { relationship: rel, direction: "in", other: rel.source });
|
|
176
|
+
return this.adjacency = adjacency, adjacency;
|
|
177
|
+
}
|
|
178
|
+
putElement(element) {
|
|
179
|
+
this.elements.set(element.id, element), element.uuid && element.uuid !== element.id && this.uuidToPath.set(element.uuid, element.id);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A committed changeset's changes are already in the base model, so a base element or
|
|
183
|
+
* relationship matching one of its adds is that add, not an earlier copy of it.
|
|
184
|
+
*/
|
|
185
|
+
isCommitted() {
|
|
186
|
+
return this.changeset.status === "committed";
|
|
187
|
+
}
|
|
188
|
+
apply(change) {
|
|
189
|
+
switch (change.type) {
|
|
190
|
+
case "add": {
|
|
191
|
+
if (!change.after)
|
|
192
|
+
return;
|
|
193
|
+
const element = fromRecord(change.after, change.layerName, "added", change.elementId), existing = this.getElement(element.id);
|
|
194
|
+
if (existing && existing.status === "unchanged" && !this.isCommitted()) {
|
|
195
|
+
existing.alreadyInBase = !0;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.putElement(element);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
case "update": {
|
|
202
|
+
if (!change.after)
|
|
203
|
+
return;
|
|
204
|
+
const existing = this.getElement(change.elementId), status = existing?.status === "added" ? "added" : "updated", element = fromRecord(change.after, change.layerName, status, change.elementId);
|
|
205
|
+
element.before = existing?.before ?? change.before, this.putElement(element);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
case "delete": {
|
|
209
|
+
const existing = this.getElement(change.elementId);
|
|
210
|
+
if (!existing)
|
|
211
|
+
return;
|
|
212
|
+
existing.status === "added" ? this.elements.delete(existing.id) : (existing.before = existing.before ?? change.before, existing.status = "deleted");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
case "relationship-add": {
|
|
216
|
+
const data = change.after;
|
|
217
|
+
if (!data?.source || !data.target || !data.predicate)
|
|
218
|
+
return;
|
|
219
|
+
const rel = {
|
|
220
|
+
source: this.resolve(data.source),
|
|
221
|
+
predicate: data.predicate,
|
|
222
|
+
target: this.resolve(data.target)
|
|
223
|
+
}, key = relationshipKey(rel), existing = this.relationshipsByKey.get(key);
|
|
224
|
+
if (existing && existing.status === "unchanged" && !this.isCommitted()) {
|
|
225
|
+
existing.alreadyInBase = !0;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.relationshipsByKey.set(key, {
|
|
229
|
+
...rel,
|
|
230
|
+
layer: data.layer ?? change.layerName,
|
|
231
|
+
...data.targetLayer ? { targetLayer: data.targetLayer } : {},
|
|
232
|
+
...data.properties ? { properties: data.properties } : {},
|
|
233
|
+
// Re-adding a relationship this changeset deleted restores it. A committed changeset's add
|
|
234
|
+
// is in the base because the changeset landed it, so it stays an add.
|
|
235
|
+
status: existing?.status === "deleted" ? "unchanged" : "added",
|
|
236
|
+
...existing?.raw ? { raw: existing.raw } : {}
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
case "relationship-delete": {
|
|
241
|
+
const data = change.before ?? change.after, [source, predicate, target] = data?.source ? [data.source, data.predicate, data.target] : change.elementId.split("::");
|
|
242
|
+
if (!source || !predicate || !target)
|
|
243
|
+
return;
|
|
244
|
+
const key = relationshipKey({
|
|
245
|
+
source: this.resolve(source),
|
|
246
|
+
predicate,
|
|
247
|
+
target: this.resolve(target)
|
|
248
|
+
}), existing = this.relationshipsByKey.get(key);
|
|
249
|
+
if (!existing)
|
|
250
|
+
return;
|
|
251
|
+
existing.status === "added" ? this.relationshipsByKey.delete(key) : existing.status = "deleted";
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function fromRecord(record, layerName, status, fallbackId) {
|
|
258
|
+
const str = (value) => typeof value == "string" && value.length > 0 ? value : void 0, id = str(record.path) ?? fallbackId ?? str(record.id) ?? "";
|
|
259
|
+
return {
|
|
260
|
+
id,
|
|
261
|
+
uuid: str(record.id),
|
|
262
|
+
layer: str(record.layer_id) ?? str(record.layer) ?? layerName,
|
|
263
|
+
type: str(record.type) ?? "",
|
|
264
|
+
name: str(record.name) ?? id,
|
|
265
|
+
description: str(record.description),
|
|
266
|
+
attributes: record.attributes ?? {},
|
|
267
|
+
sourceReference: stagedSourceReference(record),
|
|
268
|
+
status
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
ChangesetGraph,
|
|
273
|
+
changesAtRevision,
|
|
274
|
+
changesetRevision,
|
|
275
|
+
changesetRevisionToken,
|
|
276
|
+
parseRevisionToken,
|
|
277
|
+
relationshipKey,
|
|
278
|
+
sequencedChanges,
|
|
279
|
+
unstagedSince
|
|
280
|
+
};
|
|
281
|
+
//# sourceMappingURL=changeset-graph.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-graph.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * ChangesetGraph \u2014 the model as a changeset would leave it, elements and relationships both.\n *\n * The per-layer projection (`VirtualProjectionEngine`) merges staged element changes but keeps the\n * base model's relationships, so a traversal over it cannot see the connections a change adds or\n * removes. This view applies every staged change, relationship changes included, in sequence order,\n * and keeps a status on each element and relationship so callers can tell what the change touches.\n *\n * It reads the base model through `getBaseLayer()` and `getBaseRelationships()`, so it works for\n * any changeset (active or not) and is never affected by the model's own active-changeset\n * projection.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { Model } from \"./model.js\";\nimport type { Changeset, StagedChange, UnstagedChange } from \"./changeset.js\";\nimport { compactChange, nextSequenceNumber, sequenceOf } from \"./changeset.js\";\nimport type { SourceReference } from \"../types/source-reference.js\";\nimport { stagedSourceReference } from \"./staged-source-reference.js\";\n\nexport type ChangeStatus = \"added\" | \"updated\" | \"deleted\" | \"unchanged\";\n\nexport interface GraphElement {\n /** Dotted path (`layer.type.name`), the id relationships and changes use. */\n id: string;\n uuid?: string;\n layer: string;\n type: string;\n name: string;\n description?: string;\n attributes: Record<string, unknown>;\n sourceReference?: SourceReference;\n status: ChangeStatus;\n /** Element state before the change, for updated and deleted elements. */\n before?: Record<string, unknown>;\n /**\n * The changeset stages an add of this element, but the base model already has it (e.g. an as-built\n * changeset that created it was committed after the plan was staged). Commit skips such an add, so\n * the element keeps its base content and status; it is reported apart from the changeset's adds.\n */\n alreadyInBase?: true;\n}\n\nexport interface GraphRelationship {\n source: string;\n predicate: string;\n target: string;\n layer: string;\n targetLayer?: string;\n properties?: Record<string, unknown>;\n status: Exclude<ChangeStatus, \"updated\">;\n /** Endpoints as the relationship row names them (a UUID or a path), when they differ from the resolved ones. */\n raw?: { source: string; target: string };\n /** The changeset stages an add of this relationship, but the base model already has it. */\n alreadyInBase?: true;\n}\n\nexport interface Neighbor {\n relationship: GraphRelationship;\n /** \"out\" when the element is the relationship's source, \"in\" when it is the target. */\n direction: \"out\" | \"in\";\n other: string;\n}\n\n/**\n * A changeset's changes in sequence order, each with its sequence number (its index when the change\n * carries none).\n */\nexport function sequencedChanges(changeset: Changeset): Array<{ change: StagedChange; sequence: number }> {\n return (changeset.changes as StagedChange[])\n .map((change, index) => ({ change, sequence: sequenceOf(change, index) }))\n .sort((a, b) => a.sequence - b.sequence);\n}\n\n/**\n * The changes that made up the plan at `revision`, in sequence order: the staged changes numbered\n * at or below it, plus the unstaged ones (tombstones) that were staged by then and unstaged after.\n * Without a revision, the changes staged now.\n */\nexport function changesAtRevision(\n changeset: Changeset,\n revision?: number\n): Array<{ change: StagedChange; sequence: number; unstagedAt?: number }> {\n const live = sequencedChanges(changeset);\n if (revision === undefined) return live;\n // Each sequence number at or below the revision, as it stood then: the staged change, unless a\n // tombstone says it was unstaged or rewritten after the revision; then the tombstone's version.\n const bySequence = new Map<number, { change: StagedChange; sequence: number; unstagedAt?: number }>();\n for (const entry of live) if (entry.sequence <= revision) bySequence.set(entry.sequence, entry);\n for (const t of tombstonesLiveAt(changeset, revision)) {\n bySequence.set(t.sequence, {\n change: { ...t.change, sequenceNumber: t.sequence } as StagedChange,\n sequence: t.sequence,\n ...(t.rewritten ? {} : { unstagedAt: t.removedAt }),\n });\n }\n return [...bySequence.values()].sort((a, b) => a.sequence - b.sequence);\n}\n\n/**\n * For each sequence number at or below `revision` that was unstaged or rewritten after it, the\n * tombstone holding the version it had at `revision`: the earliest one removed after it.\n */\nfunction tombstonesLiveAt(changeset: Changeset, revision: number): UnstagedChange[] {\n const earliest = new Map<number, UnstagedChange>();\n for (const t of changeset.unstaged ?? []) {\n if (t.sequence > revision || t.removedAt <= revision) continue;\n const kept = earliest.get(t.sequence);\n if (!kept || t.removedAt < kept.removedAt) earliest.set(t.sequence, t);\n }\n return [...earliest.values()].sort((a, b) => a.sequence - b.sequence);\n}\n\n/**\n * Tombstones of changes that were staged at `revision` and have been unstaged (not merely\n * rewritten) since: for each such sequence number, its unstage tombstone. A change rewritten and\n * then unstaged has both; the unstage one is reported.\n */\nexport function unstagedSince(changeset: Changeset, revision: number): UnstagedChange[] {\n const live = new Set(sequencedChanges(changeset).map(({ sequence }) => sequence));\n const result: UnstagedChange[] = [];\n for (const { sequence } of tombstonesLiveAt(changeset, revision)) {\n if (live.has(sequence)) continue;\n const unstage = (changeset.unstaged ?? [])\n .filter((t) => t.sequence === sequence && !t.rewritten && t.removedAt > revision)\n .sort((x, y) => y.removedAt - x.removedAt)[0];\n if (unstage) result.push(unstage);\n }\n return result;\n}\n\n/**\n * The changeset's revision: the last sequence number it has used, a stage or an unstage (each\n * takes one), or -1 when nothing was ever staged. For a changeset never unstaged from, its last\n * change's number.\n */\nexport function changesetRevision(changeset: Changeset): number {\n return nextSequenceNumber(changeset) - 1;\n}\n\n/**\n * A token naming the plan at a revision, not just its number: `<revision>:<hash>`, where the hash\n * covers every change that made up the plan at `throughSequence` (now, by default): its type,\n * element, timestamp and content, unstaged changes included while they were staged. An update can\n * rewrite a staged change in place, so the plan behind a revision number can change; the token\n * then differs. Nothing is stored for it: the token is recomputed from what is staged and the\n * unstage tombstones.\n */\nexport function changesetRevisionToken(changeset: Changeset, throughSequence?: number): string {\n const revision = throughSequence ?? changesetRevision(changeset);\n const hash = createHash(\"sha256\");\n for (const { change, sequence } of changesAtRevision(changeset, revision)) {\n const compact = compactChange(change);\n hash.update(\n JSON.stringify([sequence, compact.type, compact.elementId, compact.timestamp ?? null, compact.before ?? null, compact.after ?? null])\n );\n hash.update(\"\\n\");\n }\n return `${revision}:${hash.digest(\"hex\").slice(0, 12)}`;\n}\n\n/** Splits a revision token into its revision and hash, or returns undefined when it is not one. */\nexport function parseRevisionToken(token: string): { revision: number; hash: string } | undefined {\n const match = /^(-1|\\d+):([0-9a-f]+)$/.exec(token);\n return match ? { revision: Number(match[1]), hash: match[2] } : undefined;\n}\n\n/** Relationship key used by staged changes: `source::predicate::target`. */\nexport function relationshipKey(rel: { source: string; predicate: string; target: string }): string {\n return `${rel.source}::${rel.predicate}::${rel.target}`;\n}\n\nexport class ChangesetGraph {\n readonly elements = new Map<string, GraphElement>();\n private readonly relationshipsByKey = new Map<string, GraphRelationship>();\n /** UUID \u2192 dotted path, so relationship rows that name elements by UUID still resolve. */\n private readonly uuidToPath = new Map<string, string>();\n private adjacency: Map<string, Neighbor[]> | null = null;\n /** The staged changes this projection applied, in sequence order. */\n private appliedChanges: StagedChange[] = [];\n private _revision = -1;\n\n private constructor(readonly changeset: Changeset) {}\n\n /** The revision this projection shows (see `changesetRevision`); -1 when nothing was staged by then. */\n get revision(): number {\n return this._revision;\n }\n\n /**\n * Builds the projected graph of `changeset` over the base model. With `throughSequence`, the plan\n * as it stood at that revision, over today's base: the changes numbered at most that, including\n * ones unstaged since (rebuilt from their tombstones).\n */\n static async build(\n model: Model,\n changeset: Changeset,\n options: { throughSequence?: number } = {}\n ): Promise<ChangesetGraph> {\n const graph = new ChangesetGraph(changeset);\n\n for (const layerName of model.getLayerNames()) {\n const layer = await model.getBaseLayer(layerName);\n if (!layer) continue;\n for (const element of layer.listElements()) {\n const record = element.toJSON() as unknown as Record<string, unknown>;\n graph.putElement(fromRecord(record, layerName, \"unchanged\"));\n }\n }\n\n // The committed relationships: a model loaded with this changeset active has already merged\n // its staged relationship changes into `model.relationships`, and they are applied below.\n const baseRelationships =\n typeof model.getBaseRelationships === \"function\"\n ? model.getBaseRelationships()\n : model.relationships.getAll();\n for (const rel of baseRelationships) {\n const resolved = {\n source: graph.resolve(rel.source),\n predicate: rel.predicate,\n target: graph.resolve(rel.target),\n };\n graph.relationshipsByKey.set(relationshipKey(resolved), {\n ...resolved,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n ...(rel.properties ? { properties: rel.properties } : {}),\n status: \"unchanged\",\n ...(resolved.source !== rel.source || resolved.target !== rel.target\n ? { raw: { source: rel.source, target: rel.target } }\n : {}),\n });\n }\n\n // Up to a revision, changes unstaged after it count again (from their tombstones).\n const through = options.throughSequence;\n const applied = changesAtRevision(changeset, through);\n graph.appliedChanges = applied.map(({ change }) => change);\n const current = changesetRevision(changeset);\n graph._revision = through === undefined ? current : Math.min(through, current);\n for (const change of graph.appliedChanges) {\n graph.apply(change);\n }\n\n return graph;\n }\n\n /** Maps a UUID to its dotted path; returns any other identifier unchanged. */\n resolve(id: string): string {\n return this.uuidToPath.get(id) ?? id;\n }\n\n getElement(id: string): GraphElement | undefined {\n return this.elements.get(this.resolve(id));\n }\n\n /** Every relationship in the projection, including the ones the changeset deletes. */\n relationships(): GraphRelationship[] {\n return [...this.relationshipsByKey.values()];\n }\n\n /** Whether the projection holds a relationship that is not deleted. */\n hasLiveRelationship(source: string, predicate: string, target: string): boolean {\n const rel = this.relationshipsByKey.get(\n relationshipKey({ source: this.resolve(source), predicate, target: this.resolve(target) })\n );\n return !!rel && rel.status !== \"deleted\";\n }\n\n getRelationship(source: string, predicate: string, target: string): GraphRelationship | undefined {\n return this.relationshipsByKey.get(\n relationshipKey({ source: this.resolve(source), predicate, target: this.resolve(target) })\n );\n }\n\n /** Elements the changeset adds, updates or deletes, in the order of their first change. */\n changedElements(): GraphElement[] {\n const seen = new Set<string>();\n const result: GraphElement[] = [];\n for (const change of this.appliedChanges) {\n if (change.type.startsWith(\"relationship-\")) continue;\n const element = this.getElement(change.elementId);\n if (!element || element.status === \"unchanged\" || seen.has(element.id)) continue;\n seen.add(element.id);\n result.push(element);\n }\n return result;\n }\n\n /** Relationships the changeset adds or deletes. */\n changedRelationships(): GraphRelationship[] {\n return this.relationships().filter((rel) => rel.status !== \"unchanged\");\n }\n\n /**\n * Staged adds the base model already has: elements and relationships the changeset adds whose id\n * (or source, predicate and target) now exists in the base. They are not counted as adds, since\n * commit skips them; a later staged update or delete of the same element still applies.\n */\n alreadyInBase(): { elements: GraphElement[]; relationships: GraphRelationship[] } {\n return {\n elements: [...this.elements.values()].filter((e) => e.alreadyInBase && e.status !== \"deleted\"),\n relationships: this.relationships().filter((r) => r.alreadyInBase && r.status === \"unchanged\"),\n };\n }\n\n /** Relationships touching an element, with the element's side of each. */\n neighbors(id: string, options: { includeDeleted?: boolean } = {}): Neighbor[] {\n const all = this.getAdjacency().get(this.resolve(id)) ?? [];\n return options.includeDeleted ? all : all.filter((n) => n.relationship.status !== \"deleted\");\n }\n\n private getAdjacency(): Map<string, Neighbor[]> {\n if (this.adjacency) return this.adjacency;\n const adjacency = new Map<string, Neighbor[]>();\n const push = (id: string, neighbor: Neighbor) => {\n const list = adjacency.get(id);\n if (list) list.push(neighbor);\n else adjacency.set(id, [neighbor]);\n };\n for (const rel of this.relationshipsByKey.values()) {\n push(rel.source, { relationship: rel, direction: \"out\", other: rel.target });\n if (rel.target !== rel.source) {\n push(rel.target, { relationship: rel, direction: \"in\", other: rel.source });\n }\n }\n this.adjacency = adjacency;\n return adjacency;\n }\n\n private putElement(element: GraphElement): void {\n this.elements.set(element.id, element);\n if (element.uuid && element.uuid !== element.id) {\n this.uuidToPath.set(element.uuid, element.id);\n }\n }\n\n /**\n * A committed changeset's changes are already in the base model, so a base element or\n * relationship matching one of its adds is that add, not an earlier copy of it.\n */\n private isCommitted(): boolean {\n return this.changeset.status === \"committed\";\n }\n\n private apply(change: StagedChange): void {\n switch (change.type) {\n case \"add\": {\n if (!change.after) return;\n const element = fromRecord(change.after, change.layerName, \"added\", change.elementId);\n const existing = this.getElement(element.id);\n if (existing && existing.status === \"unchanged\" && !this.isCommitted()) {\n // The base already has it: commit would skip this add, so the base element stands.\n // (A committed changeset's adds are in the base because it landed them; they stay adds.)\n existing.alreadyInBase = true;\n return;\n }\n this.putElement(element);\n return;\n }\n case \"update\": {\n if (!change.after) return;\n const existing = this.getElement(change.elementId);\n const status: ChangeStatus = existing?.status === \"added\" ? \"added\" : \"updated\";\n const element = fromRecord(change.after, change.layerName, status, change.elementId);\n element.before = existing?.before ?? change.before;\n this.putElement(element);\n return;\n }\n case \"delete\": {\n const existing = this.getElement(change.elementId);\n if (!existing) return;\n if (existing.status === \"added\") {\n this.elements.delete(existing.id);\n } else {\n existing.before = existing.before ?? change.before;\n existing.status = \"deleted\";\n }\n return;\n }\n case \"relationship-add\": {\n const data = change.after as Partial<GraphRelationship> | undefined;\n if (!data?.source || !data.target || !data.predicate) return;\n const rel = {\n source: this.resolve(data.source),\n predicate: data.predicate,\n target: this.resolve(data.target),\n };\n const key = relationshipKey(rel);\n const existing = this.relationshipsByKey.get(key);\n if (existing && existing.status === \"unchanged\" && !this.isCommitted()) {\n // The base already has it (a staged add made before it was committed there).\n existing.alreadyInBase = true;\n return;\n }\n this.relationshipsByKey.set(key, {\n ...rel,\n layer: data.layer ?? change.layerName,\n ...(data.targetLayer ? { targetLayer: data.targetLayer } : {}),\n ...(data.properties ? { properties: data.properties } : {}),\n // Re-adding a relationship this changeset deleted restores it. A committed changeset's add\n // is in the base because the changeset landed it, so it stays an add.\n status: existing?.status === \"deleted\" ? \"unchanged\" : \"added\",\n ...(existing?.raw ? { raw: existing.raw } : {}),\n });\n return;\n }\n case \"relationship-delete\": {\n const data = (change.before ?? change.after) as Partial<GraphRelationship> | undefined;\n const [source, predicate, target] = data?.source\n ? [data.source, data.predicate, data.target]\n : change.elementId.split(\"::\");\n if (!source || !predicate || !target) return;\n const key = relationshipKey({\n source: this.resolve(source),\n predicate,\n target: this.resolve(target),\n });\n const existing = this.relationshipsByKey.get(key);\n if (!existing) return;\n if (existing.status === \"added\") this.relationshipsByKey.delete(key);\n else existing.status = \"deleted\";\n return;\n }\n }\n }\n}\n\nfunction fromRecord(\n record: Record<string, unknown>,\n layerName: string,\n status: ChangeStatus,\n fallbackId?: string\n): GraphElement {\n const str = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.length > 0 ? value : undefined;\n const id = str(record.path) ?? fallbackId ?? str(record.id) ?? \"\";\n return {\n id,\n uuid: str(record.id),\n layer: str(record.layer_id) ?? str(record.layer) ?? layerName,\n type: str(record.type) ?? \"\",\n name: str(record.name) ?? id,\n description: str(record.description),\n attributes: (record.attributes as Record<string, unknown> | undefined) ?? {},\n sourceReference: stagedSourceReference(record),\n status,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CLIError, ErrorCategory } from "../utils/errors.js";
|
|
2
|
+
const CHANGESET_ONLY_ENV = "DR_CHANGESET_ONLY", ON_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes", "on"]), OFF_VALUES = /* @__PURE__ */ new Set(["false", "0", "no", "off"]), warnedValues = /* @__PURE__ */ new Set();
|
|
3
|
+
function isChangesetOnly(env = process.env) {
|
|
4
|
+
const raw = env[CHANGESET_ONLY_ENV];
|
|
5
|
+
if (raw === void 0)
|
|
6
|
+
return !1;
|
|
7
|
+
const value = raw.trim().toLowerCase();
|
|
8
|
+
return value === "" || OFF_VALUES.has(value) ? !1 : (ON_VALUES.has(value) || warnedValues.has(raw) || (warnedValues.add(raw), console.error(`[dr] ${CHANGESET_ONLY_ENV}=${JSON.stringify(raw)} is not a recognised value (use true/1/yes/on or false/0/no/off); treating it as on, so base-model writes are refused.`)), !0);
|
|
9
|
+
}
|
|
10
|
+
function resetChangesetOnlyWarnings() {
|
|
11
|
+
warnedValues.clear();
|
|
12
|
+
}
|
|
13
|
+
class ChangesetOnlyError extends CLIError {
|
|
14
|
+
constructor(...args) {
|
|
15
|
+
super(...args), this.name = "ChangesetOnlyError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertBaseModelWritable(env = process.env) {
|
|
19
|
+
if (isChangesetOnly(env))
|
|
20
|
+
throw new ChangesetOnlyError("This session can only change the model through a changeset, and no changeset is active.", ErrorCategory.USER, [
|
|
21
|
+
'Run "dr changeset create <name>" (it activates the new changeset), or "dr changeset activate <name>"',
|
|
22
|
+
"Then re-run the command: it will be staged in the changeset for the user to review and commit"
|
|
23
|
+
], { operation: "save model", context: `${CHANGESET_ONLY_ENV}=${env[CHANGESET_ONLY_ENV]}` });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
CHANGESET_ONLY_ENV,
|
|
27
|
+
ChangesetOnlyError,
|
|
28
|
+
assertBaseModelWritable,
|
|
29
|
+
isChangesetOnly,
|
|
30
|
+
resetChangesetOnlyWarnings
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=changeset-only-guard.js.map
|