@api-client/core 0.18.16 → 0.18.17

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.
@@ -1,6 +1,15 @@
1
1
  import type { JsonEdge, Node } from '@api-client/graph/graph/types.js'
2
2
  import { Graph } from '@api-client/graph/graph/Graph.js'
3
- import type { DataDomainGraph, DomainGraphEdge, DomainGraphNodeType, SerializedGraph } from './types.js'
3
+ import type {
4
+ DataDomainGraph,
5
+ DomainGraphEdge,
6
+ DomainGraphNodeType,
7
+ SerializedGraph,
8
+ DeserializationMode,
9
+ DeserializationResult,
10
+ DeserializationIssue,
11
+ DeserializeOptions,
12
+ } from './types.js'
4
13
  import {
5
14
  DomainAssociationKind,
6
15
  DomainEntityKind,
@@ -78,7 +87,154 @@ function writeEdges(g: DataDomainGraph, domainKey: string): JsonEdge<DomainGraph
78
87
  return result
79
88
  }
80
89
 
90
+ /**
91
+ * Validates the graph consistency before serialization.
92
+ * Ensures that all required edges exist for properties and associations.
93
+ * @param g The graph to validate
94
+ * @param domainKey The domain key to validate
95
+ * @throws Error if the graph is inconsistent
96
+ */
97
+ function validateGraphConsistency(g: DataDomainGraph, domainKey: string): void {
98
+ const validationErrors: string[] = []
99
+
100
+ // Get all nodes that belong to this domain
101
+ const domainNodes = new Set<string>()
102
+ for (const nodeId of g.nodes()) {
103
+ const node = g.node(nodeId)
104
+ if (node && node.domain.key === domainKey) {
105
+ domainNodes.add(nodeId)
106
+ }
107
+ }
108
+
109
+ // Validate that properties and associations have parent edges
110
+ for (const nodeId of domainNodes) {
111
+ const node = g.node(nodeId)
112
+ if (!node) continue
113
+
114
+ if (node.kind === DomainPropertyKind || node.kind === DomainAssociationKind) {
115
+ // These nodes must have exactly one incoming edge from their parent entity
116
+ const incomingEdges = [...g.inEdges(nodeId)]
117
+ const parentEdges = incomingEdges.filter((edge) => {
118
+ const sourceNode = g.node(edge.v)
119
+ return sourceNode && sourceNode.domain.key === domainKey
120
+ })
121
+
122
+ if (parentEdges.length === 0) {
123
+ const nodeType = node.kind === DomainPropertyKind ? 'Property' : 'Association'
124
+ validationErrors.push(
125
+ `${nodeType} "${node.info.name}" (${nodeId}) has no parent entity edge. ` +
126
+ `This will cause deserialization to fail.`
127
+ )
128
+ } else if (parentEdges.length > 1) {
129
+ const nodeType = node.kind === DomainPropertyKind ? 'Property' : 'Association'
130
+ const parentIds = parentEdges.map((e) => e.v).join(', ')
131
+ validationErrors.push(
132
+ `${nodeType} "${node.info.name}" (${nodeId}) has multiple parent edges: ${parentIds}. ` +
133
+ `This may cause deserialization issues.`
134
+ )
135
+ } else {
136
+ // Validate that the parent is actually an entity
137
+ const parentEdge = parentEdges[0]
138
+ const parentNode = g.node(parentEdge.v)
139
+ if (!parentNode || parentNode.kind !== DomainEntityKind) {
140
+ const nodeType = node.kind === DomainPropertyKind ? 'Property' : 'Association'
141
+ validationErrors.push(
142
+ `${nodeType} "${node.info.name}" (${nodeId}) has a parent that is not an entity. ` +
143
+ `Parent: ${parentNode?.kind || 'unknown'} (${parentEdge.v})`
144
+ )
145
+ }
146
+ }
147
+ }
148
+
149
+ // Validate that entities have model parents
150
+ if (node.kind === DomainEntityKind) {
151
+ const parents = [...g.parents(nodeId)]
152
+ const hasModelParent = parents.some((parentId) => {
153
+ const parent = g.node(parentId)
154
+ return parent && parent.kind === DomainModelKind
155
+ })
156
+
157
+ if (!hasModelParent) {
158
+ validationErrors.push(
159
+ `Entity "${node.info.name}" (${nodeId}) has no model parent. ` + `Entities must belong to a model.`
160
+ )
161
+ }
162
+ }
163
+
164
+ // Validate that models have either namespace parents or are at root level
165
+ if (node.kind === DomainModelKind) {
166
+ const parents = [...g.parents(nodeId)]
167
+ const hasNamespaceParent = parents.some((parentId) => {
168
+ const parent = g.node(parentId)
169
+ return parent && parent.kind === DomainNamespaceKind
170
+ })
171
+
172
+ // Models can either be at root level (no parents) or have a namespace parent
173
+ // They should not have other types of parents
174
+ if (parents.length > 0 && !hasNamespaceParent) {
175
+ const invalidParents = parents
176
+ .map((parentId) => {
177
+ const parent = g.node(parentId)
178
+ return `${parent?.kind || 'unknown'} (${parentId})`
179
+ })
180
+ .join(', ')
181
+ validationErrors.push(
182
+ `Model "${node.info.name}" (${nodeId}) has invalid parent types: ${invalidParents}. ` +
183
+ `Models can only belong to namespaces or be at root level.`
184
+ )
185
+ }
186
+ }
187
+ }
188
+
189
+ // Validate association targets exist
190
+ for (const nodeId of domainNodes) {
191
+ const node = g.node(nodeId)
192
+ if (node && node.kind === DomainAssociationKind) {
193
+ const association = node as DomainAssociation
194
+ const outgoingEdges = [...g.outEdges(nodeId)]
195
+
196
+ // Check that association has target edges
197
+ const targetEdges = outgoingEdges.filter((edge) => {
198
+ const edgeData = g.edge(edge)
199
+ return edgeData && edgeData.type === 'association'
200
+ })
201
+
202
+ if (targetEdges.length === 0) {
203
+ validationErrors.push(
204
+ `Association "${association.info.name}" (${nodeId}) has no target entities. ` +
205
+ `Associations must reference at least one target entity.`
206
+ )
207
+ }
208
+
209
+ // Validate that all target entities exist
210
+ for (const targetEdge of targetEdges) {
211
+ const targetNode = g.node(targetEdge.w)
212
+ if (!targetNode) {
213
+ validationErrors.push(
214
+ `Association "${association.info.name}" (${nodeId}) references non-existent target: ${targetEdge.w}`
215
+ )
216
+ } else if (targetNode.kind !== DomainEntityKind) {
217
+ validationErrors.push(
218
+ `Association "${association.info.name}" (${nodeId}) references non-entity target: ` +
219
+ `${targetNode.kind} (${targetEdge.w})`
220
+ )
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ if (validationErrors.length > 0) {
227
+ throw new Error(
228
+ `Graph consistency validation failed for domain "${domainKey}":\n` +
229
+ validationErrors.map((error) => ` - ${error}`).join('\n')
230
+ )
231
+ }
232
+ }
233
+
81
234
  export function serialize(g: DataDomainGraph, domainKey: string): SerializedGraph {
235
+ // Validate graph consistency before serialization
236
+ validateGraphConsistency(g, domainKey)
237
+
82
238
  const json: SerializedGraph = {
83
239
  options: {
84
240
  directed: g.isDirected(),
@@ -198,43 +354,124 @@ function findEdgeParent(child: string, edges: JsonEdge<DomainGraphEdge>[]): stri
198
354
  * @param root The DataDomain instance to use as the root for the graph
199
355
  * @param entry The node entry to restore
200
356
  * @param edges The list of serialized graph edges
201
- * @returns The restored node instance
202
- * @throws Error if the entry is malformed or the kind is unknown
357
+ * @param mode The deserialization mode
358
+ * @param issues Array to collect issues in lenient mode
359
+ * @returns The restored node instance or undefined if failed in lenient mode
360
+ * @throws Error if the entry is malformed or the kind is unknown in strict mode
203
361
  */
204
- function prepareNode(root: DataDomain, entry: unknown, edges: JsonEdge<DomainGraphEdge>[]): DomainGraphNodeType {
362
+ function prepareNode(
363
+ root: DataDomain,
364
+ entry: unknown,
365
+ edges: JsonEdge<DomainGraphEdge>[],
366
+ mode: DeserializationMode,
367
+ issues: DeserializationIssue[]
368
+ ): DomainGraphNodeType | undefined {
205
369
  if (!entry) {
206
- throw new Error(`Unable to restore data domain graph. Malformed node entry`)
370
+ const issue: DeserializationIssue = {
371
+ type: 'malformed_entry',
372
+ severity: 'error',
373
+ message: 'Unable to restore data domain graph. Malformed node entry',
374
+ }
375
+ issues.push(issue)
376
+
377
+ if (mode === 'strict') {
378
+ throw new Error(issue.message)
379
+ }
380
+ return undefined
207
381
  }
208
- const domainElement = entry as { kind?: string }
382
+
383
+ const domainElement = entry as { kind?: string; key?: string }
209
384
  if (!domainElement.kind) {
210
- throw new Error(`Unable to restore data domain graph. Malformed node entry`)
211
- }
212
- if (domainElement.kind === DomainNamespaceKind) {
213
- return new DomainNamespace(root, domainElement as DomainNamespaceSchema)
214
- }
215
- if (domainElement.kind === DomainModelKind) {
216
- return new DomainModel(root, domainElement as DomainModelSchema)
217
- }
218
- if (domainElement.kind === DomainEntityKind) {
219
- return new DomainEntity(root, domainElement as DomainEntitySchema)
220
- }
221
- if (domainElement.kind === DomainPropertyKind) {
222
- const typed = domainElement as DomainPropertySchema
223
- const parent = findEdgeParent(typed.key, edges)
224
- if (!parent) {
225
- throw new Error(`Unable to restore data domain graph. Malformed node entry`)
385
+ const issue: DeserializationIssue = {
386
+ type: 'malformed_entry',
387
+ severity: 'error',
388
+ message: 'Unable to restore data domain graph. Node entry missing kind property',
389
+ affectedKey: domainElement.key,
226
390
  }
227
- return new DomainProperty(root, parent, typed)
391
+ issues.push(issue)
392
+
393
+ if (mode === 'strict') {
394
+ throw new Error(issue.message)
395
+ }
396
+ return undefined
228
397
  }
229
- if (domainElement.kind === DomainAssociationKind) {
230
- const typed = domainElement as DomainAssociationSchema
231
- const parent = findEdgeParent(typed.key, edges)
232
- if (!parent) {
233
- throw new Error(`Unable to restore data domain graph. Malformed node entry`)
398
+
399
+ try {
400
+ if (domainElement.kind === DomainNamespaceKind) {
401
+ return new DomainNamespace(root, domainElement as DomainNamespaceSchema)
402
+ }
403
+ if (domainElement.kind === DomainModelKind) {
404
+ return new DomainModel(root, domainElement as DomainModelSchema)
405
+ }
406
+ if (domainElement.kind === DomainEntityKind) {
407
+ return new DomainEntity(root, domainElement as DomainEntitySchema)
408
+ }
409
+ if (domainElement.kind === DomainPropertyKind) {
410
+ const typed = domainElement as DomainPropertySchema
411
+ const parent = findEdgeParent(typed.key, edges)
412
+ if (!parent) {
413
+ const issue: DeserializationIssue = {
414
+ type: 'missing_edge',
415
+ severity: 'error',
416
+ message: `Property "${typed.info?.name || typed.key}" has no parent entity edge`,
417
+ affectedKey: typed.key,
418
+ resolution: mode === 'lenient' ? 'Property will be skipped' : undefined,
419
+ }
420
+ issues.push(issue)
421
+
422
+ if (mode === 'strict') {
423
+ throw new Error(`Unable to restore data domain graph. Malformed node entry`)
424
+ }
425
+ return undefined
426
+ }
427
+ return new DomainProperty(root, parent, typed)
428
+ }
429
+ if (domainElement.kind === DomainAssociationKind) {
430
+ const typed = domainElement as DomainAssociationSchema
431
+ const parent = findEdgeParent(typed.key, edges)
432
+ if (!parent) {
433
+ const issue: DeserializationIssue = {
434
+ type: 'missing_edge',
435
+ severity: 'error',
436
+ message: `Association "${typed.info?.name || typed.key}" has no parent entity edge`,
437
+ affectedKey: typed.key,
438
+ resolution: mode === 'lenient' ? 'Association will be skipped' : undefined,
439
+ }
440
+ issues.push(issue)
441
+ if (mode === 'strict') {
442
+ throw new Error(`Unable to restore data domain graph. Malformed node entry`)
443
+ }
444
+ return undefined
445
+ }
446
+ return new DomainAssociation(root, parent, typed)
447
+ }
448
+
449
+ const issue: DeserializationIssue = {
450
+ type: 'unknown_kind',
451
+ severity: 'error',
452
+ message: `Unknown node kind: ${domainElement.kind}`,
453
+ affectedKey: domainElement.key,
234
454
  }
235
- return new DomainAssociation(root, parent, typed)
455
+ issues.push(issue)
456
+
457
+ if (mode === 'strict') {
458
+ throw new Error(`Unable to restore data domain graph. Unknown node kind ${domainElement.kind}`)
459
+ }
460
+ return undefined
461
+ } catch (error) {
462
+ const issue: DeserializationIssue = {
463
+ type: 'malformed_entry',
464
+ severity: 'error',
465
+ message: `Failed to create node: ${error instanceof Error ? error.message : String(error)}`,
466
+ affectedKey: domainElement.key,
467
+ }
468
+ issues.push(issue)
469
+
470
+ if (mode === 'strict') {
471
+ throw error
472
+ }
473
+ return undefined
236
474
  }
237
- throw new Error(`Unable to restore data domain graph. Unknown node kind ${domainElement.kind}`)
238
475
  }
239
476
 
240
477
  /**
@@ -246,74 +483,223 @@ function prepareNode(root: DataDomain, entry: unknown, edges: JsonEdge<DomainGra
246
483
  *
247
484
  * @param root The DataDomain instance to use as the root for the graph
248
485
  * @param json The previously serialized graph
249
- * @returns Deserialized graph instance
486
+ * @param dependencies An array of foreign data domains to register with this domain
487
+ * @param mode The deserialization mode - 'strict' throws errors, 'lenient' collects issues
488
+ * @returns DeserializationResult with graph, issues, and success status
489
+ * @throws Error in strict mode when any issue is encountered
250
490
  */
251
- export function deserialize(root: DataDomain, json?: SerializedGraph, dependencies?: DataDomain[]): DataDomainGraph {
491
+ export function deserialize(root: DataDomain, options: DeserializeOptions = {}): DeserializationResult {
492
+ const { json, dependencies, mode = 'strict' } = options
252
493
  const g = new Graph<unknown, DomainGraphNodeType, DomainGraphEdge>({
253
494
  compound: true,
254
495
  multigraph: true,
255
496
  directed: true,
256
497
  })
498
+ const issues: DeserializationIssue[] = []
257
499
  let foreignNodes = new Set<string>()
258
500
  let foreignEdges = new Set<string>()
501
+
502
+ // Process dependencies
259
503
  if (dependencies) {
260
504
  for (const dependency of dependencies) {
261
- const result = mergeGraph(g, dependency.graph, dependency.key)
262
- if (result.edges.size) {
263
- foreignEdges = new Set([...foreignEdges, ...result.edges])
264
- }
265
- if (result.nodes.size) {
266
- foreignNodes = new Set([...foreignNodes, ...result.nodes])
505
+ try {
506
+ const result = mergeGraph(g, dependency.graph, dependency.key)
507
+ if (result.edges.size) {
508
+ foreignEdges = new Set([...foreignEdges, ...result.edges])
509
+ }
510
+ if (result.nodes.size) {
511
+ foreignNodes = new Set([...foreignNodes, ...result.nodes])
512
+ }
513
+ root.dependencies.set(dependency.key, dependency)
514
+ } catch (error) {
515
+ const message = `Failed to merge dependency "${dependency.key}": ${error instanceof Error ? error.message : String(error)}`
516
+
517
+ if (mode === 'strict') {
518
+ throw new Error(message)
519
+ }
520
+
521
+ const issue: DeserializationIssue = {
522
+ type: 'missing_dependency',
523
+ severity: 'error',
524
+ message,
525
+ affectedKey: dependency.key,
526
+ resolution: 'Dependency will be skipped',
527
+ }
528
+ issues.push(issue)
267
529
  }
268
- root.dependencies.set(dependency.key, dependency)
269
530
  }
270
531
  }
532
+
271
533
  if (!json) {
272
- return g
534
+ return {
535
+ graph: g,
536
+ issues,
537
+ success: true,
538
+ }
273
539
  }
540
+
541
+ // Process nodes
274
542
  if (Array.isArray(json.nodes)) {
275
543
  // 1st pass - set up nodes
276
544
  const parentInfo = new Map<string, string[]>()
277
545
  for (const entry of json.nodes) {
278
- g.setNode(entry.v, prepareNode(root, entry.value, json.edges))
279
- if (entry.parents) {
280
- parentInfo.set(entry.v, entry.parents)
546
+ const node = prepareNode(root, entry.value, json.edges, mode, issues)
547
+ if (node) {
548
+ g.setNode(entry.v, node)
549
+ if (entry.parents) {
550
+ parentInfo.set(entry.v, entry.parents)
551
+ }
281
552
  }
553
+ // Note: prepareNode throws in strict mode, so we won't reach here if there's an error
282
554
  }
555
+
283
556
  // 2nd pass - set up parents
284
557
  for (const [key, parents] of parentInfo) {
285
- // In data domain graph, all nodes that can have parents can only have a single parent.
286
- // It's the business logic of the library.
287
- // Parent-child relationships:
288
- // - Namespace -> Namespace
289
- // - Namespace -> Model
290
- // - Model -> Entity
291
- // Entities and Association are associated with the parent entity through edges.
558
+ // Verify the node still exists (might have been skipped in lenient mode)
559
+ if (!g.hasNode(key)) {
560
+ const issue: DeserializationIssue = {
561
+ type: 'missing_node',
562
+ severity: 'warning',
563
+ message: `Cannot set parents for missing node: ${key}`,
564
+ affectedKey: key,
565
+ resolution: 'Parent relationship will be skipped',
566
+ }
567
+ issues.push(issue)
568
+ continue
569
+ }
570
+
292
571
  for (const parent of parents) {
293
- g.setParent(key, parent)
572
+ // Verify parent exists
573
+ if (!g.hasNode(parent)) {
574
+ const issue: DeserializationIssue = {
575
+ type: 'missing_node',
576
+ severity: 'warning',
577
+ message: `Parent node "${parent}" not found for child "${key}"`,
578
+ affectedKey: key,
579
+ context: { parentKey: parent },
580
+ resolution: 'Parent relationship will be skipped',
581
+ }
582
+ issues.push(issue)
583
+ continue
584
+ }
585
+
586
+ try {
587
+ g.setParent(key, parent)
588
+ } catch (error) {
589
+ const message = `Failed to set parent "${parent}" for child "${key}": ${error instanceof Error ? error.message : String(error)}`
590
+ const issue: DeserializationIssue = {
591
+ type: 'invalid_parent',
592
+ severity: 'warning',
593
+ message,
594
+ affectedKey: key,
595
+ context: { parentKey: parent },
596
+ resolution: 'Parent relationship will be skipped',
597
+ }
598
+ issues.push(issue)
599
+ }
294
600
  }
295
601
  }
296
602
  }
603
+
604
+ // Process edges
297
605
  if (Array.isArray(json.edges)) {
298
606
  for (const entry of json.edges) {
299
607
  if (!entry.value) {
300
- throw new Error(`Unable to restore data domain graph. Malformed edge entry`)
608
+ const message = 'Edge entry missing value'
609
+
610
+ if (mode === 'strict') {
611
+ throw new Error(message)
612
+ }
613
+
614
+ const issue: DeserializationIssue = {
615
+ type: 'malformed_entry',
616
+ severity: 'error',
617
+ message,
618
+ context: { edge: entry },
619
+ }
620
+ issues.push(issue)
621
+ continue
301
622
  }
623
+
624
+ // Handle foreign edges
302
625
  if (entry.value.foreign) {
303
626
  // The `v` has to be local to the graph. The `w` must be foreign.
304
627
  if (!foreignNodes.has(entry.w)) {
305
- // console.warn(`Missing foreign node: ${entry.w}. Skipping edge.`)
628
+ // Note, we don't consider this an error, just a warning.
629
+ // Because of that, we don't throw an error here.
630
+ const issue: DeserializationIssue = {
631
+ type: 'missing_node',
632
+ severity: 'warning',
633
+ message: `Missing foreign node: ${entry.w}`,
634
+ affectedKey: entry.w,
635
+ resolution: 'Edge will be skipped',
636
+ }
637
+ issues.push(issue)
306
638
  continue
307
639
  }
308
640
  }
641
+
642
+ // Check if nodes exist for the edge
309
643
  if (foreignNodes.has(entry.v) || foreignNodes.has(entry.w)) {
310
644
  if (!g.hasNode(entry.v) || !g.hasNode(entry.w)) {
311
- // console.warn(`Missing foreign node: ${entry.v} or ${entry.w}. Skipping edge.`)
645
+ const issue: DeserializationIssue = {
646
+ type: 'missing_node',
647
+ severity: 'warning',
648
+ message: `Missing foreign node for edge: ${entry.v} -> ${entry.w}`,
649
+ context: { sourceNode: entry.v, targetNode: entry.w },
650
+ resolution: 'Edge will be skipped',
651
+ }
652
+ issues.push(issue)
653
+ continue
654
+ }
655
+ } else {
656
+ // Both nodes should be local - verify they exist
657
+ if (!g.hasNode(entry.v)) {
658
+ const issue: DeserializationIssue = {
659
+ type: 'missing_node',
660
+ severity: 'warning',
661
+ message: `Source node not found for edge: ${entry.v}`,
662
+ affectedKey: entry.v,
663
+ resolution: 'Edge will be skipped',
664
+ }
665
+ issues.push(issue)
666
+ continue
667
+ }
668
+
669
+ if (!g.hasNode(entry.w)) {
670
+ const issue: DeserializationIssue = {
671
+ type: 'missing_node',
672
+ severity: 'warning',
673
+ message: `Target node not found for edge: ${entry.w}`,
674
+ affectedKey: entry.w,
675
+ resolution: 'Edge will be skipped',
676
+ }
677
+ issues.push(issue)
312
678
  continue
313
679
  }
314
680
  }
315
- g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value)
681
+
682
+ try {
683
+ g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value)
684
+ } catch (error) {
685
+ const message = `Failed to create edge ${entry.v} -> ${entry.w}: ${error instanceof Error ? error.message : String(error)}`
686
+ const issue: DeserializationIssue = {
687
+ type: 'malformed_entry',
688
+ severity: 'warning',
689
+ message,
690
+ context: { sourceNode: entry.v, targetNode: entry.w },
691
+ resolution: 'Edge will be skipped',
692
+ }
693
+ issues.push(issue)
694
+ }
316
695
  }
317
696
  }
318
- return g
697
+
698
+ const hasErrors = issues.some((issue) => issue.severity === 'error')
699
+
700
+ return {
701
+ graph: g,
702
+ issues,
703
+ success: !hasErrors,
704
+ }
319
705
  }
@@ -13,6 +13,7 @@ import type {
13
13
  DomainAssociationKind,
14
14
  DataDomainKind,
15
15
  } from '../models/kinds.js'
16
+ import type { DataDomain } from './DataDomain.js'
16
17
 
17
18
  export interface DataDomainRemoveOptions {
18
19
  /**
@@ -55,7 +56,7 @@ export interface DomainGraphEdge {
55
56
  /**
56
57
  * The type of the edge.
57
58
  * - `association` The edge is to an association object.
58
- * - When coming **from** an entiry (the `v` property), that entity owns the association.
59
+ * - When coming **from** an entity (the `v` property), that entity owns the association.
59
60
  * - When coming **to** an entity (the `w` property), that entity is the target of the association.
60
61
  * An association can have multiple targets.
61
62
  * - `property` The edge is to a property object. Can only be created between an entity and a property.
@@ -684,7 +685,7 @@ export interface MatchUserPropertyAccessRule extends BaseAccessRule {
684
685
  /**
685
686
  * The action is allowed if the authenticated user's email domain matches a specific domain.
686
687
  * This is used to restrict access based on the user's email address.
687
- * For example, only users with an email address from "mycompany.com" can access certain resources.
688
+ * For example, only users with an email address from "my-company.com" can access certain resources.
688
689
  */
689
690
  export interface MatchEmailDomainAccessRule extends BaseAccessRule {
690
691
  type: 'matchEmailDomain'
@@ -822,3 +823,73 @@ export interface DomainImpactItem {
822
823
  */
823
824
  parent?: string
824
825
  }
826
+
827
+ export interface DeserializeOptions {
828
+ /**
829
+ * The mode to use for deserialization.
830
+ */
831
+ mode?: DeserializationMode
832
+ /**
833
+ * The serialized graph to deserialize.
834
+ * This is the JSON representation of the graph.
835
+ */
836
+ json?: SerializedGraph
837
+ /**
838
+ * The list of foreign domains that this domain depends on.
839
+ */
840
+ dependencies?: DataDomain[]
841
+ }
842
+
843
+ /**
844
+ * Describes the mode for deserializing a domain graph.
845
+ */
846
+ export type DeserializationMode = 'strict' | 'lenient'
847
+
848
+ /**
849
+ * Describes an issue found during deserialization.
850
+ */
851
+ export interface DeserializationIssue {
852
+ /**
853
+ * The type of issue encountered.
854
+ */
855
+ type: 'missing_node' | 'missing_edge' | 'invalid_parent' | 'missing_dependency' | 'malformed_entry' | 'unknown_kind'
856
+ /**
857
+ * The severity of the issue.
858
+ */
859
+ severity: 'error' | 'warning' | 'info'
860
+ /**
861
+ * A human-readable description of the issue.
862
+ */
863
+ message: string
864
+ /**
865
+ * The key of the affected node, edge, or entity if applicable.
866
+ */
867
+ affectedKey?: string
868
+ /**
869
+ * Additional context about the issue.
870
+ */
871
+ context?: Record<string, unknown>
872
+ /**
873
+ * The action taken to handle this issue in lenient mode.
874
+ */
875
+ resolution?: string
876
+ }
877
+
878
+ /**
879
+ * The result of a deserialization operation.
880
+ */
881
+ export interface DeserializationResult {
882
+ /**
883
+ * The deserialized graph.
884
+ */
885
+ graph: DataDomainGraph
886
+ /**
887
+ * Issues encountered during deserialization.
888
+ */
889
+ issues: DeserializationIssue[]
890
+ /**
891
+ * Whether the deserialization was successful.
892
+ * This is set to true when a critical failures occurred.
893
+ */
894
+ success: boolean
895
+ }