@centry-digital/bukku-mcp 1.1.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/LICENSE +21 -0
- package/README.md +269 -0
- package/build/client/bukku-client.d.ts +62 -0
- package/build/client/bukku-client.js +195 -0
- package/build/config/env.d.ts +19 -0
- package/build/config/env.js +36 -0
- package/build/errors/transform.d.ts +14 -0
- package/build/errors/transform.js +141 -0
- package/build/errors/transform.test.d.ts +1 -0
- package/build/errors/transform.test.js +101 -0
- package/build/index.d.ts +13 -0
- package/build/index.js +52 -0
- package/build/tools/cache/reference-cache.d.ts +42 -0
- package/build/tools/cache/reference-cache.js +63 -0
- package/build/tools/configs/account.d.ts +17 -0
- package/build/tools/configs/account.js +28 -0
- package/build/tools/configs/bank-money-in.d.ts +10 -0
- package/build/tools/configs/bank-money-in.js +22 -0
- package/build/tools/configs/bank-money-out.d.ts +10 -0
- package/build/tools/configs/bank-money-out.js +22 -0
- package/build/tools/configs/bank-transfer.d.ts +11 -0
- package/build/tools/configs/bank-transfer.js +23 -0
- package/build/tools/configs/contact-group.d.ts +11 -0
- package/build/tools/configs/contact-group.js +19 -0
- package/build/tools/configs/contact.d.ts +14 -0
- package/build/tools/configs/contact.js +25 -0
- package/build/tools/configs/delivery-order.d.ts +8 -0
- package/build/tools/configs/delivery-order.js +20 -0
- package/build/tools/configs/file.d.ts +18 -0
- package/build/tools/configs/file.js +26 -0
- package/build/tools/configs/goods-received-note.d.ts +8 -0
- package/build/tools/configs/goods-received-note.js +20 -0
- package/build/tools/configs/journal-entry.d.ts +14 -0
- package/build/tools/configs/journal-entry.js +26 -0
- package/build/tools/configs/location.d.ts +20 -0
- package/build/tools/configs/location.js +28 -0
- package/build/tools/configs/product-bundle.d.ts +18 -0
- package/build/tools/configs/product-bundle.js +29 -0
- package/build/tools/configs/product-group.d.ts +14 -0
- package/build/tools/configs/product-group.js +22 -0
- package/build/tools/configs/product.d.ts +24 -0
- package/build/tools/configs/product.js +35 -0
- package/build/tools/configs/purchase-bill.d.ts +9 -0
- package/build/tools/configs/purchase-bill.js +21 -0
- package/build/tools/configs/purchase-credit-note.d.ts +8 -0
- package/build/tools/configs/purchase-credit-note.js +20 -0
- package/build/tools/configs/purchase-order.d.ts +8 -0
- package/build/tools/configs/purchase-order.js +20 -0
- package/build/tools/configs/purchase-payment.d.ts +8 -0
- package/build/tools/configs/purchase-payment.js +20 -0
- package/build/tools/configs/purchase-refund.d.ts +8 -0
- package/build/tools/configs/purchase-refund.js +20 -0
- package/build/tools/configs/sales-credit-note.d.ts +8 -0
- package/build/tools/configs/sales-credit-note.js +20 -0
- package/build/tools/configs/sales-invoice.d.ts +8 -0
- package/build/tools/configs/sales-invoice.js +20 -0
- package/build/tools/configs/sales-order.d.ts +8 -0
- package/build/tools/configs/sales-order.js +20 -0
- package/build/tools/configs/sales-payment.d.ts +8 -0
- package/build/tools/configs/sales-payment.js +20 -0
- package/build/tools/configs/sales-quote.d.ts +8 -0
- package/build/tools/configs/sales-quote.js +20 -0
- package/build/tools/configs/sales-refund.d.ts +8 -0
- package/build/tools/configs/sales-refund.js +20 -0
- package/build/tools/configs/tag-group.d.ts +11 -0
- package/build/tools/configs/tag-group.js +22 -0
- package/build/tools/configs/tag.d.ts +11 -0
- package/build/tools/configs/tag.js +22 -0
- package/build/tools/custom/account-tools.d.ts +21 -0
- package/build/tools/custom/account-tools.js +119 -0
- package/build/tools/custom/contact-archive.d.ts +17 -0
- package/build/tools/custom/contact-archive.js +72 -0
- package/build/tools/custom/control-panel-archive.d.ts +17 -0
- package/build/tools/custom/control-panel-archive.js +72 -0
- package/build/tools/custom/file-upload.d.ts +17 -0
- package/build/tools/custom/file-upload.js +43 -0
- package/build/tools/custom/journal-entry-tools.d.ts +20 -0
- package/build/tools/custom/journal-entry-tools.js +109 -0
- package/build/tools/custom/location-tools.d.ts +20 -0
- package/build/tools/custom/location-tools.js +96 -0
- package/build/tools/custom/product-archive.d.ts +17 -0
- package/build/tools/custom/product-archive.js +124 -0
- package/build/tools/custom/reference-data.d.ts +21 -0
- package/build/tools/custom/reference-data.js +108 -0
- package/build/tools/factory.d.ts +26 -0
- package/build/tools/factory.js +224 -0
- package/build/tools/registry.d.ts +22 -0
- package/build/tools/registry.js +140 -0
- package/build/tools/validation/double-entry.d.ts +46 -0
- package/build/tools/validation/double-entry.js +66 -0
- package/build/types/api-responses.d.ts +21 -0
- package/build/types/api-responses.js +6 -0
- package/build/types/bukku.d.ts +93 -0
- package/build/types/bukku.js +11 -0
- package/build/utils/logger.d.ts +6 -0
- package/build/utils/logger.js +8 -0
- package/package.json +51 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP to MCP Error Transformation
|
|
3
|
+
* Converts HTTP error responses into conversational MCP error messages
|
|
4
|
+
*/
|
|
5
|
+
export function transformHttpError(status, body, operation) {
|
|
6
|
+
// Handle authentication errors (401)
|
|
7
|
+
if (status === 401) {
|
|
8
|
+
return {
|
|
9
|
+
isError: true,
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: `Bukku authentication failed for "${operation}". The BUKKU_API_TOKEN environment variable is either missing or invalid. Please check your token and restart the server with the correct credentials.`,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Handle permission errors (403)
|
|
19
|
+
if (status === 403) {
|
|
20
|
+
return {
|
|
21
|
+
isError: true,
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: `You don't have permission to "${operation}". Please check your Bukku account permissions and ensure you have access to this resource.`,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Handle not found errors (404)
|
|
31
|
+
if (status === 404) {
|
|
32
|
+
return {
|
|
33
|
+
isError: true,
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: 'text',
|
|
37
|
+
text: `I couldn't find that item when trying to "${operation}". Try listing the available items first to see what's accessible.`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Handle validation errors (400, 422)
|
|
43
|
+
if (status === 400 || status === 422) {
|
|
44
|
+
const parsedBody = body;
|
|
45
|
+
const errors = parsedBody?.errors;
|
|
46
|
+
if (errors && typeof errors === 'object') {
|
|
47
|
+
// Multiple validation errors - show all at once
|
|
48
|
+
const errorMessages = Object.entries(errors)
|
|
49
|
+
.map(([field, messages]) => ` - ${field}: ${messages.join(', ')}`)
|
|
50
|
+
.join('\n');
|
|
51
|
+
return {
|
|
52
|
+
isError: true,
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: `Validation failed for "${operation}":\n${errorMessages}\n\nPlease fix these issues and try again.`,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Single error message
|
|
63
|
+
const message = parsedBody?.message || 'Invalid request';
|
|
64
|
+
return {
|
|
65
|
+
isError: true,
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: 'text',
|
|
69
|
+
text: `${message} when trying to "${operation}". Please check your input and try again.`,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Handle service unavailable (503)
|
|
76
|
+
if (status === 503) {
|
|
77
|
+
return {
|
|
78
|
+
isError: true,
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: `Bukku is temporarily unavailable while trying to "${operation}". Please try again in a few moments.`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Handle server errors (500+)
|
|
88
|
+
if (status !== null && status >= 500) {
|
|
89
|
+
// Include response body for debugging server errors
|
|
90
|
+
// Often these contain validation errors that should have been 400s
|
|
91
|
+
const parsedBody = body;
|
|
92
|
+
const bodyText = body ? `\n\nServer response: ${JSON.stringify(parsedBody, null, 2)}` : '';
|
|
93
|
+
return {
|
|
94
|
+
isError: true,
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: 'text',
|
|
98
|
+
text: `An unexpected error occurred on Bukku's servers while trying to "${operation}". Please try again, and if the issue persists, contact Bukku support.${bodyText}`,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Fallback for unknown status codes
|
|
104
|
+
return {
|
|
105
|
+
isError: true,
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: `An error occurred while trying to "${operation}". Please check your request and try again.`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function transformNetworkError(error, operation) {
|
|
115
|
+
// Check if it's a network-related error
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
117
|
+
if (errorMessage.includes('fetch') ||
|
|
118
|
+
errorMessage.includes('connect') ||
|
|
119
|
+
errorMessage.includes('network') ||
|
|
120
|
+
error instanceof TypeError) {
|
|
121
|
+
return {
|
|
122
|
+
isError: true,
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: 'text',
|
|
126
|
+
text: `Couldn't connect to Bukku while trying to "${operation}". Please check your internet connection and ensure the Bukku API is accessible.`,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Fallback for unknown errors
|
|
132
|
+
return {
|
|
133
|
+
isError: true,
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: 'text',
|
|
137
|
+
text: `An unexpected error occurred while trying to "${operation}": ${errorMessage}. Please try again.`,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { transformHttpError, transformNetworkError } from './transform.ts';
|
|
4
|
+
describe('transformHttpError', () => {
|
|
5
|
+
test('401 error produces auth failure message mentioning BUKKU_API_TOKEN', () => {
|
|
6
|
+
const result = transformHttpError(401, { message: 'Unauthorized' }, 'create invoice');
|
|
7
|
+
assert.strictEqual(result.isError, true);
|
|
8
|
+
assert.ok(Array.isArray(result.content));
|
|
9
|
+
assert.strictEqual(result.content.length, 1);
|
|
10
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
11
|
+
const text = result.content[0].text;
|
|
12
|
+
assert.ok(text.includes('Bukku') || text.includes('token'), 'Should mention Bukku or token');
|
|
13
|
+
assert.ok(text.includes('BUKKU_API_TOKEN'), 'Should mention BUKKU_API_TOKEN');
|
|
14
|
+
assert.ok(text.length > 20, 'Should suggest next step');
|
|
15
|
+
});
|
|
16
|
+
test('403 error produces permission error with suggestion', () => {
|
|
17
|
+
const result = transformHttpError(403, { message: 'Forbidden' }, 'list contacts');
|
|
18
|
+
assert.strictEqual(result.isError, true);
|
|
19
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
20
|
+
const text = result.content[0].text;
|
|
21
|
+
assert.ok(text.toLowerCase().includes('permission') || text.toLowerCase().includes('access'), 'Should mention permission or access');
|
|
22
|
+
assert.ok(text.length > 20, 'Should suggest next step');
|
|
23
|
+
});
|
|
24
|
+
test('404 error suggests listing available items', () => {
|
|
25
|
+
const result = transformHttpError(404, { message: 'Not found' }, 'get sales invoice');
|
|
26
|
+
assert.strictEqual(result.isError, true);
|
|
27
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
28
|
+
const text = result.content[0].text;
|
|
29
|
+
assert.ok(text.toLowerCase().includes("couldn't find") || text.toLowerCase().includes('not found'), 'Should indicate not found');
|
|
30
|
+
assert.ok(text.toLowerCase().includes('list') || text.toLowerCase().includes('available'), 'Should suggest listing items');
|
|
31
|
+
});
|
|
32
|
+
test('400 error shows all validation errors at once', () => {
|
|
33
|
+
const result = transformHttpError(400, {
|
|
34
|
+
message: 'Validation failed',
|
|
35
|
+
errors: {
|
|
36
|
+
contact_id: ['required'],
|
|
37
|
+
date: ['invalid format'],
|
|
38
|
+
},
|
|
39
|
+
}, 'create invoice');
|
|
40
|
+
assert.strictEqual(result.isError, true);
|
|
41
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
42
|
+
const text = result.content[0].text;
|
|
43
|
+
assert.ok(text.includes('contact_id'), 'Should include contact_id error');
|
|
44
|
+
assert.ok(text.includes('date'), 'Should include date error');
|
|
45
|
+
assert.ok(text.length > 20, 'Should suggest next step');
|
|
46
|
+
});
|
|
47
|
+
test('400 error without errors field shows message', () => {
|
|
48
|
+
const result = transformHttpError(400, { message: 'Something wrong' }, 'update order');
|
|
49
|
+
assert.strictEqual(result.isError, true);
|
|
50
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
51
|
+
const text = result.content[0].text;
|
|
52
|
+
assert.ok(text.includes('Something wrong'), 'Should include error message');
|
|
53
|
+
assert.ok(text.length > 20, 'Should suggest next step');
|
|
54
|
+
});
|
|
55
|
+
test('422 validation error handled like 400', () => {
|
|
56
|
+
const result = transformHttpError(422, {
|
|
57
|
+
message: 'Unprocessable',
|
|
58
|
+
errors: {
|
|
59
|
+
items: ['at least one item required'],
|
|
60
|
+
},
|
|
61
|
+
}, 'create invoice');
|
|
62
|
+
assert.strictEqual(result.isError, true);
|
|
63
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
64
|
+
const text = result.content[0].text;
|
|
65
|
+
assert.ok(text.includes('items'), 'Should include items error');
|
|
66
|
+
assert.ok(text.length > 20, 'Should suggest next step');
|
|
67
|
+
});
|
|
68
|
+
test('500 error produces helpful fallback with retry suggestion', () => {
|
|
69
|
+
const result = transformHttpError(500, { message: 'Internal error' }, 'list invoices');
|
|
70
|
+
assert.strictEqual(result.isError, true);
|
|
71
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
72
|
+
const text = result.content[0].text;
|
|
73
|
+
assert.ok(text.toLowerCase().includes('unexpected') || text.toLowerCase().includes('error'), 'Should indicate unexpected error');
|
|
74
|
+
assert.ok(text.toLowerCase().includes('try') || text.toLowerCase().includes('again'), 'Should suggest trying again');
|
|
75
|
+
});
|
|
76
|
+
test('503 error suggests trying later', () => {
|
|
77
|
+
const result = transformHttpError(503, { message: 'Service unavailable' }, 'create payment');
|
|
78
|
+
assert.strictEqual(result.isError, true);
|
|
79
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
80
|
+
const text = result.content[0].text;
|
|
81
|
+
assert.ok(text.toLowerCase().includes('unavailable') || text.toLowerCase().includes('temporarily'), 'Should indicate service unavailable');
|
|
82
|
+
assert.ok(text.toLowerCase().includes('later') || text.toLowerCase().includes('try'), 'Should suggest trying later');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('transformNetworkError', () => {
|
|
86
|
+
test('network error suggests checking connection', () => {
|
|
87
|
+
const networkError = new TypeError('Failed to fetch');
|
|
88
|
+
const result = transformNetworkError(networkError, 'list contacts');
|
|
89
|
+
assert.strictEqual(result.isError, true);
|
|
90
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
91
|
+
const text = result.content[0].text;
|
|
92
|
+
assert.ok(text.toLowerCase().includes('connect') || text.toLowerCase().includes('network'), 'Should mention connection or network');
|
|
93
|
+
assert.ok(text.length > 20, 'Should suggest next step');
|
|
94
|
+
});
|
|
95
|
+
test('handles unknown error gracefully', () => {
|
|
96
|
+
const result = transformNetworkError('unknown error', 'create invoice');
|
|
97
|
+
assert.strictEqual(result.isError, true);
|
|
98
|
+
assert.strictEqual(result.content[0].type, 'text');
|
|
99
|
+
assert.ok(result.content[0].text.length > 10, 'Should provide some error message');
|
|
100
|
+
});
|
|
101
|
+
});
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bukku MCP Server Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Startup sequence:
|
|
6
|
+
* 1. Validate environment variables (BUKKU_API_TOKEN, BUKKU_COMPANY_SUBDOMAIN)
|
|
7
|
+
* 2. Create BukkuClient with validated env
|
|
8
|
+
* 3. Validate API token via lightweight API call
|
|
9
|
+
* 4. Create MCP server and register tools
|
|
10
|
+
* 5. Connect via stdio transport
|
|
11
|
+
* 6. Log startup to stderr
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/build/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bukku MCP Server Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Startup sequence:
|
|
6
|
+
* 1. Validate environment variables (BUKKU_API_TOKEN, BUKKU_COMPANY_SUBDOMAIN)
|
|
7
|
+
* 2. Create BukkuClient with validated env
|
|
8
|
+
* 3. Validate API token via lightweight API call
|
|
9
|
+
* 4. Create MCP server and register tools
|
|
10
|
+
* 5. Connect via stdio transport
|
|
11
|
+
* 6. Log startup to stderr
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
|
+
import { validateEnv } from "./config/env.js";
|
|
18
|
+
import { BukkuClient } from "./client/bukku-client.js";
|
|
19
|
+
import { registerAllTools } from "./tools/registry.js";
|
|
20
|
+
import { log } from "./utils/logger.js";
|
|
21
|
+
// Read package.json version dynamically
|
|
22
|
+
const packageJsonPath = new URL("../package.json", import.meta.url);
|
|
23
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(packageJsonPath), "utf-8"));
|
|
24
|
+
/**
|
|
25
|
+
* Main entry point.
|
|
26
|
+
* Exits process if configuration or authentication fails.
|
|
27
|
+
*/
|
|
28
|
+
async function main() {
|
|
29
|
+
// Step 1: Validate environment variables
|
|
30
|
+
const env = validateEnv();
|
|
31
|
+
// Step 2: Create authenticated Bukku API client
|
|
32
|
+
const client = new BukkuClient(env);
|
|
33
|
+
// Step 3: Validate API token
|
|
34
|
+
await client.validateToken();
|
|
35
|
+
// Step 4: Create MCP server
|
|
36
|
+
const server = new McpServer({
|
|
37
|
+
name: "bukku",
|
|
38
|
+
version: pkg.version,
|
|
39
|
+
});
|
|
40
|
+
// Step 5: Register all tools
|
|
41
|
+
const toolCount = registerAllTools(server, client);
|
|
42
|
+
// Step 6: Connect via stdio transport
|
|
43
|
+
const transport = new StdioServerTransport();
|
|
44
|
+
await server.connect(transport);
|
|
45
|
+
// Step 7: Log startup to stderr
|
|
46
|
+
log(`Bukku MCP server started (${toolCount} tools registered)`);
|
|
47
|
+
}
|
|
48
|
+
// Execute main and handle fatal errors
|
|
49
|
+
main().catch((error) => {
|
|
50
|
+
log("Fatal error during startup:", error);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference Data Cache
|
|
3
|
+
*
|
|
4
|
+
* Transparent in-memory cache for Bukku reference data (tax codes, currencies, etc.)
|
|
5
|
+
* with 5-minute TTL per roadmap requirement LIST-02.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - In-memory Map-based storage
|
|
9
|
+
* - 5-minute TTL (configurable for testing)
|
|
10
|
+
* - Lazy deletion: expired entries removed on access
|
|
11
|
+
* - Cache key = reference data type name (e.g., "tax_codes", "currencies")
|
|
12
|
+
*/
|
|
13
|
+
export declare class ReferenceDataCache {
|
|
14
|
+
private cache;
|
|
15
|
+
private readonly ttlMs;
|
|
16
|
+
/**
|
|
17
|
+
* Create a new reference data cache
|
|
18
|
+
* @param ttlMs Time-to-live in milliseconds (default: 5 minutes)
|
|
19
|
+
*/
|
|
20
|
+
constructor(ttlMs?: number);
|
|
21
|
+
/**
|
|
22
|
+
* Get cached data by key
|
|
23
|
+
* @param key Cache key (reference data type name)
|
|
24
|
+
* @returns Cached data or undefined if not found or expired
|
|
25
|
+
*/
|
|
26
|
+
get<T>(key: string): T | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Store data in cache with TTL
|
|
29
|
+
* @param key Cache key (reference data type name)
|
|
30
|
+
* @param data Data to cache
|
|
31
|
+
*/
|
|
32
|
+
set<T>(key: string, data: T): void;
|
|
33
|
+
/**
|
|
34
|
+
* Invalidate a specific cache entry
|
|
35
|
+
* @param key Cache key to invalidate
|
|
36
|
+
*/
|
|
37
|
+
invalidate(key: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Clear all cached entries
|
|
40
|
+
*/
|
|
41
|
+
clear(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference Data Cache
|
|
3
|
+
*
|
|
4
|
+
* Transparent in-memory cache for Bukku reference data (tax codes, currencies, etc.)
|
|
5
|
+
* with 5-minute TTL per roadmap requirement LIST-02.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - In-memory Map-based storage
|
|
9
|
+
* - 5-minute TTL (configurable for testing)
|
|
10
|
+
* - Lazy deletion: expired entries removed on access
|
|
11
|
+
* - Cache key = reference data type name (e.g., "tax_codes", "currencies")
|
|
12
|
+
*/
|
|
13
|
+
export class ReferenceDataCache {
|
|
14
|
+
cache = new Map();
|
|
15
|
+
ttlMs;
|
|
16
|
+
/**
|
|
17
|
+
* Create a new reference data cache
|
|
18
|
+
* @param ttlMs Time-to-live in milliseconds (default: 5 minutes)
|
|
19
|
+
*/
|
|
20
|
+
constructor(ttlMs = 5 * 60 * 1000) {
|
|
21
|
+
this.ttlMs = ttlMs;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get cached data by key
|
|
25
|
+
* @param key Cache key (reference data type name)
|
|
26
|
+
* @returns Cached data or undefined if not found or expired
|
|
27
|
+
*/
|
|
28
|
+
get(key) {
|
|
29
|
+
const entry = this.cache.get(key);
|
|
30
|
+
if (!entry)
|
|
31
|
+
return undefined;
|
|
32
|
+
// Check expiration (lazy deletion)
|
|
33
|
+
if (Date.now() > entry.expiresAt) {
|
|
34
|
+
this.cache.delete(key);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return entry.data;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Store data in cache with TTL
|
|
41
|
+
* @param key Cache key (reference data type name)
|
|
42
|
+
* @param data Data to cache
|
|
43
|
+
*/
|
|
44
|
+
set(key, data) {
|
|
45
|
+
this.cache.set(key, {
|
|
46
|
+
data,
|
|
47
|
+
expiresAt: Date.now() + this.ttlMs,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Invalidate a specific cache entry
|
|
52
|
+
* @param key Cache key to invalidate
|
|
53
|
+
*/
|
|
54
|
+
invalidate(key) {
|
|
55
|
+
this.cache.delete(key);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Clear all cached entries
|
|
59
|
+
*/
|
|
60
|
+
clear() {
|
|
61
|
+
this.cache.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Account Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /accounts
|
|
6
|
+
* Response keys: account (singular), accounts (plural)
|
|
7
|
+
* Operations: get, create, update, delete ONLY — NO list operation
|
|
8
|
+
* - List omitted because Phase 5's list-accounts reference data tool already
|
|
9
|
+
* occupies that tool name (provides cached quick lookup)
|
|
10
|
+
* - Use search-accounts (custom tool in account-tools.ts) for filtered search
|
|
11
|
+
* of the chart of accounts
|
|
12
|
+
* - Archive/unarchive handled by custom tools (account-tools.ts), not status
|
|
13
|
+
* update, because API expects { is_archived: boolean }
|
|
14
|
+
* Delete constraint: Only accounts with no children, not in locked system type,
|
|
15
|
+
* and not used in transactions can be deleted
|
|
16
|
+
*/
|
|
17
|
+
export declare const accountConfig: CrudEntityConfig;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* API: /accounts
|
|
5
|
+
* Response keys: account (singular), accounts (plural)
|
|
6
|
+
* Operations: get, create, update, delete ONLY — NO list operation
|
|
7
|
+
* - List omitted because Phase 5's list-accounts reference data tool already
|
|
8
|
+
* occupies that tool name (provides cached quick lookup)
|
|
9
|
+
* - Use search-accounts (custom tool in account-tools.ts) for filtered search
|
|
10
|
+
* of the chart of accounts
|
|
11
|
+
* - Archive/unarchive handled by custom tools (account-tools.ts), not status
|
|
12
|
+
* update, because API expects { is_archived: boolean }
|
|
13
|
+
* Delete constraint: Only accounts with no children, not in locked system type,
|
|
14
|
+
* and not used in transactions can be deleted
|
|
15
|
+
*/
|
|
16
|
+
export const accountConfig = {
|
|
17
|
+
entity: "account",
|
|
18
|
+
apiBasePath: "/accounts",
|
|
19
|
+
singularKey: "account",
|
|
20
|
+
pluralKey: "accounts",
|
|
21
|
+
description: "account",
|
|
22
|
+
operations: ["get", "create", "update", "delete"],
|
|
23
|
+
hasStatusUpdate: false,
|
|
24
|
+
listFilters: [],
|
|
25
|
+
businessRules: {
|
|
26
|
+
delete: "Only accounts that are not assigned to locked system type, have no children, and are not used in transactions can be deleted. Use archive-account instead if the account has transaction history.",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bank Money In Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /banking/incomes
|
|
6
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
7
|
+
* Money In represents incoming cash transactions (deposits, receipts, etc.)
|
|
8
|
+
* Shares the same status lifecycle as sales/purchase transactions.
|
|
9
|
+
*/
|
|
10
|
+
export declare const bankMoneyInConfig: CrudEntityConfig;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bank Money In Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* API: /banking/incomes
|
|
5
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
6
|
+
* Money In represents incoming cash transactions (deposits, receipts, etc.)
|
|
7
|
+
* Shares the same status lifecycle as sales/purchase transactions.
|
|
8
|
+
*/
|
|
9
|
+
export const bankMoneyInConfig = {
|
|
10
|
+
entity: "bank-money-in",
|
|
11
|
+
apiBasePath: "/banking/incomes",
|
|
12
|
+
singularKey: "transaction",
|
|
13
|
+
pluralKey: "transactions",
|
|
14
|
+
description: "bank money in transaction",
|
|
15
|
+
operations: ["list", "get", "create", "update", "delete"],
|
|
16
|
+
hasStatusUpdate: true,
|
|
17
|
+
listFilters: ["contact_id", "account_id", "email_status"],
|
|
18
|
+
businessRules: {
|
|
19
|
+
delete: "Only draft and void money in transactions can be deleted. Ready or pending approval transactions cannot be deleted — use update-bank-money-in-status to void a ready transaction instead.",
|
|
20
|
+
statusTransitions: "Valid transitions: draft -> pending_approval, draft -> ready, pending_approval -> ready, ready -> void. A void transaction is final and cannot be changed. There is no way to revert from ready, pending_approval, or void back to draft.",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bank Money Out Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /banking/expenses
|
|
6
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
7
|
+
* Money Out represents outgoing cash transactions (payments, disbursements, etc.)
|
|
8
|
+
* Shares the same status lifecycle as sales/purchase transactions.
|
|
9
|
+
*/
|
|
10
|
+
export declare const bankMoneyOutConfig: CrudEntityConfig;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bank Money Out Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* API: /banking/expenses
|
|
5
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
6
|
+
* Money Out represents outgoing cash transactions (payments, disbursements, etc.)
|
|
7
|
+
* Shares the same status lifecycle as sales/purchase transactions.
|
|
8
|
+
*/
|
|
9
|
+
export const bankMoneyOutConfig = {
|
|
10
|
+
entity: "bank-money-out",
|
|
11
|
+
apiBasePath: "/banking/expenses",
|
|
12
|
+
singularKey: "transaction",
|
|
13
|
+
pluralKey: "transactions",
|
|
14
|
+
description: "bank money out transaction",
|
|
15
|
+
operations: ["list", "get", "create", "update", "delete"],
|
|
16
|
+
hasStatusUpdate: true,
|
|
17
|
+
listFilters: ["contact_id", "account_id", "email_status"],
|
|
18
|
+
businessRules: {
|
|
19
|
+
delete: "Only draft and void money out transactions can be deleted. Ready or pending approval transactions cannot be deleted — use update-bank-money-out-status to void a ready transaction instead.",
|
|
20
|
+
statusTransitions: "Valid transitions: draft -> pending_approval, draft -> ready, pending_approval -> ready, ready -> void. A void transaction is final and cannot be changed. There is no way to revert from ready, pending_approval, or void back to draft.",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bank Transfer Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /banking/transfers
|
|
6
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
7
|
+
* Transfers are account-to-account movements (no contact involved).
|
|
8
|
+
* Has fewer list filters than money in/out — only account_id (no contact_id, no email_status).
|
|
9
|
+
* Shares the same status lifecycle as other banking/sales/purchase transactions.
|
|
10
|
+
*/
|
|
11
|
+
export declare const bankTransferConfig: CrudEntityConfig;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bank Transfer Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* API: /banking/transfers
|
|
5
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
6
|
+
* Transfers are account-to-account movements (no contact involved).
|
|
7
|
+
* Has fewer list filters than money in/out — only account_id (no contact_id, no email_status).
|
|
8
|
+
* Shares the same status lifecycle as other banking/sales/purchase transactions.
|
|
9
|
+
*/
|
|
10
|
+
export const bankTransferConfig = {
|
|
11
|
+
entity: "bank-transfer",
|
|
12
|
+
apiBasePath: "/banking/transfers",
|
|
13
|
+
singularKey: "transaction",
|
|
14
|
+
pluralKey: "transactions",
|
|
15
|
+
description: "bank transfer",
|
|
16
|
+
operations: ["list", "get", "create", "update", "delete"],
|
|
17
|
+
hasStatusUpdate: true,
|
|
18
|
+
listFilters: ["account_id"],
|
|
19
|
+
businessRules: {
|
|
20
|
+
delete: "Only draft and void transfers can be deleted. Ready or pending approval transfers cannot be deleted — use update-bank-transfer-status to void a ready transfer instead.",
|
|
21
|
+
statusTransitions: "Valid transitions: draft -> pending_approval, draft -> ready, pending_approval -> ready, ready -> void. A void transfer is final and cannot be changed. There is no way to revert from ready, pending_approval, or void back to draft.",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Contact Group Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /contacts/groups
|
|
6
|
+
* Response keys: group (singular), groups (plural)
|
|
7
|
+
* Groups organize contacts into categories.
|
|
8
|
+
* Simplest entity config — no status operations, no entity-specific filters,
|
|
9
|
+
* no business rules. Factory provides base pagination parameters.
|
|
10
|
+
*/
|
|
11
|
+
export declare const contactGroupConfig: CrudEntityConfig;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact Group Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* API: /contacts/groups
|
|
5
|
+
* Response keys: group (singular), groups (plural)
|
|
6
|
+
* Groups organize contacts into categories.
|
|
7
|
+
* Simplest entity config — no status operations, no entity-specific filters,
|
|
8
|
+
* no business rules. Factory provides base pagination parameters.
|
|
9
|
+
*/
|
|
10
|
+
export const contactGroupConfig = {
|
|
11
|
+
entity: "contact-group",
|
|
12
|
+
apiBasePath: "/contacts/groups",
|
|
13
|
+
singularKey: "group",
|
|
14
|
+
pluralKey: "groups",
|
|
15
|
+
description: "contact group",
|
|
16
|
+
operations: ["list", "get", "create", "update", "delete"],
|
|
17
|
+
hasStatusUpdate: false,
|
|
18
|
+
listFilters: [],
|
|
19
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Contact Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /contacts
|
|
6
|
+
* Response keys: contact (singular), contacts (plural) — NOT transaction/transactions
|
|
7
|
+
* This is the first non-transaction entity; uses custom wrapper keys.
|
|
8
|
+
* Archive/unarchive is handled by separate custom tools (contact-archive.ts),
|
|
9
|
+
* NOT the factory status tool, because the API expects { is_archived: boolean }
|
|
10
|
+
* instead of { status: string }.
|
|
11
|
+
* List filter: status accepts ALL, ACTIVE, or INACTIVE values.
|
|
12
|
+
* List filter: type accepts customer, supplier, or employee values.
|
|
13
|
+
*/
|
|
14
|
+
export declare const contactConfig: CrudEntityConfig;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact Entity Configuration
|
|
3
|
+
*
|
|
4
|
+
* API: /contacts
|
|
5
|
+
* Response keys: contact (singular), contacts (plural) — NOT transaction/transactions
|
|
6
|
+
* This is the first non-transaction entity; uses custom wrapper keys.
|
|
7
|
+
* Archive/unarchive is handled by separate custom tools (contact-archive.ts),
|
|
8
|
+
* NOT the factory status tool, because the API expects { is_archived: boolean }
|
|
9
|
+
* instead of { status: string }.
|
|
10
|
+
* List filter: status accepts ALL, ACTIVE, or INACTIVE values.
|
|
11
|
+
* List filter: type accepts customer, supplier, or employee values.
|
|
12
|
+
*/
|
|
13
|
+
export const contactConfig = {
|
|
14
|
+
entity: "contact",
|
|
15
|
+
apiBasePath: "/contacts",
|
|
16
|
+
singularKey: "contact",
|
|
17
|
+
pluralKey: "contacts",
|
|
18
|
+
description: "contact",
|
|
19
|
+
operations: ["list", "get", "create", "update", "delete"],
|
|
20
|
+
hasStatusUpdate: false,
|
|
21
|
+
listFilters: ["group_id", "status", "type", "is_myinvois_ready"],
|
|
22
|
+
businessRules: {
|
|
23
|
+
delete: "Only contacts with no linked transactions can be deleted. Archive instead if the contact has transaction history.",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CrudEntityConfig } from "../../types/bukku.js";
|
|
2
|
+
/**
|
|
3
|
+
* Delivery Order Entity Configuration
|
|
4
|
+
*
|
|
5
|
+
* API: /sales/delivery_orders
|
|
6
|
+
* Response keys: transaction (singular), transactions (plural)
|
|
7
|
+
*/
|
|
8
|
+
export declare const deliveryOrderConfig: CrudEntityConfig;
|