From 7a217ff034f625ae23d6e0964e88693a0dfb6470 Mon Sep 17 00:00:00 2001 From: Felipe Lalanne <1822826+pipex@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:55:20 -0400 Subject: [PATCH 1/2] Move Plan types to planner/plan.ts This allows to centralize how a Node is created and the id is generated Change-type: minor --- lib/planner/findPlan.ts | 22 ++--------- lib/planner/index.ts | 4 +- lib/planner/plan.ts | 86 +++++++++++++++++++++++++++++++++++++++++ lib/planner/types.ts | 61 ----------------------------- 4 files changed, 93 insertions(+), 80 deletions(-) create mode 100644 lib/planner/plan.ts diff --git a/lib/planner/findPlan.ts b/lib/planner/findPlan.ts index 10ce4f6..c7b5c6a 100644 --- a/lib/planner/findPlan.ts +++ b/lib/planner/findPlan.ts @@ -1,11 +1,11 @@ import { Context } from '../context'; import { Diff } from '../diff'; -import { createHash } from 'crypto'; import { Operation } from '../operation'; import { Path } from '../path'; import { Pointer } from '../pointer'; import { Action, Instruction, Method, Task } from '../task'; -import { PlannerConfig, Plan, Node } from './types'; +import { PlannerConfig } from './types'; +import { Plan, Node } from './plan'; import { isTaskApplicable } from './utils'; import assert from '../assert'; @@ -30,21 +30,6 @@ function planHasId(id: string, node: Node | null): boolean { return false; } -function createNodeId(s: TState, a: Action): string { - // md5 should be good enough for this purpose - // and it's the fastest of the available options - return createHash('md5') - .update( - JSON.stringify({ - id: a.id, - path: a.path, - state: s, - ...(a.target && { target: a.target }), - }), - ) - .digest('hex'); -} - function tryAction( action: Action, { @@ -67,7 +52,8 @@ function tryAction( assert(initialPlan.success); // Generate an id for the potential node - const id = createNodeId(initialPlan.state, action); + const node = Node.of(initialPlan.state, action); + const id = node.id; // Detect loops in the plan if (planHasId(id, initialPlan.start)) { diff --git a/lib/planner/index.ts b/lib/planner/index.ts index 826b4bf..76d3208 100644 --- a/lib/planner/index.ts +++ b/lib/planner/index.ts @@ -2,9 +2,11 @@ import { Diff } from '../diff'; import { Target } from '../target'; import { Task } from '../task'; import { findPlan } from './findPlan'; -import { PlannerConfig, Plan, Node } from './types'; +import { PlannerConfig } from './types'; +import { Plan, Node } from './plan'; export * from './types'; +export * from './plan'; export interface Planner { /** diff --git a/lib/planner/plan.ts b/lib/planner/plan.ts new file mode 100644 index 0000000..c4b2ec6 --- /dev/null +++ b/lib/planner/plan.ts @@ -0,0 +1,86 @@ +import { createHash } from 'crypto'; + +import { Action } from '../task'; +import { PlanningStats } from './types'; + +/** + * A node defines a specific step in a plan. + */ +export interface Node { + /** + * Unique id for the node. This is calculated from the + * action metadata and the current runtime state expected + * by the planner. This is used for loop detection in the plan. + */ + readonly id: string; + + /** + * The action to execute + */ + readonly action: Action; + + /** + * The next step in the plan + */ + next: Node | null; +} + +export type Plan = + | { + /** + * A plan was found + */ + success: true; + + /** + * The initial step in the plan. If the start + * node is null, that means the plan is empty. + */ + start: Node | null; + + /** + * The expected state at the end of the plan. This is + * probably not useful for end users, but is useful to keep + * track of intermediate steps in the planning process. + */ + state: TState; + + /** + * The resulting stats of the planning process + */ + stats: PlanningStats; + } + | { + /** + * A plan could not be found + */ + success: false; + + /** + * The resulting stats of the planning process + */ + stats: PlanningStats; + }; + +export const Node = { + of: (s: TState, a: Action): Node => { + // md5 should be good enough for this purpose + // and it's the fastest of the available options + const id = createHash('md5') + .update( + JSON.stringify({ + id: a.id, + path: a.path, + state: s, + ...(a.target && { target: a.target }), + }), + ) + .digest('hex'); + + return { + id, + action: a, + next: null, + }; + }, +}; diff --git a/lib/planner/types.ts b/lib/planner/types.ts index 5ac1faf..94795f9 100644 --- a/lib/planner/types.ts +++ b/lib/planner/types.ts @@ -1,5 +1,3 @@ -import { Action } from '../task'; - /** * Stats about the planning process */ @@ -27,62 +25,3 @@ export interface PlannerConfig { */ trace: (...args: any[]) => void; } - -/** - * A node defines a specific step in a plan. - */ -export interface Node { - /** - * Unique id for the node. This is calculated from the - * action metadata and the current runtime state expected - * by the planner. This is used for loop detection in the plan. - */ - readonly id: string; - - /** - * The action to execute - */ - readonly action: Action; - - /** - * The next step in the plan - */ - next: Node | null; -} - -export type Plan = - | { - /** - * A plan was found - */ - success: true; - - /** - * The initial step in the plan. If the start - * node is null, that means the plan is empty. - */ - start: Node | null; - - /** - * The expected state at the end of the plan. This is - * probably not useful for end users, but is useful to keep - * track of intermediate steps in the planning process. - */ - state: TState; - - /** - * The resulting stats of the planning process - */ - stats: PlanningStats; - } - | { - /** - * A plan could not be found - */ - success: false; - - /** - * The resulting stats of the planning process - */ - stats: PlanningStats; - }; From c299fcb2e025cbd70a59b7cfa343b83a31dbc29d Mon Sep 17 00:00:00 2001 From: Felipe Lalanne <1822826+pipex@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:00:26 -0400 Subject: [PATCH 2/2] Expose planner API via mahler/planner This also removes the export for planner functionality on the top level. Use of `Planner` object should be generally rare and limited to testing. Exposing the planner API via a namespace makes that more evident. Change-type: minor --- lib/index.ts | 1 - package.json | 3 ++- tests/composer/planner.ts | 2 +- tests/orchestrator/planner.ts | 2 +- tsconfig.json | 3 +++ 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 403fa50..901a47a 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,4 @@ export * from './agent'; -export * from './planner'; export * from './observable'; export * from './sensor'; export * from './task'; diff --git a/package.json b/package.json index 76b0b7d..2097213 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "main": "build/index.js", "exports": { ".": "./build/index.js", - "./testing": "./build/testing/index.js" + "./testing": "./build/testing/index.js", + "./planner": "./build/planner/index.js" }, "types": "build/index.d.ts", "keywords": [ diff --git a/tests/composer/planner.ts b/tests/composer/planner.ts index 33f1190..e9668dc 100644 --- a/tests/composer/planner.ts +++ b/tests/composer/planner.ts @@ -1,4 +1,4 @@ -import { Planner } from 'mahler'; +import { Planner } from 'mahler/planner'; import { console } from '~/test-utils'; import { App } from './state'; diff --git a/tests/orchestrator/planner.ts b/tests/orchestrator/planner.ts index 066fc86..61c0768 100644 --- a/tests/orchestrator/planner.ts +++ b/tests/orchestrator/planner.ts @@ -1,4 +1,4 @@ -import { Planner } from 'mahler'; +import { Planner } from 'mahler/planner'; import { console } from '~/test-utils'; import { Device } from './state'; diff --git a/tsconfig.json b/tsconfig.json index debd931..eb6aad6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,9 @@ "mahler/testing": [ "lib/testing/index.ts" ], + "mahler/planner": [ + "lib/planner/index.ts" + ], "~/test-utils": [ "tests/index.ts" ]