@nocobase/flow-engine 2.1.0-alpha.12 → 2.1.0-alpha.13

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.
Files changed (40) hide show
  1. package/lib/FlowDefinition.d.ts +0 -4
  2. package/lib/index.d.ts +0 -1
  3. package/lib/index.js +1 -3
  4. package/lib/models/DisplayItemModel.d.ts +1 -1
  5. package/lib/models/EditableItemModel.d.ts +1 -1
  6. package/lib/models/FilterableItemModel.d.ts +1 -1
  7. package/lib/runjs-context/snippets/index.js +13 -2
  8. package/lib/{server.d.ts → runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts} +3 -2
  9. package/lib/{server.js → runjs-context/snippets/scene/detail/set-field-style.snippet.js} +27 -9
  10. package/{src/server.ts → lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts} +3 -3
  11. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  12. package/lib/types.d.ts +0 -221
  13. package/package.json +4 -4
  14. package/src/__tests__/runjsSnippets.test.ts +21 -0
  15. package/src/index.ts +0 -1
  16. package/src/models/DisplayItemModel.tsx +1 -1
  17. package/src/models/EditableItemModel.tsx +1 -1
  18. package/src/models/FilterableItemModel.tsx +1 -1
  19. package/src/runjs-context/snippets/index.ts +12 -1
  20. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  21. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  22. package/src/types.ts +0 -262
  23. package/lib/FlowSchemaRegistry.d.ts +0 -154
  24. package/lib/FlowSchemaRegistry.js +0 -1427
  25. package/lib/flow-schema-registry/fieldBinding.d.ts +0 -32
  26. package/lib/flow-schema-registry/fieldBinding.js +0 -165
  27. package/lib/flow-schema-registry/modelPatches.d.ts +0 -16
  28. package/lib/flow-schema-registry/modelPatches.js +0 -235
  29. package/lib/flow-schema-registry/schemaInference.d.ts +0 -17
  30. package/lib/flow-schema-registry/schemaInference.js +0 -207
  31. package/lib/flow-schema-registry/utils.d.ts +0 -25
  32. package/lib/flow-schema-registry/utils.js +0 -293
  33. package/server.d.ts +0 -1
  34. package/server.js +0 -1
  35. package/src/FlowSchemaRegistry.ts +0 -1799
  36. package/src/__tests__/FlowSchemaRegistry.test.ts +0 -1951
  37. package/src/flow-schema-registry/fieldBinding.ts +0 -171
  38. package/src/flow-schema-registry/modelPatches.ts +0 -260
  39. package/src/flow-schema-registry/schemaInference.ts +0 -210
  40. package/src/flow-schema-registry/utils.ts +0 -268
