@arcote.tech/arc-process 0.7.28

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.
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Profil elementów Arka dla fragmentu process.
3
+ *
4
+ * Definiuje rodzaje elementów (aggregate/command/event/…) z paletą
5
+ * Event Storming, relacje domenowe z semantyką przepływu, grupy doczepień
6
+ * (dawne REL_SIDE/INPUT_KINDS jako DANE profilu) oraz rozwiązywanie
7
+ * referencji `<Arc>` (przeniesione z arc-map/src/refs.ts).
8
+ *
9
+ * `ArcGraphInput` to strukturalny podzbiór MapJson z arc-map — arc-map
10
+ * podaje swój obiekt bez konwersji.
11
+ */
12
+
13
+ import { defineProfile } from "../model/profile";
14
+ import type {
15
+ ContextInfo,
16
+ MethodRef,
17
+ ProcessGraph,
18
+ ProcessNode,
19
+ ResolvedRef,
20
+ } from "../model/types";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Paleta (Event Storming / Event Modeling)
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export const ARC_ELEMENT_COLOR: Record<string, string> = {
27
+ command: "#1c7ed6", // blue — command
28
+ event: "#f76707", // orange — domain event
29
+ aggregate: "#f59f00", // gold/amber — aggregate
30
+ view: "#2f9e44", // green — read model / view
31
+ "static-view": "#0ca678", // teal — static read model
32
+ listener: "#7048e8", // grape/lilac — policy / reaction
33
+ route: "#e64980", // pink — external boundary
34
+ page: "#4263eb", // indigo — UI / page
35
+ context: "#7c3aed", // violet — zwinięty kontekst
36
+ note: "#eab308", // yellow — notatka
37
+ unknown: "#868e96", // slate
38
+ };
39
+
40
+ export const ARC_EDGE_COLOR: Record<string, string> = {
41
+ emits: "#ea580c",
42
+ handles: "#2563eb",
43
+ listens: "#c026d3",
44
+ queries: "#16a34a",
45
+ mutates: "#e11d48",
46
+ uiQuery: "#4263eb",
47
+ uiMutation: "#4263eb",
48
+ };
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Referencje `<Arc>` (port z arc-map/src/refs.ts)
52
+ // ---------------------------------------------------------------------------
53
+
54
+ /** Propsy referencyjne `<Arc>` (jeden rodzaj na wystąpienie). */
55
+ export interface ArcRefProps {
56
+ command?: string;
57
+ event?: string;
58
+ view?: string;
59
+ staticView?: string;
60
+ listener?: string;
61
+ route?: string;
62
+ aggregate?: string;
63
+ /** Z `aggregate`: metoda mutująca. */
64
+ mutationMethod?: string;
65
+ /** Z `aggregate`: metoda odczytu. */
66
+ queryMethod?: string;
67
+ /** Zarejestrowana strona (arc `page()`), po ścieżce routingu. */
68
+ page?: string;
69
+ /** Komponent React / plik UI (dopasowanie do skanu ts-morph). */
70
+ component?: string;
71
+ }
72
+
73
+ /**
74
+ * Kanoniczna tożsamość strony = jej ŚCIEŻKA routingu, bez query/hash i
75
+ * końcowego slasha (root "/" zachowany). Normalizacja tu ORAZ przy budowie
76
+ * nodów stron gwarantuje, że `<Arc page="/checkout">` i nod `/checkout`
77
+ * zlewają się niezależnie od przypadkowego `?query`.
78
+ */
79
+ export function normalizePagePath(path: string): string {
80
+ let p = path.split("?")[0].split("#")[0].trim();
81
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
82
+ return p;
83
+ }
84
+
85
+ /**
86
+ * Mapuje propsy `<Arc>` na rozwiązaną referencję lub null, gdy brak
87
+ * rozpoznawanego rodzaju. Czysto strukturalne — istnienie noda waliduje
88
+ * model przeciw grafowi (czerwony chip po stronie front).
89
+ */
90
+ export function resolveArcRef(props: ArcRefProps): ResolvedRef | null {
91
+ if (props.command) {
92
+ return { nodeId: props.command, kind: "command", key: `command:${props.command}` };
93
+ }
94
+ if (props.view) {
95
+ return { nodeId: props.view, kind: "view", key: `view:${props.view}` };
96
+ }
97
+ if (props.staticView) {
98
+ return {
99
+ nodeId: props.staticView,
100
+ kind: "static-view",
101
+ key: `static-view:${props.staticView}`,
102
+ };
103
+ }
104
+ if (props.listener) {
105
+ return { nodeId: props.listener, kind: "listener", key: `listener:${props.listener}` };
106
+ }
107
+ if (props.route) {
108
+ return { nodeId: props.route, kind: "route", key: `route:${props.route}` };
109
+ }
110
+ if (props.page) {
111
+ const path = normalizePagePath(props.page);
112
+ return { nodeId: path, kind: "page", key: `page:${path}` };
113
+ }
114
+ if (props.component) {
115
+ return { nodeId: props.component, kind: "page", key: `component:${props.component}` };
116
+ }
117
+ if (props.aggregate) {
118
+ if (props.mutationMethod) {
119
+ return {
120
+ nodeId: props.aggregate,
121
+ kind: "aggregate",
122
+ method: { name: props.mutationMethod, kind: "mutation" },
123
+ key: `aggregate:${props.aggregate}#mutate:${props.mutationMethod}`,
124
+ };
125
+ }
126
+ if (props.queryMethod) {
127
+ return {
128
+ nodeId: props.aggregate,
129
+ kind: "aggregate",
130
+ method: { name: props.queryMethod, kind: "query" },
131
+ key: `aggregate:${props.aggregate}#query:${props.queryMethod}`,
132
+ };
133
+ }
134
+ // `<Arc aggregate="x" event="e">` — inline event jest osobnym nodem.
135
+ if (props.event) {
136
+ return { nodeId: props.event, kind: "event", key: `event:${props.event}` };
137
+ }
138
+ return { nodeId: props.aggregate, kind: "aggregate", key: `aggregate:${props.aggregate}` };
139
+ }
140
+ // Samodzielny event (bez agregatu).
141
+ if (props.event) {
142
+ return { nodeId: props.event, kind: "event", key: `event:${props.event}` };
143
+ }
144
+ return null;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Wejście grafu (strukturalny podzbiór MapJson)
149
+ // ---------------------------------------------------------------------------
150
+
151
+ /** Metoda elementu (podzbiór MapElementMethod z arc-map). */
152
+ export interface ArcGraphMethod {
153
+ name: string;
154
+ kind: string;
155
+ queryDeps?: string[];
156
+ mutationDeps?: string[];
157
+ }
158
+
159
+ /** Element (podzbiór MapElement z arc-map — MapJson jest zgodny strukturalnie). */
160
+ export interface ArcGraphElement {
161
+ id: string;
162
+ name: string;
163
+ kind: string;
164
+ contextPath?: string[];
165
+ methods?: ArcGraphMethod[];
166
+ queryDeps?: string[];
167
+ mutationDeps?: string[];
168
+ }
169
+
170
+ export interface ArcGraphEdge {
171
+ source: string;
172
+ target: string;
173
+ kind: string;
174
+ }
175
+
176
+ export interface ArcGraphComponent {
177
+ id: string;
178
+ file: string;
179
+ name?: string;
180
+ }
181
+
182
+ export interface ArcGraphComponentEdge {
183
+ source: string;
184
+ target: string;
185
+ usage: "query" | "mutation";
186
+ }
187
+
188
+ /** Strukturalny podzbiór MapJson przyjmowany przez {@link toProcessGraph}. */
189
+ export interface ArcGraphInput {
190
+ elements: ArcGraphElement[];
191
+ edges: ArcGraphEdge[];
192
+ components?: ArcGraphComponent[];
193
+ componentEdges?: ArcGraphComponentEdge[];
194
+ contexts?: ContextInfo[];
195
+ }
196
+
197
+ /** Przekształca dane mapy Arka na generyczny graf procesu. */
198
+ export function toProcessGraph(input: ArcGraphInput): ProcessGraph {
199
+ const nodes: ProcessNode[] = input.elements.map((el) => ({
200
+ id: el.id,
201
+ kind: el.kind,
202
+ label: el.name,
203
+ contextPath: el.contextPath,
204
+ // Cały element wędruje do meta — profil (forwardDeps) i UI czytają
205
+ // z niego metody/deps/opisy bez zwężania na poziomie rdzenia.
206
+ meta: el as unknown as Record<string, unknown>,
207
+ }));
208
+ // Komponenty React (warstwa UI z ts-morph) — pseudo-nody rodzaju "page".
209
+ for (const c of input.components ?? []) {
210
+ nodes.push({
211
+ id: c.id,
212
+ kind: "page",
213
+ label: c.name ?? c.file,
214
+ meta: { ui: true, file: c.file },
215
+ });
216
+ }
217
+ const relations = [
218
+ ...input.edges.map((e) => ({ from: e.source, to: e.target, kind: e.kind })),
219
+ ...(input.componentEdges ?? []).map((e) => ({
220
+ from: e.source,
221
+ to: e.target,
222
+ kind: e.usage === "query" ? "uiQuery" : "uiMutation",
223
+ })),
224
+ ];
225
+ return { nodes, relations, contexts: input.contexts ?? [] };
226
+ }
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // Profil
230
+ // ---------------------------------------------------------------------------
231
+
232
+ /** Per-metodowe forward-zależności agregatów Arka. */
233
+ function arcForwardDeps(
234
+ node: ProcessNode,
235
+ method?: MethodRef,
236
+ ): Partial<Record<string, string[]>> | null {
237
+ const meta = node.meta as ArcGraphElement | undefined;
238
+ if (!meta) return null;
239
+ if (method) {
240
+ const m = meta.methods?.find((x) => x.name === method.name);
241
+ return { queries: m?.queryDeps ?? [], mutates: m?.mutationDeps ?? [] };
242
+ }
243
+ return { queries: meta.queryDeps ?? [], mutates: meta.mutationDeps ?? [] };
244
+ }
245
+
246
+ /**
247
+ * Profil Arka. Dawne REL_SIDE staje się `defaultSide` grup doczepień:
248
+ * wejścia (queries/queriedBy/mutatedBy/handledBy/usedByUI) domyślnie na W,
249
+ * wyjścia (mutates) na E — a MDX może nadpisać per wystąpienie w 8 kierunkach.
250
+ */
251
+ export const arcProfile = defineProfile({
252
+ id: "arc",
253
+ kinds: {
254
+ command: { color: ARC_ELEMENT_COLOR.command, label: "command" },
255
+ event: { color: ARC_ELEMENT_COLOR.event, label: "event" },
256
+ aggregate: { color: ARC_ELEMENT_COLOR.aggregate, label: "aggregate" },
257
+ view: { color: ARC_ELEMENT_COLOR.view, label: "view" },
258
+ "static-view": { color: ARC_ELEMENT_COLOR["static-view"], label: "static view" },
259
+ listener: { color: ARC_ELEMENT_COLOR.listener, label: "listener" },
260
+ route: { color: ARC_ELEMENT_COLOR.route, label: "route" },
261
+ page: { color: ARC_ELEMENT_COLOR.page, label: "page" },
262
+ context: { color: ARC_ELEMENT_COLOR.context, label: "context" },
263
+ note: { color: ARC_ELEMENT_COLOR.note, label: "note" },
264
+ unknown: { color: ARC_ELEMENT_COLOR.unknown },
265
+ },
266
+ relations: {
267
+ emits: { color: ARC_EDGE_COLOR.emits, semantics: "out" },
268
+ mutates: { color: ARC_EDGE_COLOR.mutates, semantics: "out" },
269
+ handles: { color: ARC_EDGE_COLOR.handles, semantics: "in" },
270
+ listens: { color: ARC_EDGE_COLOR.listens, semantics: "in" },
271
+ queries: { color: ARC_EDGE_COLOR.queries, semantics: "in" },
272
+ uiQuery: { color: ARC_EDGE_COLOR.uiQuery, semantics: "in", label: "czyta", dashed: true },
273
+ uiMutation: {
274
+ color: ARC_EDGE_COLOR.uiMutation,
275
+ semantics: "out",
276
+ label: "wywołuje",
277
+ dashed: true,
278
+ },
279
+ },
280
+ attachmentGroups: {
281
+ queries: {
282
+ relations: ["queries"],
283
+ direction: "out",
284
+ defaultSide: "W",
285
+ color: ARC_EDGE_COLOR.queries,
286
+ intoNode: true,
287
+ label: "czyta",
288
+ },
289
+ mutates: {
290
+ relations: ["mutates"],
291
+ direction: "out",
292
+ defaultSide: "E",
293
+ color: ARC_EDGE_COLOR.mutates,
294
+ intoNode: false,
295
+ label: "zapisuje",
296
+ },
297
+ mutatedBy: {
298
+ relations: ["mutates", "emits"],
299
+ direction: "in",
300
+ defaultSide: "W",
301
+ color: ARC_EDGE_COLOR.mutates,
302
+ intoNode: true,
303
+ label: "zapisywany przez",
304
+ },
305
+ queriedBy: {
306
+ relations: ["queries"],
307
+ direction: "in",
308
+ defaultSide: "W",
309
+ color: ARC_EDGE_COLOR.queries,
310
+ intoNode: true,
311
+ label: "czytany przez",
312
+ },
313
+ handledBy: {
314
+ relations: ["handles", "listens"],
315
+ direction: "in",
316
+ defaultSide: "W",
317
+ color: ARC_EDGE_COLOR.handles,
318
+ intoNode: true,
319
+ label: "obsługiwany przez",
320
+ },
321
+ usedByUI: {
322
+ relations: ["uiQuery", "uiMutation"],
323
+ direction: "in",
324
+ defaultSide: "W",
325
+ color: ARC_ELEMENT_COLOR.page,
326
+ intoNode: true,
327
+ label: "używany w UI",
328
+ },
329
+ },
330
+ resolveRef: (props) => resolveArcRef(props as ArcRefProps),
331
+ forwardDeps: arcForwardDeps,
332
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Wizytówki kontekstów — custom renderery boxa kontekstu na diagramie.
3
+ *
4
+ * Kontekst deklaruje komponent w core przez opaque slot
5
+ * `context([...], { card: contextCard(MojaKarta) })`; ponieważ JSON grafu
6
+ * nie przenosi komponentów, po stronie klienta wizytówki rejestruje się
7
+ * w rejestrze: `registerContextCard("chat", ChatCard)` (klucz: id LUB nazwa
8
+ * kontekstu). Brak wpisu = {@link DefaultContextCard}.
9
+ */
10
+
11
+ import type { ComponentType, ReactNode } from "react";
12
+ import type { ContextInfo } from "../model/types";
13
+ import { GLASS, tint } from "./theme";
14
+
15
+ export interface ContextCardProps {
16
+ context: ContextInfo;
17
+ /** Czy box jest zwinięty (true) czy otwarty (obrys wokół wnętrza). */
18
+ collapsed: boolean;
19
+ /** Liczba kroków/elementów ukrytych w boxie (gdy znana). */
20
+ memberCount?: number;
21
+ /** Przełącza zwinięcie (klik w box = wejście poziom niżej). */
22
+ onToggle: () => void;
23
+ /** Etykieta narracji z `<Arc context>` (gdy inna niż tytuł kontekstu). */
24
+ label?: string;
25
+ }
26
+
27
+ /** Typowany konstruktor slotu `card` dla core `ContextOptions` (React-free tam). */
28
+ export function contextCard(component: ComponentType<ContextCardProps>): unknown {
29
+ return component;
30
+ }
31
+
32
+ const registry = new Map<string, ComponentType<ContextCardProps>>();
33
+
34
+ /** Zarejestruj wizytówkę pod id lub nazwą kontekstu (np. "chat", "ctx_chat"). */
35
+ export function registerContextCard(
36
+ key: string,
37
+ component: ComponentType<ContextCardProps>,
38
+ ): void {
39
+ registry.set(key, component);
40
+ }
41
+
42
+ /** Znajdź wizytówkę dla kontekstu: po id, potem po nazwie. */
43
+ export function cardFor(
44
+ ctx: ContextInfo,
45
+ ): ComponentType<ContextCardProps> | undefined {
46
+ return registry.get(ctx.id) ?? (ctx.name ? registry.get(ctx.name) : undefined);
47
+ }
48
+
49
+ const CONTEXT_COLOR = "#7c3aed";
50
+
51
+ export function DefaultContextCard({
52
+ context,
53
+ collapsed,
54
+ memberCount,
55
+ onToggle,
56
+ label,
57
+ }: ContextCardProps): ReactNode {
58
+ const title = context.title ?? context.name ?? context.id;
59
+ return (
60
+ <div
61
+ onClick={onToggle}
62
+ title={context.description ?? title}
63
+ style={{
64
+ width: "100%",
65
+ height: "100%",
66
+ boxSizing: "border-box",
67
+ borderRadius: 14,
68
+ border: `1.5px ${collapsed ? "solid" : "dashed"} ${CONTEXT_COLOR}`,
69
+ background: collapsed ? tint(CONTEXT_COLOR, 0.1) : "transparent",
70
+ backdropFilter: collapsed ? GLASS.blurSoft : undefined,
71
+ boxShadow: collapsed ? GLASS.shadow : "none",
72
+ padding: "8px 12px",
73
+ display: "flex",
74
+ flexDirection: "column",
75
+ gap: 3,
76
+ cursor: "pointer",
77
+ }}
78
+ >
79
+ <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
80
+ <span
81
+ style={{
82
+ width: 8,
83
+ height: 8,
84
+ borderRadius: 99,
85
+ background: CONTEXT_COLOR,
86
+ boxShadow: `0 0 0 3px ${tint(CONTEXT_COLOR, 0.18)}`,
87
+ }}
88
+ />
89
+ <span style={{ fontWeight: 700, fontSize: 12.5, color: GLASS.text }}>{title}</span>
90
+ {memberCount ? (
91
+ <span
92
+ style={{
93
+ fontSize: 10,
94
+ color: GLASS.textMuted,
95
+ background: tint(CONTEXT_COLOR, 0.1),
96
+ borderRadius: 99,
97
+ padding: "0 7px",
98
+ }}
99
+ >
100
+ {memberCount}
101
+ </span>
102
+ ) : null}
103
+ <span style={{ marginLeft: "auto", fontSize: 10, color: GLASS.textFaint }}>
104
+ {collapsed ? "▸ otwórz" : "▾ zwiń"}
105
+ </span>
106
+ </div>
107
+ {collapsed && label && label !== title ? (
108
+ <div style={{ fontSize: 11.5, color: GLASS.textMuted }}>{label}</div>
109
+ ) : null}
110
+ </div>
111
+ );
112
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @arcote.tech/arc-process/react — komponenty React/MDX fragmentu process.
3
+ */
4
+
5
+ export { ProcessDiagram, type ProcessDiagramProps } from "./process-diagram";
6
+ export { makeNodeTypes } from "./nodes";
7
+ export {
8
+ cardFor,
9
+ contextCard,
10
+ DefaultContextCard,
11
+ registerContextCard,
12
+ type ContextCardProps,
13
+ } from "./context-card";
14
+ export { GLASS, BG_GRADIENT, MISSING_COLOR, tint } from "./theme";
15
+ export { Arc, type ArcProps } from "../mdx/arc";
16
+ export {
17
+ Branch,
18
+ Continue,
19
+ Entry,
20
+ Lane,
21
+ Note,
22
+ Parallel,
23
+ Step,
24
+ } from "../mdx/structure";
25
+ export { MdxView, parseProcessFrontmatter, type ProcessFrontmatter } from "../mdx/mdx-view";
26
+ export { ArcRefsProvider } from "../mdx/ref-context";
27
+ export {
28
+ FocusContext,
29
+ ProcessDataContext,
30
+ RegisterContext,
31
+ StructureContext,
32
+ ViewStateContext,
33
+ useFocus,
34
+ useProcessData,
35
+ useRegister,
36
+ useStructure,
37
+ useViewState,
38
+ type FocusState,
39
+ type ProcessData,
40
+ type RegEntry,
41
+ type ViewStateActions,
42
+ } from "../mdx/contexts";