@ankhorage/contracts 26.5.0 → 26.7.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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/flow.d.ts +239 -0
- package/dist/flow.d.ts.map +1 -0
- package/dist/flow.js +2 -0
- package/dist/flow.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/structure/generated.d.ts +2 -2
- package/dist/structure/generated.js +2 -2
- package/dist/structure/generated.js.map +1 -1
- package/dist/website.d.ts +260 -0
- package/dist/website.d.ts.map +1 -0
- package/dist/website.js +2 -0
- package/dist/website.js.map +1 -0
- package/package.json +10 -2
- package/src/flow.test.ts +218 -0
- package/src/flow.ts +254 -0
- package/src/flowPackedConsumer.test.ts +98 -0
- package/src/index.ts +2 -0
- package/src/structure/generated.ts +2 -2
- package/src/website.test.ts +39 -0
- package/src/website.ts +287 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { MediaAssetReference } from './media.js';
|
|
2
|
+
import type { SerializableValue } from './serializable.js';
|
|
3
|
+
import type { UiNode } from './types.js';
|
|
4
|
+
/*** A versioned, portable capture of the observable state of one website. */
|
|
5
|
+
export interface WebsiteObservationDocument {
|
|
6
|
+
readonly schemaVersion: 1;
|
|
7
|
+
readonly documentId: string;
|
|
8
|
+
readonly collectedAt: string;
|
|
9
|
+
readonly source: WebsiteSource;
|
|
10
|
+
readonly site: WebsiteSite;
|
|
11
|
+
readonly pages: readonly WebsitePageObservation[];
|
|
12
|
+
}
|
|
13
|
+
/*** The scraper identity and immutable crawl revision that produced a website document. */
|
|
14
|
+
export interface WebsiteSource {
|
|
15
|
+
readonly crawlerId: string;
|
|
16
|
+
readonly crawlerVersion: string;
|
|
17
|
+
readonly crawlId: string;
|
|
18
|
+
readonly revision: string | null;
|
|
19
|
+
}
|
|
20
|
+
/*** A site identity and its canonical entry URL. */
|
|
21
|
+
export interface WebsiteSite {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly url: string;
|
|
24
|
+
}
|
|
25
|
+
/*** One observed page, including its ordered navigation and captured render states. */
|
|
26
|
+
export interface WebsitePageObservation {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
readonly url: string;
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly navigation: readonly WebsiteNavigationItem[];
|
|
31
|
+
readonly viewports: readonly WebsiteViewport[];
|
|
32
|
+
readonly states: readonly WebsiteState[];
|
|
33
|
+
readonly nodes: readonly WebsiteObservedNode[];
|
|
34
|
+
readonly assets: readonly WebsiteAsset[];
|
|
35
|
+
}
|
|
36
|
+
/*** One ordered, source-linked navigation target observed on a page. */
|
|
37
|
+
export interface WebsiteNavigationItem {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly label: string;
|
|
40
|
+
readonly url: string;
|
|
41
|
+
readonly sourceRef: string;
|
|
42
|
+
}
|
|
43
|
+
/*** A rendered viewport used while observing responsive website state. */
|
|
44
|
+
export interface WebsiteViewport {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly width: number;
|
|
47
|
+
readonly height: number;
|
|
48
|
+
readonly devicePixelRatio: number;
|
|
49
|
+
}
|
|
50
|
+
/*** A named rendered state, such as default, hover, or expanded. */
|
|
51
|
+
export interface WebsiteState {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly name: string;
|
|
54
|
+
}
|
|
55
|
+
/*** A stable DOM node and every source-linked observation collected for it. */
|
|
56
|
+
export interface WebsiteObservedNode {
|
|
57
|
+
readonly sourceRef: string;
|
|
58
|
+
readonly parentSourceRef: string | null;
|
|
59
|
+
readonly ancestorSourceRefs: readonly string[];
|
|
60
|
+
readonly dom: WebsiteDomNode;
|
|
61
|
+
readonly textRuns: readonly WebsiteTextRun[];
|
|
62
|
+
readonly links: readonly WebsiteLink[];
|
|
63
|
+
readonly media: readonly WebsiteMediaReference[];
|
|
64
|
+
readonly observations: readonly WebsiteNodeObservation[];
|
|
65
|
+
}
|
|
66
|
+
/*** A portable DOM identity with safe semantic attributes only. */
|
|
67
|
+
export interface WebsiteDomNode {
|
|
68
|
+
readonly tagName: string;
|
|
69
|
+
readonly childIndex: number;
|
|
70
|
+
readonly attributes: Readonly<Record<string, string>>;
|
|
71
|
+
}
|
|
72
|
+
/*** A text run retained separately from its DOM node so inline styled spans are not lost. */
|
|
73
|
+
export interface WebsiteTextRun {
|
|
74
|
+
readonly sourceRef: string;
|
|
75
|
+
readonly text: string;
|
|
76
|
+
readonly start: number;
|
|
77
|
+
readonly end: number;
|
|
78
|
+
}
|
|
79
|
+
/*** A source-linked hyperlink observed on a DOM node. */
|
|
80
|
+
export interface WebsiteLink {
|
|
81
|
+
readonly sourceRef: string;
|
|
82
|
+
readonly url: string;
|
|
83
|
+
readonly label: string | null;
|
|
84
|
+
}
|
|
85
|
+
/*** An image, video, audio, or embedded media reference observed on a DOM node. */
|
|
86
|
+
export interface WebsiteMediaReference {
|
|
87
|
+
readonly sourceRef: string;
|
|
88
|
+
readonly assetId: string | null;
|
|
89
|
+
readonly url: string;
|
|
90
|
+
readonly kind: 'audio' | 'document' | 'embed' | 'image' | 'video';
|
|
91
|
+
readonly alt: string | null;
|
|
92
|
+
}
|
|
93
|
+
/*** One viewport/state-specific visual observation of a DOM node. */
|
|
94
|
+
export interface WebsiteNodeObservation {
|
|
95
|
+
readonly viewportId: string;
|
|
96
|
+
readonly stateId: string;
|
|
97
|
+
readonly availability: WebsiteAvailability;
|
|
98
|
+
readonly rect: WebsiteBoundingRect | null;
|
|
99
|
+
readonly css: WebsiteComputedCss | null;
|
|
100
|
+
readonly provenance: WebsiteObservationProvenance;
|
|
101
|
+
}
|
|
102
|
+
/*** Whether a source value was available without fabricating a replacement. */
|
|
103
|
+
export interface WebsiteAvailability {
|
|
104
|
+
readonly status: 'available' | 'missing';
|
|
105
|
+
readonly missing: WebsiteMissingValue | null;
|
|
106
|
+
}
|
|
107
|
+
/*** The explicit reason a website source value could not be observed. */
|
|
108
|
+
export interface WebsiteMissingValue {
|
|
109
|
+
readonly code: string;
|
|
110
|
+
readonly message: string;
|
|
111
|
+
}
|
|
112
|
+
/*** A bounding rectangle measured in CSS pixels. */
|
|
113
|
+
export interface WebsiteBoundingRect {
|
|
114
|
+
readonly x: number;
|
|
115
|
+
readonly y: number;
|
|
116
|
+
readonly width: number;
|
|
117
|
+
readonly height: number;
|
|
118
|
+
}
|
|
119
|
+
/*** Computed visual CSS values needed to describe, not assess, a rendered website. */
|
|
120
|
+
export interface WebsiteComputedCss {
|
|
121
|
+
readonly display: string;
|
|
122
|
+
readonly position: string;
|
|
123
|
+
readonly visibility: string;
|
|
124
|
+
readonly color: string;
|
|
125
|
+
readonly backgroundColor: string;
|
|
126
|
+
readonly borderColor: string;
|
|
127
|
+
readonly borderRadius: string;
|
|
128
|
+
readonly borderWidth: string;
|
|
129
|
+
readonly boxShadow: string;
|
|
130
|
+
readonly fontFamily: string;
|
|
131
|
+
readonly fontSize: string;
|
|
132
|
+
readonly fontStyle: string;
|
|
133
|
+
readonly fontWeight: string;
|
|
134
|
+
readonly letterSpacing: string;
|
|
135
|
+
readonly lineHeight: string;
|
|
136
|
+
readonly textAlign: string;
|
|
137
|
+
readonly textDecoration: string;
|
|
138
|
+
readonly opacity: string;
|
|
139
|
+
readonly overflow: string;
|
|
140
|
+
readonly overflowX: string;
|
|
141
|
+
readonly overflowY: string;
|
|
142
|
+
readonly backgroundImage: string;
|
|
143
|
+
readonly backgroundPosition: string;
|
|
144
|
+
readonly backgroundRepeat: string;
|
|
145
|
+
readonly backgroundSize: string;
|
|
146
|
+
readonly margin: WebsiteBoxEdges;
|
|
147
|
+
readonly padding: WebsiteBoxEdges;
|
|
148
|
+
readonly alignContent: string;
|
|
149
|
+
readonly alignItems: string;
|
|
150
|
+
readonly alignSelf: string;
|
|
151
|
+
readonly gap: string;
|
|
152
|
+
readonly flexDirection: string;
|
|
153
|
+
readonly flexBasis: string;
|
|
154
|
+
readonly flexGrow: string;
|
|
155
|
+
readonly flexShrink: string;
|
|
156
|
+
readonly flexWrap: string;
|
|
157
|
+
readonly justifyContent: string;
|
|
158
|
+
readonly justifyItems: string;
|
|
159
|
+
readonly justifySelf: string;
|
|
160
|
+
readonly order: string;
|
|
161
|
+
readonly placeContent: string;
|
|
162
|
+
readonly placeItems: string;
|
|
163
|
+
readonly placeSelf: string;
|
|
164
|
+
readonly gridAutoColumns: string;
|
|
165
|
+
readonly gridAutoFlow: string;
|
|
166
|
+
readonly gridAutoRows: string;
|
|
167
|
+
readonly gridColumn: string;
|
|
168
|
+
readonly gridColumnEnd: string;
|
|
169
|
+
readonly gridColumnStart: string;
|
|
170
|
+
readonly gridRow: string;
|
|
171
|
+
readonly gridRowEnd: string;
|
|
172
|
+
readonly gridRowStart: string;
|
|
173
|
+
readonly gridTemplateColumns: string;
|
|
174
|
+
readonly gridTemplateRows: string;
|
|
175
|
+
}
|
|
176
|
+
/*** The four computed edges of a CSS box value. */
|
|
177
|
+
export interface WebsiteBoxEdges {
|
|
178
|
+
readonly top: string;
|
|
179
|
+
readonly right: string;
|
|
180
|
+
readonly bottom: string;
|
|
181
|
+
readonly left: string;
|
|
182
|
+
}
|
|
183
|
+
/*** How an observation was obtained from the source page. */
|
|
184
|
+
export interface WebsiteObservationProvenance {
|
|
185
|
+
readonly sourceUrl: string;
|
|
186
|
+
readonly capturedAt: string;
|
|
187
|
+
readonly method: 'browser' | 'html' | 'network';
|
|
188
|
+
}
|
|
189
|
+
/*** A reusable media asset discovered during the crawl. */
|
|
190
|
+
export interface WebsiteAsset {
|
|
191
|
+
readonly id: string;
|
|
192
|
+
readonly url: string;
|
|
193
|
+
readonly kind: WebsiteMediaReference['kind'];
|
|
194
|
+
readonly contentType: string | null;
|
|
195
|
+
readonly sourceRefs: readonly string[];
|
|
196
|
+
}
|
|
197
|
+
/*** A versioned canonical component recognition derived from website observations. */
|
|
198
|
+
export interface WebsiteRecognitionDocument {
|
|
199
|
+
readonly schemaVersion: 1;
|
|
200
|
+
readonly documentId: string;
|
|
201
|
+
readonly observedDocumentId: string;
|
|
202
|
+
readonly recognizedAt: string;
|
|
203
|
+
readonly source: WebsiteSource;
|
|
204
|
+
readonly site: WebsiteSite;
|
|
205
|
+
readonly navigation: readonly WebsiteNavigationItem[];
|
|
206
|
+
readonly pages: readonly WebsitePageRecognition[];
|
|
207
|
+
readonly designTokens: readonly WebsiteDesignToken[];
|
|
208
|
+
readonly media: readonly WebsiteRecognizedMedia[];
|
|
209
|
+
}
|
|
210
|
+
/*** A recognized page with its canonical UiNode tree and retained source mapping. */
|
|
211
|
+
export interface WebsitePageRecognition {
|
|
212
|
+
readonly id: string;
|
|
213
|
+
readonly url: string;
|
|
214
|
+
readonly path: string;
|
|
215
|
+
readonly root: UiNode;
|
|
216
|
+
readonly nodes: readonly WebsiteRecognizedNode[];
|
|
217
|
+
}
|
|
218
|
+
/*** A source-linked canonical UiNode mapping; component shape remains owned by UiNode. */
|
|
219
|
+
export interface WebsiteRecognizedNode {
|
|
220
|
+
readonly id: UiNode['id'];
|
|
221
|
+
readonly componentType: UiNode['type'];
|
|
222
|
+
readonly sourceRefs: readonly string[];
|
|
223
|
+
readonly confidence: number;
|
|
224
|
+
readonly props: readonly WebsiteRecognizedProp[];
|
|
225
|
+
readonly responsive: readonly WebsiteResponsiveVariant[];
|
|
226
|
+
readonly status: 'recognized' | 'unresolved';
|
|
227
|
+
readonly unresolved: WebsiteUnresolvedNode | null;
|
|
228
|
+
}
|
|
229
|
+
/*** A normalized UiNode property and the exact serializable values observed before normalization. */
|
|
230
|
+
export interface WebsiteRecognizedProp {
|
|
231
|
+
readonly name: string;
|
|
232
|
+
readonly value: SerializableValue;
|
|
233
|
+
readonly originalValues: readonly SerializableValue[];
|
|
234
|
+
}
|
|
235
|
+
/*** A viewport-specific UiNode property override retained by the recognition. */
|
|
236
|
+
export interface WebsiteResponsiveVariant {
|
|
237
|
+
readonly viewportId: string;
|
|
238
|
+
readonly props: readonly WebsiteRecognizedProp[];
|
|
239
|
+
}
|
|
240
|
+
/*** A visible source node that could not be mapped without silently discarding it. */
|
|
241
|
+
export interface WebsiteUnresolvedNode {
|
|
242
|
+
readonly reason: string;
|
|
243
|
+
readonly sourceRefs: readonly string[];
|
|
244
|
+
}
|
|
245
|
+
/*** A normalized design token with its exact source values retained. */
|
|
246
|
+
export interface WebsiteDesignToken {
|
|
247
|
+
readonly id: string;
|
|
248
|
+
readonly kind: 'color' | 'radius' | 'shadow' | 'spacing' | 'typography';
|
|
249
|
+
readonly value: SerializableValue;
|
|
250
|
+
readonly originalValues: readonly SerializableValue[];
|
|
251
|
+
readonly sourceRefs: readonly string[];
|
|
252
|
+
}
|
|
253
|
+
/*** A recognized portable media reference, optionally compatible with an app media asset. */
|
|
254
|
+
export interface WebsiteRecognizedMedia {
|
|
255
|
+
readonly id: string;
|
|
256
|
+
readonly sourceRefs: readonly string[];
|
|
257
|
+
readonly originalUrl: string;
|
|
258
|
+
readonly asset: MediaAssetReference | null;
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=website.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"website.d.ts","sourceRoot":"","sources":["../src/website.ts"],"names":[],"mappings":"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"}
|
package/dist/website.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"website.js","sourceRoot":"","sources":["../src/website.ts"],"names":[],"mappings":"","sourcesContent":["import type { MediaAssetReference } from './media.js';\nimport type { SerializableValue } from './serializable.js';\nimport type { UiNode } from './types.js';\n\n/*** A versioned, portable capture of the observable state of one website. */\nexport interface WebsiteObservationDocument {\n readonly schemaVersion: 1;\n readonly documentId: string;\n readonly collectedAt: string;\n readonly source: WebsiteSource;\n readonly site: WebsiteSite;\n readonly pages: readonly WebsitePageObservation[];\n}\n\n/*** The scraper identity and immutable crawl revision that produced a website document. */\nexport interface WebsiteSource {\n readonly crawlerId: string;\n readonly crawlerVersion: string;\n readonly crawlId: string;\n readonly revision: string | null;\n}\n\n/*** A site identity and its canonical entry URL. */\nexport interface WebsiteSite {\n readonly id: string;\n readonly url: string;\n}\n\n/*** One observed page, including its ordered navigation and captured render states. */\nexport interface WebsitePageObservation {\n readonly id: string;\n readonly url: string;\n readonly path: string;\n readonly navigation: readonly WebsiteNavigationItem[];\n readonly viewports: readonly WebsiteViewport[];\n readonly states: readonly WebsiteState[];\n readonly nodes: readonly WebsiteObservedNode[];\n readonly assets: readonly WebsiteAsset[];\n}\n\n/*** One ordered, source-linked navigation target observed on a page. */\nexport interface WebsiteNavigationItem {\n readonly id: string;\n readonly label: string;\n readonly url: string;\n readonly sourceRef: string;\n}\n\n/*** A rendered viewport used while observing responsive website state. */\nexport interface WebsiteViewport {\n readonly id: string;\n readonly width: number;\n readonly height: number;\n readonly devicePixelRatio: number;\n}\n\n/*** A named rendered state, such as default, hover, or expanded. */\nexport interface WebsiteState {\n readonly id: string;\n readonly name: string;\n}\n\n/*** A stable DOM node and every source-linked observation collected for it. */\nexport interface WebsiteObservedNode {\n readonly sourceRef: string;\n readonly parentSourceRef: string | null;\n readonly ancestorSourceRefs: readonly string[];\n readonly dom: WebsiteDomNode;\n readonly textRuns: readonly WebsiteTextRun[];\n readonly links: readonly WebsiteLink[];\n readonly media: readonly WebsiteMediaReference[];\n readonly observations: readonly WebsiteNodeObservation[];\n}\n\n/*** A portable DOM identity with safe semantic attributes only. */\nexport interface WebsiteDomNode {\n readonly tagName: string;\n readonly childIndex: number;\n readonly attributes: Readonly<Record<string, string>>;\n}\n\n/*** A text run retained separately from its DOM node so inline styled spans are not lost. */\nexport interface WebsiteTextRun {\n readonly sourceRef: string;\n readonly text: string;\n readonly start: number;\n readonly end: number;\n}\n\n/*** A source-linked hyperlink observed on a DOM node. */\nexport interface WebsiteLink {\n readonly sourceRef: string;\n readonly url: string;\n readonly label: string | null;\n}\n\n/*** An image, video, audio, or embedded media reference observed on a DOM node. */\nexport interface WebsiteMediaReference {\n readonly sourceRef: string;\n readonly assetId: string | null;\n readonly url: string;\n readonly kind: 'audio' | 'document' | 'embed' | 'image' | 'video';\n readonly alt: string | null;\n}\n\n/*** One viewport/state-specific visual observation of a DOM node. */\nexport interface WebsiteNodeObservation {\n readonly viewportId: string;\n readonly stateId: string;\n readonly availability: WebsiteAvailability;\n readonly rect: WebsiteBoundingRect | null;\n readonly css: WebsiteComputedCss | null;\n readonly provenance: WebsiteObservationProvenance;\n}\n\n/*** Whether a source value was available without fabricating a replacement. */\nexport interface WebsiteAvailability {\n readonly status: 'available' | 'missing';\n readonly missing: WebsiteMissingValue | null;\n}\n\n/*** The explicit reason a website source value could not be observed. */\nexport interface WebsiteMissingValue {\n readonly code: string;\n readonly message: string;\n}\n\n/*** A bounding rectangle measured in CSS pixels. */\nexport interface WebsiteBoundingRect {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n}\n\n/*** Computed visual CSS values needed to describe, not assess, a rendered website. */\nexport interface WebsiteComputedCss {\n readonly display: string;\n readonly position: string;\n readonly visibility: string;\n readonly color: string;\n readonly backgroundColor: string;\n readonly borderColor: string;\n readonly borderRadius: string;\n readonly borderWidth: string;\n readonly boxShadow: string;\n readonly fontFamily: string;\n readonly fontSize: string;\n readonly fontStyle: string;\n readonly fontWeight: string;\n readonly letterSpacing: string;\n readonly lineHeight: string;\n readonly textAlign: string;\n readonly textDecoration: string;\n readonly opacity: string;\n readonly overflow: string;\n readonly overflowX: string;\n readonly overflowY: string;\n readonly backgroundImage: string;\n readonly backgroundPosition: string;\n readonly backgroundRepeat: string;\n readonly backgroundSize: string;\n readonly margin: WebsiteBoxEdges;\n readonly padding: WebsiteBoxEdges;\n readonly alignContent: string;\n readonly alignItems: string;\n readonly alignSelf: string;\n readonly gap: string;\n readonly flexDirection: string;\n readonly flexBasis: string;\n readonly flexGrow: string;\n readonly flexShrink: string;\n readonly flexWrap: string;\n readonly justifyContent: string;\n readonly justifyItems: string;\n readonly justifySelf: string;\n readonly order: string;\n readonly placeContent: string;\n readonly placeItems: string;\n readonly placeSelf: string;\n readonly gridAutoColumns: string;\n readonly gridAutoFlow: string;\n readonly gridAutoRows: string;\n readonly gridColumn: string;\n readonly gridColumnEnd: string;\n readonly gridColumnStart: string;\n readonly gridRow: string;\n readonly gridRowEnd: string;\n readonly gridRowStart: string;\n readonly gridTemplateColumns: string;\n readonly gridTemplateRows: string;\n}\n\n/*** The four computed edges of a CSS box value. */\nexport interface WebsiteBoxEdges {\n readonly top: string;\n readonly right: string;\n readonly bottom: string;\n readonly left: string;\n}\n\n/*** How an observation was obtained from the source page. */\nexport interface WebsiteObservationProvenance {\n readonly sourceUrl: string;\n readonly capturedAt: string;\n readonly method: 'browser' | 'html' | 'network';\n}\n\n/*** A reusable media asset discovered during the crawl. */\nexport interface WebsiteAsset {\n readonly id: string;\n readonly url: string;\n readonly kind: WebsiteMediaReference['kind'];\n readonly contentType: string | null;\n readonly sourceRefs: readonly string[];\n}\n\n/*** A versioned canonical component recognition derived from website observations. */\nexport interface WebsiteRecognitionDocument {\n readonly schemaVersion: 1;\n readonly documentId: string;\n readonly observedDocumentId: string;\n readonly recognizedAt: string;\n readonly source: WebsiteSource;\n readonly site: WebsiteSite;\n readonly navigation: readonly WebsiteNavigationItem[];\n readonly pages: readonly WebsitePageRecognition[];\n readonly designTokens: readonly WebsiteDesignToken[];\n readonly media: readonly WebsiteRecognizedMedia[];\n}\n\n/*** A recognized page with its canonical UiNode tree and retained source mapping. */\nexport interface WebsitePageRecognition {\n readonly id: string;\n readonly url: string;\n readonly path: string;\n readonly root: UiNode;\n readonly nodes: readonly WebsiteRecognizedNode[];\n}\n\n/*** A source-linked canonical UiNode mapping; component shape remains owned by UiNode. */\nexport interface WebsiteRecognizedNode {\n readonly id: UiNode['id'];\n readonly componentType: UiNode['type'];\n readonly sourceRefs: readonly string[];\n readonly confidence: number;\n readonly props: readonly WebsiteRecognizedProp[];\n readonly responsive: readonly WebsiteResponsiveVariant[];\n readonly status: 'recognized' | 'unresolved';\n readonly unresolved: WebsiteUnresolvedNode | null;\n}\n\n/*** A normalized UiNode property and the exact serializable values observed before normalization. */\nexport interface WebsiteRecognizedProp {\n readonly name: string;\n readonly value: SerializableValue;\n readonly originalValues: readonly SerializableValue[];\n}\n\n/*** A viewport-specific UiNode property override retained by the recognition. */\nexport interface WebsiteResponsiveVariant {\n readonly viewportId: string;\n readonly props: readonly WebsiteRecognizedProp[];\n}\n\n/*** A visible source node that could not be mapped without silently discarding it. */\nexport interface WebsiteUnresolvedNode {\n readonly reason: string;\n readonly sourceRefs: readonly string[];\n}\n\n/*** A normalized design token with its exact source values retained. */\nexport interface WebsiteDesignToken {\n readonly id: string;\n readonly kind: 'color' | 'radius' | 'shadow' | 'spacing' | 'typography';\n readonly value: SerializableValue;\n readonly originalValues: readonly SerializableValue[];\n readonly sourceRefs: readonly string[];\n}\n\n/*** A recognized portable media reference, optionally compatible with an app media asset. */\nexport interface WebsiteRecognizedMedia {\n readonly id: string;\n readonly sourceRefs: readonly string[];\n readonly originalUrl: string;\n readonly asset: MediaAssetReference | null;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/contracts",
|
|
3
|
-
"version": "26.
|
|
3
|
+
"version": "26.7.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"ankh": {
|
|
6
6
|
"category": "contracts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@ankhorage/utility": "^1.10.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@ankhorage/devtools": "^2.4.
|
|
47
|
+
"@ankhorage/devtools": "^2.4.3",
|
|
48
48
|
"@ankhorage/paradox": "^0.3.6",
|
|
49
49
|
"@types/bun": "^1.4.2",
|
|
50
50
|
"@types/node": "^26.6.6",
|
|
@@ -115,6 +115,10 @@
|
|
|
115
115
|
"types": "./dist/ui.d.ts",
|
|
116
116
|
"default": "./dist/ui.js"
|
|
117
117
|
},
|
|
118
|
+
"./website": {
|
|
119
|
+
"types": "./dist/website.d.ts",
|
|
120
|
+
"default": "./dist/website.js"
|
|
121
|
+
},
|
|
118
122
|
"./media": {
|
|
119
123
|
"types": "./dist/media.d.ts",
|
|
120
124
|
"default": "./dist/media.js"
|
|
@@ -138,6 +142,10 @@
|
|
|
138
142
|
"./capability": {
|
|
139
143
|
"types": "./dist/capability.d.ts",
|
|
140
144
|
"default": "./dist/capability.js"
|
|
145
|
+
},
|
|
146
|
+
"./flow": {
|
|
147
|
+
"types": "./dist/flow.d.ts",
|
|
148
|
+
"default": "./dist/flow.js"
|
|
141
149
|
}
|
|
142
150
|
},
|
|
143
151
|
"description": "Serializable app, capability, binding, theme, auth, and secret-store contracts for Ankhorage.",
|
package/src/flow.test.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from 'bun:test';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
FlowApprovalRequest,
|
|
8
|
+
FlowDefinition,
|
|
9
|
+
FlowNodeConfig,
|
|
10
|
+
FlowRunSnapshot,
|
|
11
|
+
FlowSecretRef,
|
|
12
|
+
} from './flow.js';
|
|
13
|
+
|
|
14
|
+
type JsonRecord = Readonly<Record<string, unknown>>;
|
|
15
|
+
|
|
16
|
+
const isRecord = (value: unknown): value is JsonRecord =>
|
|
17
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
+
|
|
19
|
+
const isString = (value: unknown): value is string => typeof value === 'string';
|
|
20
|
+
|
|
21
|
+
const isFlowFixture = (value: unknown): value is FlowDefinition => {
|
|
22
|
+
if (
|
|
23
|
+
!isRecord(value) ||
|
|
24
|
+
!isString(value.id) ||
|
|
25
|
+
!Number.isInteger(value.version) ||
|
|
26
|
+
!isString(value.title)
|
|
27
|
+
) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
Array.isArray(value.nodes) &&
|
|
33
|
+
value.nodes.every(
|
|
34
|
+
(node) =>
|
|
35
|
+
isRecord(node) &&
|
|
36
|
+
isString(node.id) &&
|
|
37
|
+
isRecord(node.capability) &&
|
|
38
|
+
isString(node.capability.providerId) &&
|
|
39
|
+
isString(node.capability.capabilityId) &&
|
|
40
|
+
Array.isArray(node.ports) &&
|
|
41
|
+
node.ports.every(
|
|
42
|
+
(port) =>
|
|
43
|
+
isRecord(port) &&
|
|
44
|
+
isString(port.id) &&
|
|
45
|
+
(port.direction === 'input' || port.direction === 'output'),
|
|
46
|
+
),
|
|
47
|
+
) &&
|
|
48
|
+
Array.isArray(value.edges) &&
|
|
49
|
+
value.edges.every(
|
|
50
|
+
(edge) =>
|
|
51
|
+
isRecord(edge) &&
|
|
52
|
+
isString(edge.id) &&
|
|
53
|
+
isRecord(edge.source) &&
|
|
54
|
+
isString(edge.source.nodeId) &&
|
|
55
|
+
isString(edge.source.portId) &&
|
|
56
|
+
isRecord(edge.target) &&
|
|
57
|
+
isString(edge.target.nodeId) &&
|
|
58
|
+
isString(edge.target.portId),
|
|
59
|
+
) &&
|
|
60
|
+
Array.isArray(value.triggers) &&
|
|
61
|
+
value.triggers.every(
|
|
62
|
+
(trigger) => isRecord(trigger) && isString(trigger.id) && isString(trigger.kind),
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const definition = {
|
|
68
|
+
id: 'social-publish',
|
|
69
|
+
version: 3,
|
|
70
|
+
title: 'Publish approved social media',
|
|
71
|
+
nodes: [
|
|
72
|
+
{
|
|
73
|
+
id: 'compose',
|
|
74
|
+
capability: { providerId: '@ankhorage/flow/social', capabilityId: 'post.compose' },
|
|
75
|
+
config: {
|
|
76
|
+
audience: { kind: 'inline', value: { segment: 'subscribers' } },
|
|
77
|
+
credential: { kind: 'secret', ref: 'flow/social/publisher' },
|
|
78
|
+
},
|
|
79
|
+
ports: [
|
|
80
|
+
{ id: 'draft', direction: 'input', required: true, data: { schema: { type: 'object' } } },
|
|
81
|
+
{ id: 'post', direction: 'output', data: { schema: { type: 'object' } } },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'notify',
|
|
86
|
+
capability: { providerId: '@ankhorage/flow/social', capabilityId: 'notification.send' },
|
|
87
|
+
ports: [{ id: 'post', direction: 'input', required: true }],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
edges: [
|
|
91
|
+
{
|
|
92
|
+
id: 'notify-success',
|
|
93
|
+
source: { nodeId: 'compose', portId: 'post' },
|
|
94
|
+
target: { nodeId: 'notify', portId: 'post' },
|
|
95
|
+
condition: { kind: 'outcome', outcome: 'success' },
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
triggers: [{ id: 'manual', kind: 'manual' }],
|
|
99
|
+
} as const satisfies FlowDefinition;
|
|
100
|
+
|
|
101
|
+
const run = {
|
|
102
|
+
id: 'run-42',
|
|
103
|
+
flow: { id: definition.id, version: definition.version },
|
|
104
|
+
status: 'waiting_for_approval',
|
|
105
|
+
trigger: { triggerId: 'manual', kind: 'manual' },
|
|
106
|
+
createdAt: '2026-11-12T09:00:00.000Z',
|
|
107
|
+
steps: [
|
|
108
|
+
{
|
|
109
|
+
id: 'step-compose',
|
|
110
|
+
nodeId: 'compose',
|
|
111
|
+
status: 'succeeded',
|
|
112
|
+
attempt: 1,
|
|
113
|
+
output: {
|
|
114
|
+
post: { kind: 'artifact', artifactId: 'artifact-post', mediaType: 'application/json' },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
} as const satisfies FlowRunSnapshot;
|
|
119
|
+
|
|
120
|
+
const approval = {
|
|
121
|
+
id: 'approval-7',
|
|
122
|
+
runId: run.id,
|
|
123
|
+
step: { id: 'step-compose', nodeId: 'compose', attempt: 1, runId: run.id },
|
|
124
|
+
status: 'pending',
|
|
125
|
+
requestedAt: '2026-11-12T09:01:00.000Z',
|
|
126
|
+
context: { kind: 'artifact', artifactId: 'artifact-post' },
|
|
127
|
+
} as const satisfies FlowApprovalRequest;
|
|
128
|
+
|
|
129
|
+
describe('Flow contract serialization', () => {
|
|
130
|
+
it('round-trips a standalone versioned workflow fixture', () => {
|
|
131
|
+
const fixturePath = join(process.cwd(), 'test/flow/fixtures/versioned-workflow.json');
|
|
132
|
+
const fixture = JSON.parse(readFileSync(fixturePath, 'utf8')) as unknown;
|
|
133
|
+
|
|
134
|
+
expect(isFlowFixture(fixture)).toBe(true);
|
|
135
|
+
expect(JSON.parse(JSON.stringify(fixture))).toEqual(fixture);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('rejects malformed fixture structure independently of JSON serialization', () => {
|
|
139
|
+
const malformedFixture = {
|
|
140
|
+
id: 'missing-port-direction',
|
|
141
|
+
version: 1,
|
|
142
|
+
title: 'Malformed fixture',
|
|
143
|
+
nodes: [
|
|
144
|
+
{
|
|
145
|
+
id: 'compose',
|
|
146
|
+
capability: { providerId: '@ankhorage/flow/social', capabilityId: 'post.compose' },
|
|
147
|
+
ports: [{ id: 'post', direction: 'sideways' }],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
edges: [],
|
|
151
|
+
triggers: [],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
expect(isFlowFixture(malformedFixture)).toBe(false);
|
|
155
|
+
expect(JSON.parse(JSON.stringify(malformedFixture))).toEqual(malformedFixture);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('round-trips a versioned graph, run snapshot, and approval without runtime values', () => {
|
|
159
|
+
expect(JSON.parse(JSON.stringify({ definition, run, approval }))).toEqual({
|
|
160
|
+
definition,
|
|
161
|
+
run,
|
|
162
|
+
approval,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Flow contract boundaries', () => {
|
|
168
|
+
it('accepts secret references but excludes inline secret material from their contract', () => {
|
|
169
|
+
const secret = {
|
|
170
|
+
kind: 'secret',
|
|
171
|
+
ref: 'flow/social/publisher',
|
|
172
|
+
} as const satisfies FlowSecretRef;
|
|
173
|
+
|
|
174
|
+
expect(JSON.parse(JSON.stringify(secret))).toEqual(secret);
|
|
175
|
+
// @ts-expect-error Flow secret references must not carry secret material.
|
|
176
|
+
const inlineSecret: FlowSecretRef = {
|
|
177
|
+
kind: 'secret',
|
|
178
|
+
ref: 'flow/social/publisher',
|
|
179
|
+
value: 'secret',
|
|
180
|
+
};
|
|
181
|
+
expect(inlineSecret).toBeDefined();
|
|
182
|
+
|
|
183
|
+
// @ts-expect-error Node configuration requires an explicit data or secret reference.
|
|
184
|
+
const plaintextCredential: FlowNodeConfig = { credential: 'api-key' };
|
|
185
|
+
expect(plaintextCredential).toBeDefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('requires a JSON Pointer whenever a condition compares a port value', () => {
|
|
189
|
+
// @ts-expect-error Value conditions must name an RFC 6901 JSON Pointer, including '' for root.
|
|
190
|
+
const implicitRootComparison: FlowDefinition = {
|
|
191
|
+
...definition,
|
|
192
|
+
edges: [
|
|
193
|
+
{
|
|
194
|
+
id: 'implicit-root',
|
|
195
|
+
source: { nodeId: 'compose', portId: 'post' },
|
|
196
|
+
target: { nodeId: 'notify', portId: 'post' },
|
|
197
|
+
condition: { kind: 'value', equals: 'published' },
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
expect(implicitRootComparison).toBeDefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('keeps node ports directional and edge endpoints precise', () => {
|
|
205
|
+
// @ts-expect-error Edges may only originate at a named port.
|
|
206
|
+
const incompleteEdge: FlowDefinition = {
|
|
207
|
+
...definition,
|
|
208
|
+
edges: [
|
|
209
|
+
{
|
|
210
|
+
id: 'missing-port',
|
|
211
|
+
source: { nodeId: 'compose' },
|
|
212
|
+
target: { nodeId: 'notify', portId: 'post' },
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
expect(incompleteEdge).toBeDefined();
|
|
217
|
+
});
|
|
218
|
+
});
|