@adecore/plan 0.21.1 → 0.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/apply.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { PlanChecksSchema, PlanItemIdSchema, PlanKindSchema, PlanMetaSchema, PlanOpSchema, PlanSchema, PlanSectionSchema, PlanStepSchema, PlanStepStateSchema, PlanTextSchema } from './protocol.js';
2
2
  import { z } from 'zod';
3
3
  import { canApply } from './permissions.js';
4
- import { allItems, allSteps, effectiveChecks, locateItem, refuse, structureProblem } from './tree.js';
4
+ import { allItems, allSteps, effectiveChecks, locateItem, missingItem, refuse, structureProblem } from './tree.js';
5
5
  const ID_ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789';
6
6
  export function randomItemId() {
7
7
  const bytes = crypto.getRandomValues(new Uint8Array(6));
@@ -59,7 +59,7 @@ function placeFor(plan, type, under, after) {
59
59
  if (under !== undefined) {
60
60
  const location = locateItem(plan, under);
61
61
  if (!location) {
62
- return refuse('plan-missing-item', `The plan has no item "${under}"`);
62
+ return missingItem(under);
63
63
  }
64
64
  const target = location.item;
65
65
  if (target.type === 'text') {
@@ -77,7 +77,7 @@ function placeFor(plan, type, under, after) {
77
77
  else if (after !== undefined) {
78
78
  const location = locateItem(plan, after);
79
79
  if (!location) {
80
- return refuse('plan-missing-item', `The plan has no item "${after}"`);
80
+ return missingItem(after);
81
81
  }
82
82
  parent = location.parent;
83
83
  siblings = location.siblings;
@@ -116,6 +116,16 @@ function pruneEmptySteps(items) {
116
116
  }
117
117
  }
118
118
  }
119
+ function newItem(op, id) {
120
+ const base = { id, title: op.title, ...(op.description ? { description: op.description } : {}) };
121
+ if (op.type === 'section') {
122
+ return { type: 'section', ...base, items: [] };
123
+ }
124
+ if (op.type === 'text') {
125
+ return { type: 'text', ...base };
126
+ }
127
+ return { type: 'step', ...base, ...(op.checks ? { checks: op.checks } : {}) };
128
+ }
119
129
  function applyOne(plan, op, options, mint, dropped) {
120
130
  const verdict = canApply(op, options.actor, plan);
121
131
  if (!verdict.ok) {
@@ -160,13 +170,7 @@ function applyOne(plan, op, options, mint, dropped) {
160
170
  if ('ok' in place) {
161
171
  return place;
162
172
  }
163
- const base = { id, title: op.title, ...(op.description ? { description: op.description } : {}) };
164
- const item = op.type === 'section'
165
- ? { type: 'section', ...base, items: [] }
166
- : op.type === 'text'
167
- ? { type: 'text', ...base }
168
- : { type: 'step', ...base, ...(op.checks ? { checks: op.checks } : {}) };
169
- place.siblings.splice(place.index, 0, item);
173
+ place.siblings.splice(place.index, 0, newItem(op, id));
170
174
  becomeParent(place.parent, dropped);
171
175
  return null;
172
176
  }
@@ -1,11 +1,11 @@
1
- import { allSteps, effectiveChecks, holdsPersonState, isParentStep, locateItem, refuse } from './tree.js';
1
+ import { allSteps, effectiveChecks, holdsPersonState, isParentStep, locateItem, missingItem, refuse } from './tree.js';
2
2
  const ALLOWED = { ok: true };
3
3
  const PERSON_OPS = new Set(['set', 'note', 'unlock']);
4
4
  const AGENT_OPS = new Set(['set', 'note', 'add', 'edit', 'move', 'remove', 'meta']);
5
5
  function stepFor(plan, id, leaf) {
6
6
  const location = locateItem(plan, id);
7
7
  if (!location) {
8
- return refuse('plan-missing-item', `The plan has no item "${id}"`);
8
+ return missingItem(id);
9
9
  }
10
10
  if (location.item.type !== 'step') {
11
11
  return refuse('plan-not-a-step', `"${id}" is a ${location.item.type}, not a step`);
@@ -90,7 +90,7 @@ export function canApply(op, actor, plan) {
90
90
  case 'edit': {
91
91
  const location = locateItem(plan, op.id);
92
92
  if (!location) {
93
- return refuse('plan-missing-item', `The plan has no item "${op.id}"`);
93
+ return missingItem(op.id);
94
94
  }
95
95
  const item = location.item;
96
96
  if (item.type !== 'step') {
@@ -118,14 +118,14 @@ export function canApply(op, actor, plan) {
118
118
  }
119
119
  case 'move': {
120
120
  if (!locateItem(plan, op.id)) {
121
- return refuse('plan-missing-item', `The plan has no item "${op.id}"`);
121
+ return missingItem(op.id);
122
122
  }
123
123
  return mayHoldChildren(plan, op.under);
124
124
  }
125
125
  case 'remove': {
126
126
  const location = locateItem(plan, op.id);
127
127
  if (!location) {
128
- return refuse('plan-missing-item', `The plan has no item "${op.id}"`);
128
+ return missingItem(op.id);
129
129
  }
130
130
  if (holdsPersonState(location.item)) {
131
131
  return refuse('set-by-person', `A person checked "${op.id}" or a step under it, so it stays`);
package/dist/tree.d.ts CHANGED
@@ -6,6 +6,7 @@ export interface PlanRefusal {
6
6
  message: string;
7
7
  }
8
8
  export declare function refuse(code: PlanRefusalCode, message: string): PlanRefusal;
9
+ export declare function missingItem(id: string): PlanRefusal;
9
10
  export interface PlanLocation {
10
11
  item: PlanItem;
11
12
  siblings: PlanItem[];
package/dist/tree.js CHANGED
@@ -2,6 +2,9 @@ import { PLAN_LIMITS } from './protocol.js';
2
2
  export function refuse(code, message) {
3
3
  return { ok: false, code, message };
4
4
  }
5
+ export function missingItem(id) {
6
+ return refuse('plan-missing-item', `The plan has no item "${id}"`);
7
+ }
5
8
  function childrenOf(item) {
6
9
  if (item.type === 'section') {
7
10
  return item.items;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/plan",
3
- "version": "0.21.1",
3
+ "version": "0.21.2",
4
4
  "description": "Plan schemas, atomic tree operations, actor permissions and Markdown.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "author": "Bas Milius",
package/src/apply.ts CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  } from './protocol.ts';
22
22
  import { z } from 'zod';
23
23
  import { canApply } from './permissions.ts';
24
- import { allItems, allSteps, effectiveChecks, locateItem, refuse, structureProblem, type PlanRefusal } from './tree.ts';
24
+ import { allItems, allSteps, effectiveChecks, locateItem, missingItem, refuse, structureProblem, type PlanRefusal } from './tree.ts';
25
25
 
26
26
  export interface PlanApplyOptions {
27
27
  actor: PlanActor;
@@ -107,7 +107,7 @@ function placeFor(
107
107
  if (under !== undefined) {
108
108
  const location = locateItem(plan, under);
109
109
  if (!location) {
110
- return refuse('plan-missing-item', `The plan has no item "${under}"`);
110
+ return missingItem(under);
111
111
  }
112
112
  const target = location.item;
113
113
  if (target.type === 'text') {
@@ -123,7 +123,7 @@ function placeFor(
123
123
  } else if (after !== undefined) {
124
124
  const location = locateItem(plan, after);
125
125
  if (!location) {
126
- return refuse('plan-missing-item', `The plan has no item "${after}"`);
126
+ return missingItem(after);
127
127
  }
128
128
  parent = location.parent;
129
129
  siblings = location.siblings;
@@ -165,6 +165,17 @@ function pruneEmptySteps(items: readonly PlanItem[]): void {
165
165
  }
166
166
  }
167
167
 
168
+ function newItem(op: Extract<PlanOp, { op: 'add' }>, id: string): PlanItem {
169
+ const base = { id, title: op.title, ...(op.description ? { description: op.description } : {}) };
170
+ if (op.type === 'section') {
171
+ return { type: 'section', ...base, items: [] };
172
+ }
173
+ if (op.type === 'text') {
174
+ return { type: 'text', ...base };
175
+ }
176
+ return { type: 'step', ...base, ...(op.checks ? { checks: op.checks } : {}) };
177
+ }
178
+
168
179
  function applyOne(plan: Plan, op: PlanOp, options: PlanApplyOptions, mint: () => string, dropped: string[]): PlanRefusal | null {
169
180
  const verdict = canApply(op, options.actor, plan);
170
181
  if (!verdict.ok) {
@@ -209,14 +220,7 @@ function applyOne(plan: Plan, op: PlanOp, options: PlanApplyOptions, mint: () =>
209
220
  if ('ok' in place) {
210
221
  return place;
211
222
  }
212
- const base = { id, title: op.title, ...(op.description ? { description: op.description } : {}) };
213
- const item: PlanItem =
214
- op.type === 'section'
215
- ? { type: 'section', ...base, items: [] }
216
- : op.type === 'text'
217
- ? { type: 'text', ...base }
218
- : { type: 'step', ...base, ...(op.checks ? { checks: op.checks } : {}) };
219
- place.siblings.splice(place.index, 0, item);
223
+ place.siblings.splice(place.index, 0, newItem(op, id));
220
224
  becomeParent(place.parent, dropped);
221
225
  return null;
222
226
  }
@@ -1,5 +1,5 @@
1
1
  import type { Plan, PlanActor, PlanOp, PlanStep, PlanStepState } from './protocol.ts';
2
- import { allSteps, effectiveChecks, holdsPersonState, isParentStep, locateItem, refuse, type PlanRefusal } from './tree.ts';
2
+ import { allSteps, effectiveChecks, holdsPersonState, isParentStep, locateItem, missingItem, refuse, type PlanRefusal } from './tree.ts';
3
3
 
4
4
  export type PlanVerdict = { ok: true } | PlanRefusal;
5
5
 
@@ -11,7 +11,7 @@ const AGENT_OPS: ReadonlySet<PlanOp['op']> = new Set(['set', 'note', 'add', 'edi
11
11
  function stepFor(plan: Plan, id: string, leaf: boolean): PlanStep | PlanRefusal {
12
12
  const location = locateItem(plan, id);
13
13
  if (!location) {
14
- return refuse('plan-missing-item', `The plan has no item "${id}"`);
14
+ return missingItem(id);
15
15
  }
16
16
  if (location.item.type !== 'step') {
17
17
  return refuse('plan-not-a-step', `"${id}" is a ${location.item.type}, not a step`);
@@ -99,7 +99,7 @@ export function canApply(op: PlanOp, actor: PlanActor, plan: Plan): PlanVerdict
99
99
  case 'edit': {
100
100
  const location = locateItem(plan, op.id);
101
101
  if (!location) {
102
- return refuse('plan-missing-item', `The plan has no item "${op.id}"`);
102
+ return missingItem(op.id);
103
103
  }
104
104
  const item = location.item;
105
105
  if (item.type !== 'step') {
@@ -127,14 +127,14 @@ export function canApply(op: PlanOp, actor: PlanActor, plan: Plan): PlanVerdict
127
127
  }
128
128
  case 'move': {
129
129
  if (!locateItem(plan, op.id)) {
130
- return refuse('plan-missing-item', `The plan has no item "${op.id}"`);
130
+ return missingItem(op.id);
131
131
  }
132
132
  return mayHoldChildren(plan, op.under);
133
133
  }
134
134
  case 'remove': {
135
135
  const location = locateItem(plan, op.id);
136
136
  if (!location) {
137
- return refuse('plan-missing-item', `The plan has no item "${op.id}"`);
137
+ return missingItem(op.id);
138
138
  }
139
139
  if (holdsPersonState(location.item)) {
140
140
  return refuse('set-by-person', `A person checked "${op.id}" or a step under it, so it stays`);
package/src/tree.ts CHANGED
@@ -27,6 +27,10 @@ export function refuse(code: PlanRefusalCode, message: string): PlanRefusal {
27
27
  return { ok: false, code, message };
28
28
  }
29
29
 
30
+ export function missingItem(id: string): PlanRefusal {
31
+ return refuse('plan-missing-item', `The plan has no item "${id}"`);
32
+ }
33
+
30
34
  /* Where an item stands: the array that holds it, so an operation can splice it, and the step or section around it. */
31
35
  export interface PlanLocation {
32
36
  item: PlanItem;