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.
Files changed (42) hide show
  1. package/CHANGELOG.md +8 -8
  2. package/LICENSE +21 -21
  3. package/README.md +127 -70
  4. package/bin/tally-simple.js +175 -175
  5. package/docs/api.html +41 -0
  6. package/docs/api.md +20 -19
  7. package/docs/architecture.html +74 -0
  8. package/docs/architecture.md +65 -43
  9. package/docs/developer.html +57 -0
  10. package/docs/developer.md +41 -0
  11. package/docs/end-user.html +49 -0
  12. package/docs/end-user.md +34 -0
  13. package/docs/index.html +32 -0
  14. package/docs/repo-developer.html +91 -0
  15. package/docs/repo-developer.md +107 -0
  16. package/package.json +55 -47
  17. package/src/index.d.ts +20 -2
  18. package/src/index.js +8 -8
  19. package/src/v1/external-api/api.js +67 -67
  20. package/src/v1/external-api/api.json +4 -4
  21. package/src/v1/external-api/index.js +1 -1
  22. package/src/v1/index.js +1 -1
  23. package/src/v1/source.json +40 -40
  24. package/src/v1/traverse.js +103 -103
  25. package/src/v1/traverseObject/index.js +39 -39
  26. package/src/v2/external-api/api.js +67 -0
  27. package/src/v2/external-api/api.json +5 -0
  28. package/src/v2/external-api/index.js +1 -0
  29. package/src/v2/index.js +1 -0
  30. package/src/v2/source.json +40 -0
  31. package/src/v2/traverse.js +103 -0
  32. package/src/v2/traverseObject/index.js +39 -0
  33. package/src/v3/external-api/api.js +67 -0
  34. package/src/v3/external-api/api.json +6 -0
  35. package/src/v3/external-api/index.js +1 -0
  36. package/src/v3/index.js +1 -0
  37. package/src/v3/source.json +48 -0
  38. package/src/v3/traverse.js +103 -0
  39. package/src/v3/traverseObject/index.js +39 -0
  40. package/docs/cli.md +0 -32
  41. package/docs/development.md +0 -66
  42. package/docs/usage.md +0 -73
