@markwharton/liquidplanner 1.11.0 → 2.0.0
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/README.md +65 -1
- package/dist/client.d.ts +74 -45
- package/dist/client.js +285 -105
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +5 -6
- package/dist/index.d.ts +9 -5
- package/dist/index.js +9 -4
- package/dist/tree.d.ts +32 -0
- package/dist/tree.js +86 -0
- package/dist/types.d.ts +89 -38
- package/dist/utils.d.ts +31 -5
- package/dist/utils.js +56 -5
- package/dist/workflows.js +8 -6
- package/package.json +2 -2
- package/dist/cache.d.ts +0 -43
- package/dist/cache.js +0 -78
package/README.md
CHANGED
|
@@ -59,6 +59,42 @@ await client.updateTimesheetEntry(entryId, existingEntry, {
|
|
|
59
59
|
hours: existingEntry.hours + 1.5,
|
|
60
60
|
note: 'Additional work'
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
// Find items with filters
|
|
64
|
+
const { items } = await client.findItems({
|
|
65
|
+
itemType: 'tasks',
|
|
66
|
+
taskStatusGroupNot: 'done',
|
|
67
|
+
late: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Get children of an item
|
|
71
|
+
const { items: children } = await client.getChildren(parentId);
|
|
72
|
+
|
|
73
|
+
// Get workspace tree snapshot (cached, all hierarchy lookups resolved in memory)
|
|
74
|
+
const { tree } = await client.getWorkspaceTree();
|
|
75
|
+
|
|
76
|
+
// Get a member's work with full context from the tree
|
|
77
|
+
const { assignments: myWork, treeItemCount } = await client.getMyWork(memberId);
|
|
78
|
+
// Each assignment includes taskName, projectId, projectName, hierarchyPath, ancestors
|
|
79
|
+
// treeItemCount shows total items loaded (only member's assignments returned downstream)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Tree Utilities
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { buildTree, getTreeAncestors, getTreeHierarchyPath, findInTree } from '@markwharton/liquidplanner';
|
|
86
|
+
|
|
87
|
+
// Build tree from flat item array
|
|
88
|
+
const tree = buildTree(items);
|
|
89
|
+
|
|
90
|
+
// Get ancestors (root → parent order)
|
|
91
|
+
const ancestors = getTreeAncestors(tree, itemId);
|
|
92
|
+
|
|
93
|
+
// Get formatted path like "Project A › Subfolder B"
|
|
94
|
+
const path = getTreeHierarchyPath(tree, itemId);
|
|
95
|
+
|
|
96
|
+
// Find items matching a predicate
|
|
97
|
+
const lateTasks = findInTree(tree, item => item.late === true);
|
|
62
98
|
```
|
|
63
99
|
|
|
64
100
|
## API Reference
|
|
@@ -76,6 +112,11 @@ All methods return `{ data?, error? }` result objects rather than throwing excep
|
|
|
76
112
|
| `findAssignments(taskId)` | `number` | `{ assignments?, error? }` |
|
|
77
113
|
| `getMyAssignments(memberId)` | `number` | `{ assignments?, error? }` |
|
|
78
114
|
| `getMyAssignmentsWithContext(memberId, options?)` | `number, { includeProject?, includeHierarchy? }` | `{ assignments?, error? }` |
|
|
115
|
+
| `findItems(options)` | `LPFindItemsOptions` | `{ items?, error? }` |
|
|
116
|
+
| `getChildren(parentId, options?)` | `number, { itemType? }?` | `{ items?, error? }` |
|
|
117
|
+
| `getWorkspaceTree()` | — | `{ tree?, error? }` |
|
|
118
|
+
| `getMyWork(memberId)` | `number` | `{ assignments?, treeItemCount?, error? }` |
|
|
119
|
+
| `invalidateTreeCache()` | — | `void` |
|
|
79
120
|
| `getCostCodes()` | — | `{ costCodes?, error? }` |
|
|
80
121
|
| `createTimesheetEntry(entry)` | `LPTimesheetEntry` | `LPSyncResult` |
|
|
81
122
|
| `getTimesheetEntries(date, itemId?)` | `string \| string[], number?` | `{ entries?, error? }` |
|
|
@@ -93,6 +134,28 @@ const resolution = await resolveTaskToAssignment(client, taskId, memberId);
|
|
|
93
134
|
|
|
94
135
|
Resolves a Task ID to the correct Assignment ID for time logging. Handles cases where a task has multiple assignments by filtering on member ID.
|
|
95
136
|
|
|
137
|
+
### Tree Utilities
|
|
138
|
+
|
|
139
|
+
| Function | Description |
|
|
140
|
+
|----------|-------------|
|
|
141
|
+
| `buildTree(items)` | Build a navigable tree from a flat item array |
|
|
142
|
+
| `getTreeAncestors(tree, itemId)` | Get ancestors from root to parent (excludes item) |
|
|
143
|
+
| `getTreeHierarchyPath(tree, itemId)` | Formatted path like "Project A > Subfolder B" |
|
|
144
|
+
| `findInTree(tree, predicate)` | Find all items matching a predicate |
|
|
145
|
+
|
|
146
|
+
### Filter Utilities
|
|
147
|
+
|
|
148
|
+
| Function | Description |
|
|
149
|
+
|----------|-------------|
|
|
150
|
+
| `filterIs(field, value)` | `field[is]="value"` |
|
|
151
|
+
| `filterIsNot(field, value)` | `field[is_not]="value"` |
|
|
152
|
+
| `filterIn(field, values)` | `field[in]=["v1","v2"]` |
|
|
153
|
+
| `filterGt(field, value)` | `field[gt]="value"` |
|
|
154
|
+
| `filterLt(field, value)` | `field[lt]="value"` |
|
|
155
|
+
| `filterAfter(field, value)` | `field[after]="value"` — accepts YYYY-MM-DD (local timezone) or ISO |
|
|
156
|
+
| `filterBefore(field, value)` | `field[before]="value"` — accepts YYYY-MM-DD (local timezone) or ISO |
|
|
157
|
+
| `joinFilters(...filters)` | Join filter strings with `&` |
|
|
158
|
+
|
|
96
159
|
## Configuration
|
|
97
160
|
|
|
98
161
|
```typescript
|
|
@@ -114,13 +177,14 @@ const client = new LPClient({
|
|
|
114
177
|
|
|
115
178
|
| Cache Key | Default TTL |
|
|
116
179
|
|-----------|-------------|
|
|
180
|
+
| Workspace tree | 10 min |
|
|
117
181
|
| Workspace members | 5 min |
|
|
118
182
|
| Cost codes | 5 min |
|
|
119
183
|
| Items / ancestors | 5 min |
|
|
120
184
|
| Assignments | 2 min |
|
|
121
185
|
| Timesheet entries | 60s |
|
|
122
186
|
|
|
123
|
-
Write operations (`createTimesheetEntry`, `updateTimesheetEntry`) automatically invalidate timesheet cache entries. Call `client.clearCache()` to
|
|
187
|
+
Write operations (`createTimesheetEntry`, `updateTimesheetEntry`) automatically invalidate timesheet cache entries. Call `client.invalidateTreeCache()` to refresh the workspace tree, or `client.clearCache()` to clear all cached data.
|
|
124
188
|
|
|
125
189
|
### Retry
|
|
126
190
|
|
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://api-docs.liquidplanner.com/
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type { Result } from '@markwharton/api-core';
|
|
10
|
+
import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPUpsertOptions, LPAssignmentWithContext, LPAncestor, LPFindItemsOptions, LPWorkspaceTree } from './types.js';
|
|
10
11
|
/**
|
|
11
12
|
* LiquidPlanner API Client
|
|
12
13
|
*
|
|
@@ -59,6 +60,11 @@ export declare class LPClient {
|
|
|
59
60
|
* (e.g., after logging time which updates loggedHoursRollup).
|
|
60
61
|
*/
|
|
61
62
|
invalidateAssignmentsCache(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Invalidate cached workspace tree snapshot only.
|
|
65
|
+
* Use when the workspace structure changes (items created, moved, or deleted).
|
|
66
|
+
*/
|
|
67
|
+
invalidateTreeCache(): void;
|
|
62
68
|
/**
|
|
63
69
|
* Make an authenticated request to the LP API
|
|
64
70
|
*
|
|
@@ -75,34 +81,26 @@ export declare class LPClient {
|
|
|
75
81
|
* Fetch a URL and parse the response, with standardized error handling.
|
|
76
82
|
*/
|
|
77
83
|
private fetchAndParse;
|
|
84
|
+
/**
|
|
85
|
+
* Fetch and parse with isDuplicate support for mutation methods.
|
|
86
|
+
*/
|
|
87
|
+
private fetchAndParseMutation;
|
|
78
88
|
/**
|
|
79
89
|
* Validate the API token by listing workspaces
|
|
80
90
|
*/
|
|
81
|
-
validateToken(): Promise<
|
|
82
|
-
valid: boolean;
|
|
83
|
-
error?: string;
|
|
84
|
-
}>;
|
|
91
|
+
validateToken(): Promise<Result<void>>;
|
|
85
92
|
/**
|
|
86
93
|
* Get all workspaces accessible to the API token
|
|
87
94
|
*/
|
|
88
|
-
getWorkspaces(): Promise<
|
|
89
|
-
workspaces?: LPWorkspace[];
|
|
90
|
-
error?: LPErrorInfo;
|
|
91
|
-
}>;
|
|
95
|
+
getWorkspaces(): Promise<Result<LPWorkspace[]>>;
|
|
92
96
|
/**
|
|
93
97
|
* Get all members in the workspace (with pagination)
|
|
94
98
|
*/
|
|
95
|
-
getWorkspaceMembers(): Promise<
|
|
96
|
-
members?: LPMember[];
|
|
97
|
-
error?: LPErrorInfo;
|
|
98
|
-
}>;
|
|
99
|
+
getWorkspaceMembers(): Promise<Result<LPMember[]>>;
|
|
99
100
|
/**
|
|
100
101
|
* Get a single item by ID
|
|
101
102
|
*/
|
|
102
|
-
getItem(itemId: number): Promise<
|
|
103
|
-
item?: LPItem;
|
|
104
|
-
error?: LPErrorInfo;
|
|
105
|
-
}>;
|
|
103
|
+
getItem(itemId: number): Promise<Result<LPItem>>;
|
|
106
104
|
/**
|
|
107
105
|
* Get multiple items by ID in a single request (batch fetch)
|
|
108
106
|
*
|
|
@@ -111,10 +109,7 @@ export declare class LPClient {
|
|
|
111
109
|
*
|
|
112
110
|
* @param itemIds - Array of item IDs to fetch
|
|
113
111
|
*/
|
|
114
|
-
getItems(itemIds: number[]): Promise<
|
|
115
|
-
items?: LPItem[];
|
|
116
|
-
error?: LPErrorInfo;
|
|
117
|
-
}>;
|
|
112
|
+
getItems(itemIds: number[]): Promise<Result<LPItem[]>>;
|
|
118
113
|
/**
|
|
119
114
|
* Get the ancestry chain for an item
|
|
120
115
|
*
|
|
@@ -123,27 +118,18 @@ export declare class LPClient {
|
|
|
123
118
|
*
|
|
124
119
|
* @param itemId - The item ID to get ancestors for
|
|
125
120
|
*/
|
|
126
|
-
getItemAncestors(itemId: number): Promise<
|
|
127
|
-
ancestors?: LPAncestor[];
|
|
128
|
-
error?: LPErrorInfo;
|
|
129
|
-
}>;
|
|
121
|
+
getItemAncestors(itemId: number): Promise<Result<LPAncestor[]>>;
|
|
130
122
|
/**
|
|
131
123
|
* Find all assignments under a task (with pagination)
|
|
132
124
|
*/
|
|
133
|
-
findAssignments(taskId: number): Promise<
|
|
134
|
-
assignments?: LPItem[];
|
|
135
|
-
error?: LPErrorInfo;
|
|
136
|
-
}>;
|
|
125
|
+
findAssignments(taskId: number): Promise<Result<LPItem[]>>;
|
|
137
126
|
/**
|
|
138
127
|
* Get all assignments for a specific member
|
|
139
128
|
*
|
|
140
129
|
* This enables PWA apps to show a task picker populated from LP directly.
|
|
141
130
|
* Note: userId is not a supported filter field in the LP API, so we filter client-side.
|
|
142
131
|
*/
|
|
143
|
-
getMyAssignments(memberId: number): Promise<
|
|
144
|
-
assignments?: LPItem[];
|
|
145
|
-
error?: LPErrorInfo;
|
|
146
|
-
}>;
|
|
132
|
+
getMyAssignments(memberId: number): Promise<Result<LPItem[]>>;
|
|
147
133
|
/**
|
|
148
134
|
* Get assignments for a member with parent task names resolved
|
|
149
135
|
*
|
|
@@ -163,17 +149,63 @@ export declare class LPClient {
|
|
|
163
149
|
getMyAssignmentsWithContext(memberId: number, options?: {
|
|
164
150
|
includeProject?: boolean;
|
|
165
151
|
includeHierarchy?: boolean;
|
|
166
|
-
}): Promise<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
152
|
+
}): Promise<Result<LPAssignmentWithContext[]>>;
|
|
153
|
+
/**
|
|
154
|
+
* Query items with LP API filters
|
|
155
|
+
*
|
|
156
|
+
* General-purpose method that exposes the full filtering capabilities of
|
|
157
|
+
* the LP items endpoint. Not cached — use higher-level methods like
|
|
158
|
+
* getWorkspaceTree() for cached access.
|
|
159
|
+
*
|
|
160
|
+
* @see https://api-docs.liquidplanner.com/docs/plan-items
|
|
161
|
+
*
|
|
162
|
+
* @param options - Filter options (all optional, combined with AND)
|
|
163
|
+
*/
|
|
164
|
+
findItems(options: LPFindItemsOptions): Promise<Result<LPItem[]>>;
|
|
165
|
+
/**
|
|
166
|
+
* Get direct children of an item
|
|
167
|
+
*
|
|
168
|
+
* Uses parentId[is] filter to fetch immediate descendants.
|
|
169
|
+
* Optionally filter by item type.
|
|
170
|
+
*
|
|
171
|
+
* @param parentId - The parent item ID
|
|
172
|
+
* @param options - Optional item type filter
|
|
173
|
+
*/
|
|
174
|
+
getChildren(parentId: number, options?: {
|
|
175
|
+
itemType?: string;
|
|
176
|
+
}): Promise<Result<LPItem[]>>;
|
|
177
|
+
/**
|
|
178
|
+
* Fetch a snapshot of the active workspace tree
|
|
179
|
+
*
|
|
180
|
+
* Fetches all workspace items in paginated API calls, then builds a
|
|
181
|
+
* navigable tree in memory from parentId relationships.
|
|
182
|
+
*
|
|
183
|
+
* The result is cached with treeTtl (default: 10 minutes).
|
|
184
|
+
* Use invalidateTreeCache() to force a refresh.
|
|
185
|
+
*
|
|
186
|
+
* After the initial fetch, all hierarchy queries (ancestors, paths, assignments
|
|
187
|
+
* with context) can be answered from the cached tree with zero API calls.
|
|
188
|
+
*/
|
|
189
|
+
getWorkspaceTree(): Promise<Result<LPWorkspaceTree>>;
|
|
190
|
+
/**
|
|
191
|
+
* Get a member's active work with full context from the workspace tree
|
|
192
|
+
*
|
|
193
|
+
* This is the optimized alternative to getMyAssignmentsWithContext().
|
|
194
|
+
* Uses the workspace tree snapshot (cached) to resolve hierarchy locally,
|
|
195
|
+
* eliminating the N+1 ancestor request pattern.
|
|
196
|
+
*
|
|
197
|
+
* API calls: 0 (if tree cached) or 1-3 (cold load)
|
|
198
|
+
*
|
|
199
|
+
* @param memberId - The member ID to get work for
|
|
200
|
+
*/
|
|
201
|
+
getMyWork(memberId: number): Promise<Result<{
|
|
202
|
+
assignments: LPAssignmentWithContext[];
|
|
203
|
+
treeItemCount: number;
|
|
204
|
+
}>>;
|
|
170
205
|
/**
|
|
171
206
|
* Get all cost codes in the workspace (with pagination)
|
|
172
207
|
*/
|
|
173
|
-
getCostCodes(): Promise<
|
|
174
|
-
costCodes?: LPCostCode[];
|
|
175
|
-
error?: LPErrorInfo;
|
|
176
|
-
}>;
|
|
208
|
+
getCostCodes(): Promise<Result<LPCostCode[]>>;
|
|
177
209
|
/**
|
|
178
210
|
* Create a timesheet entry (log time)
|
|
179
211
|
*
|
|
@@ -195,10 +227,7 @@ export declare class LPClient {
|
|
|
195
227
|
* @param date - Date(s) in YYYY-MM-DD format (string or array)
|
|
196
228
|
* @param itemId - Optional item ID to filter by
|
|
197
229
|
*/
|
|
198
|
-
getTimesheetEntries(date: string | string[], itemId?: number): Promise<
|
|
199
|
-
entries?: LPTimesheetEntryWithId[];
|
|
200
|
-
error?: LPErrorInfo;
|
|
201
|
-
}>;
|
|
230
|
+
getTimesheetEntries(date: string | string[], itemId?: number): Promise<Result<LPTimesheetEntryWithId[]>>;
|
|
202
231
|
/**
|
|
203
232
|
* Update an existing timesheet entry
|
|
204
233
|
*
|