tally-simple 1.1.1 → 1.3.1
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/CHANGELOG.md +8 -8
- package/LICENSE +21 -21
- package/README.md +127 -70
- package/bin/tally-simple.js +175 -175
- package/docs/api.html +41 -0
- package/docs/api.md +20 -19
- package/docs/architecture.html +74 -0
- package/docs/architecture.md +65 -43
- package/docs/developer.html +57 -0
- package/docs/developer.md +41 -0
- package/docs/end-user.html +49 -0
- package/docs/end-user.md +34 -0
- package/docs/index.html +32 -0
- package/docs/repo-developer.html +91 -0
- package/docs/repo-developer.md +107 -0
- package/package.json +55 -47
- package/src/index.d.ts +20 -2
- package/src/index.js +8 -8
- package/src/v1/external-api/api.js +67 -67
- package/src/v1/external-api/api.json +4 -4
- package/src/v1/external-api/index.js +1 -1
- package/src/v1/index.js +1 -1
- package/src/v1/source.json +40 -40
- package/src/v1/traverse.js +103 -103
- package/src/v1/traverseObject/index.js +39 -39
- package/src/v2/external-api/api.js +67 -0
- package/src/v2/external-api/api.json +5 -0
- package/src/v2/external-api/index.js +1 -0
- package/src/v2/index.js +1 -0
- package/src/v2/source.json +40 -0
- package/src/v2/traverse.js +103 -0
- package/src/v2/traverseObject/index.js +39 -0
- package/src/v3/external-api/api.js +67 -0
- package/src/v3/external-api/api.json +6 -0
- package/src/v3/external-api/index.js +1 -0
- package/src/v3/index.js +1 -0
- package/src/v3/source.json +48 -0
- package/src/v3/traverse.js +103 -0
- package/src/v3/traverseObject/index.js +39 -0
- package/docs/cli.md +0 -32
- package/docs/development.md +0 -66
- package/docs/usage.md +0 -73
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { traverse } from "../traverse.js";
|
|
2
|
-
|
|
3
|
-
const traverseObject = async (
|
|
4
|
-
specJson,
|
|
5
|
-
relativePath,
|
|
6
|
-
pathToFind,
|
|
7
|
-
context = {}
|
|
8
|
-
) => {
|
|
9
|
-
const nextContext = {
|
|
10
|
-
...context,
|
|
11
|
-
connection: specJson.connection ?? context.connection,
|
|
12
|
-
request: specJson.request ?? context.request
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
for (const [key, value] of Object.entries(specJson)) {
|
|
16
|
-
if (key === "connection" || key === "request") continue;
|
|
17
|
-
|
|
18
|
-
if (typeof value === "object" && value !== null) {
|
|
19
|
-
const nextPath = relativePath
|
|
20
|
-
? `${relativePath}.${key}`
|
|
21
|
-
: key;
|
|
22
|
-
|
|
23
|
-
const result = await traverse(
|
|
24
|
-
value,
|
|
25
|
-
nextPath,
|
|
26
|
-
pathToFind,
|
|
27
|
-
nextContext
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (result !== undefined) {
|
|
31
|
-
return result;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return undefined;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export default traverseObject;
|
|
1
|
+
import { traverse } from "../traverse.js";
|
|
2
|
+
|
|
3
|
+
const traverseObject = async (
|
|
4
|
+
specJson,
|
|
5
|
+
relativePath,
|
|
6
|
+
pathToFind,
|
|
7
|
+
context = {}
|
|
8
|
+
) => {
|
|
9
|
+
const nextContext = {
|
|
10
|
+
...context,
|
|
11
|
+
connection: specJson.connection ?? context.connection,
|
|
12
|
+
request: specJson.request ?? context.request
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for (const [key, value] of Object.entries(specJson)) {
|
|
16
|
+
if (key === "connection" || key === "request") continue;
|
|
17
|
+
|
|
18
|
+
if (typeof value === "object" && value !== null) {
|
|
19
|
+
const nextPath = relativePath
|
|
20
|
+
? `${relativePath}.${key}`
|
|
21
|
+
: key;
|
|
22
|
+
|
|
23
|
+
const result = await traverse(
|
|
24
|
+
value,
|
|
25
|
+
nextPath,
|
|
26
|
+
pathToFind,
|
|
27
|
+
nextContext
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (result !== undefined) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default traverseObject;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import source from "../source.json" with { type: "json" };
|
|
2
|
+
import traverse from "../traverse.js";
|
|
3
|
+
import apiPaths from "./api.json" with { type: "json" };
|
|
4
|
+
|
|
5
|
+
const createApi = (options = {}) => {
|
|
6
|
+
const connection = {
|
|
7
|
+
...source.tally.connection,
|
|
8
|
+
...options.connection,
|
|
9
|
+
...(options.url ? { url: options.url } : {}),
|
|
10
|
+
...(options.method ? { method: options.method } : {}),
|
|
11
|
+
headers: {
|
|
12
|
+
...source.tally.connection.headers,
|
|
13
|
+
...options.connection?.headers,
|
|
14
|
+
...options.headers
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const request = options.request ?? source.tally.request;
|
|
18
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
19
|
+
const clientSource = {
|
|
20
|
+
...source,
|
|
21
|
+
tally: {
|
|
22
|
+
...source.tally,
|
|
23
|
+
connection,
|
|
24
|
+
request
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const createFunction = (path) => async (company) => {
|
|
29
|
+
if (typeof company !== "string" || !company.trim()) {
|
|
30
|
+
throw new TypeError("Company name is required.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return await traverse(clientSource, "", path, {
|
|
34
|
+
company: company.trim(),
|
|
35
|
+
connection,
|
|
36
|
+
request,
|
|
37
|
+
fetch: fetchImpl,
|
|
38
|
+
timeout: options.timeout
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const root = {};
|
|
43
|
+
|
|
44
|
+
for (const path of apiPaths) {
|
|
45
|
+
const parts = path.split(".");
|
|
46
|
+
let current = root;
|
|
47
|
+
|
|
48
|
+
parts.forEach((part, index) => {
|
|
49
|
+
const isLast = index === parts.length - 1;
|
|
50
|
+
|
|
51
|
+
if (isLast) {
|
|
52
|
+
current[part] = createFunction(path);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
current[part] ??= {};
|
|
57
|
+
current = current[part];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const rootName = apiPaths[0]?.split(".")[0];
|
|
62
|
+
|
|
63
|
+
return rootName ? root[rootName] : root;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { createApi };
|
|
67
|
+
export default createApi();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createApi, default } from "./api.js";
|
package/src/v2/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createApi, default } from "./external-api/api.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tally": {
|
|
3
|
+
"connection": {
|
|
4
|
+
"url": "http://localhost:9000",
|
|
5
|
+
"method": "POST",
|
|
6
|
+
"headers": {
|
|
7
|
+
"Content-Type": "text/xml"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"request": {
|
|
11
|
+
"body": "<ENVELOPE>\n<HEADER>\n<VERSION>1</VERSION>\n<TALLYREQUEST>Export</TALLYREQUEST>\n<TYPE>Collection</TYPE>\n<ID>TDLID</ID>\n</HEADER>\n<BODY>\n<DESC>\n<STATICVARIABLES>\n<SVEXPORTFORMAT>$$SysName:XML</SVEXPORTFORMAT>\n<SVCURRENTCOMPANY>{company}</SVCURRENTCOMPANY>\n</STATICVARIABLES>\n<TDL>\n<TDLMESSAGE>\n<COLLECTION NAME=\"TDLID\">\n{collectionBody}</COLLECTION>\n</TDLMESSAGE>\n</TDL>\n</DESC>\n</BODY>\n</ENVELOPE>"
|
|
12
|
+
},
|
|
13
|
+
"masters": {
|
|
14
|
+
"units": {
|
|
15
|
+
"fetch": {
|
|
16
|
+
"tdl": {
|
|
17
|
+
"collection": "<TYPE>Unit</TYPE>\n<FETCH>$$Alias:Name</FETCH>\n"
|
|
18
|
+
},
|
|
19
|
+
"action": "fetch"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"stockItems": {
|
|
23
|
+
"withBatches": {
|
|
24
|
+
"tdl": {
|
|
25
|
+
"collection": "<TYPE>StockItem</TYPE><FETCH>$$Alias:Name</FETCH><FETCH>BaseUnits</FETCH><FETCH>BatchAllocations.*</FETCH>"
|
|
26
|
+
},
|
|
27
|
+
"action": "fetch"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"ledgers": {
|
|
31
|
+
"withGstDetails": {
|
|
32
|
+
"tdl": {
|
|
33
|
+
"collection": "<TYPE>Ledger</TYPE><FETCH>$$Alias:Name</FETCH><FETCH>LEDGSTREGDETAILS.LIST</FETCH>"
|
|
34
|
+
},
|
|
35
|
+
"action": "fetch"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import traverseObject from "./traverseObject/index.js";
|
|
2
|
+
|
|
3
|
+
const escapeXml = (value) => value.replace(/[&<>"']/g, (character) => ({
|
|
4
|
+
"&": "&",
|
|
5
|
+
"<": "<",
|
|
6
|
+
">": ">",
|
|
7
|
+
'"': """,
|
|
8
|
+
"'": "'"
|
|
9
|
+
}[character]));
|
|
10
|
+
|
|
11
|
+
const buildRequestBody = (requestBody, { company, collection }) => {
|
|
12
|
+
return requestBody
|
|
13
|
+
.replace("{company}", escapeXml(company))
|
|
14
|
+
.replace("{collectionBody}", collection);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const execute = async (connection, xml, fetchImpl, timeout) => {
|
|
18
|
+
if (typeof fetchImpl !== "function") {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"No fetch implementation is available. Use Node.js 20+ or provide fetch to createTallyClient()."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const controller = typeof AbortController === "function"
|
|
25
|
+
? new AbortController()
|
|
26
|
+
: undefined;
|
|
27
|
+
const timeoutId = Number.isFinite(timeout) && timeout > 0
|
|
28
|
+
? setTimeout(() => controller?.abort(), timeout)
|
|
29
|
+
: undefined;
|
|
30
|
+
|
|
31
|
+
let response;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
response = await fetchImpl(connection.url, {
|
|
35
|
+
method: connection.method,
|
|
36
|
+
headers: connection.headers,
|
|
37
|
+
body: xml,
|
|
38
|
+
...(controller ? { signal: controller.signal } : {})
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (controller?.signal.aborted) {
|
|
42
|
+
throw new Error(`Tally request timed out after ${timeout} ms.`, {
|
|
43
|
+
cause: error
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw error;
|
|
48
|
+
} finally {
|
|
49
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const responseText = await response.text();
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Tally request failed with HTTP ${response.status}: ${responseText}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return responseText;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const traverse = async (
|
|
64
|
+
raka,
|
|
65
|
+
relativePath,
|
|
66
|
+
pathToFind,
|
|
67
|
+
context = {}
|
|
68
|
+
) => {
|
|
69
|
+
if (relativePath === pathToFind) {
|
|
70
|
+
if (raka?.action === "fetch") {
|
|
71
|
+
const body = buildRequestBody(
|
|
72
|
+
context.request.body,
|
|
73
|
+
{
|
|
74
|
+
company: context.company,
|
|
75
|
+
collection: raka.tdl.collection
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return await execute(
|
|
80
|
+
context.connection,
|
|
81
|
+
body,
|
|
82
|
+
context.fetch,
|
|
83
|
+
context.timeout
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return raka;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof raka === "object" && raka !== null) {
|
|
91
|
+
return await traverseObject(
|
|
92
|
+
raka,
|
|
93
|
+
relativePath,
|
|
94
|
+
pathToFind,
|
|
95
|
+
context
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export { traverse };
|
|
103
|
+
export default traverse;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { traverse } from "../traverse.js";
|
|
2
|
+
|
|
3
|
+
const traverseObject = async (
|
|
4
|
+
specJson,
|
|
5
|
+
relativePath,
|
|
6
|
+
pathToFind,
|
|
7
|
+
context = {}
|
|
8
|
+
) => {
|
|
9
|
+
const nextContext = {
|
|
10
|
+
...context,
|
|
11
|
+
connection: specJson.connection ?? context.connection,
|
|
12
|
+
request: specJson.request ?? context.request
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for (const [key, value] of Object.entries(specJson)) {
|
|
16
|
+
if (key === "connection" || key === "request") continue;
|
|
17
|
+
|
|
18
|
+
if (typeof value === "object" && value !== null) {
|
|
19
|
+
const nextPath = relativePath
|
|
20
|
+
? `${relativePath}.${key}`
|
|
21
|
+
: key;
|
|
22
|
+
|
|
23
|
+
const result = await traverse(
|
|
24
|
+
value,
|
|
25
|
+
nextPath,
|
|
26
|
+
pathToFind,
|
|
27
|
+
nextContext
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (result !== undefined) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default traverseObject;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import source from "../source.json" with { type: "json" };
|
|
2
|
+
import traverse from "../traverse.js";
|
|
3
|
+
import apiPaths from "./api.json" with { type: "json" };
|
|
4
|
+
|
|
5
|
+
const createApi = (options = {}) => {
|
|
6
|
+
const connection = {
|
|
7
|
+
...source.tally.connection,
|
|
8
|
+
...options.connection,
|
|
9
|
+
...(options.url ? { url: options.url } : {}),
|
|
10
|
+
...(options.method ? { method: options.method } : {}),
|
|
11
|
+
headers: {
|
|
12
|
+
...source.tally.connection.headers,
|
|
13
|
+
...options.connection?.headers,
|
|
14
|
+
...options.headers
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const request = options.request ?? source.tally.request;
|
|
18
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
19
|
+
const clientSource = {
|
|
20
|
+
...source,
|
|
21
|
+
tally: {
|
|
22
|
+
...source.tally,
|
|
23
|
+
connection,
|
|
24
|
+
request
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const createFunction = (path) => async (company) => {
|
|
29
|
+
if (typeof company !== "string" || !company.trim()) {
|
|
30
|
+
throw new TypeError("Company name is required.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return await traverse(clientSource, "", path, {
|
|
34
|
+
company: company.trim(),
|
|
35
|
+
connection,
|
|
36
|
+
request,
|
|
37
|
+
fetch: fetchImpl,
|
|
38
|
+
timeout: options.timeout
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const root = {};
|
|
43
|
+
|
|
44
|
+
for (const path of apiPaths) {
|
|
45
|
+
const parts = path.split(".");
|
|
46
|
+
let current = root;
|
|
47
|
+
|
|
48
|
+
parts.forEach((part, index) => {
|
|
49
|
+
const isLast = index === parts.length - 1;
|
|
50
|
+
|
|
51
|
+
if (isLast) {
|
|
52
|
+
current[part] = createFunction(path);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
current[part] ??= {};
|
|
57
|
+
current = current[part];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const rootName = apiPaths[0]?.split(".")[0];
|
|
62
|
+
|
|
63
|
+
return rootName ? root[rootName] : root;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { createApi };
|
|
67
|
+
export default createApi();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createApi, default } from "./api.js";
|
package/src/v3/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createApi, default } from "./external-api/api.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tally": {
|
|
3
|
+
"connection": {
|
|
4
|
+
"url": "http://localhost:9000",
|
|
5
|
+
"method": "POST",
|
|
6
|
+
"headers": {
|
|
7
|
+
"Content-Type": "text/xml"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"request": {
|
|
11
|
+
"body": "<ENVELOPE>\n<HEADER>\n<VERSION>1</VERSION>\n<TALLYREQUEST>Export</TALLYREQUEST>\n<TYPE>Collection</TYPE>\n<ID>TDLID</ID>\n</HEADER>\n<BODY>\n<DESC>\n<STATICVARIABLES>\n<SVEXPORTFORMAT>$$SysName:XML</SVEXPORTFORMAT>\n<SVCURRENTCOMPANY>{company}</SVCURRENTCOMPANY>\n</STATICVARIABLES>\n<TDL>\n<TDLMESSAGE>\n<COLLECTION NAME=\"TDLID\">\n{collectionBody}</COLLECTION>\n</TDLMESSAGE>\n</TDL>\n</DESC>\n</BODY>\n</ENVELOPE>"
|
|
12
|
+
},
|
|
13
|
+
"masters": {
|
|
14
|
+
"units": {
|
|
15
|
+
"fetch": {
|
|
16
|
+
"tdl": {
|
|
17
|
+
"collection": "<TYPE>Unit</TYPE>\n<FETCH>$$Alias:Name</FETCH>\n"
|
|
18
|
+
},
|
|
19
|
+
"action": "fetch"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"stockItems": {
|
|
23
|
+
"withBatches": {
|
|
24
|
+
"tdl": {
|
|
25
|
+
"collection": "<TYPE>StockItem</TYPE><FETCH>$$Alias:Name</FETCH><FETCH>BaseUnits</FETCH><FETCH>BatchAllocations.*</FETCH>"
|
|
26
|
+
},
|
|
27
|
+
"action": "fetch"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"ledgers": {
|
|
31
|
+
"withGstDetails": {
|
|
32
|
+
"tdl": {
|
|
33
|
+
"collection": "<TYPE>Ledger</TYPE><FETCH>$$Alias:Name</FETCH><FETCH>LEDGSTREGDETAILS.LIST</FETCH>"
|
|
34
|
+
},
|
|
35
|
+
"action": "fetch"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"stockGroup": {
|
|
39
|
+
"withParent": {
|
|
40
|
+
"tdl": {
|
|
41
|
+
"collection": "<TYPE>StockGroup</TYPE><FETCH>$$Alias:Name</FETCH><FETCH>Parent</FETCH>"
|
|
42
|
+
},
|
|
43
|
+
"action": "fetch"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import traverseObject from "./traverseObject/index.js";
|
|
2
|
+
|
|
3
|
+
const escapeXml = (value) => value.replace(/[&<>"']/g, (character) => ({
|
|
4
|
+
"&": "&",
|
|
5
|
+
"<": "<",
|
|
6
|
+
">": ">",
|
|
7
|
+
'"': """,
|
|
8
|
+
"'": "'"
|
|
9
|
+
}[character]));
|
|
10
|
+
|
|
11
|
+
const buildRequestBody = (requestBody, { company, collection }) => {
|
|
12
|
+
return requestBody
|
|
13
|
+
.replace("{company}", escapeXml(company))
|
|
14
|
+
.replace("{collectionBody}", collection);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const execute = async (connection, xml, fetchImpl, timeout) => {
|
|
18
|
+
if (typeof fetchImpl !== "function") {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"No fetch implementation is available. Use Node.js 20+ or provide fetch to createTallyClient()."
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const controller = typeof AbortController === "function"
|
|
25
|
+
? new AbortController()
|
|
26
|
+
: undefined;
|
|
27
|
+
const timeoutId = Number.isFinite(timeout) && timeout > 0
|
|
28
|
+
? setTimeout(() => controller?.abort(), timeout)
|
|
29
|
+
: undefined;
|
|
30
|
+
|
|
31
|
+
let response;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
response = await fetchImpl(connection.url, {
|
|
35
|
+
method: connection.method,
|
|
36
|
+
headers: connection.headers,
|
|
37
|
+
body: xml,
|
|
38
|
+
...(controller ? { signal: controller.signal } : {})
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (controller?.signal.aborted) {
|
|
42
|
+
throw new Error(`Tally request timed out after ${timeout} ms.`, {
|
|
43
|
+
cause: error
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw error;
|
|
48
|
+
} finally {
|
|
49
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const responseText = await response.text();
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Tally request failed with HTTP ${response.status}: ${responseText}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return responseText;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const traverse = async (
|
|
64
|
+
raka,
|
|
65
|
+
relativePath,
|
|
66
|
+
pathToFind,
|
|
67
|
+
context = {}
|
|
68
|
+
) => {
|
|
69
|
+
if (relativePath === pathToFind) {
|
|
70
|
+
if (raka?.action === "fetch") {
|
|
71
|
+
const body = buildRequestBody(
|
|
72
|
+
context.request.body,
|
|
73
|
+
{
|
|
74
|
+
company: context.company,
|
|
75
|
+
collection: raka.tdl.collection
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return await execute(
|
|
80
|
+
context.connection,
|
|
81
|
+
body,
|
|
82
|
+
context.fetch,
|
|
83
|
+
context.timeout
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return raka;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof raka === "object" && raka !== null) {
|
|
91
|
+
return await traverseObject(
|
|
92
|
+
raka,
|
|
93
|
+
relativePath,
|
|
94
|
+
pathToFind,
|
|
95
|
+
context
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export { traverse };
|
|
103
|
+
export default traverse;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { traverse } from "../traverse.js";
|
|
2
|
+
|
|
3
|
+
const traverseObject = async (
|
|
4
|
+
specJson,
|
|
5
|
+
relativePath,
|
|
6
|
+
pathToFind,
|
|
7
|
+
context = {}
|
|
8
|
+
) => {
|
|
9
|
+
const nextContext = {
|
|
10
|
+
...context,
|
|
11
|
+
connection: specJson.connection ?? context.connection,
|
|
12
|
+
request: specJson.request ?? context.request
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for (const [key, value] of Object.entries(specJson)) {
|
|
16
|
+
if (key === "connection" || key === "request") continue;
|
|
17
|
+
|
|
18
|
+
if (typeof value === "object" && value !== null) {
|
|
19
|
+
const nextPath = relativePath
|
|
20
|
+
? `${relativePath}.${key}`
|
|
21
|
+
: key;
|
|
22
|
+
|
|
23
|
+
const result = await traverse(
|
|
24
|
+
value,
|
|
25
|
+
nextPath,
|
|
26
|
+
pathToFind,
|
|
27
|
+
nextContext
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (result !== undefined) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default traverseObject;
|
package/docs/cli.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# CLI reference
|
|
2
|
-
|
|
3
|
-
The package publishes the tally-simple executable. With npm, use it through npx:
|
|
4
|
-
|
|
5
|
-
~~~bash
|
|
6
|
-
npx tally-simple <api-path> --company <company>
|
|
7
|
-
~~~
|
|
8
|
-
|
|
9
|
-
## Options
|
|
10
|
-
|
|
11
|
-
| Option | Meaning |
|
|
12
|
-
| --- | --- |
|
|
13
|
-
| -c, --company <name> | Company to query. Also reads TALLY_COMPANY. |
|
|
14
|
-
| -u, --url <url> | Tally endpoint. Also reads TALLY_URL. |
|
|
15
|
-
| --header <name:value> | Adds a request header. Repeat it for multiple headers. |
|
|
16
|
-
| --timeout <ms> | Aborts a request after the given number of milliseconds. |
|
|
17
|
-
| -h, --help | Prints command help. |
|
|
18
|
-
| -v, --version | Prints the installed package version. |
|
|
19
|
-
|
|
20
|
-
The command accepts masters.units.fetch and tally.masters.units.fetch equivalently. The response is written unchanged to stdout; errors and usage help are written to stderr.
|
|
21
|
-
|
|
22
|
-
Examples:
|
|
23
|
-
|
|
24
|
-
~~~bash
|
|
25
|
-
npx tally-simple masters.units.fetch --company Mani9
|
|
26
|
-
|
|
27
|
-
npx tally-simple masters.stockItems.withBatches \
|
|
28
|
-
--company Mani9 \
|
|
29
|
-
--header X-Request-Source:nightly
|
|
30
|
-
|
|
31
|
-
TALLY_COMPANY=Mani9 npx tally-simple masters.units.fetch
|
|
32
|
-
~~~
|