Skip to content

Commit c36a074

Browse files
authored
fix nested object resolution (#146)
1 parent c3141d7 commit c36a074

File tree

5 files changed

+62
-8
lines changed

5 files changed

+62
-8
lines changed

.changeset/quick-olives-rush.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@dmno/configraph": patch
3+
---
4+
5+
fixed nested object resolution

packages/configraph/src/config-node.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,16 @@ export class ConfigraphNode<NodeMetadata = any> {
207207
}
208208

209209
children: Record<string, typeof this> = {};
210+
get flatChildren(): Array<typeof this> {
211+
if (_.isEmpty(this.children)) return [];
212+
return _.flatMap(
213+
_.values(this.children),
214+
(c) => [
215+
c,
216+
...c.flatChildren,
217+
],
218+
);
219+
}
210220

211221
get parentNode(): ConfigraphNode | undefined {
212222
if (this.parent instanceof ConfigraphNode) {
@@ -282,13 +292,16 @@ export class ConfigraphNode<NodeMetadata = any> {
282292
const itemResolverCtx = new ResolverContext(this.valueResolver || this);
283293
resolverCtxAls.enterWith(itemResolverCtx);
284294

295+
285296
if (this.valueResolver) {
286297
if (!this.valueResolver.isFullyResolved) {
298+
this.debug('running node resolver');
287299
await this.valueResolver.resolve(itemResolverCtx);
288300
this.isResolved = true;
289301
if (this.resolutionError) return;
290302
}
291303
} else {
304+
this.debug('no resolver - marking as resolved');
292305
this.isResolved = true;
293306
}
294307

@@ -368,7 +381,7 @@ export class ConfigraphNode<NodeMetadata = any> {
368381
this.isFullyResolved = true;
369382

370383
debug(
371-
`${this.parentEntity?.id}/${this.path} = `,
384+
`${this.parentEntity?.id}/${this.path} =`,
372385
JSON.stringify(this.resolvedRawValue),
373386
JSON.stringify(this.resolvedValue),
374387
this.isValid ? '✅' : `❌ ${this.validationErrors?.[0]?.message}`,

packages/configraph/src/entity.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,7 @@ export class ConfigraphEntity<
288288

289289
get flatConfigNodes() {
290290
return _.flatMapDeep(this.configNodes, (node) => {
291-
if (node.children) return [node, ..._.values(node.children)];
292-
return node;
291+
return [node, node.flatChildren];
293292
});
294293
}
295294

packages/configraph/src/graph.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ export class Configraph<
187187

188188
this.initEntitiesDag();
189189

190-
const postProcessFns: Array<() => void> = [];
190+
const postProcessFns: Array<{
191+
node: ConfigraphNode,
192+
fns: Array<() => void>,
193+
}> = [];
191194
for (const entity of this.sortedEntities) {
192195
const ancestorIds = entity.ancestorIds;
193196

@@ -315,7 +318,7 @@ export class Configraph<
315318
// calls process on each item's resolver, and collects "post-processing" functions to call if necessary
316319
try {
317320
const nodePostProcessFns = node.valueResolver?.process(node);
318-
postProcessFns.push(...nodePostProcessFns || []);
321+
postProcessFns.push({ node, fns: nodePostProcessFns || [] });
319322
// TODO: handle errors - attach as schema errors to resolver / node?
320323
} catch (err) {
321324
if (err instanceof SchemaError) {
@@ -331,8 +334,18 @@ export class Configraph<
331334

332335
// after the entire graph of config nodes have been processed, we'll call post-processing functions
333336
// this is needed for `collect()` where we need child entities and their nodes to be initialized
334-
postProcessFns.forEach((postProcessFn) => {
335-
postProcessFn();
337+
postProcessFns.forEach(({ node, fns }) => {
338+
fns.forEach((fn) => {
339+
try {
340+
fn();
341+
} catch (err) {
342+
if (err instanceof SchemaError) {
343+
node.schemaErrors.push(err);
344+
} else {
345+
node.schemaErrors.push(new SchemaError(err as SchemaError));
346+
}
347+
}
348+
});
336349
});
337350

338351
// add declared dependencies to the node graph
@@ -361,10 +374,12 @@ export class Configraph<
361374
const node = this.nodesByFullPath[nodeId];
362375
// currently this resolve fn will trigger resolve on nested items
363376
const nodeWasResolved = node.isResolved;
377+
const nodeWasFullyResolved = node.isFullyResolved;
364378
await node.resolve();
365379
// for objects, the node first gets "resolved" but not "fully resolved" (where child values rolled back up)
366-
// but this is still considered progress so we track it
380+
// so we make progress (and therefore should continue) if a node transitions to resolved or fully resolved
367381
if (!nodeWasResolved && node.isResolved) resolvedCount++;
382+
else if (!nodeWasFullyResolved && node.isFullyResolved) resolvedCount++;
368383
if (!node.isFullyResolved) {
369384
nextBatchNodeIds.push(nodeId);
370385
}

packages/configraph/test/object-node.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,26 @@ describe('object config nodes', async () => {
215215
});
216216
});
217217
});
218+
219+
describe('multiple nested objects', () => {
220+
test('nested objects must completely resolve', async () => {
221+
const g = new Configraph();
222+
const e = g.createEntity({
223+
configSchema: {
224+
rootObj: {
225+
extends: ConfigraphBaseTypes.object({
226+
r1: {},
227+
childObj: {
228+
extends: ConfigraphBaseTypes.object({
229+
c1: { value: 'c1' },
230+
}),
231+
},
232+
}),
233+
},
234+
},
235+
});
236+
await g.resolveConfig();
237+
expect(e.configNodes.rootObj.resolvedValue).toEqual({ childObj: { c1: 'c1' } });
238+
});
239+
});
218240
});

0 commit comments

Comments
 (0)