@markwharton/liquidplanner 1.4.0 → 1.5.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/dist/client.d.ts +11 -11
- package/dist/client.js +21 -17
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +4 -3
- package/dist/workflows.js +2 -2
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://api-docs.liquidplanner.com/
|
|
8
8
|
*/
|
|
9
|
-
import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPUpsertOptions, LPAssignmentWithContext, LPAncestor } from './types.js';
|
|
9
|
+
import type { LPConfig, LPWorkspace, LPMember, LPItem, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPUpsertOptions, LPAssignmentWithContext, LPAncestor, LPErrorInfo } from './types.js';
|
|
10
10
|
/**
|
|
11
11
|
* LiquidPlanner API Client
|
|
12
12
|
*
|
|
@@ -51,21 +51,21 @@ export declare class LPClient {
|
|
|
51
51
|
*/
|
|
52
52
|
getWorkspaces(): Promise<{
|
|
53
53
|
workspaces?: LPWorkspace[];
|
|
54
|
-
error?:
|
|
54
|
+
error?: LPErrorInfo;
|
|
55
55
|
}>;
|
|
56
56
|
/**
|
|
57
57
|
* Get all members in the workspace (with pagination)
|
|
58
58
|
*/
|
|
59
59
|
getWorkspaceMembers(): Promise<{
|
|
60
60
|
members?: LPMember[];
|
|
61
|
-
error?:
|
|
61
|
+
error?: LPErrorInfo;
|
|
62
62
|
}>;
|
|
63
63
|
/**
|
|
64
64
|
* Get a single item by ID
|
|
65
65
|
*/
|
|
66
66
|
getItem(itemId: number): Promise<{
|
|
67
67
|
item?: LPItem;
|
|
68
|
-
error?:
|
|
68
|
+
error?: LPErrorInfo;
|
|
69
69
|
}>;
|
|
70
70
|
/**
|
|
71
71
|
* Get multiple items by ID in a single request (batch fetch)
|
|
@@ -77,7 +77,7 @@ export declare class LPClient {
|
|
|
77
77
|
*/
|
|
78
78
|
getItems(itemIds: number[]): Promise<{
|
|
79
79
|
items?: LPItem[];
|
|
80
|
-
error?:
|
|
80
|
+
error?: LPErrorInfo;
|
|
81
81
|
}>;
|
|
82
82
|
/**
|
|
83
83
|
* Get the ancestry chain for an item
|
|
@@ -89,14 +89,14 @@ export declare class LPClient {
|
|
|
89
89
|
*/
|
|
90
90
|
getItemAncestors(itemId: number): Promise<{
|
|
91
91
|
ancestors?: LPAncestor[];
|
|
92
|
-
error?:
|
|
92
|
+
error?: LPErrorInfo;
|
|
93
93
|
}>;
|
|
94
94
|
/**
|
|
95
95
|
* Find all assignments under a task (with pagination)
|
|
96
96
|
*/
|
|
97
97
|
findAssignments(taskId: number): Promise<{
|
|
98
98
|
assignments?: LPItem[];
|
|
99
|
-
error?:
|
|
99
|
+
error?: LPErrorInfo;
|
|
100
100
|
}>;
|
|
101
101
|
/**
|
|
102
102
|
* Get all assignments for a specific member
|
|
@@ -106,7 +106,7 @@ export declare class LPClient {
|
|
|
106
106
|
*/
|
|
107
107
|
getMyAssignments(memberId: number): Promise<{
|
|
108
108
|
assignments?: LPItem[];
|
|
109
|
-
error?:
|
|
109
|
+
error?: LPErrorInfo;
|
|
110
110
|
}>;
|
|
111
111
|
/**
|
|
112
112
|
* Get assignments for a member with parent task names resolved
|
|
@@ -129,14 +129,14 @@ export declare class LPClient {
|
|
|
129
129
|
includeHierarchy?: boolean;
|
|
130
130
|
}): Promise<{
|
|
131
131
|
assignments?: LPAssignmentWithContext[];
|
|
132
|
-
error?:
|
|
132
|
+
error?: LPErrorInfo;
|
|
133
133
|
}>;
|
|
134
134
|
/**
|
|
135
135
|
* Get all cost codes in the workspace (with pagination)
|
|
136
136
|
*/
|
|
137
137
|
getCostCodes(): Promise<{
|
|
138
138
|
costCodes?: LPCostCode[];
|
|
139
|
-
error?:
|
|
139
|
+
error?: LPErrorInfo;
|
|
140
140
|
}>;
|
|
141
141
|
/**
|
|
142
142
|
* Create a timesheet entry (log time)
|
|
@@ -158,7 +158,7 @@ export declare class LPClient {
|
|
|
158
158
|
*/
|
|
159
159
|
getTimesheetEntries(date: string, itemId?: number): Promise<{
|
|
160
160
|
entries?: LPTimesheetEntryWithId[];
|
|
161
|
-
error?:
|
|
161
|
+
error?: LPErrorInfo;
|
|
162
162
|
}>;
|
|
163
163
|
/**
|
|
164
164
|
* Update an existing timesheet entry
|
package/dist/client.js
CHANGED
|
@@ -96,8 +96,8 @@ export class LPClient {
|
|
|
96
96
|
const response = await this.fetch(url);
|
|
97
97
|
if (!response.ok) {
|
|
98
98
|
const errorText = await response.text();
|
|
99
|
-
const { message } = parseLPErrorResponse(errorText, response.status);
|
|
100
|
-
return { error: message };
|
|
99
|
+
const { message, isDuplicate } = parseLPErrorResponse(errorText, response.status);
|
|
100
|
+
return { error: { message, statusCode: response.status, isDuplicate } };
|
|
101
101
|
}
|
|
102
102
|
const result = await response.json();
|
|
103
103
|
const workspaces = (result.data || []).map(ws => ({
|
|
@@ -107,7 +107,7 @@ export class LPClient {
|
|
|
107
107
|
return { workspaces };
|
|
108
108
|
}
|
|
109
109
|
catch (error) {
|
|
110
|
-
return { error: getErrorMessage(error) };
|
|
110
|
+
return { error: { message: getErrorMessage(error), statusCode: 0 } };
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
// ============================================================================
|
|
@@ -144,17 +144,17 @@ export class LPClient {
|
|
|
144
144
|
const response = await this.fetch(url);
|
|
145
145
|
if (!response.ok) {
|
|
146
146
|
const errorText = await response.text();
|
|
147
|
-
const { message } = parseLPErrorResponse(errorText, response.status);
|
|
148
|
-
return { error: message };
|
|
147
|
+
const { message, isDuplicate } = parseLPErrorResponse(errorText, response.status);
|
|
148
|
+
return { error: { message, statusCode: response.status, isDuplicate } };
|
|
149
149
|
}
|
|
150
150
|
const result = await response.json();
|
|
151
151
|
if (!result.data || result.data.length === 0) {
|
|
152
|
-
return { error: `Item ${itemId} not found
|
|
152
|
+
return { error: { message: `Item ${itemId} not found`, statusCode: 404 } };
|
|
153
153
|
}
|
|
154
154
|
return { item: transformItem(result.data[0]) };
|
|
155
155
|
}
|
|
156
156
|
catch (error) {
|
|
157
|
-
return { error: getErrorMessage(error) };
|
|
157
|
+
return { error: { message: getErrorMessage(error), statusCode: 0 } };
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
@@ -192,8 +192,8 @@ export class LPClient {
|
|
|
192
192
|
});
|
|
193
193
|
if (!response.ok) {
|
|
194
194
|
const errorText = await response.text();
|
|
195
|
-
const { message } = parseLPErrorResponse(errorText, response.status);
|
|
196
|
-
return { error: message };
|
|
195
|
+
const { message, isDuplicate } = parseLPErrorResponse(errorText, response.status);
|
|
196
|
+
return { error: { message, statusCode: response.status, isDuplicate } };
|
|
197
197
|
}
|
|
198
198
|
const json = (await response.json());
|
|
199
199
|
// Handle both { data: [...] } and direct array responses
|
|
@@ -206,7 +206,7 @@ export class LPClient {
|
|
|
206
206
|
return { ancestors };
|
|
207
207
|
}
|
|
208
208
|
catch (error) {
|
|
209
|
-
return { error: getErrorMessage(error) };
|
|
209
|
+
return { error: { message: getErrorMessage(error), statusCode: 0 } };
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
/**
|
|
@@ -276,9 +276,13 @@ export class LPClient {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
const ancestorResults = await Promise.all([...assignmentsByParent.entries()].map(async ([parentId, assignment]) => {
|
|
279
|
-
const { ancestors } = await this.getItemAncestors(assignment.id);
|
|
280
|
-
return { parentId, ancestors };
|
|
279
|
+
const { ancestors, error } = await this.getItemAncestors(assignment.id);
|
|
280
|
+
return { parentId, ancestors, error };
|
|
281
281
|
}));
|
|
282
|
+
const firstError = ancestorResults.find(r => r.error);
|
|
283
|
+
if (firstError) {
|
|
284
|
+
return { error: firstError.error };
|
|
285
|
+
}
|
|
282
286
|
for (const { parentId, ancestors } of ancestorResults) {
|
|
283
287
|
ancestorMap.set(parentId, ancestors);
|
|
284
288
|
}
|
|
@@ -404,13 +408,13 @@ export class LPClient {
|
|
|
404
408
|
if (!response.ok) {
|
|
405
409
|
const errorText = await response.text();
|
|
406
410
|
const { message, isDuplicate } = parseLPErrorResponse(errorText, response.status);
|
|
407
|
-
return { success: false, error: message, isDuplicate };
|
|
411
|
+
return { success: false, error: message, statusCode: response.status, isDuplicate };
|
|
408
412
|
}
|
|
409
413
|
const result = await response.json();
|
|
410
414
|
return { success: true, entryId: result.id };
|
|
411
415
|
}
|
|
412
416
|
catch (error) {
|
|
413
|
-
return { success: false, error: getErrorMessage(error) };
|
|
417
|
+
return { success: false, error: getErrorMessage(error), statusCode: 0 };
|
|
414
418
|
}
|
|
415
419
|
}
|
|
416
420
|
/**
|
|
@@ -484,12 +488,12 @@ export class LPClient {
|
|
|
484
488
|
if (!response.ok) {
|
|
485
489
|
const errorText = await response.text();
|
|
486
490
|
const { message } = parseLPErrorResponse(errorText, response.status);
|
|
487
|
-
return { success: false, error: message };
|
|
491
|
+
return { success: false, error: message, statusCode: response.status };
|
|
488
492
|
}
|
|
489
493
|
return { success: true, entryId };
|
|
490
494
|
}
|
|
491
495
|
catch (error) {
|
|
492
|
-
return { success: false, error: getErrorMessage(error) };
|
|
496
|
+
return { success: false, error: getErrorMessage(error), statusCode: 0 };
|
|
493
497
|
}
|
|
494
498
|
}
|
|
495
499
|
/**
|
|
@@ -531,7 +535,7 @@ export class LPClient {
|
|
|
531
535
|
// Fetch existing entries for this date/item first
|
|
532
536
|
const { entries, error: fetchError } = await this.getTimesheetEntries(entry.date, entry.itemId);
|
|
533
537
|
if (fetchError) {
|
|
534
|
-
return { success: false, error: fetchError };
|
|
538
|
+
return { success: false, error: fetchError.message, statusCode: fetchError.statusCode };
|
|
535
539
|
}
|
|
536
540
|
// Find matching entry
|
|
537
541
|
// If no costCodeId specified, match any entry (LP uses assignment's default)
|
package/dist/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
export { LPClient } from './client.js';
|
|
30
30
|
export { resolveTaskToAssignment } from './workflows.js';
|
|
31
|
-
export type { LPConfig, LPItemType, LPItem, LPAncestor, LPWorkspace, LPMember, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPTaskResolution, LPResult, LPUpsertOptions, LPAssignmentWithContext, } from './types.js';
|
|
31
|
+
export type { LPConfig, LPItemType, LPItem, LPAncestor, LPWorkspace, LPMember, LPCostCode, LPSyncResult, LPTimesheetEntry, LPTimesheetEntryWithId, LPTaskResolution, LPResult, LPUpsertOptions, LPAssignmentWithContext, LPErrorInfo, } from './types.js';
|
|
32
32
|
export { hoursToMinutes, normalizeItemType, buildAuthHeader, filterIs, filterIn, paginatedFetch, } from './utils.js';
|
|
33
33
|
export type { PaginateOptions } from './utils.js';
|
|
34
34
|
export { LP_API_BASE } from './constants.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -119,6 +119,8 @@ export interface LPSyncResult {
|
|
|
119
119
|
entryId?: number;
|
|
120
120
|
/** Error message (if failed) */
|
|
121
121
|
error?: string;
|
|
122
|
+
/** HTTP status code (if failed) - useful for detecting rate limits (429) */
|
|
123
|
+
statusCode?: number;
|
|
122
124
|
/** Whether the error was due to a duplicate entry */
|
|
123
125
|
isDuplicate?: boolean;
|
|
124
126
|
}
|
|
@@ -184,3 +186,16 @@ export interface LPAssignmentWithContext extends LPItem {
|
|
|
184
186
|
/** Formatted hierarchy path like "Project A › Subfolder B" (undefined if not requested) */
|
|
185
187
|
hierarchyPath?: string;
|
|
186
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Structured error information from LP API
|
|
191
|
+
*
|
|
192
|
+
* Preserves HTTP status code for proper error handling (e.g., 429 rate limits).
|
|
193
|
+
*/
|
|
194
|
+
export interface LPErrorInfo {
|
|
195
|
+
/** Human-readable error message */
|
|
196
|
+
message: string;
|
|
197
|
+
/** HTTP status code from the response */
|
|
198
|
+
statusCode: number;
|
|
199
|
+
/** Whether this error indicates a duplicate entry */
|
|
200
|
+
isDuplicate?: boolean;
|
|
201
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LiquidPlanner Utility Functions
|
|
3
3
|
*/
|
|
4
|
-
import type { LPItemType } from './types.js';
|
|
4
|
+
import type { LPItemType, LPErrorInfo } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Build a URL-encoded filter for LP API: field[is]="value"
|
|
7
7
|
*/
|
|
@@ -30,7 +30,7 @@ export interface PaginateOptions<TRaw, TResult> {
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function paginatedFetch<TRaw, TResult>(options: PaginateOptions<TRaw, TResult>): Promise<{
|
|
32
32
|
results?: TResult[];
|
|
33
|
-
error?:
|
|
33
|
+
error?: LPErrorInfo;
|
|
34
34
|
}>;
|
|
35
35
|
/**
|
|
36
36
|
* Convert decimal hours to minutes
|
package/dist/utils.js
CHANGED
|
@@ -36,8 +36,8 @@ export async function paginatedFetch(options) {
|
|
|
36
36
|
const response = await fetchFn(url);
|
|
37
37
|
if (!response.ok) {
|
|
38
38
|
const errorText = await response.text();
|
|
39
|
-
const { message } = parseLPErrorResponse(errorText, response.status);
|
|
40
|
-
return { error: message };
|
|
39
|
+
const { message, isDuplicate } = parseLPErrorResponse(errorText, response.status);
|
|
40
|
+
return { error: { message, statusCode: response.status, isDuplicate } };
|
|
41
41
|
}
|
|
42
42
|
const result = await response.json();
|
|
43
43
|
const rawData = result.data || [];
|
|
@@ -49,7 +49,8 @@ export async function paginatedFetch(options) {
|
|
|
49
49
|
return { results: allResults };
|
|
50
50
|
}
|
|
51
51
|
catch (error) {
|
|
52
|
-
|
|
52
|
+
// Network errors or JSON parse errors don't have HTTP status codes
|
|
53
|
+
return { error: { message: getErrorMessage(error), statusCode: 0 } };
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
/**
|
package/dist/workflows.js
CHANGED
|
@@ -42,7 +42,7 @@ export async function resolveTaskToAssignment(client, itemId, lpMemberId) {
|
|
|
42
42
|
return {
|
|
43
43
|
inputItem: { id: itemId, name: null, itemType: 'Task' },
|
|
44
44
|
assignmentId: 0,
|
|
45
|
-
error: fetchError || 'Item not found',
|
|
45
|
+
error: fetchError?.message || 'Item not found',
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
// Step 2: Check item type and resolve accordingly
|
|
@@ -62,7 +62,7 @@ export async function resolveTaskToAssignment(client, itemId, lpMemberId) {
|
|
|
62
62
|
return {
|
|
63
63
|
inputItem: item,
|
|
64
64
|
assignmentId: 0,
|
|
65
|
-
error: `Failed to find assignments: ${assignmentError}`,
|
|
65
|
+
error: `Failed to find assignments: ${assignmentError.message}`,
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
if (!assignments || assignments.length === 0) {
|