@synap-core/workspace-templates 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +113 -1
- package/dist/index.js +136 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/composition-graph.test.ts +135 -0
- package/src/composition-graph.ts +298 -0
- package/src/index.ts +13 -0
package/dist/index.d.ts
CHANGED
|
@@ -751,6 +751,118 @@ declare function isHashVersion(version: string | undefined): boolean;
|
|
|
751
751
|
*/
|
|
752
752
|
declare function computeTemplateUpdates(installed: InstalledTemplate[], catalog: CatalogEntry[], bundleVersion?: string): TemplateUpdateCheck[];
|
|
753
753
|
|
|
754
|
+
/**
|
|
755
|
+
* layerTemplateGraph — the KEYSTONE composition-graph engine.
|
|
756
|
+
* ============================================================
|
|
757
|
+
*
|
|
758
|
+
* Today the CLI (`synap-cli/src/commands/market.ts` `buildCompositionTrees`)
|
|
759
|
+
* renders template composition as a nested TREE: `buildDependencyNode`
|
|
760
|
+
* rebuilds a fresh `TreeNode` for every OCCURRENCE of a slug on every path, so
|
|
761
|
+
* a widely-required base (e.g. `foundation`) prints once per branch that
|
|
762
|
+
* reaches it (6× under a suite like `enterprise-os`). Its "shared" tag is only
|
|
763
|
+
* top-level-package granularity — there is no true fan-in count.
|
|
764
|
+
*
|
|
765
|
+
* This module replaces that with a LAYERED DAG: every node appears EXACTLY
|
|
766
|
+
* ONCE, positioned at its longest-path depth, with edges converging on it —
|
|
767
|
+
* so `foundation` sinks to the bedrock row with N inbound edges instead of
|
|
768
|
+
* being redrawn N times. It is the single source of truth for template
|
|
769
|
+
* composition; the CLI, browser dialogs, backend boot-ordering, and (later)
|
|
770
|
+
* the Atlas all consume it.
|
|
771
|
+
*
|
|
772
|
+
* PURE + TRANSPORT-AGNOSTIC (same discipline as `catalog.ts`): this is the
|
|
773
|
+
* ONLY module `synap-cli`, `browser`, `synap-control-plane-api`, and
|
|
774
|
+
* `synap-backend` can all resolve. No React, no dagre/d3, no fetch, no
|
|
775
|
+
* Node-only APIs. Pixel layout lives elsewhere (browser-only) — this module
|
|
776
|
+
* only computes topology (layers + edges + memoized nodes), never coordinates.
|
|
777
|
+
*
|
|
778
|
+
* CONSUMES drift, never recomputes it: `installed` / `updates` are read
|
|
779
|
+
* straight off `catalog.ts`'s `InstalledTemplate`-shaped rows and
|
|
780
|
+
* `computeTemplateUpdates()` output — this module does no semver comparison
|
|
781
|
+
* of its own.
|
|
782
|
+
*/
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* An installed template row, keyed by slug — the shape this engine needs to
|
|
786
|
+
* mark a node `installed` and populate `workspaceIds`.
|
|
787
|
+
*
|
|
788
|
+
* NOTE ON NAMING: `catalog.ts`'s `InstalledTemplate` is `{ slug, version }`
|
|
789
|
+
* only — it was built for `computeTemplateUpdates`'s narrower need (a version
|
|
790
|
+
* to diff) and carries no `workspaceId`. `CompositionNode.workspaceIds` is
|
|
791
|
+
* part of the ratified API and cannot be populated from that shape, so this
|
|
792
|
+
* is a REAL conflict, not a preference: this module declares its own,
|
|
793
|
+
* slightly richer row instead of importing `catalog.ts`'s `InstalledTemplate`
|
|
794
|
+
* under that name. Every field of `catalog.ts`'s `InstalledTemplate` is a
|
|
795
|
+
* subset of this one, so a caller building `InstalledTemplate[]` for
|
|
796
|
+
* `computeTemplateUpdates` can build ONE array of `CompositionInstalledEntry`
|
|
797
|
+
* and pass it to both doors.
|
|
798
|
+
*/
|
|
799
|
+
interface CompositionInstalledEntry {
|
|
800
|
+
slug: string;
|
|
801
|
+
workspaceId: string;
|
|
802
|
+
workspaceName?: string;
|
|
803
|
+
/** `settings.packageVersion`, when the pod stamped one. Omit when unknown. */
|
|
804
|
+
version?: string;
|
|
805
|
+
}
|
|
806
|
+
/** One compose-relation dependency, attached to the BASE node it overlays onto. */
|
|
807
|
+
interface CompositionOverlay {
|
|
808
|
+
slug: string;
|
|
809
|
+
name: string;
|
|
810
|
+
installed: boolean;
|
|
811
|
+
}
|
|
812
|
+
/** One edge in the raw dependency graph, exactly as declared (kept even when broken by a cycle). */
|
|
813
|
+
interface CompositionEdge {
|
|
814
|
+
from: string;
|
|
815
|
+
to: string;
|
|
816
|
+
relation: "compose" | "require";
|
|
817
|
+
depKind?: string;
|
|
818
|
+
}
|
|
819
|
+
interface CompositionNode {
|
|
820
|
+
slug: string;
|
|
821
|
+
name: string;
|
|
822
|
+
/** Longest-path depth: `1 + max(layer of every node that depends on this one)`. Roots = 0, bedrock = max. */
|
|
823
|
+
layer: number;
|
|
824
|
+
/** TRUE fan-in — every slug (node or compose-only overlay source) that declares a dependency on this one. */
|
|
825
|
+
dependents: string[];
|
|
826
|
+
/** Which of the input `roots` transitively reach this node (self-inclusive for a root). */
|
|
827
|
+
roots: string[];
|
|
828
|
+
installed: boolean;
|
|
829
|
+
workspaceIds: string[];
|
|
830
|
+
installedVersion?: string;
|
|
831
|
+
latestVersion?: string | null;
|
|
832
|
+
updateAvailable: boolean;
|
|
833
|
+
noVersionInfo: boolean;
|
|
834
|
+
/** `compose`-relation deps that overlay onto THIS node (see rule 2 — the overlay source itself is never a layer node). */
|
|
835
|
+
overlays: CompositionOverlay[];
|
|
836
|
+
}
|
|
837
|
+
interface CompositionGraph {
|
|
838
|
+
nodes: Map<string, CompositionNode>;
|
|
839
|
+
edges: CompositionEdge[];
|
|
840
|
+
/** `layers[0]` = roots … `layers[n]` = bedrock. Compose-overlay SOURCES never appear here (rule 2). */
|
|
841
|
+
layers: CompositionNode[][];
|
|
842
|
+
/** Broken back-edges `[from, to]` — surfaced, never silently swallowed (rule 4). */
|
|
843
|
+
cycles: string[][];
|
|
844
|
+
}
|
|
845
|
+
interface LayerTemplateGraphInput {
|
|
846
|
+
/** Top-level slugs to walk from. Always land at `layer: 0`. */
|
|
847
|
+
roots: string[];
|
|
848
|
+
/** INJECTED edge source (rule 5) — never hardcoded to the bundled map. See `bundledEdgesOf`. */
|
|
849
|
+
edgesOf: (slug: string) => readonly TemplateDependency[];
|
|
850
|
+
nameOf?: (slug: string) => string | undefined;
|
|
851
|
+
installed?: CompositionInstalledEntry[];
|
|
852
|
+
updates?: TemplateUpdateCheck[];
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Default `edgesOf` — reads `dependencies[]` off the BUNDLED template map
|
|
856
|
+
* only. This is what the current CLI walker is hardcoded to, which is
|
|
857
|
+
* precisely the live bug rule 5 fixes: a remote/private CP-only package's
|
|
858
|
+
* `definition.dependencies` are invisible to it, so its subtree silently
|
|
859
|
+
* renders empty. Callers with a remote catalog entry available should pass
|
|
860
|
+
* their OWN `edgesOf` that falls back to `entry.remote.definition.dependencies`
|
|
861
|
+
* (or similar) for slugs this default can't see.
|
|
862
|
+
*/
|
|
863
|
+
declare function bundledEdgesOf(slug: string): readonly TemplateDependency[];
|
|
864
|
+
declare function layerTemplateGraph(input: LayerTemplateGraphInput): CompositionGraph;
|
|
865
|
+
|
|
754
866
|
/**
|
|
755
867
|
* @synap-core/workspace-templates — Definition adapter
|
|
756
868
|
*
|
|
@@ -955,4 +1067,4 @@ interface PackageDefinitionOutput {
|
|
|
955
1067
|
*/
|
|
956
1068
|
declare function toPackageDefinition(slugOrYaml: string | WorkspaceYaml): PackageDefinitionOutput;
|
|
957
1069
|
|
|
958
|
-
export { type AssembleCatalogInput, type BentoViewBlockWire, type BentoWidgetBlockWire, type BundledCatalogEntry, type BundledEntry, type CatalogEntry, type CpPackageRow, type InstalledTemplate, type MergeCatalogInput, type MergeCatalogResult, PACKAGE_DEPENDENCY_KINDS, type PackageDefinitionOutput, type PackageDependencyKind, type RemoteCatalogEntry, type RemoteEntry, type RemoteSourceStatus, type SplitBentoBlocks, type TemplateActionPlacement, type TemplateAppKind, type TemplateAppRef, type TemplateAutomation, type TemplateBentoBlock, type TemplateDefaultSource, type TemplateDependency, type TemplateDisplayTemplate, type TemplateEntityLink, type TemplateExtendsRef, type TemplateIntegration, type TemplateLayoutConfig, type TemplateMeta, type TemplateOnboarding, type TemplateOnboardingCollectTarget, type TemplatePlaybook, type TemplatePlaybookParam, type TemplateProfile, type TemplateProfileEntityBentoTemplates, type TemplateProperty, type TemplateSeedEntity, type TemplateSidebarItem, type TemplateSidebarSurface, type TemplateSourceRole, type TemplateSuggestedRelation, type TemplateUpdateCheck, type TemplateView, type ToDefinitionResult, WORKSPACE_TEMPLATES, type WorkspaceDefinitionInput, type WorkspaceTemplateSlug, type WorkspaceYaml, assembleCatalog, computeTemplateUpdates, getWorkspaceTemplate, isHashVersion, listPublicTemplates, listWorkspaceTemplates, mergeCatalog, rowToRemoteEntry, splitBentoBlocks, toPackageDefinition, toWorkspaceDefinition };
|
|
1070
|
+
export { type AssembleCatalogInput, type BentoViewBlockWire, type BentoWidgetBlockWire, type BundledCatalogEntry, type BundledEntry, type CatalogEntry, type CompositionEdge, type CompositionGraph, type CompositionInstalledEntry, type CompositionNode, type CompositionOverlay, type CpPackageRow, type InstalledTemplate, type LayerTemplateGraphInput, type MergeCatalogInput, type MergeCatalogResult, PACKAGE_DEPENDENCY_KINDS, type PackageDefinitionOutput, type PackageDependencyKind, type RemoteCatalogEntry, type RemoteEntry, type RemoteSourceStatus, type SplitBentoBlocks, type TemplateActionPlacement, type TemplateAppKind, type TemplateAppRef, type TemplateAutomation, type TemplateBentoBlock, type TemplateDefaultSource, type TemplateDependency, type TemplateDisplayTemplate, type TemplateEntityLink, type TemplateExtendsRef, type TemplateIntegration, type TemplateLayoutConfig, type TemplateMeta, type TemplateOnboarding, type TemplateOnboardingCollectTarget, type TemplatePlaybook, type TemplatePlaybookParam, type TemplateProfile, type TemplateProfileEntityBentoTemplates, type TemplateProperty, type TemplateSeedEntity, type TemplateSidebarItem, type TemplateSidebarSurface, type TemplateSourceRole, type TemplateSuggestedRelation, type TemplateUpdateCheck, type TemplateView, type ToDefinitionResult, WORKSPACE_TEMPLATES, type WorkspaceDefinitionInput, type WorkspaceTemplateSlug, type WorkspaceYaml, assembleCatalog, bundledEdgesOf, computeTemplateUpdates, getWorkspaceTemplate, isHashVersion, layerTemplateGraph, listPublicTemplates, listWorkspaceTemplates, mergeCatalog, rowToRemoteEntry, splitBentoBlocks, toPackageDefinition, toWorkspaceDefinition };
|
package/dist/index.js
CHANGED
|
@@ -26876,6 +26876,140 @@ function computeTemplateUpdates(installed, catalog, bundleVersion) {
|
|
|
26876
26876
|
});
|
|
26877
26877
|
}
|
|
26878
26878
|
|
|
26879
|
+
// src/composition-graph.ts
|
|
26880
|
+
function bundledEdgesOf(slug) {
|
|
26881
|
+
return getWorkspaceTemplate(slug)?.dependencies ?? [];
|
|
26882
|
+
}
|
|
26883
|
+
function layerTemplateGraph(input) {
|
|
26884
|
+
const { roots, edgesOf } = input;
|
|
26885
|
+
const nameOf = input.nameOf ?? (() => void 0);
|
|
26886
|
+
const rootSet = new Set(roots);
|
|
26887
|
+
const installedBySlug = /* @__PURE__ */ new Map();
|
|
26888
|
+
for (const entry of input.installed ?? []) {
|
|
26889
|
+
const list = installedBySlug.get(entry.slug);
|
|
26890
|
+
if (list) list.push(entry);
|
|
26891
|
+
else installedBySlug.set(entry.slug, [entry]);
|
|
26892
|
+
}
|
|
26893
|
+
const updatesBySlug = new Map((input.updates ?? []).map((u) => [u.slug, u]));
|
|
26894
|
+
const edges = [];
|
|
26895
|
+
const cycles = [];
|
|
26896
|
+
const touched = new Set(roots);
|
|
26897
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
26898
|
+
const dependentsMap = /* @__PURE__ */ new Map();
|
|
26899
|
+
const composeEdges = [];
|
|
26900
|
+
const discovered = /* @__PURE__ */ new Set();
|
|
26901
|
+
function addAdjacency(from, to) {
|
|
26902
|
+
const set = adjacency.get(from);
|
|
26903
|
+
if (set) set.add(to);
|
|
26904
|
+
else adjacency.set(from, /* @__PURE__ */ new Set([to]));
|
|
26905
|
+
}
|
|
26906
|
+
function addDependent(dependency, dependent) {
|
|
26907
|
+
const set = dependentsMap.get(dependency);
|
|
26908
|
+
if (set) set.add(dependent);
|
|
26909
|
+
else dependentsMap.set(dependency, /* @__PURE__ */ new Set([dependent]));
|
|
26910
|
+
}
|
|
26911
|
+
function walk(slug, ancestors) {
|
|
26912
|
+
const deps = edgesOf(slug) ?? [];
|
|
26913
|
+
for (const dep of deps) {
|
|
26914
|
+
const relation = dep.relation ?? "require";
|
|
26915
|
+
touched.add(dep.slug);
|
|
26916
|
+
if (ancestors.has(dep.slug)) {
|
|
26917
|
+
cycles.push([slug, dep.slug]);
|
|
26918
|
+
edges.push({ from: slug, to: dep.slug, relation, depKind: dep.kind });
|
|
26919
|
+
continue;
|
|
26920
|
+
}
|
|
26921
|
+
edges.push({ from: slug, to: dep.slug, relation, depKind: dep.kind });
|
|
26922
|
+
addAdjacency(slug, dep.slug);
|
|
26923
|
+
addDependent(dep.slug, slug);
|
|
26924
|
+
if (relation === "compose") {
|
|
26925
|
+
composeEdges.push({ from: slug, to: dep.slug, depKind: dep.kind });
|
|
26926
|
+
}
|
|
26927
|
+
if (!discovered.has(dep.slug)) {
|
|
26928
|
+
discovered.add(dep.slug);
|
|
26929
|
+
const nextAncestors = new Set(ancestors);
|
|
26930
|
+
nextAncestors.add(slug);
|
|
26931
|
+
walk(dep.slug, nextAncestors);
|
|
26932
|
+
}
|
|
26933
|
+
}
|
|
26934
|
+
}
|
|
26935
|
+
for (const root of roots) {
|
|
26936
|
+
if (discovered.has(root)) continue;
|
|
26937
|
+
discovered.add(root);
|
|
26938
|
+
walk(root, /* @__PURE__ */ new Set([root]));
|
|
26939
|
+
}
|
|
26940
|
+
const composeSources = new Set(composeEdges.map((e) => e.from));
|
|
26941
|
+
const overlaysByBase = /* @__PURE__ */ new Map();
|
|
26942
|
+
for (const e of composeEdges) {
|
|
26943
|
+
const isInstalled = installedBySlug.has(e.from);
|
|
26944
|
+
const overlay = { slug: e.from, name: nameOf(e.from) ?? e.from, installed: isInstalled };
|
|
26945
|
+
const list = overlaysByBase.get(e.to);
|
|
26946
|
+
if (list) list.push(overlay);
|
|
26947
|
+
else overlaysByBase.set(e.to, [overlay]);
|
|
26948
|
+
}
|
|
26949
|
+
const nodeSlugs = [...touched].filter((s) => !composeSources.has(s)).sort();
|
|
26950
|
+
const rootsReaching = /* @__PURE__ */ new Map();
|
|
26951
|
+
for (const root of roots) {
|
|
26952
|
+
const visited = /* @__PURE__ */ new Set([root]);
|
|
26953
|
+
const queue = [root];
|
|
26954
|
+
const addReach = (s) => {
|
|
26955
|
+
const set = rootsReaching.get(s);
|
|
26956
|
+
if (set) set.add(root);
|
|
26957
|
+
else rootsReaching.set(s, /* @__PURE__ */ new Set([root]));
|
|
26958
|
+
};
|
|
26959
|
+
addReach(root);
|
|
26960
|
+
while (queue.length) {
|
|
26961
|
+
const cur = queue.shift();
|
|
26962
|
+
for (const next of adjacency.get(cur) ?? []) {
|
|
26963
|
+
if (visited.has(next)) continue;
|
|
26964
|
+
visited.add(next);
|
|
26965
|
+
addReach(next);
|
|
26966
|
+
queue.push(next);
|
|
26967
|
+
}
|
|
26968
|
+
}
|
|
26969
|
+
}
|
|
26970
|
+
const layerMemo = /* @__PURE__ */ new Map();
|
|
26971
|
+
function layerOf(slug, stack) {
|
|
26972
|
+
if (rootSet.has(slug)) return 0;
|
|
26973
|
+
const memoized = layerMemo.get(slug);
|
|
26974
|
+
if (memoized !== void 0) return memoized;
|
|
26975
|
+
if (stack.has(slug)) return 0;
|
|
26976
|
+
stack.add(slug);
|
|
26977
|
+
const dependents = dependentsMap.get(slug);
|
|
26978
|
+
const layer = !dependents || dependents.size === 0 ? 0 : 1 + Math.max(...[...dependents].map((d) => layerOf(d, stack)));
|
|
26979
|
+
stack.delete(slug);
|
|
26980
|
+
layerMemo.set(slug, layer);
|
|
26981
|
+
return layer;
|
|
26982
|
+
}
|
|
26983
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
26984
|
+
for (const slug of nodeSlugs) {
|
|
26985
|
+
const installedEntries = installedBySlug.get(slug) ?? [];
|
|
26986
|
+
const workspaceIds = installedEntries.map((e) => e.workspaceId).filter(Boolean);
|
|
26987
|
+
const installed = workspaceIds.length > 0;
|
|
26988
|
+
const update = updatesBySlug.get(slug);
|
|
26989
|
+
const installedVersion = update?.installedVersion || installedEntries[0]?.version;
|
|
26990
|
+
const noVersionInfo = installed && (update === void 0 || update.latestVersion === void 0);
|
|
26991
|
+
nodes.set(slug, {
|
|
26992
|
+
slug,
|
|
26993
|
+
name: nameOf(slug) ?? slug,
|
|
26994
|
+
layer: layerOf(slug, /* @__PURE__ */ new Set()),
|
|
26995
|
+
dependents: [...dependentsMap.get(slug) ?? []].sort(),
|
|
26996
|
+
roots: [...rootsReaching.get(slug) ?? []].sort(),
|
|
26997
|
+
installed,
|
|
26998
|
+
workspaceIds,
|
|
26999
|
+
installedVersion,
|
|
27000
|
+
latestVersion: update?.latestVersion ?? null,
|
|
27001
|
+
updateAvailable: update?.updateAvailable ?? false,
|
|
27002
|
+
noVersionInfo,
|
|
27003
|
+
overlays: overlaysByBase.get(slug) ?? []
|
|
27004
|
+
});
|
|
27005
|
+
}
|
|
27006
|
+
const maxLayer = Math.max(0, ...[...nodes.values()].map((n) => n.layer));
|
|
27007
|
+
const layers = Array.from({ length: maxLayer + 1 }, () => []);
|
|
27008
|
+
for (const node of nodes.values()) layers[node.layer].push(node);
|
|
27009
|
+
for (const bucket of layers) bucket.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
27010
|
+
return { nodes, edges, layers, cycles };
|
|
27011
|
+
}
|
|
27012
|
+
|
|
26879
27013
|
// src/define.ts
|
|
26880
27014
|
function toAutomationWire(automations) {
|
|
26881
27015
|
return automations?.map((a) => ({
|
|
@@ -27034,9 +27168,11 @@ export {
|
|
|
27034
27168
|
PACKAGE_DEPENDENCY_KINDS,
|
|
27035
27169
|
WORKSPACE_TEMPLATES,
|
|
27036
27170
|
assembleCatalog,
|
|
27171
|
+
bundledEdgesOf,
|
|
27037
27172
|
computeTemplateUpdates,
|
|
27038
27173
|
getWorkspaceTemplate,
|
|
27039
27174
|
isHashVersion,
|
|
27175
|
+
layerTemplateGraph,
|
|
27040
27176
|
listPublicTemplates,
|
|
27041
27177
|
listWorkspaceTemplates,
|
|
27042
27178
|
mergeCatalog,
|