package/package.json CHANGED
@@ -1,47 +1,55 @@
1
- {
2
- "name": "tally-simple",
3
- "version": "1.1.1",
4
- "description": "A small, typed JavaScript client and CLI for querying Tally through TDL definitions.",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "types": "./src/index.d.ts",
9
- "import": "./src/index.js",
10
- "default": "./src/index.js"
11
- },
12
- "./package.json": "./package.json"
13
- },
14
- "bin": {
15
- "tally-simple": "bin/tally-simple.js"
16
- },
17
- "main": "./src/index.js",
18
- "types": "./src/index.d.ts",
19
- "files": [
20
- "bin",
21
- "src",
22
- "docs",
23
- "README.md",
24
- "CHANGELOG.md",
25
- "LICENSE"
26
- ],
27
- "scripts": {
28
- "generate:dts": "node generate-dts.js",
29
- "test": "node --test test/test.js test/units.js",
30
- "verify": "npm run generate:dts && npm test",
31
- "prepack": "npm run verify",
32
- "prepublishOnly": "npm run verify"
33
- },
34
- "keywords": [
35
- "tally",
36
- "tdl",
37
- "api",
38
- "accounting"
39
- ],
40
- "license": "MIT",
41
- "engines": {
42
- "node": ">=20.10"
43
- },
44
- "publishConfig": {
45
- "access": "public"
46
- }
47
- }
1
+ {
2
+ "name": "tally-simple",
3
+ "version": "1.3.1",
4
+ "description": "A small, typed JavaScript client and CLI for querying Tally through TDL definitions.",
5
+ "homepage": "https://github.com/keshavsoft/tally-simple#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/keshavsoft/tally-simple.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/keshavsoft/tally-simple/issues"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./src/index.d.ts",
17
+ "import": "./src/index.js",
18
+ "default": "./src/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "bin": {
23
+ "tally-simple": "./bin/tally-simple.js"
24
+ },
25
+ "main": "./src/index.js",
26
+ "types": "./src/index.d.ts",
27
+ "files": [
28
+ "bin",
29
+ "src",
30
+ "docs",
31
+ "README.md",
32
+ "CHANGELOG.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "generate:dts": "node generate-dts.js",
37
+ "test": "node --test test/test.js test/units.js",
38
+ "verify": "npm run generate:dts && npm test",
39
+ "prepack": "npm run verify",
40
+ "prepublishOnly": "npm run verify"
41
+ },
42
+ "keywords": [
43
+ "tally",
44
+ "tdl",
45
+ "api",
46
+ "accounting"
47
+ ],
48
+ "license": "MIT",
49
+ "engines": {
50
+ "node": ">=20.10"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ }
55
+ }
package/src/index.d.ts CHANGED
@@ -1,8 +1,17 @@
1
+ /**
2
+ * Generated from src/v3/external-api/api.json and src/v3/source.json.
3
+ * Do not edit this file directly. Run npm run generate:dts instead.
4
+ */
1
5
  export interface TallyClientOptions {
6
+ /** Tally HTTP endpoint. Defaults to http://localhost:9000. */
2
7
  url?: string;
8
+ /** HTTP method used for the Tally request. Defaults to POST. */
3
9
  method?: string;
10
+ /** Additional request headers. */
4
11
  headers?: Record<string, string>;
12
+ /** Abort the request after this many milliseconds. */
5
13
  timeout?: number;
14
+ /** Optional fetch-compatible implementation for tests or adapters. */
6
15
  fetch?: (url: string, options: {
7
16
  method: string;
8
17
  headers: Record<string, string>;
@@ -18,19 +27,28 @@ export interface TallyClientOptions {
18
27
  export type TallyApi = {
19
28
  masters: {
20
29
  units: {
30
+ /** Runs tally.masters.units.fetch and returns Tally's raw XML response. */
21
31
  fetch: (company: string) => Promise<string>;
22
32
  };
23
33
  stockItems: {
34
+ /** Runs tally.masters.stockItems.withBatches and returns Tally's raw XML response. */
24
35
  withBatches: (company: string) => Promise<string>;
25
36
  };
26
37
  ledgers: {
38
+ /** Runs tally.masters.ledgers.withGstDetails and returns Tally's raw XML response. */
27
39
  withGstDetails: (company: string) => Promise<string>;
28
40
  };
41
+ stockGroup: {
42
+ /** Runs tally.masters.stockGroup.withParent and returns Tally's raw XML response. */
43
+ withParent: (company: string) => Promise<string>;
44
+ };
29
45
  };
30
46
  };
31
47
 
32
- declare const tally: TallyApi;
33
-
48
+ /** Create an independently configured Tally client. */
34
49
  export declare const createTallyClient: (options?: TallyClientOptions) => TallyApi;
50
+
51
+ /** Default Tally client using the package connection defaults. */
35
52
  export declare const tally: TallyApi;
53
+
36
54
  export default tally;
package/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { createApi } from "./v1/index.js";
2
-
3
- const createTallyClient = (options = {}) => createApi(options);
4
-
5
- const tally = createTallyClient();
6
-
7
- export { createTallyClient, tally };
8
- export default tally;
1
+ import { createApi } from "./v3/index.js";
2
+
3
+ const createTallyClient = (options = {}) => createApi(options);
4
+
5
+ const tally = createTallyClient();
6
+
7
+ export { createTallyClient, tally };
8
+ export default tally;
@@ -1,67 +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();
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();
@@ -1,5 +1,5 @@
1
- [
2
- "tally.masters.units.fetch",
3
- "tally.masters.stockItems.withBatches",
4
- "tally.masters.ledgers.withGstDetails"
1
+ [
2
+ "tally.masters.units.fetch",
3
+ "tally.masters.stockItems.withBatches",
4
+ "tally.masters.ledgers.withGstDetails"
5
5
  ]
@@ -1 +1 @@
1
- export { createApi, default } from "./api.js";
1
+ export { createApi, default } from "./api.js";
package/src/v1/index.js CHANGED
@@ -1 +1 @@
1
- export { createApi, default } from "./external-api/api.js";
1
+ export { createApi, default } from "./external-api/api.js";
@@ -1,40 +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
- }
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
+ }
@@ -1,103 +1,103 @@
1
- import traverseObject from "./traverseObject/index.js";
2
-
3
- const escapeXml = (value) => value.replace(/[&<>"']/g, (character) => ({
4
- "&": "&amp;",
5
- "<": "&lt;",
6
- ">": "&gt;",
7
- '"': "&quot;",
8
- "'": "&apos;"
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;
1
+ import traverseObject from "./traverseObject/index.js";
2
+
3
+ const escapeXml = (value) => value.replace(/[&<>"']/g, (character) => ({
4
+ "&": "&amp;",
5
+ "<": "&lt;",
6
+ ">": "&gt;",
7
+ '"': "&quot;",
8
+ "'": "&apos;"
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;