@@ -1,1799 +0,0 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
- import _ from 'lodash';
11
- import type {
12
- ActionDefinition,
13
- FlowActionSchemaContribution,
14
- FlowFieldBindingContextContribution,
15
- FlowFieldBindingContribution,
16
- FlowFieldModelCompatibility,
17
- FlowSchemaBundleDocument,
18
- FlowSchemaBundleNode,
19
- FlowSchemaBundleSlotCatalog,
20
- FlowSchemaContextEdge,
21
- FlowSchemaDocs,
22
- FlowDynamicHint,
23
- FlowSchemaInventoryContribution,
24
- FlowJsonSchema,
25
- FlowModelSchemaPatch,
26
- FlowModelSchemaContribution,
27
- FlowModelMeta,
28
- FlowSchemaCoverage,
29
- FlowSchemaDetail,
30
- FlowSchemaDocument,
31
- FlowModelSchemaExposure,
32
- FlowSchemaPublicDocument,
33
- FlowSubModelSlotSchema,
34
- ModelConstructor,
35
- StepDefinition,
36
- } from './types';
37
- import {
38
- buildFieldModelCompatibility,
39
- matchesFieldBinding,
40
- normalizeFieldBindingContextContribution,
41
- normalizeFieldBindingContribution,
42
- type RegisteredFieldBinding,
43
- type RegisteredFieldBindingContext,
44
- } from './flow-schema-registry/fieldBinding';
45
- import {
46
- applyModelSchemaPatch,
47
- matchesDescendantSchemaPatch,
48
- normalizeSubModelSlots,
49
- resolveChildSchemaPatch,
50
- } from './flow-schema-registry/modelPatches';
51
- import { inferParamsSchemaFromUiSchema, type StepSchemaResolution } from './flow-schema-registry/schemaInference';
52
- import {
53
- JSON_SCHEMA_DRAFT_07,
54
- buildSkeletonFromSchema,
55
- collectAllowedUses,
56
- createFlowHint,
57
- deepFreezePlainGraph,
58
- hashString,
59
- mergeSchemas,
60
- normalizeSchemaDocs,
61
- normalizeSchemaHints,
62
- normalizeStringArray,
63
- stableStringify,
64
- toSchemaTitle,
65
- } from './flow-schema-registry/utils';
66
-
67
- export type RegisteredActionSchema = {
68
- name: string;
69
- title?: string;
70
- definition?: ActionDefinition;
71
- schema?: FlowJsonSchema;
72
- docs: FlowSchemaDocs;
73
- coverage: FlowSchemaCoverage;
74
- dynamicHints: FlowDynamicHint[];
75
- };
76
-
77
- export type RegisteredModelSchema = {
78
- use: string;
79
- modelClass?: ModelConstructor;
80
- stepParamsSchema?: FlowJsonSchema;
81
- flowRegistrySchema?: FlowJsonSchema;
82
- subModelSlots?: Record<string, FlowSubModelSlotSchema>;
83
- flowRegistrySchemaPatch?: FlowJsonSchema;
84
- title?: string;
85
- examples: any[];
86
- docs: FlowSchemaDocs;
87
- skeleton?: any;
88
- dynamicHints: FlowDynamicHint[];
89
- coverage: FlowSchemaCoverage;
90
- exposure: FlowModelSchemaExposure;
91
- abstract: boolean;
92
- allowDirectUse: boolean;
93
- suggestedUses: string[];
94
- };
95
-
96
- type ModelPatchContribution = {
97
- patch: FlowModelSchemaPatch;
98
- source: FlowSchemaCoverage['source'];
99
- strict?: boolean;
100
- };
101
-
102
- export class FlowSchemaRegistry {
103
- private readonly modelSchemas = new Map<string, RegisteredModelSchema>();
104
- private readonly actionSchemas = new Map<string, RegisteredActionSchema>();
105
- private readonly fieldBindingContexts = new Map<string, RegisteredFieldBindingContext>();
106
- private readonly fieldBindings = new Map<string, RegisteredFieldBinding[]>();
107
- private readonly resolvedModelCache = new Map<string, RegisteredModelSchema>();
108
- private readonly modelSnapshotSchemaCache = new Map<string, FlowJsonSchema>();
109
- private readonly compactModelSnapshotSchemaCache = new Map<string, FlowJsonSchema>();
110
- private readonly modelSchemaHashCache = new Map<string, string>();
111
- private readonly compactModelSchemaHashCache = new Map<string, string>();
112
- private readonly modelDocumentCache = new Map<string, FlowSchemaDocument>();
113
- private readonly modelLocalDynamicHintsCache = new Map<string, FlowDynamicHint[]>();
114
- private readonly publicModelDocumentCache = new Map<string, FlowSchemaPublicDocument>();
115
- private readonly publicTreeRoots = new Set<string>();
116
- private readonly slotUseExpansions = new Map<string, string[]>();
117
-
118
- private invalidateDerivedCaches() {
119
- this.resolvedModelCache.clear();
120
- this.modelSnapshotSchemaCache.clear();
121
- this.compactModelSnapshotSchemaCache.clear();
122
- this.modelSchemaHashCache.clear();
123
- this.compactModelSchemaHashCache.clear();
124
- this.modelDocumentCache.clear();
125
- this.modelLocalDynamicHintsCache.clear();
126
- this.publicModelDocumentCache.clear();
127
- }
128
-
129
- registerAction(action: ActionDefinition | ({ name: string } & Partial<ActionDefinition>)) {
130
- const name = String(action?.name || '').trim();
131
- if (!name) return;
132
-
133
- const previous = this.actionSchemas.get(name);
134
- const inferred = inferParamsSchemaFromUiSchema(name, action.uiSchema as any, `actions.${name}`);
135
- const mergedSchema = mergeSchemas(action.paramsSchema || inferred.schema, action.paramsSchemaPatch);
136
- const explicit = !!action.paramsSchema || !!action.paramsSchemaPatch;
137
- const coverageStatus: FlowSchemaCoverage['status'] = explicit
138
- ? inferred.schema
139
- ? 'mixed'
140
- : 'manual'
141
- : inferred.coverage;
142
- const docs = normalizeSchemaDocs({
143
- ...previous?.docs,
144
- ...action.schemaDocs,
145
- examples: action.schemaDocs?.examples || previous?.docs?.examples,
146
- dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(action.schemaDocs?.dynamicHints || [])],
147
- });
148
-
149
- this.actionSchemas.set(name, {
150
- name,
151
- title: action.title,
152
- definition: action as ActionDefinition,
153
- schema: mergedSchema,
154
- coverage: {
155
- status: mergedSchema ? coverageStatus : 'unresolved',
156
- source: previous?.coverage.source || 'official',
157
- strict: previous?.coverage.strict,
158
- },
159
- docs,
160
- dynamicHints: normalizeSchemaHints([
161
- ...(previous?.dynamicHints || []),
162
- ...(inferred.hints || []),
163
- ...(docs.dynamicHints || []),
164
- ]),
165
- });
166
- this.invalidateDerivedCaches();
167
- }
168
-
169
- registerActions(actions: Record<string, ActionDefinition>) {
170
- for (const action of Object.values(actions || {})) {
171
- this.registerAction(action);
172
- }
173
- }
174
-
175
- registerActionContribution(contribution: FlowActionSchemaContribution) {
176
- const name = String(contribution?.name || '').trim();
177
- if (!name) return;
178
-
179
- const previous = this.actionSchemas.get(name);
180
- const docs = normalizeSchemaDocs({
181
- ...previous?.docs,
182
- ...contribution.docs,
183
- examples: contribution.docs?.examples || previous?.docs?.examples,
184
- dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(contribution.docs?.dynamicHints || [])],
185
- commonPatterns: contribution.docs?.commonPatterns || previous?.docs?.commonPatterns,
186
- antiPatterns: contribution.docs?.antiPatterns || previous?.docs?.antiPatterns,
187
- minimalExample:
188
- contribution.docs?.minimalExample !== undefined
189
- ? contribution.docs.minimalExample
190
- : previous?.docs?.minimalExample,
191
- });
192
- this.actionSchemas.set(name, {
193
- name,
194
- title: contribution.title || previous?.title,
195
- definition: previous?.definition,
196
- schema: contribution.paramsSchema ? _.cloneDeep(contribution.paramsSchema) : previous?.schema,
197
- docs,
198
- coverage: {
199
- status: contribution.paramsSchema ? 'manual' : previous?.coverage.status || 'unresolved',
200
- source: contribution.source || previous?.coverage.source || 'official',
201
- strict: contribution.strict ?? previous?.coverage.strict,
202
- },
203
- dynamicHints: normalizeSchemaHints([...(previous?.dynamicHints || []), ...(docs.dynamicHints || [])]),
204
- });
205
- this.invalidateDerivedCaches();
206
- }
207
-
208
- registerActionContributions(
209
- contributions: FlowActionSchemaContribution[] | Record<string, FlowActionSchemaContribution>,
210
- ) {
211
- const values = Array.isArray(contributions) ? contributions : Object.values(contributions || {});
212
- for (const contribution of values) {
213
- this.registerActionContribution(contribution);
214
- }
215
- }
216
-
217
- registerFieldBindingContext(contribution: FlowFieldBindingContextContribution, fallbackName?: string) {
218
- const normalized = normalizeFieldBindingContextContribution(contribution, fallbackName);
219
- if (!normalized) {
220
- return;
221
- }
222
-
223
- const previous = this.fieldBindingContexts.get(normalized.name);
224
- this.fieldBindingContexts.set(normalized.name, {
225
- name: normalized.name,
226
- inherits: _.uniq([...(previous?.inherits || []), ...normalized.inherits]),
227
- });
228
- this.invalidateDerivedCaches();
229
- }
230
-
231
- registerFieldBindingContexts(
232
- contributions:
233
- | FlowFieldBindingContextContribution[]
234
- | Record<string, FlowFieldBindingContextContribution>
235
- | undefined,
236
- ) {
237
- if (!contributions) {
238
- return;
239
- }
240
-
241
- if (Array.isArray(contributions)) {
242
- for (const contribution of contributions) {
243
- this.registerFieldBindingContext(contribution);
244
- }
245
- return;
246
- }
247
-
248
- for (const [name, contribution] of Object.entries(contributions)) {
249
- if (!contribution) {
250
- continue;
251
- }
252
- this.registerFieldBindingContext(contribution, name);
253
- }
254
- }
255
-
256
- registerFieldBinding(contribution: FlowFieldBindingContribution, source: FlowSchemaCoverage['source'] = 'official') {
257
- const normalized = normalizeFieldBindingContribution(contribution, source);
258
- if (!normalized) {
259
- return;
260
- }
261
-
262
- const bindings = this.fieldBindings.get(normalized.context) || [];
263
- bindings.push(normalized);
264
- this.fieldBindings.set(normalized.context, bindings);
265
- this.invalidateDerivedCaches();
266
- }
267
-
268
- registerFieldBindings(
269
- contributions:
270
- | FlowFieldBindingContribution[]
271
- | Record<string, FlowFieldBindingContribution | FlowFieldBindingContribution[]>
272
- | undefined,
273
- source: FlowSchemaCoverage['source'] = 'official',
274
- ) {
275
- if (!contributions) {
276
- return;
277
- }
278
-
279
- if (Array.isArray(contributions)) {
280
- for (const contribution of contributions) {
281
- this.registerFieldBinding(contribution, source);
282
- }
283
- return;
284
- }
285
-
286
- for (const [context, contribution] of Object.entries(contributions)) {
287
- if (!contribution) {
288
- continue;
289
- }
290
-
291
- const items = Array.isArray(contribution) ? contribution : [contribution];
292
- for (const item of items) {
293
- this.registerFieldBinding(
294
- {
295
- ...item,
296
- context: item.context || context,
297
- },
298
- source,
299
- );
300
- }
301
- }
302
- }
303
-
304
- registerModel(use: string, options: Partial<RegisteredModelSchema>) {
305
- const name = String(use || '').trim();
306
- if (!name) return;
307
- const previous = this.modelSchemas.get(name);
308
- this.modelSchemas.set(name, {
309
- use: name,
310
- title: options.title || previous?.title,
311
- modelClass: options.modelClass || previous?.modelClass,
312
- stepParamsSchema: options.stepParamsSchema || previous?.stepParamsSchema,
313
- flowRegistrySchema: options.flowRegistrySchema || previous?.flowRegistrySchema,
314
- subModelSlots: normalizeSubModelSlots(options.subModelSlots || previous?.subModelSlots),
315
- flowRegistrySchemaPatch: options.flowRegistrySchemaPatch || previous?.flowRegistrySchemaPatch,
316
- examples: options.examples || previous?.examples || [],
317
- docs: normalizeSchemaDocs({
318
- ...previous?.docs,
319
- ...options.docs,
320
- examples: options.docs?.examples || previous?.docs?.examples,
321
- dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(options.docs?.dynamicHints || [])],
322
- commonPatterns: options.docs?.commonPatterns || previous?.docs?.commonPatterns,
323
- antiPatterns: options.docs?.antiPatterns || previous?.docs?.antiPatterns,
324
- minimalExample:
325
- options.docs?.minimalExample !== undefined ? options.docs.minimalExample : previous?.docs?.minimalExample,
326
- }),
327
- skeleton: options.skeleton !== undefined ? _.cloneDeep(options.skeleton) : previous?.skeleton,
328
- dynamicHints: normalizeSchemaHints([...(previous?.dynamicHints || []), ...(options.dynamicHints || [])]),
329
- coverage: options.coverage || previous?.coverage || { status: 'unresolved', source: 'third-party' },
330
- exposure: options.exposure ?? previous?.exposure ?? 'public',
331
- abstract: options.abstract ?? previous?.abstract ?? false,
332
- allowDirectUse: options.allowDirectUse ?? previous?.allowDirectUse ?? true,
333
- suggestedUses: normalizeStringArray(options.suggestedUses || previous?.suggestedUses),
334
- });
335
- this.invalidateDerivedCaches();
336
- }
337
-
338
- registerModelContribution(contribution: FlowModelSchemaContribution) {
339
- const use = String(contribution?.use || '').trim();
340
- if (!use) return;
341
-
342
- const previous = this.modelSchemas.get(use);
343
- const hasSchemaContribution =
344
- !!contribution.stepParamsSchema ||
345
- !!contribution.flowRegistrySchema ||
346
- !!contribution.flowRegistrySchemaPatch ||
347
- !!contribution.subModelSlots;
348
- const docs = normalizeSchemaDocs({
349
- ...previous?.docs,
350
- ...contribution.docs,
351
- examples: contribution.examples || contribution.docs?.examples || previous?.docs?.examples,
352
- dynamicHints: [...(previous?.docs?.dynamicHints || []), ...(contribution.docs?.dynamicHints || [])],
353
- commonPatterns: contribution.docs?.commonPatterns || previous?.docs?.commonPatterns,
354
- antiPatterns: contribution.docs?.antiPatterns || previous?.docs?.antiPatterns,
355
- minimalExample:
356
- contribution.docs?.minimalExample !== undefined
357
- ? contribution.docs.minimalExample
358
- : previous?.docs?.minimalExample,
359
- });
360
- this.registerModel(use, {
361
- title: contribution.title,
362
- stepParamsSchema: contribution.stepParamsSchema ? _.cloneDeep(contribution.stepParamsSchema) : undefined,
363
- flowRegistrySchema: contribution.flowRegistrySchema ? _.cloneDeep(contribution.flowRegistrySchema) : undefined,
364
- subModelSlots: contribution.subModelSlots ? normalizeSubModelSlots(contribution.subModelSlots) : undefined,
365
- flowRegistrySchemaPatch: contribution.flowRegistrySchemaPatch
366
- ? _.cloneDeep(contribution.flowRegistrySchemaPatch)
367
- : undefined,
368
- examples: contribution.examples || docs.examples || [],
369
- docs,
370
- skeleton: contribution.skeleton,
371
- dynamicHints: [...(contribution.dynamicHints || []), ...(docs.dynamicHints || [])],
372
- coverage: hasSchemaContribution
373
- ? {
374
- status: previous?.coverage.status === 'auto' ? 'mixed' : 'manual',
375
- source: contribution.source || previous?.coverage.source || 'official',
376
- strict: contribution.strict ?? previous?.coverage.strict,
377
- }
378
- : previous?.coverage || {
379
- status: 'unresolved',
380
- source: contribution.source || 'official',
381
- strict: contribution.strict,
382
- },
383
- exposure: contribution.exposure,
384
- abstract: contribution.abstract,
385
- allowDirectUse: contribution.allowDirectUse,
386
- suggestedUses: contribution.suggestedUses,
387
- });
388
- }
389
-
390
- registerModelContributions(
391
- contributions: FlowModelSchemaContribution[] | Record<string, FlowModelSchemaContribution>,
392
- ) {
393
- const values = Array.isArray(contributions) ? contributions : Object.values(contributions || {});
394
- for (const contribution of values) {
395
- this.registerModelContribution(contribution);
396
- }
397
- }
398
-
399
- registerModelClass(use: string, modelClass: ModelConstructor) {
400
- const meta = ((modelClass as any).meta || {}) as FlowModelMeta;
401
- const schemaMeta = meta.schema || {};
402
- const inferredSlots = this.inferSubModelSlotsFromModelClass(use, modelClass);
403
- const inferredHints = this.collectModelDynamicHints(use, modelClass, meta);
404
- const hasManual =
405
- !!schemaMeta.stepParamsSchema ||
406
- !!schemaMeta.flowRegistrySchema ||
407
- !!schemaMeta.subModelSlots ||
408
- !!schemaMeta.flowRegistrySchemaPatch;
409
- const hasAuto = !!Object.keys(inferredSlots).length;
410
-
411
- this.registerModel(use, {
412
- modelClass,
413
- title: toSchemaTitle(meta.label, use),
414
- stepParamsSchema: schemaMeta.stepParamsSchema,
415
- flowRegistrySchema: schemaMeta.flowRegistrySchema,
416
- subModelSlots: Object.keys(schemaMeta.subModelSlots || {}).length ? schemaMeta.subModelSlots : inferredSlots,
417
- flowRegistrySchemaPatch: schemaMeta.flowRegistrySchemaPatch,
418
- examples: schemaMeta.examples || schemaMeta.docs?.examples || [],
419
- docs: schemaMeta.docs,
420
- skeleton: schemaMeta.skeleton,
421
- dynamicHints: [...(schemaMeta.dynamicHints || []), ...(schemaMeta.docs?.dynamicHints || []), ...inferredHints],
422
- coverage: {
423
- status: hasManual && hasAuto ? 'mixed' : hasManual ? 'manual' : hasAuto ? 'auto' : 'unresolved',
424
- source: schemaMeta.source || 'official',
425
- strict: schemaMeta.strict,
426
- },
427
- exposure: schemaMeta.exposure,
428
- abstract: schemaMeta.abstract,
429
- allowDirectUse: schemaMeta.allowDirectUse,
430
- suggestedUses: schemaMeta.suggestedUses,
431
- });
432
- }
433
-
434
- registerModels(models: Record<string, ModelConstructor | undefined>) {
435
- for (const [use, modelClass] of Object.entries(models || {})) {
436
- if (modelClass) {
437
- this.registerModelClass(use, modelClass);
438
- }
439
- }
440
- }
441
-
442
- registerInventory(inventory: FlowSchemaInventoryContribution | undefined, _source: FlowSchemaCoverage['source']) {
443
- if (!inventory) {
444
- return;
445
- }
446
-
447
- for (const use of normalizeStringArray(inventory.publicTreeRoots)) {
448
- this.publicTreeRoots.add(use);
449
- }
450
-
451
- for (const item of inventory.slotUseExpansions || []) {
452
- const parentUse = String(item?.parentUse || '').trim();
453
- const slotKey = String(item?.slotKey || '').trim();
454
- const uses = normalizeStringArray(item?.uses);
455
- if (!parentUse || !slotKey || uses.length === 0) {
456
- continue;
457
- }
458
-
459
- const key = this.createSlotUseExpansionKey(parentUse, slotKey);
460
- this.slotUseExpansions.set(key, _.uniq([...(this.slotUseExpansions.get(key) || []), ...uses]));
461
- }
462
- this.invalidateDerivedCaches();
463
- }
464
-
465
- resolveFieldBindingCandidates(
466
- context: string,
467
- options: {
468
- interface?: string;
469
- fieldType?: string;
470
- association?: boolean;
471
- targetCollectionTemplate?: string;
472
- } = {},
473
- ) {
474
- const contextChain = this.resolveFieldBindingContextChain(context);
475
- const entries = contextChain.flatMap((contextName, contextIndex) =>
476
- (this.fieldBindings.get(contextName) || []).map((binding, bindingIndex) => ({
477
- binding,
478
- contextIndex,
479
- bindingIndex,
480
- })),
481
- );
482
-
483
- const candidates = entries
484
- .filter(({ binding }) => matchesFieldBinding(binding, options))
485
- .filter(({ binding }) => this.hasQueryableModel(binding.use))
486
- .sort((a, b) => {
487
- if (a.binding.isDefault !== b.binding.isDefault) {
488
- return a.binding.isDefault ? -1 : 1;
489
- }
490
-
491
- const aOrder = typeof a.binding.order === 'number' ? a.binding.order : Number.MAX_SAFE_INTEGER;
492
- const bOrder = typeof b.binding.order === 'number' ? b.binding.order : Number.MAX_SAFE_INTEGER;
493
- if (aOrder !== bOrder) {
494
- return aOrder - bOrder;
495
- }
496
-
497
- if (a.contextIndex !== b.contextIndex) {
498
- return a.contextIndex - b.contextIndex;
499
- }
500
-
501
- return a.bindingIndex - b.bindingIndex;
502
- })
503
- .map(({ binding }) => ({
504
- use: binding.use,
505
- defaultProps: binding.defaultProps === undefined ? undefined : _.cloneDeep(binding.defaultProps),
506
- compatibility: buildFieldModelCompatibility(binding),
507
- }));
508
-
509
- return _.uniqBy(candidates, (candidate) => `${candidate.use}:${stableStringify(candidate.compatibility)}`);
510
- }
511
-
512
- getAction(name: string): RegisteredActionSchema | undefined {
513
- return this.actionSchemas.get(name);
514
- }
515
-
516
- listActionNames(): string[] {
517
- return Array.from(this.actionSchemas.keys()).sort();
518
- }
519
-
520
- getModel(use: string): RegisteredModelSchema | undefined {
521
- return this.modelSchemas.get(use);
522
- }
523
-
524
- private resolveFieldBindingContextChain(context: string, visited = new Set<string>()): string[] {
525
- const name = String(context || '').trim();
526
- if (!name || visited.has(name)) {
527
- return [];
528
- }
529
-
530
- visited.add(name);
531
-
532
- const chain = [name];
533
- const inherits = this.fieldBindingContexts.get(name)?.inherits || [];
534
- for (const inheritedContext of inherits) {
535
- for (const item of this.resolveFieldBindingContextChain(inheritedContext, visited)) {
536
- if (!chain.includes(item)) {
537
- chain.push(item);
538
- }
539
- }
540
- }
541
-
542
- return chain;
543
- }
544
-
545
- private resolveModelSchemaRef(use: string, contextChain: FlowSchemaContextEdge[] = []): RegisteredModelSchema {
546
- const name = String(use || '').trim();
547
- const cacheKey = `${name}::${stableStringify(contextChain)}`;
548
- const cached = this.resolvedModelCache.get(cacheKey);
549
- if (cached) {
550
- return cached;
551
- }
552
-
553
- const registered = this.modelSchemas.get(name);
554
- const resolved: RegisteredModelSchema = {
555
- use: name,
556
- title: registered?.title || name,
557
- modelClass: registered?.modelClass,
558
- stepParamsSchema: registered?.stepParamsSchema
559
- ? _.cloneDeep(registered.stepParamsSchema)
560
- : this.buildInferredStepParamsSchema(name),
561
- flowRegistrySchema: registered?.flowRegistrySchema
562
- ? _.cloneDeep(registered.flowRegistrySchema)
563
- : this.buildInferredFlowRegistrySchema(name),
564
- subModelSlots: normalizeSubModelSlots(registered?.subModelSlots),
565
- flowRegistrySchemaPatch: registered?.flowRegistrySchemaPatch
566
- ? _.cloneDeep(registered.flowRegistrySchemaPatch)
567
- : undefined,
568
- examples: _.cloneDeep(registered?.examples || []),
569
- docs: normalizeSchemaDocs(registered?.docs),
570
- skeleton: registered?.skeleton === undefined ? undefined : _.cloneDeep(registered.skeleton),
571
- dynamicHints: normalizeSchemaHints(registered?.dynamicHints),
572
- coverage: registered?.coverage || { status: 'unresolved', source: 'third-party' },
573
- exposure: registered?.exposure || 'public',
574
- abstract: registered?.abstract ?? false,
575
- allowDirectUse: registered?.allowDirectUse ?? true,
576
- suggestedUses: normalizeStringArray(registered?.suggestedUses),
577
- };
578
-
579
- for (const contribution of this.collectContextPatches(name, contextChain)) {
580
- applyModelSchemaPatch(resolved, contribution.patch, contribution.source, contribution.strict);
581
- }
582
-
583
- const frozen = deepFreezePlainGraph(resolved);
584
- this.resolvedModelCache.set(cacheKey, frozen);
585
- return frozen;
586
- }
587
-
588
- resolveModelSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): RegisteredModelSchema {
589
- return _.cloneDeep(this.resolveModelSchemaRef(use, contextChain));
590
- }
591
-
592
- private isPublicModel(model?: Pick<RegisteredModelSchema, 'exposure'>): boolean {
593
- return (model?.exposure || 'public') === 'public';
594
- }
595
-
596
- private isQueryableModel(model?: Pick<RegisteredModelSchema, 'abstract'>): boolean {
597
- return !!model && model.abstract !== true;
598
- }
599
-
600
- getSuggestedUses(use: string): string[] {
601
- const model = this.modelSchemas.get(String(use || '').trim());
602
- if (model?.suggestedUses?.length) {
603
- return normalizeStringArray(model.suggestedUses);
604
- }
605
- return this.listModelUses({ directUseOnly: true })
606
- .filter((item) => item !== use)
607
- .slice(0, 20);
608
- }
609
-
610
- hasQueryableModel(use: string): boolean {
611
- const model = this.modelSchemas.get(String(use || '').trim());
612
- return this.isQueryableModel(model);
613
- }
614
-
615
- isDirectUseAllowed(use: string): boolean {
616
- const model = this.modelSchemas.get(String(use || '').trim());
617
- return model ? this.isQueryableModel(model) && model.allowDirectUse !== false : true;
618
- }
619
-
620
- listPublicTreeRoots(): string[] {
621
- return Array.from(this.publicTreeRoots).sort();
622
- }
623
-
624
- listModelUses(options: { publicOnly?: boolean; directUseOnly?: boolean; queryableOnly?: boolean } = {}): string[] {
625
- const { publicOnly = false, directUseOnly = false, queryableOnly = false } = options;
626
- return Array.from(this.modelSchemas.values())
627
- .filter((model) => !publicOnly || (this.isPublicModel(model) && this.isQueryableModel(model)))
628
- .filter((model) => !queryableOnly || this.isQueryableModel(model))
629
- .filter((model) => !directUseOnly || (this.isQueryableModel(model) && model.allowDirectUse !== false))
630
- .map((model) => model.use)
631
- .sort();
632
- }
633
-
634
- getSchemaBundle(uses?: string[]): FlowSchemaBundleDocument {
635
- return {
636
- items: (Array.isArray(uses) && uses.length > 0 ? uses.filter((use) => this.hasQueryableModel(use)) : []).map(
637
- (use) => this.buildBundleNode(use, [], new Set<string>()),
638
- ),
639
- };
640
- }
641
-
642
- getModelDocument(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowSchemaDocument {
643
- return _.cloneDeep(this.getModelDocumentRef(use, contextChain));
644
- }
645
-
646
- getModelDocumentRef(use: string, contextChain: FlowSchemaContextEdge[] = []): Readonly<FlowSchemaDocument> {
647
- const cacheKey = this.createContextVisitKey(use, contextChain);
648
- const cached = this.modelDocumentCache.get(cacheKey);
649
- if (cached) {
650
- return cached;
651
- }
652
- const document = this.buildModelDocument(use, contextChain, new Set<string>());
653
- const frozen = deepFreezePlainGraph(document);
654
- this.modelDocumentCache.set(cacheKey, frozen);
655
- return frozen;
656
- }
657
-
658
- getPublicModelDocument(
659
- use: string,
660
- options: { detail?: FlowSchemaDetail; contextChain?: FlowSchemaContextEdge[] } = {},
661
- ): FlowSchemaPublicDocument {
662
- return _.cloneDeep(this.getPublicModelDocumentRef(use, options));
663
- }
664
-
665
- getPublicModelDocumentRef(
666
- use: string,
667
- options: { detail?: FlowSchemaDetail; contextChain?: FlowSchemaContextEdge[] } = {},
668
- ): Readonly<FlowSchemaPublicDocument> {
669
- const detail = options.detail === 'full' ? 'full' : 'compact';
670
- const contextChain = options.contextChain || [];
671
- const cacheKey = `${detail}::${this.createContextVisitKey(use, contextChain)}`;
672
- const cached = this.publicModelDocumentCache.get(cacheKey);
673
- if (cached) {
674
- return cached;
675
- }
676
-
677
- const document =
678
- detail === 'full'
679
- ? this.buildFullPublicModelDocument(use, contextChain)
680
- : this.buildCompactPublicModelDocument(use, contextChain);
681
- const frozen = deepFreezePlainGraph(document);
682
- this.publicModelDocumentCache.set(cacheKey, frozen);
683
- return frozen;
684
- }
685
-
686
- private buildFullPublicModelDocument(use: string, contextChain: FlowSchemaContextEdge[]): FlowSchemaPublicDocument {
687
- const document = this.getModelDocumentRef(use, contextChain);
688
- return {
689
- use: document.use,
690
- title: document.title,
691
- jsonSchema: document.jsonSchema,
692
- dynamicHints: document.dynamicHints,
693
- minimalExample: document.minimalExample,
694
- commonPatterns: document.commonPatterns,
695
- antiPatterns: document.antiPatterns,
696
- hash: document.hash,
697
- source: document.source,
698
- };
699
- }
700
-
701
- private buildCompactPublicModelDocument(
702
- use: string,
703
- contextChain: FlowSchemaContextEdge[],
704
- ): FlowSchemaPublicDocument {
705
- const resolved = this.resolveModelSchemaRef(use, contextChain);
706
- const jsonSchema = this.buildCompactModelSnapshotSchemaRef(use, contextChain);
707
- const minimalExample = _.cloneDeep(
708
- resolved?.docs?.minimalExample ??
709
- resolved?.examples?.[0] ??
710
- resolved?.skeleton ??
711
- buildSkeletonFromSchema(jsonSchema),
712
- );
713
-
714
- return {
715
- use,
716
- title: resolved?.title || use,
717
- jsonSchema,
718
- dynamicHints: [...this.getModelLocalDynamicHintsRef(use, contextChain)],
719
- minimalExample,
720
- commonPatterns: _.cloneDeep(resolved?.docs?.commonPatterns || []),
721
- antiPatterns: _.cloneDeep(resolved?.docs?.antiPatterns || []),
722
- hash: this.getCompactModelSchemaHash(use, contextChain),
723
- source: (resolved?.coverage || { source: 'third-party' as const }).source,
724
- };
725
- }
726
-
727
- private getModelLocalDynamicHintsRef(
728
- use: string,
729
- contextChain: FlowSchemaContextEdge[] = [],
730
- ): Readonly<FlowDynamicHint[]> {
731
- const cacheKey = this.createContextVisitKey(use, contextChain);
732
- const cached = this.modelLocalDynamicHintsCache.get(cacheKey);
733
- if (cached) {
734
- return cached;
735
- }
736
-
737
- const hints = this.buildModelLocalDynamicHints(use, contextChain);
738
- const frozen = deepFreezePlainGraph(hints);
739
- this.modelLocalDynamicHintsCache.set(cacheKey, frozen);
740
- return frozen;
741
- }
742
-
743
- private buildModelLocalDynamicHints(use: string, contextChain: FlowSchemaContextEdge[]): FlowDynamicHint[] {
744
- const resolved = this.resolveModelSchemaRef(use, contextChain);
745
- const baseCoverage = resolved.coverage || { status: 'unresolved', source: 'third-party' as const };
746
- const flowDiagnostics = this.collectFlowSchemaDiagnostics(use);
747
- const slotHints = Object.entries(resolved?.subModelSlots || {}).map(([slotKey, slot]) => {
748
- const allowedUses = this.resolveSlotAllowedUses(use, slotKey, slot);
749
- return createFlowHint(
750
- {
751
- kind: slot.dynamic || slot.fieldBindingContext ? 'dynamic-children' : 'manual-schema-required',
752
- path: `${use}.subModels.${slotKey}`,
753
- message:
754
- slot.description ||
755
- `${use}.subModels.${slotKey} accepts ${slot.type}${
756
- allowedUses.length ? `: ${allowedUses.join(', ')}` : ''
757
- }.`,
758
- },
759
- {
760
- slotRules: {
761
- slotKey,
762
- type: slot.type,
763
- allowedUses,
764
- },
765
- },
766
- );
767
- });
768
- const coverage = this.buildDocumentCoverage(baseCoverage, flowDiagnostics.statuses);
769
-
770
- return normalizeSchemaHints([
771
- ...(resolved?.dynamicHints || []),
772
- ...slotHints,
773
- ...flowDiagnostics.hints,
774
- ...(coverage.status === 'unresolved'
775
- ? [
776
- createFlowHint(
777
- {
778
- kind: 'unresolved-model' as const,
779
- path: use,
780
- message: `${use} has no registered server-safe schema contribution yet.`,
781
- },
782
- {
783
- unresolvedReason: 'missing-model-contribution',
784
- recommendedFallback: { use },
785
- },
786
- ),
787
- ]
788
- : []),
789
- ]);
790
- }
791
-
792
- getModelSchemaHash(use: string, contextChain: FlowSchemaContextEdge[] = []): string {
793
- const cacheKey = this.createContextVisitKey(use, contextChain);
794
- const cached = this.modelSchemaHashCache.get(cacheKey);
795
- if (cached) {
796
- return cached;
797
- }
798
- const hash = hashString(stableStringify(this.buildModelSnapshotSchemaRef(use, contextChain)));
799
- this.modelSchemaHashCache.set(cacheKey, hash);
800
- return hash;
801
- }
802
-
803
- getCompactModelSchemaHash(use: string, contextChain: FlowSchemaContextEdge[] = []): string {
804
- const cacheKey = this.createContextVisitKey(use, contextChain);
805
- const cached = this.compactModelSchemaHashCache.get(cacheKey);
806
- if (cached) {
807
- return cached;
808
- }
809
- const hash = hashString(stableStringify(this.buildCompactModelSnapshotSchemaRef(use, contextChain)));
810
- this.compactModelSchemaHashCache.set(cacheKey, hash);
811
- return hash;
812
- }
813
-
814
- private createSlotUseExpansionKey(parentUse: string, slotKey: string): string {
815
- return `${parentUse}::${slotKey}`;
816
- }
817
-
818
- private getSlotUseExpansionUses(parentUse: string, slotKey: string): string[] {
819
- return this.slotUseExpansions.get(this.createSlotUseExpansionKey(parentUse, slotKey)) || [];
820
- }
821
-
822
- resolveSlotAllowedUses(parentUse: string, slotKey: string, slot?: FlowSubModelSlotSchema): string[] {
823
- if (!slot) {
824
- return [];
825
- }
826
-
827
- if (slot.fieldBindingContext) {
828
- return _.uniq(this.resolveFieldBindingCandidates(slot.fieldBindingContext).map((candidate) => candidate.use));
829
- }
830
-
831
- return _.uniq([...collectAllowedUses(slot), ...this.getSlotUseExpansionUses(parentUse, slotKey)]);
832
- }
833
-
834
- private buildModelDocument(
835
- use: string,
836
- contextChain: FlowSchemaContextEdge[],
837
- visited: Set<string>,
838
- ): FlowSchemaDocument {
839
- const resolved = this.resolveModelSchemaRef(use, contextChain);
840
- const baseCoverage = resolved.coverage || { status: 'unresolved', source: 'third-party' as const };
841
- const flowDiagnostics = this.collectFlowSchemaDiagnostics(use);
842
- const coverage = this.buildDocumentCoverage(baseCoverage, flowDiagnostics.statuses);
843
- const dynamicHints = normalizeSchemaHints([
844
- ...this.getModelLocalDynamicHintsRef(use, contextChain),
845
- ...this.collectNestedDocumentHints(use, contextChain, visited),
846
- ]);
847
-
848
- const jsonSchema = this.buildModelSnapshotSchemaRef(use, contextChain);
849
- const skeleton = _.cloneDeep(resolved?.skeleton ?? buildSkeletonFromSchema(jsonSchema));
850
- const minimalExample = _.cloneDeep(resolved?.docs?.minimalExample ?? resolved?.examples?.[0] ?? skeleton);
851
- const hash = this.getModelSchemaHash(use, contextChain);
852
-
853
- return {
854
- use,
855
- title: resolved?.title || use,
856
- jsonSchema,
857
- coverage,
858
- dynamicHints,
859
- examples: resolved?.examples || [],
860
- minimalExample,
861
- commonPatterns: _.cloneDeep(resolved?.docs?.commonPatterns || []),
862
- antiPatterns: _.cloneDeep(resolved?.docs?.antiPatterns || []),
863
- skeleton,
864
- hash,
865
- source: coverage.source,
866
- };
867
- }
868
-
869
- private buildBundleNode(
870
- use: string,
871
- contextChain: FlowSchemaContextEdge[],
872
- visited: Set<string>,
873
- compatibility?: FlowFieldModelCompatibility,
874
- ): FlowSchemaBundleNode {
875
- const resolved = this.resolveModelSchemaRef(use, contextChain);
876
- const visitKey = this.createContextVisitKey(use, contextChain);
877
- const node: FlowSchemaBundleNode = {
878
- use,
879
- title: resolved?.title || use,
880
- };
881
-
882
- if (compatibility) {
883
- node.compatibility = _.cloneDeep(compatibility);
884
- }
885
-
886
- if (visited.has(visitKey) || this.isContextCycle(use, contextChain)) {
887
- return node;
888
- }
889
-
890
- visited.add(visitKey);
891
- try {
892
- const subModelCatalog = this.buildBundleSubModelCatalog(use, resolved?.subModelSlots, contextChain, visited);
893
- if (subModelCatalog && Object.keys(subModelCatalog).length > 0) {
894
- node.subModelCatalog = subModelCatalog;
895
- }
896
- return node;
897
- } finally {
898
- visited.delete(visitKey);
899
- }
900
- }
901
-
902
- private buildBundleSubModelCatalog(
903
- parentUse: string,
904
- slots: Record<string, FlowSubModelSlotSchema> | undefined,
905
- contextChain: FlowSchemaContextEdge[],
906
- visited: Set<string>,
907
- ): Record<string, FlowSchemaBundleSlotCatalog> | undefined {
908
- if (!slots || Object.keys(slots).length === 0) {
909
- return undefined;
910
- }
911
-
912
- const entries = Object.entries(slots).map(([slotKey, slot]) => {
913
- const edgeBase = {
914
- parentUse,
915
- slotKey,
916
- };
917
- const fieldBindingCandidates = slot.fieldBindingContext
918
- ? this.resolveFieldBindingCandidates(slot.fieldBindingContext)
919
- : [];
920
- const allowedUses =
921
- fieldBindingCandidates.length > 0
922
- ? _.uniq(fieldBindingCandidates.map((item) => item.use))
923
- : this.resolveSlotAllowedUses(parentUse, slotKey, slot);
924
- const catalog: FlowSchemaBundleSlotCatalog = {
925
- type: slot.type,
926
- candidates:
927
- fieldBindingCandidates.length > 0
928
- ? fieldBindingCandidates.map((candidate) =>
929
- this.buildBundleNode(
930
- candidate.use,
931
- [
932
- ...contextChain,
933
- {
934
- ...edgeBase,
935
- childUse: candidate.use,
936
- },
937
- ],
938
- visited,
939
- candidate.compatibility,
940
- ),
941
- )
942
- : allowedUses.map((childUse) =>
943
- this.buildBundleNode(
944
- childUse,
945
- [
946
- ...contextChain,
947
- {
948
- ...edgeBase,
949
- childUse,
950
- },
951
- ],
952
- visited,
953
- ),
954
- ),
955
- };
956
-
957
- if (slot.required !== undefined) {
958
- catalog.required = slot.required;
959
- }
960
-
961
- if (slot.type === 'array' && typeof slot.minItems === 'number') {
962
- catalog.minItems = slot.minItems;
963
- }
964
-
965
- if (allowedUses.length === 0) {
966
- catalog.open = true;
967
- }
968
-
969
- return [slotKey, catalog] as const;
970
- });
971
-
972
- return entries.length ? Object.fromEntries(entries) : undefined;
973
- }
974
-
975
- private createContextVisitKey(use: string, contextChain: FlowSchemaContextEdge[]): string {
976
- return `${use}::${stableStringify(contextChain)}`;
977
- }
978
-
979
- private isContextCycle(use: string, contextChain: FlowSchemaContextEdge[]): boolean {
980
- if (!contextChain.length) {
981
- return false;
982
- }
983
-
984
- const ancestorUses = [contextChain[0].parentUse, ...contextChain.slice(0, -1).map((edge) => edge.childUse)];
985
- return ancestorUses.includes(use);
986
- }
987
-
988
- buildModelSnapshotSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
989
- return _.cloneDeep(this.buildModelSnapshotSchemaRef(use, contextChain));
990
- }
991
-
992
- buildCompactModelSnapshotSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
993
- return _.cloneDeep(this.buildCompactModelSnapshotSchemaRef(use, contextChain));
994
- }
995
-
996
- private buildModelSnapshotSchemaRef(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
997
- const cacheKey = this.createContextVisitKey(use, contextChain);
998
- const cached = this.modelSnapshotSchemaCache.get(cacheKey);
999
- if (cached) {
1000
- return cached;
1001
- }
1002
- const schema = this.buildModelSnapshotSchemaInternal(use, contextChain, new Set<string>());
1003
- const frozen = deepFreezePlainGraph(schema);
1004
- this.modelSnapshotSchemaCache.set(cacheKey, frozen);
1005
- return frozen;
1006
- }
1007
-
1008
- private buildCompactModelSnapshotSchemaRef(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1009
- const cacheKey = this.createContextVisitKey(use, contextChain);
1010
- const cached = this.compactModelSnapshotSchemaCache.get(cacheKey);
1011
- if (cached) {
1012
- return cached;
1013
- }
1014
- const schema = this.buildCompactModelSnapshotSchemaInternal(use, contextChain);
1015
- const frozen = deepFreezePlainGraph(schema);
1016
- this.compactModelSnapshotSchemaCache.set(cacheKey, frozen);
1017
- return frozen;
1018
- }
1019
-
1020
- private buildCompactModelSnapshotSchemaInternal(use: string, contextChain: FlowSchemaContextEdge[]): FlowJsonSchema {
1021
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1022
- const subModelsSchema = this.buildCompactSubModelsSchemaFromSlots(use, resolved?.subModelSlots, contextChain);
1023
- return this.buildSnapshotShellSchema(use, resolved, subModelsSchema);
1024
- }
1025
-
1026
- private buildModelSnapshotSchemaInternal(
1027
- use: string,
1028
- contextChain: FlowSchemaContextEdge[],
1029
- visited: Set<string>,
1030
- ): FlowJsonSchema {
1031
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1032
- const visitKey = this.createContextVisitKey(use, contextChain);
1033
-
1034
- if (visited.has(visitKey) || this.isContextCycle(use, contextChain)) {
1035
- return this.buildTruncatedSnapshotSchema(use, resolved);
1036
- }
1037
-
1038
- visited.add(visitKey);
1039
- try {
1040
- return this.buildSnapshotSchemaFromResolved(use, resolved, contextChain, visited);
1041
- } finally {
1042
- visited.delete(visitKey);
1043
- }
1044
- }
1045
-
1046
- private buildSnapshotSchemaFromResolved(
1047
- use: string | undefined,
1048
- resolved: RegisteredModelSchema,
1049
- contextChain: FlowSchemaContextEdge[],
1050
- visited: Set<string>,
1051
- ): FlowJsonSchema {
1052
- const subModelsSchema = this.buildSubModelsSchemaFromSlots(
1053
- use || '',
1054
- resolved?.subModelSlots,
1055
- contextChain,
1056
- visited,
1057
- );
1058
- return this.buildSnapshotShellSchema(use, resolved, subModelsSchema);
1059
- }
1060
-
1061
- private buildSnapshotShellSchema(
1062
- use: string | undefined,
1063
- resolved: RegisteredModelSchema,
1064
- subModelsSchema: FlowJsonSchema,
1065
- ): FlowJsonSchema {
1066
- const flowRegistrySchema = resolved?.flowRegistrySchema || { type: 'object', additionalProperties: true };
1067
- const stepParamsSchema = resolved?.stepParamsSchema || { type: 'object', additionalProperties: true };
1068
- return {
1069
- $schema: JSON_SCHEMA_DRAFT_07,
1070
- type: 'object',
1071
- properties: {
1072
- uid: { type: 'string' },
1073
- use: use ? { const: use } : { type: 'string' },
1074
- async: { type: 'boolean' },
1075
- parentId: { type: 'string' },
1076
- subKey: { type: 'string' },
1077
- subType: { type: 'string', enum: ['object', 'array'] },
1078
- sortIndex: { type: 'number' },
1079
- stepParams: stepParamsSchema,
1080
- flowRegistry: mergeSchemas(flowRegistrySchema, resolved?.flowRegistrySchemaPatch) || {
1081
- type: 'object',
1082
- additionalProperties: true,
1083
- },
1084
- subModels: subModelsSchema,
1085
- },
1086
- required: ['uid', 'use'],
1087
- additionalProperties: true,
1088
- };
1089
- }
1090
-
1091
- private buildTruncatedSnapshotSchema(use: string | undefined, resolved: RegisteredModelSchema): FlowJsonSchema {
1092
- return this.buildSnapshotShellSchema(use, resolved, {
1093
- type: 'object',
1094
- additionalProperties: true,
1095
- });
1096
- }
1097
-
1098
- buildStaticFlowRegistrySchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1099
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1100
- const schema = mergeSchemas(resolved?.flowRegistrySchema, resolved?.flowRegistrySchemaPatch);
1101
- if (schema) {
1102
- return _.cloneDeep(schema);
1103
- }
1104
- return { type: 'object', additionalProperties: true };
1105
- }
1106
-
1107
- buildStaticStepParamsSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1108
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1109
- if (resolved?.stepParamsSchema) {
1110
- return _.cloneDeep(resolved.stepParamsSchema);
1111
- }
1112
- return { type: 'object', additionalProperties: true };
1113
- }
1114
-
1115
- buildSubModelsSchema(use: string, contextChain: FlowSchemaContextEdge[] = []): FlowJsonSchema {
1116
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1117
- return this.buildSubModelsSchemaFromSlots(use, resolved?.subModelSlots, contextChain, new Set<string>());
1118
- }
1119
-
1120
- private buildCompactSubModelsSchemaFromSlots(
1121
- parentUse: string,
1122
- slots: Record<string, FlowSubModelSlotSchema> | undefined,
1123
- contextChain: FlowSchemaContextEdge[] = [],
1124
- ): FlowJsonSchema {
1125
- if (!slots || Object.keys(slots).length === 0) {
1126
- return { type: 'object', additionalProperties: true };
1127
- }
1128
-
1129
- const properties: Record<string, FlowJsonSchema> = {};
1130
- const required: string[] = [];
1131
- for (const [slotKey, slot] of Object.entries(slots)) {
1132
- const itemSchema = this.buildCompactSlotTargetSchema(parentUse, slotKey, slot, contextChain);
1133
- properties[slotKey] =
1134
- slot.type === 'array'
1135
- ? {
1136
- type: 'array',
1137
- ...(typeof slot.minItems === 'number' ? { minItems: slot.minItems } : {}),
1138
- items: itemSchema,
1139
- }
1140
- : itemSchema;
1141
- if (slot.required) {
1142
- required.push(slotKey);
1143
- }
1144
- }
1145
-
1146
- return {
1147
- type: 'object',
1148
- properties,
1149
- ...(required.length ? { required } : {}),
1150
- additionalProperties: true,
1151
- };
1152
- }
1153
-
1154
- private buildSubModelsSchemaFromSlots(
1155
- parentUse: string,
1156
- slots: Record<string, FlowSubModelSlotSchema> | undefined,
1157
- contextChain: FlowSchemaContextEdge[] = [],
1158
- visited: Set<string>,
1159
- ): FlowJsonSchema {
1160
- if (!slots || Object.keys(slots).length === 0) {
1161
- return { type: 'object', additionalProperties: true };
1162
- }
1163
- const properties: Record<string, FlowJsonSchema> = {};
1164
- const required: string[] = [];
1165
- for (const [slotKey, slot] of Object.entries(slots)) {
1166
- const itemSchema = this.buildSlotTargetSchema(parentUse, slotKey, slot, contextChain, visited);
1167
- properties[slotKey] =
1168
- slot.type === 'array'
1169
- ? {
1170
- type: 'array',
1171
- ...(typeof slot.minItems === 'number' ? { minItems: slot.minItems } : {}),
1172
- items: itemSchema,
1173
- }
1174
- : itemSchema;
1175
- if (slot.required) {
1176
- required.push(slotKey);
1177
- }
1178
- }
1179
- return {
1180
- type: 'object',
1181
- properties,
1182
- ...(required.length ? { required } : {}),
1183
- additionalProperties: true,
1184
- };
1185
- }
1186
-
1187
- private buildInferredFlowRegistrySchema(use: string): FlowJsonSchema | undefined {
1188
- const registered = this.modelSchemas.get(use);
1189
- const modelClass = registered?.modelClass as any;
1190
- const flowsMap = modelClass?.globalFlowRegistry?.getFlows?.() as Map<string, any> | undefined;
1191
- if (!flowsMap?.size) {
1192
- return undefined;
1193
- }
1194
-
1195
- const properties: Record<string, FlowJsonSchema> = {};
1196
- for (const [flowKey, flowDef] of flowsMap.entries()) {
1197
- const stepProperties: Record<string, FlowJsonSchema> = {};
1198
- const steps = flowDef?.steps || {};
1199
- for (const [stepKey, stepDef] of Object.entries(steps)) {
1200
- stepProperties[stepKey] = this.buildStepDefinitionSchema(stepDef as StepDefinition);
1201
- }
1202
- properties[flowKey] = {
1203
- type: 'object',
1204
- properties: {
1205
- key: { type: 'string' },
1206
- title: { type: 'string' },
1207
- manual: { type: 'boolean' },
1208
- sort: { type: 'number' },
1209
- on: this.buildFlowOnSchema(),
1210
- steps: {
1211
- type: 'object',
1212
- properties: stepProperties,
1213
- additionalProperties: false,
1214
- },
1215
- },
1216
- additionalProperties: true,
1217
- };
1218
- }
1219
- return {
1220
- type: 'object',
1221
- properties,
1222
- additionalProperties: true,
1223
- };
1224
- }
1225
-
1226
- private buildInferredStepParamsSchema(use: string): FlowJsonSchema | undefined {
1227
- const registered = this.modelSchemas.get(use);
1228
- const modelClass = registered?.modelClass as any;
1229
- const flowsMap = modelClass?.globalFlowRegistry?.getFlows?.() as Map<string, any> | undefined;
1230
- if (!flowsMap?.size) {
1231
- return undefined;
1232
- }
1233
-
1234
- const properties: Record<string, FlowJsonSchema> = {};
1235
- for (const [flowKey, flowDef] of flowsMap.entries()) {
1236
- const stepProperties: Record<string, FlowJsonSchema> = {};
1237
- const steps = flowDef?.steps || {};
1238
- for (const [stepKey, stepDef] of Object.entries(steps)) {
1239
- const resolved = this.resolveStepParamsSchema(stepDef as StepDefinition, `${use}.${flowKey}.${stepKey}`);
1240
- stepProperties[stepKey] = resolved.schema || { type: 'object', additionalProperties: true };
1241
- }
1242
- properties[flowKey] = {
1243
- type: 'object',
1244
- properties: stepProperties,
1245
- additionalProperties: false,
1246
- };
1247
- }
1248
- return {
1249
- type: 'object',
1250
- properties,
1251
- additionalProperties: true,
1252
- };
1253
- }
1254
-
1255
- buildFlowOnSchema(): FlowJsonSchema {
1256
- return {
1257
- anyOf: [
1258
- { type: 'string' },
1259
- {
1260
- type: 'object',
1261
- properties: {
1262
- eventName: { type: 'string' },
1263
- defaultParams: { type: 'object', additionalProperties: true },
1264
- phase: {
1265
- type: 'string',
1266
- enum: ['beforeAllFlows', 'afterAllFlows', 'beforeFlow', 'afterFlow', 'beforeStep', 'afterStep'],
1267
- },
1268
- flowKey: { type: 'string' },
1269
- stepKey: { type: 'string' },
1270
- },
1271
- required: ['eventName'],
1272
- additionalProperties: true,
1273
- },
1274
- ],
1275
- };
1276
- }
1277
-
1278
- buildStepDefinitionSchema(step: StepDefinition): FlowJsonSchema {
1279
- return {
1280
- type: 'object',
1281
- properties: {
1282
- key: { type: 'string' },
1283
- flowKey: { type: 'string' },
1284
- title: { type: 'string' },
1285
- use: { type: 'string' },
1286
- sort: { type: 'number' },
1287
- preset: { type: 'boolean' },
1288
- isAwait: { type: 'boolean' },
1289
- manual: { type: 'boolean' },
1290
- on: this.buildFlowOnSchema(),
1291
- defaultParams: { type: ['object', 'array', 'string', 'number', 'boolean', 'null'] as any },
1292
- paramsSchemaOverride: { type: 'object', additionalProperties: true },
1293
- },
1294
- additionalProperties: true,
1295
- };
1296
- }
1297
-
1298
- resolveStepParamsSchema(step: StepDefinition, path: string): StepSchemaResolution {
1299
- if (step.paramsSchemaOverride) {
1300
- return {
1301
- schema: _.cloneDeep(step.paramsSchemaOverride),
1302
- hints: normalizeSchemaHints(step.schemaDocs?.dynamicHints),
1303
- coverage: 'manual',
1304
- };
1305
- }
1306
-
1307
- if (step.use) {
1308
- const action = this.getAction(step.use);
1309
- if (action?.schema) {
1310
- return {
1311
- schema: _.cloneDeep(action.schema),
1312
- hints: normalizeSchemaHints([...(action.dynamicHints || []), ...(step.schemaDocs?.dynamicHints || [])]),
1313
- coverage:
1314
- action.coverage.status === 'auto' || action.coverage.status === 'manual'
1315
- ? action.coverage.status
1316
- : action.coverage.status === 'unresolved'
1317
- ? 'unresolved'
1318
- : 'mixed',
1319
- };
1320
- }
1321
- return {
1322
- schema: { type: 'object', additionalProperties: true },
1323
- hints: [
1324
- createFlowHint(
1325
- {
1326
- kind: 'unresolved-action',
1327
- path,
1328
- message: `${path} references unresolved action "${step.use}".`,
1329
- },
1330
- {
1331
- unresolvedReason: 'missing-action-contribution',
1332
- recommendedFallback: { use: step.use, params: {} },
1333
- },
1334
- ),
1335
- ...(step.schemaDocs?.dynamicHints || []),
1336
- ],
1337
- coverage: 'unresolved',
1338
- };
1339
- }
1340
-
1341
- const inferred = inferParamsSchemaFromUiSchema(path, step.uiSchema as any, path);
1342
- return {
1343
- schema: mergeSchemas(inferred.schema, step.paramsSchemaPatch),
1344
- hints: normalizeSchemaHints([...(inferred.hints || []), ...(step.schemaDocs?.dynamicHints || [])]),
1345
- coverage: step.paramsSchemaPatch ? 'mixed' : inferred.coverage,
1346
- };
1347
- }
1348
-
1349
- private buildSlotTargetSchema(
1350
- parentUse: string,
1351
- slotKey: string,
1352
- slot: FlowSubModelSlotSchema,
1353
- contextChain: FlowSchemaContextEdge[],
1354
- visited: Set<string>,
1355
- ): FlowJsonSchema {
1356
- if (slot.fieldBindingContext) {
1357
- const candidateUses = _.uniq(
1358
- this.resolveFieldBindingCandidates(slot.fieldBindingContext).map((item) => item.use),
1359
- );
1360
- if (candidateUses.length > 0) {
1361
- const candidateSchemas = candidateUses.map((use) =>
1362
- this.buildModelSnapshotSchemaInternal(
1363
- use,
1364
- [
1365
- ...contextChain,
1366
- {
1367
- parentUse,
1368
- slotKey,
1369
- childUse: use,
1370
- },
1371
- ],
1372
- visited,
1373
- ),
1374
- );
1375
-
1376
- if (slot.schema) {
1377
- return {
1378
- anyOf: [...candidateSchemas, _.cloneDeep(slot.schema)],
1379
- };
1380
- }
1381
-
1382
- return candidateSchemas.length === 1 ? candidateSchemas[0] : { oneOf: candidateSchemas };
1383
- }
1384
- }
1385
-
1386
- const allowedUses = this.resolveSlotAllowedUses(parentUse, slotKey, slot);
1387
- if (allowedUses.length > 0) {
1388
- const knownSchemas = allowedUses.map((use) =>
1389
- this.buildModelSnapshotSchemaInternal(
1390
- use,
1391
- [
1392
- ...contextChain,
1393
- {
1394
- parentUse,
1395
- slotKey,
1396
- childUse: use,
1397
- },
1398
- ],
1399
- visited,
1400
- ),
1401
- );
1402
- if (slot.schema) {
1403
- return {
1404
- anyOf: [...knownSchemas, _.cloneDeep(slot.schema)],
1405
- };
1406
- }
1407
- if (knownSchemas.length === 1) {
1408
- return knownSchemas[0];
1409
- }
1410
- return {
1411
- oneOf: knownSchemas,
1412
- };
1413
- }
1414
- if (slot.childSchemaPatch || slot.descendantSchemaPatches?.length) {
1415
- return this.buildAnonymousSlotSnapshotSchema(parentUse, slotKey, slot, contextChain, visited);
1416
- }
1417
- if (slot.schema && !slot.childSchemaPatch && !slot.descendantSchemaPatches?.length) {
1418
- return _.cloneDeep(slot.schema);
1419
- }
1420
- return { type: 'object', additionalProperties: true };
1421
- }
1422
-
1423
- private buildCompactSlotTargetSchema(
1424
- parentUse: string,
1425
- slotKey: string,
1426
- slot: FlowSubModelSlotSchema,
1427
- contextChain: FlowSchemaContextEdge[],
1428
- ): FlowJsonSchema {
1429
- if (slot.fieldBindingContext) {
1430
- const candidateUses = _.uniq(
1431
- this.resolveFieldBindingCandidates(slot.fieldBindingContext).map((item) => item.use),
1432
- );
1433
- if (candidateUses.length > 0) {
1434
- const candidateSchemas = candidateUses.map((use) =>
1435
- this.buildCompactSlotCandidateSchema(use, [
1436
- ...contextChain,
1437
- {
1438
- parentUse,
1439
- slotKey,
1440
- childUse: use,
1441
- },
1442
- ]),
1443
- );
1444
-
1445
- if (slot.schema) {
1446
- return {
1447
- anyOf: [...candidateSchemas, _.cloneDeep(slot.schema)],
1448
- };
1449
- }
1450
-
1451
- return candidateSchemas.length === 1 ? candidateSchemas[0] : { oneOf: candidateSchemas };
1452
- }
1453
- }
1454
-
1455
- const allowedUses = this.resolveSlotAllowedUses(parentUse, slotKey, slot);
1456
- if (allowedUses.length > 0) {
1457
- const knownSchemas = allowedUses.map((use) =>
1458
- this.buildCompactSlotCandidateSchema(use, [
1459
- ...contextChain,
1460
- {
1461
- parentUse,
1462
- slotKey,
1463
- childUse: use,
1464
- },
1465
- ]),
1466
- );
1467
-
1468
- if (slot.schema) {
1469
- return {
1470
- anyOf: [...knownSchemas, _.cloneDeep(slot.schema)],
1471
- };
1472
- }
1473
-
1474
- return knownSchemas.length === 1 ? knownSchemas[0] : { oneOf: knownSchemas };
1475
- }
1476
-
1477
- if (slot.childSchemaPatch || slot.descendantSchemaPatches?.length) {
1478
- return this.buildCompactAnonymousSlotSnapshotSchema(parentUse, slotKey, slot);
1479
- }
1480
-
1481
- if (slot.schema && !slot.childSchemaPatch && !slot.descendantSchemaPatches?.length) {
1482
- return _.cloneDeep(slot.schema);
1483
- }
1484
-
1485
- return { type: 'object', additionalProperties: true };
1486
- }
1487
-
1488
- private buildCompactSlotCandidateSchema(use: string, contextChain: FlowSchemaContextEdge[]): FlowJsonSchema {
1489
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1490
- return this.buildTruncatedSnapshotSchema(use, resolved);
1491
- }
1492
-
1493
- private buildCompactAnonymousSlotSnapshotSchema(
1494
- parentUse: string,
1495
- slotKey: string,
1496
- slot: FlowSubModelSlotSchema,
1497
- ): FlowJsonSchema {
1498
- const anonymousResolved = this.createAnonymousResolvedSchema(parentUse, slotKey, slot);
1499
- return this.buildTruncatedSnapshotSchema(undefined, anonymousResolved);
1500
- }
1501
-
1502
- private buildAnonymousSlotSnapshotSchema(
1503
- parentUse: string,
1504
- slotKey: string,
1505
- slot: FlowSubModelSlotSchema,
1506
- contextChain: FlowSchemaContextEdge[],
1507
- visited: Set<string>,
1508
- ): FlowJsonSchema {
1509
- const anonymousResolved = this.createAnonymousResolvedSchema(parentUse, slotKey, slot);
1510
- return this.buildSnapshotSchemaFromResolved(
1511
- '',
1512
- anonymousResolved,
1513
- [
1514
- ...contextChain,
1515
- {
1516
- parentUse,
1517
- slotKey,
1518
- childUse: '',
1519
- },
1520
- ],
1521
- visited,
1522
- );
1523
- }
1524
-
1525
- private createAnonymousResolvedSchema(
1526
- parentUse: string,
1527
- slotKey: string,
1528
- slot: FlowSubModelSlotSchema,
1529
- ): RegisteredModelSchema {
1530
- const anonymousResolved: RegisteredModelSchema = {
1531
- use: '',
1532
- title: slot.description || `${parentUse || 'AnonymousModel'}.${slotKey}`,
1533
- examples: [],
1534
- docs: normalizeSchemaDocs(),
1535
- dynamicHints: [],
1536
- coverage: {
1537
- status: 'unresolved',
1538
- source: 'third-party',
1539
- },
1540
- exposure: 'internal',
1541
- abstract: false,
1542
- allowDirectUse: true,
1543
- suggestedUses: [],
1544
- };
1545
-
1546
- const directPatch = resolveChildSchemaPatch(slot, '');
1547
- if (directPatch) {
1548
- applyModelSchemaPatch(anonymousResolved, directPatch, 'third-party');
1549
- }
1550
-
1551
- return anonymousResolved;
1552
- }
1553
-
1554
- private collectNestedDocumentHints(
1555
- use: string,
1556
- contextChain: FlowSchemaContextEdge[],
1557
- visited: Set<string>,
1558
- ): FlowDynamicHint[] {
1559
- const visitKey = this.createContextVisitKey(use, contextChain);
1560
- if (visited.has(visitKey) || this.isContextCycle(use, contextChain)) {
1561
- return [];
1562
- }
1563
-
1564
- visited.add(visitKey);
1565
- try {
1566
- const resolved = this.resolveModelSchemaRef(use, contextChain);
1567
- const hints: FlowDynamicHint[] = [];
1568
- for (const [slotKey, slot] of Object.entries(resolved.subModelSlots || {})) {
1569
- const childUses = this.resolveSlotAllowedUses(use, slotKey, slot);
1570
- for (const childUse of childUses) {
1571
- const childContext = [
1572
- ...contextChain,
1573
- {
1574
- parentUse: use,
1575
- slotKey,
1576
- childUse,
1577
- },
1578
- ];
1579
- const childDocument = this.buildModelDocument(childUse, childContext, visited);
1580
- const basePath = `${use}.subModels.${slotKey}`;
1581
- hints.push(...childDocument.dynamicHints.map((hint) => this.prefixNestedHint(hint, basePath, childUse)));
1582
- }
1583
- }
1584
- return normalizeSchemaHints(hints);
1585
- } finally {
1586
- visited.delete(visitKey);
1587
- }
1588
- }
1589
-
1590
- private prefixNestedHint(hint: FlowDynamicHint, basePath: string, childUse: string): FlowDynamicHint {
1591
- if (!hint.path) {
1592
- return {
1593
- ...hint,
1594
- path: basePath,
1595
- };
1596
- }
1597
-
1598
- if (hint.path === childUse) {
1599
- return {
1600
- ...hint,
1601
- path: basePath,
1602
- };
1603
- }
1604
-
1605
- if (hint.path.startsWith(`${childUse}.`)) {
1606
- return {
1607
- ...hint,
1608
- path: `${basePath}.${hint.path.slice(childUse.length + 1)}`,
1609
- };
1610
- }
1611
-
1612
- return {
1613
- ...hint,
1614
- path: `${basePath}.${hint.path}`,
1615
- };
1616
- }
1617
-
1618
- private collectContextPatches(use: string, contextChain: FlowSchemaContextEdge[]): ModelPatchContribution[] {
1619
- if (!contextChain.length) {
1620
- return [];
1621
- }
1622
-
1623
- const contributions: ModelPatchContribution[] = [];
1624
- const targetEdgeIndex = contextChain.length - 1;
1625
-
1626
- for (let index = 0; index < contextChain.length; index++) {
1627
- const edge = contextChain[index];
1628
- const parentContext = contextChain.slice(0, index);
1629
- const parentResolved = this.resolveModelSchemaRef(edge.parentUse, parentContext);
1630
- const slot = parentResolved.subModelSlots?.[edge.slotKey];
1631
- if (!slot) {
1632
- continue;
1633
- }
1634
-
1635
- const remainingEdges = contextChain.slice(index + 1);
1636
- for (const patch of slot.descendantSchemaPatches || []) {
1637
- if (matchesDescendantSchemaPatch(patch, remainingEdges)) {
1638
- contributions.push({
1639
- patch: patch.patch,
1640
- source: parentResolved.coverage.source,
1641
- strict: parentResolved.coverage.strict,
1642
- });
1643
- }
1644
- }
1645
-
1646
- if (index === targetEdgeIndex) {
1647
- const directPatch = resolveChildSchemaPatch(slot, use);
1648
- if (directPatch) {
1649
- contributions.push({
1650
- patch: directPatch,
1651
- source: parentResolved.coverage.source,
1652
- strict: parentResolved.coverage.strict,
1653
- });
1654
- }
1655
- }
1656
- }
1657
-
1658
- return contributions;
1659
- }
1660
-
1661
- private collectModelDynamicHints(use: string, modelClass: ModelConstructor, meta: FlowModelMeta): FlowDynamicHint[] {
1662
- const hints: FlowDynamicHint[] = [];
1663
- if (typeof meta.children === 'function') {
1664
- hints.push(
1665
- createFlowHint(
1666
- {
1667
- kind: 'dynamic-children',
1668
- path: `${use}.meta.children`,
1669
- message: `${use} uses function-based children and only static slot hints are available.`,
1670
- },
1671
- {
1672
- unresolvedReason: 'function-children',
1673
- },
1674
- ),
1675
- );
1676
- }
1677
- if (typeof meta.createModelOptions === 'function') {
1678
- hints.push(
1679
- createFlowHint(
1680
- {
1681
- kind: 'dynamic-children',
1682
- path: `${use}.meta.createModelOptions`,
1683
- message: `${use} uses function-based createModelOptions and may need manual sub-model slot schema.`,
1684
- },
1685
- {
1686
- unresolvedReason: 'function-create-model-options',
1687
- },
1688
- ),
1689
- );
1690
- }
1691
- if (typeof (modelClass as any).defineChildren === 'function') {
1692
- hints.push(
1693
- createFlowHint(
1694
- {
1695
- kind: 'dynamic-children',
1696
- path: `${use}.defineChildren`,
1697
- message: `${use} defines dynamic children. Schema generation only keeps static baseline.`,
1698
- },
1699
- {
1700
- unresolvedReason: 'runtime-define-children',
1701
- },
1702
- ),
1703
- );
1704
- }
1705
- return hints;
1706
- }
1707
-
1708
- private collectFlowSchemaDiagnostics(use: string): {
1709
- hints: FlowDynamicHint[];
1710
- statuses: FlowSchemaCoverage['status'][];
1711
- } {
1712
- const registered = this.modelSchemas.get(use);
1713
- const modelClass = registered?.modelClass as any;
1714
- const flowsMap = modelClass?.globalFlowRegistry?.getFlows?.() as Map<string, any> | undefined;
1715
- if (!flowsMap?.size) {
1716
- return {
1717
- hints: [],
1718
- statuses: [],
1719
- };
1720
- }
1721
-
1722
- const hints: FlowDynamicHint[] = [];
1723
- const statuses: FlowSchemaCoverage['status'][] = [];
1724
- for (const [flowKey, flowDef] of flowsMap.entries()) {
1725
- for (const [stepKey, stepDef] of Object.entries(flowDef?.steps || {})) {
1726
- const resolved = this.resolveStepParamsSchema(stepDef as StepDefinition, `${use}.${flowKey}.${stepKey}`);
1727
- if (resolved.hints?.length) {
1728
- hints.push(...resolved.hints);
1729
- }
1730
- statuses.push(resolved.coverage);
1731
- }
1732
- }
1733
-
1734
- return {
1735
- hints: normalizeSchemaHints(hints),
1736
- statuses,
1737
- };
1738
- }
1739
-
1740
- private buildDocumentCoverage(
1741
- base: FlowSchemaCoverage,
1742
- stepStatuses: FlowSchemaCoverage['status'][],
1743
- ): FlowSchemaCoverage {
1744
- const statuses = new Set<FlowSchemaCoverage['status']>([base.status, ...stepStatuses].filter(Boolean));
1745
-
1746
- let status: FlowSchemaCoverage['status'] = base.status;
1747
- if (statuses.size > 1) {
1748
- statuses.delete('unresolved');
1749
- if (statuses.size === 0) {
1750
- status = 'unresolved';
1751
- } else {
1752
- status = 'mixed';
1753
- }
1754
- } else if (statuses.size === 1) {
1755
- status = (Array.from(statuses)[0] || 'unresolved') as FlowSchemaCoverage['status'];
1756
- }
1757
-
1758
- return {
1759
- ...base,
1760
- status,
1761
- };
1762
- }
1763
-
1764
- private inferSubModelSlotsFromModelClass(
1765
- use: string,
1766
- modelClass: ModelConstructor,
1767
- ): Record<string, FlowSubModelSlotSchema> {
1768
- const meta = (((modelClass as any).meta || {}) as FlowModelMeta).schema;
1769
- if (meta?.subModelSlots) {
1770
- return _.cloneDeep(meta.subModelSlots);
1771
- }
1772
-
1773
- const createModelOptions = ((modelClass as any).meta || {}).createModelOptions;
1774
- if (!_.isPlainObject(createModelOptions) || !_.isPlainObject((createModelOptions as any).subModels)) {
1775
- return {};
1776
- }
1777
-
1778
- const slots: Record<string, FlowSubModelSlotSchema> = {};
1779
- for (const [slotKey, slotValue] of Object.entries((createModelOptions as any).subModels || {})) {
1780
- if (Array.isArray(slotValue)) {
1781
- const first = slotValue[0] as any;
1782
- slots[slotKey] = {
1783
- type: 'array',
1784
- ...(typeof first?.use === 'string' ? { use: first.use } : {}),
1785
- };
1786
- } else if (_.isPlainObject(slotValue)) {
1787
- slots[slotKey] = {
1788
- type: 'object',
1789
- ...((slotValue as any).use ? { use: (slotValue as any).use } : {}),
1790
- };
1791
- } else {
1792
- slots[slotKey] = {
1793
- type: 'object',
1794
- };
1795
- }
1796
- }
1797
- return slots;
1798
- }
1799
- }