@parix/cli 0.1.6 → 0.1.8
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 +11 -3
- package/dist/cli.cjs +10 -2
- package/dist/cli.js +10 -2
- package/dist/commands/auth.cjs +3 -0
- package/dist/commands/auth.js +3 -0
- package/dist/commands/skill-content.cjs +77 -0
- package/dist/commands/skill-content.d.cts +1 -0
- package/dist/commands/skill-content.d.ts +1 -0
- package/dist/commands/skill-content.js +74 -0
- package/dist/commands/skill.cjs +10 -0
- package/dist/commands/skill.d.cts +2 -0
- package/dist/commands/skill.d.ts +2 -0
- package/dist/commands/skill.js +7 -0
- package/dist/commands/tb.cjs +13 -1
- package/dist/commands/tb.js +13 -1
- package/dist/lib/loopback-server.cjs +25 -3
- package/dist/lib/loopback-server.js +25 -3
- package/dist/lib/tb-payloads.cjs +139 -34
- package/dist/lib/tb-payloads.d.cts +5 -0
- package/dist/lib/tb-payloads.d.ts +5 -0
- package/dist/lib/tb-payloads.js +139 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ parix --help
|
|
|
23
23
|
|
|
24
24
|
## Commands
|
|
25
25
|
|
|
26
|
+
Agent skill:
|
|
27
|
+
|
|
28
|
+
- `parix skill`: print the embedded Parix agent skill as raw Markdown
|
|
29
|
+
|
|
26
30
|
Auth:
|
|
27
31
|
|
|
28
32
|
- `parix auth login`
|
|
@@ -90,7 +94,7 @@ Supported operations:
|
|
|
90
94
|
|
|
91
95
|
TigerBeetle commands support both styles:
|
|
92
96
|
|
|
93
|
-
- flag-driven payloads for common workflows such as `--from`, `--to`, `--amount`, `--ledger`, `--code`, repeated `--id`, and repeated `--flag`
|
|
97
|
+
- flag-driven payloads for common workflows such as `--from`, `--to`, `--amount`, `--ledger`, `--code`, `--timestamp`, `--user-data-128`, `--user-data-64`, `--user-data-32`, repeated `--id`, and repeated `--flag`
|
|
94
98
|
- raw JSON overrides with `--payload '<json>'` or `--file ./payload.json`
|
|
95
99
|
|
|
96
100
|
Operational notes:
|
|
@@ -98,6 +102,10 @@ Operational notes:
|
|
|
98
102
|
- database and TB commands use the active organization from the current `parix` token
|
|
99
103
|
- `db:read` and `db:write` OIDC scopes are enforced on the Worker API surface
|
|
100
104
|
- newly provisioned databases may take a short time to become ready for TB operations; the CLI/API includes readiness retries for fresh databases
|
|
105
|
+
- `--timestamp` is required with the `imported` account or transfer flag, is expressed in nanoseconds, and must be greater than 0 and less than 2^63
|
|
106
|
+
- transfer `--timeout` values are expressed in seconds
|
|
107
|
+
- post-pending transfers require `--pending-id` and `--amount`; void-pending transfers require `--pending-id`; omitted account IDs, ledger, code, and void amount are sent as zero to inherit them
|
|
108
|
+
- linked account or transfer batches must use `--payload` or `--file`
|
|
101
109
|
|
|
102
110
|
## Development
|
|
103
111
|
|
|
@@ -148,8 +156,8 @@ Recommended release flow:
|
|
|
148
156
|
|
|
149
157
|
```bash
|
|
150
158
|
# bump version in package.json
|
|
151
|
-
git tag v0.1.
|
|
159
|
+
git tag v0.1.7
|
|
152
160
|
git push origin main --tags
|
|
153
161
|
```
|
|
154
162
|
|
|
155
|
-
Then publish a GitHub Release from tag `v0.1.
|
|
163
|
+
Then publish a GitHub Release from tag `v0.1.7`. The publish workflow verifies that the release tag matches `package.json`, packs the npm tarball (`parix-cli-<version>.tgz`), publishes that tarball to npm, and uploads the same `.tgz` as a GitHub Release asset.
|
package/dist/cli.cjs
CHANGED
|
@@ -7,6 +7,7 @@ const node_module_1 = require("node:module");
|
|
|
7
7
|
const api_1 = require("./commands/api.cjs");
|
|
8
8
|
const auth_1 = require("./commands/auth.cjs");
|
|
9
9
|
const database_1 = require("./commands/database.cjs");
|
|
10
|
+
const skill_1 = require("./commands/skill.cjs");
|
|
10
11
|
const tb_1 = require("./commands/tb.cjs");
|
|
11
12
|
const loadPackageJson = (0, node_module_1.createRequire)(require("url").pathToFileURL(__filename));
|
|
12
13
|
const packageJson = loadPackageJson('../package.json');
|
|
@@ -20,11 +21,18 @@ async function main() {
|
|
|
20
21
|
.addCommand((0, auth_1.createAuthCommand)())
|
|
21
22
|
.addCommand((0, api_1.createApiCommand)())
|
|
22
23
|
.addCommand((0, database_1.createDatabaseCommand)())
|
|
24
|
+
.addCommand((0, skill_1.createSkillCommand)())
|
|
23
25
|
.addCommand((0, tb_1.createTbCommand)());
|
|
24
26
|
await program.parseAsync(process.argv);
|
|
25
27
|
}
|
|
26
|
-
main()
|
|
28
|
+
main()
|
|
29
|
+
.then(() => {
|
|
30
|
+
// CLIs that open HTTP servers / use undici fetch can leave sockets that keep
|
|
31
|
+
// Node alive after the command is done. Exit explicitly once work is finished.
|
|
32
|
+
process.exit(process.exitCode ?? 0);
|
|
33
|
+
})
|
|
34
|
+
.catch((error) => {
|
|
27
35
|
const message = error instanceof Error ? error.message : String(error);
|
|
28
36
|
prompts_1.log.error(message);
|
|
29
|
-
process.
|
|
37
|
+
process.exit(1);
|
|
30
38
|
});
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createRequire } from 'node:module';
|
|
|
5
5
|
import { createApiCommand } from "./commands/api.js";
|
|
6
6
|
import { createAuthCommand } from "./commands/auth.js";
|
|
7
7
|
import { createDatabaseCommand } from "./commands/database.js";
|
|
8
|
+
import { createSkillCommand } from "./commands/skill.js";
|
|
8
9
|
import { createTbCommand } from "./commands/tb.js";
|
|
9
10
|
const loadPackageJson = createRequire(import.meta.url);
|
|
10
11
|
const packageJson = loadPackageJson('../package.json');
|
|
@@ -18,11 +19,18 @@ async function main() {
|
|
|
18
19
|
.addCommand(createAuthCommand())
|
|
19
20
|
.addCommand(createApiCommand())
|
|
20
21
|
.addCommand(createDatabaseCommand())
|
|
22
|
+
.addCommand(createSkillCommand())
|
|
21
23
|
.addCommand(createTbCommand());
|
|
22
24
|
await program.parseAsync(process.argv);
|
|
23
25
|
}
|
|
24
|
-
main()
|
|
26
|
+
main()
|
|
27
|
+
.then(() => {
|
|
28
|
+
// CLIs that open HTTP servers / use undici fetch can leave sockets that keep
|
|
29
|
+
// Node alive after the command is done. Exit explicitly once work is finished.
|
|
30
|
+
process.exit(process.exitCode ?? 0);
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
25
33
|
const message = error instanceof Error ? error.message : String(error);
|
|
26
34
|
log.error(message);
|
|
27
|
-
process.
|
|
35
|
+
process.exit(1);
|
|
28
36
|
});
|
package/dist/commands/auth.cjs
CHANGED
|
@@ -69,6 +69,9 @@ async function handleLogin(options) {
|
|
|
69
69
|
waitSpinner.start('Waiting for browser callback');
|
|
70
70
|
const callbackResult = await loopbackServer.waitForResult();
|
|
71
71
|
waitSpinner.stop('Received browser callback');
|
|
72
|
+
// Shut down the loopback server before token exchange so browser keep-alive
|
|
73
|
+
// sockets cannot keep the process open after sign-in completes.
|
|
74
|
+
await loopbackServer.close().catch(() => { });
|
|
72
75
|
const tokenSet = await (0, oauth_api_1.exchangeAuthorizationCode)({
|
|
73
76
|
baseUrl,
|
|
74
77
|
code: callbackResult.code,
|
package/dist/commands/auth.js
CHANGED
|
@@ -66,6 +66,9 @@ async function handleLogin(options) {
|
|
|
66
66
|
waitSpinner.start('Waiting for browser callback');
|
|
67
67
|
const callbackResult = await loopbackServer.waitForResult();
|
|
68
68
|
waitSpinner.stop('Received browser callback');
|
|
69
|
+
// Shut down the loopback server before token exchange so browser keep-alive
|
|
70
|
+
// sockets cannot keep the process open after sign-in completes.
|
|
71
|
+
await loopbackServer.close().catch(() => { });
|
|
69
72
|
const tokenSet = await exchangeAuthorizationCode({
|
|
70
73
|
baseUrl,
|
|
71
74
|
code: callbackResult.code,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PARIX_AGENT_SKILL = void 0;
|
|
4
|
+
exports.PARIX_AGENT_SKILL = [
|
|
5
|
+
'---',
|
|
6
|
+
'name: parix',
|
|
7
|
+
'description: "Operate Parix through the `parix` CLI: inspect authentication and active organization context, manage database lifecycle, call authenticated Parix API endpoints, and run TigerBeetle queries or mutations. Use for Parix CLI setup, sessions, database catalog/list/info/create/remove work, Parix API requests, and TigerBeetle account or transfer operations."',
|
|
8
|
+
'---',
|
|
9
|
+
'',
|
|
10
|
+
'# Parix CLI',
|
|
11
|
+
'',
|
|
12
|
+
'Use the installed `parix` binary. Prefer scoped `--help` over guessing flags.',
|
|
13
|
+
'',
|
|
14
|
+
'## Start safely',
|
|
15
|
+
'',
|
|
16
|
+
'1. Run `command -v parix`. If it is absent, ask the user to install it with `npm install -g @parix/cli`.',
|
|
17
|
+
'2. Run `parix auth status` before authenticated work. Confirm the base URL and active organization before any mutation. Status may refresh and persist tokens or organization context, so it is not strictly read-only. If no usable session exists, ask the user to complete `parix auth login`; login opens a browser OAuth flow.',
|
|
18
|
+
'3. Treat `https://parix.io` as production. Login resolves its target as explicit `--base-url`, then `PARIX_BASE_URL`, then production; it ignores any URL in the stored session. Later commands resolve explicit URL, then stored URL, then environment, then production. Pass `--base-url <url>` explicitly when the environment matters.',
|
|
19
|
+
'4. Use `parix database`, not `parix db`; no `db` alias exists.',
|
|
20
|
+
'',
|
|
21
|
+
'Never read or print `~/.config/parix/session.json`; it contains access and refresh tokens.',
|
|
22
|
+
'',
|
|
23
|
+
'`parix auth logout` deletes the local session without confirmation; run it only when explicitly requested.',
|
|
24
|
+
'',
|
|
25
|
+
'## Discover before changing state',
|
|
26
|
+
'',
|
|
27
|
+
'```bash',
|
|
28
|
+
'parix database catalog',
|
|
29
|
+
'parix database list',
|
|
30
|
+
'parix database info <database-id>',
|
|
31
|
+
'parix database <command> --help',
|
|
32
|
+
'parix tb <command> --help',
|
|
33
|
+
'```',
|
|
34
|
+
'',
|
|
35
|
+
'Database and TigerBeetle commands operate in the active organization from the current session. Stop if that organization or target environment is not the intended one.',
|
|
36
|
+
'',
|
|
37
|
+
'Use `--json` when a database or TigerBeetle command needs the complete response, but do not assume it is directly pipeable to `jq`: current output is wrapped by Clack formatting.',
|
|
38
|
+
'',
|
|
39
|
+
'## Manage databases',
|
|
40
|
+
'',
|
|
41
|
+
'- Run `parix database catalog` before creation and use provider, region, cluster, size, and storage identifiers returned by it.',
|
|
42
|
+
'- Treat `parix database create` as potentially billable. Run it only when the user explicitly requests the exact resource. `--storage-gb` must be a positive integer.',
|
|
43
|
+
'- Inspect the create response, including `success`, billing, and provisioning fields; exit code 0 alone does not prove provisioning started. Only when `success` is true and a database ID is returned, follow with `parix database info <database-id>`.',
|
|
44
|
+
'- Treat `parix database remove <database-id>` as destructive. Resolve the exact ID, obtain explicit confirmation, and only then use `--yes` for a non-interactive run. Removal is queued; verify the returned workflow/status rather than claiming immediate deletion.',
|
|
45
|
+
'',
|
|
46
|
+
'## Run TigerBeetle operations',
|
|
47
|
+
'',
|
|
48
|
+
'Read operations are `lookup-accounts`, `lookup-transfers`, `get-account-transfers`, `get-account-balances`, `query-accounts`, and `query-transfers`. `create-accounts` and `create-transfers` mutate data; run them only when explicitly requested.',
|
|
49
|
+
'',
|
|
50
|
+
'Choose exactly one payload style:',
|
|
51
|
+
'',
|
|
52
|
+
'- flags for a single/common operation;',
|
|
53
|
+
"- `--payload '<json>'` for raw JSON; or",
|
|
54
|
+
'- `--file <path>` for a JSON file.',
|
|
55
|
+
'',
|
|
56
|
+
'`--file` takes precedence over `--payload`, and either raw form bypasses flag-built payloads, defaults, and validation. Flag-built account creation requires `--ledger` and `--code`. Ordinary transfer creation requires `--from`, `--to`, `--amount`, `--ledger`, and `--code`. Post-pending transfer creation requires `--flag post_pending_transfer`, `--pending-id`, and `--amount`; debit/credit IDs, ledger, and code may be omitted to inherit them. Void-pending transfer creation requires `--flag void_pending_transfer` and `--pending-id`; amount and the same inherited fields may be omitted. Imported accounts and transfers also require `--timestamp` in nanoseconds, greater than 0 and less than 2^63; nonzero `--timeout` values are in seconds and limited to non-imported pending transfers. Linked batches require `--payload` or `--file`.',
|
|
57
|
+
'',
|
|
58
|
+
'Supply stable explicit `--id` values for retryable mutations; omitted IDs are newly generated on each run. Flag-built queries and account filters default to `--limit 100`.',
|
|
59
|
+
'',
|
|
60
|
+
'Newly provisioned databases may not be ready immediately. Check `parix database info` and retry read-only readiness checks before TigerBeetle mutations.',
|
|
61
|
+
'',
|
|
62
|
+
'## Call the authenticated API',
|
|
63
|
+
'',
|
|
64
|
+
'```bash',
|
|
65
|
+
'parix api /api/v1/session',
|
|
66
|
+
'parix api /api/v1/example -X POST -d \'{"key":"value"}\'',
|
|
67
|
+
'```',
|
|
68
|
+
'',
|
|
69
|
+
"Prefer relative `/api/...` paths. An absolute HTTP(S) URL also receives the stored bearer token, so use one only when the user explicitly trusts that host. Repeat `-H 'name:value'` for headers; a body defaults to `content-type: application/json` unless overridden.",
|
|
70
|
+
'',
|
|
71
|
+
'Always inspect the printed HTTP status: `parix api` can exit successfully for an HTTP error response.',
|
|
72
|
+
'',
|
|
73
|
+
'## Verify outcomes',
|
|
74
|
+
'',
|
|
75
|
+
'Report the target base URL and organization, the exact command class used, the response status/body, and any asynchronous provisioning or deletion state. Do not infer success from process exit alone.',
|
|
76
|
+
'',
|
|
77
|
+
].join('\n');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PARIX_AGENT_SKILL: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PARIX_AGENT_SKILL: string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const PARIX_AGENT_SKILL = [
|
|
2
|
+
'---',
|
|
3
|
+
'name: parix',
|
|
4
|
+
'description: "Operate Parix through the `parix` CLI: inspect authentication and active organization context, manage database lifecycle, call authenticated Parix API endpoints, and run TigerBeetle queries or mutations. Use for Parix CLI setup, sessions, database catalog/list/info/create/remove work, Parix API requests, and TigerBeetle account or transfer operations."',
|
|
5
|
+
'---',
|
|
6
|
+
'',
|
|
7
|
+
'# Parix CLI',
|
|
8
|
+
'',
|
|
9
|
+
'Use the installed `parix` binary. Prefer scoped `--help` over guessing flags.',
|
|
10
|
+
'',
|
|
11
|
+
'## Start safely',
|
|
12
|
+
'',
|
|
13
|
+
'1. Run `command -v parix`. If it is absent, ask the user to install it with `npm install -g @parix/cli`.',
|
|
14
|
+
'2. Run `parix auth status` before authenticated work. Confirm the base URL and active organization before any mutation. Status may refresh and persist tokens or organization context, so it is not strictly read-only. If no usable session exists, ask the user to complete `parix auth login`; login opens a browser OAuth flow.',
|
|
15
|
+
'3. Treat `https://parix.io` as production. Login resolves its target as explicit `--base-url`, then `PARIX_BASE_URL`, then production; it ignores any URL in the stored session. Later commands resolve explicit URL, then stored URL, then environment, then production. Pass `--base-url <url>` explicitly when the environment matters.',
|
|
16
|
+
'4. Use `parix database`, not `parix db`; no `db` alias exists.',
|
|
17
|
+
'',
|
|
18
|
+
'Never read or print `~/.config/parix/session.json`; it contains access and refresh tokens.',
|
|
19
|
+
'',
|
|
20
|
+
'`parix auth logout` deletes the local session without confirmation; run it only when explicitly requested.',
|
|
21
|
+
'',
|
|
22
|
+
'## Discover before changing state',
|
|
23
|
+
'',
|
|
24
|
+
'```bash',
|
|
25
|
+
'parix database catalog',
|
|
26
|
+
'parix database list',
|
|
27
|
+
'parix database info <database-id>',
|
|
28
|
+
'parix database <command> --help',
|
|
29
|
+
'parix tb <command> --help',
|
|
30
|
+
'```',
|
|
31
|
+
'',
|
|
32
|
+
'Database and TigerBeetle commands operate in the active organization from the current session. Stop if that organization or target environment is not the intended one.',
|
|
33
|
+
'',
|
|
34
|
+
'Use `--json` when a database or TigerBeetle command needs the complete response, but do not assume it is directly pipeable to `jq`: current output is wrapped by Clack formatting.',
|
|
35
|
+
'',
|
|
36
|
+
'## Manage databases',
|
|
37
|
+
'',
|
|
38
|
+
'- Run `parix database catalog` before creation and use provider, region, cluster, size, and storage identifiers returned by it.',
|
|
39
|
+
'- Treat `parix database create` as potentially billable. Run it only when the user explicitly requests the exact resource. `--storage-gb` must be a positive integer.',
|
|
40
|
+
'- Inspect the create response, including `success`, billing, and provisioning fields; exit code 0 alone does not prove provisioning started. Only when `success` is true and a database ID is returned, follow with `parix database info <database-id>`.',
|
|
41
|
+
'- Treat `parix database remove <database-id>` as destructive. Resolve the exact ID, obtain explicit confirmation, and only then use `--yes` for a non-interactive run. Removal is queued; verify the returned workflow/status rather than claiming immediate deletion.',
|
|
42
|
+
'',
|
|
43
|
+
'## Run TigerBeetle operations',
|
|
44
|
+
'',
|
|
45
|
+
'Read operations are `lookup-accounts`, `lookup-transfers`, `get-account-transfers`, `get-account-balances`, `query-accounts`, and `query-transfers`. `create-accounts` and `create-transfers` mutate data; run them only when explicitly requested.',
|
|
46
|
+
'',
|
|
47
|
+
'Choose exactly one payload style:',
|
|
48
|
+
'',
|
|
49
|
+
'- flags for a single/common operation;',
|
|
50
|
+
"- `--payload '<json>'` for raw JSON; or",
|
|
51
|
+
'- `--file <path>` for a JSON file.',
|
|
52
|
+
'',
|
|
53
|
+
'`--file` takes precedence over `--payload`, and either raw form bypasses flag-built payloads, defaults, and validation. Flag-built account creation requires `--ledger` and `--code`. Ordinary transfer creation requires `--from`, `--to`, `--amount`, `--ledger`, and `--code`. Post-pending transfer creation requires `--flag post_pending_transfer`, `--pending-id`, and `--amount`; debit/credit IDs, ledger, and code may be omitted to inherit them. Void-pending transfer creation requires `--flag void_pending_transfer` and `--pending-id`; amount and the same inherited fields may be omitted. Imported accounts and transfers also require `--timestamp` in nanoseconds, greater than 0 and less than 2^63; nonzero `--timeout` values are in seconds and limited to non-imported pending transfers. Linked batches require `--payload` or `--file`.',
|
|
54
|
+
'',
|
|
55
|
+
'Supply stable explicit `--id` values for retryable mutations; omitted IDs are newly generated on each run. Flag-built queries and account filters default to `--limit 100`.',
|
|
56
|
+
'',
|
|
57
|
+
'Newly provisioned databases may not be ready immediately. Check `parix database info` and retry read-only readiness checks before TigerBeetle mutations.',
|
|
58
|
+
'',
|
|
59
|
+
'## Call the authenticated API',
|
|
60
|
+
'',
|
|
61
|
+
'```bash',
|
|
62
|
+
'parix api /api/v1/session',
|
|
63
|
+
'parix api /api/v1/example -X POST -d \'{"key":"value"}\'',
|
|
64
|
+
'```',
|
|
65
|
+
'',
|
|
66
|
+
"Prefer relative `/api/...` paths. An absolute HTTP(S) URL also receives the stored bearer token, so use one only when the user explicitly trusts that host. Repeat `-H 'name:value'` for headers; a body defaults to `content-type: application/json` unless overridden.",
|
|
67
|
+
'',
|
|
68
|
+
'Always inspect the printed HTTP status: `parix api` can exit successfully for an HTTP error response.',
|
|
69
|
+
'',
|
|
70
|
+
'## Verify outcomes',
|
|
71
|
+
'',
|
|
72
|
+
'Report the target base URL and organization, the exact command class used, the response status/body, and any asynchronous provisioning or deletion state. Do not infer success from process exit alone.',
|
|
73
|
+
'',
|
|
74
|
+
].join('\n');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSkillCommand = createSkillCommand;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const skill_content_1 = require("./skill-content.cjs");
|
|
6
|
+
function createSkillCommand() {
|
|
7
|
+
return new commander_1.Command('skill').description('Print the agent skill file to stdout').action(() => {
|
|
8
|
+
process.stdout.write(skill_content_1.PARIX_AGENT_SKILL);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { PARIX_AGENT_SKILL } from "./skill-content.js";
|
|
3
|
+
export function createSkillCommand() {
|
|
4
|
+
return new Command('skill').description('Print the agent skill file to stdout').action(() => {
|
|
5
|
+
process.stdout.write(PARIX_AGENT_SKILL);
|
|
6
|
+
});
|
|
7
|
+
}
|
package/dist/commands/tb.cjs
CHANGED
|
@@ -41,6 +41,7 @@ function addCreateAccountsCommand(parent) {
|
|
|
41
41
|
.option('--ledger <ledger>', 'Ledger id')
|
|
42
42
|
.option('--code <code>', 'Account code')
|
|
43
43
|
.option('--flag <flag>', 'Account flag name or bitfield value', collectValues, [])
|
|
44
|
+
.option('--timestamp <nanoseconds>', 'Imported timestamp, greater than 0 and less than 2^63')
|
|
44
45
|
.option('--user-data-128 <value>', 'user_data_128')
|
|
45
46
|
.option('--user-data-64 <value>', 'user_data_64')
|
|
46
47
|
.option('--user-data-32 <value>', 'user_data_32')
|
|
@@ -56,6 +57,7 @@ function addCreateAccountsCommand(parent) {
|
|
|
56
57
|
flags: options.flag,
|
|
57
58
|
id: options.id,
|
|
58
59
|
ledger: options.ledger,
|
|
60
|
+
timestamp: options.timestamp,
|
|
59
61
|
userData128: options.userData128,
|
|
60
62
|
userData32: options.userData32,
|
|
61
63
|
userData64: options.userData64,
|
|
@@ -80,11 +82,17 @@ function addCreateTransfersCommand(parent) {
|
|
|
80
82
|
.option('--code <code>', 'Transfer code')
|
|
81
83
|
.option('--flag <flag>', 'Transfer flag name or bitfield value', collectValues, [])
|
|
82
84
|
.option('--pending-id <id>', 'Pending transfer id')
|
|
83
|
-
.option('--timeout <
|
|
85
|
+
.option('--timeout <seconds>', 'Pending transfer timeout in seconds')
|
|
86
|
+
.option('--timestamp <nanoseconds>', 'Imported timestamp, greater than 0 and less than 2^63')
|
|
87
|
+
.option('--user-data-128 <value>', 'user_data_128')
|
|
88
|
+
.option('--user-data-64 <value>', 'user_data_64')
|
|
89
|
+
.option('--user-data-32 <value>', 'user_data_32')
|
|
84
90
|
.addHelpText('after', [
|
|
85
91
|
'',
|
|
86
92
|
'Examples:',
|
|
87
93
|
' parix tb create-transfers db_123 --id 2000 --from 1000 --to 1001 --amount 1 --ledger 1 --code 1',
|
|
94
|
+
' parix tb create-transfers db_123 --id 2001 --pending-id 2000 --amount 25 --flag post_pending_transfer',
|
|
95
|
+
' parix tb create-transfers db_123 --id 2001 --pending-id 2000 --flag void_pending_transfer',
|
|
88
96
|
' parix tb create-transfers db_123 --file ./transfer.json',
|
|
89
97
|
].join('\n'))
|
|
90
98
|
.action(async (databaseId, options) => {
|
|
@@ -97,7 +105,11 @@ function addCreateTransfersCommand(parent) {
|
|
|
97
105
|
id: options.id,
|
|
98
106
|
ledger: options.ledger,
|
|
99
107
|
pendingId: options.pendingId,
|
|
108
|
+
timestamp: options.timestamp,
|
|
100
109
|
timeout: options.timeout,
|
|
110
|
+
userData128: options.userData128,
|
|
111
|
+
userData32: options.userData32,
|
|
112
|
+
userData64: options.userData64,
|
|
101
113
|
}));
|
|
102
114
|
await executeTbOperation(databaseId, 'create_transfers', payload, options);
|
|
103
115
|
});
|
package/dist/commands/tb.js
CHANGED
|
@@ -38,6 +38,7 @@ function addCreateAccountsCommand(parent) {
|
|
|
38
38
|
.option('--ledger <ledger>', 'Ledger id')
|
|
39
39
|
.option('--code <code>', 'Account code')
|
|
40
40
|
.option('--flag <flag>', 'Account flag name or bitfield value', collectValues, [])
|
|
41
|
+
.option('--timestamp <nanoseconds>', 'Imported timestamp, greater than 0 and less than 2^63')
|
|
41
42
|
.option('--user-data-128 <value>', 'user_data_128')
|
|
42
43
|
.option('--user-data-64 <value>', 'user_data_64')
|
|
43
44
|
.option('--user-data-32 <value>', 'user_data_32')
|
|
@@ -53,6 +54,7 @@ function addCreateAccountsCommand(parent) {
|
|
|
53
54
|
flags: options.flag,
|
|
54
55
|
id: options.id,
|
|
55
56
|
ledger: options.ledger,
|
|
57
|
+
timestamp: options.timestamp,
|
|
56
58
|
userData128: options.userData128,
|
|
57
59
|
userData32: options.userData32,
|
|
58
60
|
userData64: options.userData64,
|
|
@@ -77,11 +79,17 @@ function addCreateTransfersCommand(parent) {
|
|
|
77
79
|
.option('--code <code>', 'Transfer code')
|
|
78
80
|
.option('--flag <flag>', 'Transfer flag name or bitfield value', collectValues, [])
|
|
79
81
|
.option('--pending-id <id>', 'Pending transfer id')
|
|
80
|
-
.option('--timeout <
|
|
82
|
+
.option('--timeout <seconds>', 'Pending transfer timeout in seconds')
|
|
83
|
+
.option('--timestamp <nanoseconds>', 'Imported timestamp, greater than 0 and less than 2^63')
|
|
84
|
+
.option('--user-data-128 <value>', 'user_data_128')
|
|
85
|
+
.option('--user-data-64 <value>', 'user_data_64')
|
|
86
|
+
.option('--user-data-32 <value>', 'user_data_32')
|
|
81
87
|
.addHelpText('after', [
|
|
82
88
|
'',
|
|
83
89
|
'Examples:',
|
|
84
90
|
' parix tb create-transfers db_123 --id 2000 --from 1000 --to 1001 --amount 1 --ledger 1 --code 1',
|
|
91
|
+
' parix tb create-transfers db_123 --id 2001 --pending-id 2000 --amount 25 --flag post_pending_transfer',
|
|
92
|
+
' parix tb create-transfers db_123 --id 2001 --pending-id 2000 --flag void_pending_transfer',
|
|
85
93
|
' parix tb create-transfers db_123 --file ./transfer.json',
|
|
86
94
|
].join('\n'))
|
|
87
95
|
.action(async (databaseId, options) => {
|
|
@@ -94,7 +102,11 @@ function addCreateTransfersCommand(parent) {
|
|
|
94
102
|
id: options.id,
|
|
95
103
|
ledger: options.ledger,
|
|
96
104
|
pendingId: options.pendingId,
|
|
105
|
+
timestamp: options.timestamp,
|
|
97
106
|
timeout: options.timeout,
|
|
107
|
+
userData128: options.userData128,
|
|
108
|
+
userData32: options.userData32,
|
|
109
|
+
userData64: options.userData64,
|
|
98
110
|
}));
|
|
99
111
|
await executeTbOperation(databaseId, 'create_transfers', payload, options);
|
|
100
112
|
});
|
|
@@ -37,6 +37,8 @@ async function startLoopbackServer(options) {
|
|
|
37
37
|
rejectResult = reject;
|
|
38
38
|
});
|
|
39
39
|
server.on('request', (request, response) => {
|
|
40
|
+
// One-shot OAuth loopback: do not keep browser sockets alive after the response.
|
|
41
|
+
response.setHeader('connection', 'close');
|
|
40
42
|
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
|
|
41
43
|
if (requestUrl.pathname === '/favicon.ico') {
|
|
42
44
|
response.writeHead(204);
|
|
@@ -120,17 +122,37 @@ function listen(server, port) {
|
|
|
120
122
|
}
|
|
121
123
|
function closeServer(server) {
|
|
122
124
|
return new Promise((resolve, reject) => {
|
|
125
|
+
let settled = false;
|
|
126
|
+
const finish = (error) => {
|
|
127
|
+
if (settled) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
settled = true;
|
|
131
|
+
if (error) {
|
|
132
|
+
reject(error);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
resolve();
|
|
136
|
+
};
|
|
123
137
|
server.close((error) => {
|
|
124
138
|
if (error) {
|
|
125
139
|
if ('code' in error && error.code === 'ERR_SERVER_NOT_RUNNING') {
|
|
126
|
-
|
|
140
|
+
finish();
|
|
127
141
|
return;
|
|
128
142
|
}
|
|
129
|
-
|
|
143
|
+
finish(error);
|
|
130
144
|
return;
|
|
131
145
|
}
|
|
132
|
-
|
|
146
|
+
finish();
|
|
133
147
|
});
|
|
148
|
+
// `server.close()` waits for existing connections. Browsers often keep the
|
|
149
|
+
// loopback socket open via HTTP keep-alive, which would hang login forever
|
|
150
|
+
// after the session is already stored. Force those sockets closed (Node >= 18.2).
|
|
151
|
+
server.closeAllConnections();
|
|
152
|
+
// Last resort: never let close block the CLI indefinitely.
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
finish();
|
|
155
|
+
}, 250).unref();
|
|
134
156
|
});
|
|
135
157
|
}
|
|
136
158
|
function buildHtml(title, message) {
|
|
@@ -34,6 +34,8 @@ export async function startLoopbackServer(options) {
|
|
|
34
34
|
rejectResult = reject;
|
|
35
35
|
});
|
|
36
36
|
server.on('request', (request, response) => {
|
|
37
|
+
// One-shot OAuth loopback: do not keep browser sockets alive after the response.
|
|
38
|
+
response.setHeader('connection', 'close');
|
|
37
39
|
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
|
|
38
40
|
if (requestUrl.pathname === '/favicon.ico') {
|
|
39
41
|
response.writeHead(204);
|
|
@@ -117,17 +119,37 @@ function listen(server, port) {
|
|
|
117
119
|
}
|
|
118
120
|
function closeServer(server) {
|
|
119
121
|
return new Promise((resolve, reject) => {
|
|
122
|
+
let settled = false;
|
|
123
|
+
const finish = (error) => {
|
|
124
|
+
if (settled) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
settled = true;
|
|
128
|
+
if (error) {
|
|
129
|
+
reject(error);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
resolve();
|
|
133
|
+
};
|
|
120
134
|
server.close((error) => {
|
|
121
135
|
if (error) {
|
|
122
136
|
if ('code' in error && error.code === 'ERR_SERVER_NOT_RUNNING') {
|
|
123
|
-
|
|
137
|
+
finish();
|
|
124
138
|
return;
|
|
125
139
|
}
|
|
126
|
-
|
|
140
|
+
finish(error);
|
|
127
141
|
return;
|
|
128
142
|
}
|
|
129
|
-
|
|
143
|
+
finish();
|
|
130
144
|
});
|
|
145
|
+
// `server.close()` waits for existing connections. Browsers often keep the
|
|
146
|
+
// loopback socket open via HTTP keep-alive, which would hang login forever
|
|
147
|
+
// after the session is already stored. Force those sockets closed (Node >= 18.2).
|
|
148
|
+
server.closeAllConnections();
|
|
149
|
+
// Last resort: never let close block the CLI indefinitely.
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
finish();
|
|
152
|
+
}, 250).unref();
|
|
131
153
|
});
|
|
132
154
|
}
|
|
133
155
|
function buildHtml(title, message) {
|
package/dist/lib/tb-payloads.cjs
CHANGED
|
@@ -9,24 +9,56 @@ exports.buildAccountFilterPayload = buildAccountFilterPayload;
|
|
|
9
9
|
exports.buildQueryPayload = buildQueryPayload;
|
|
10
10
|
const promises_1 = require("node:fs/promises");
|
|
11
11
|
const SPLIT_IDS_REGEX = /[\s,]+/;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]
|
|
12
|
+
const DECIMAL_INTEGER_REGEX = /^\d+$/;
|
|
13
|
+
const LINKED_FLAG = 1n;
|
|
14
|
+
const ACCOUNT_IMPORTED_FLAG = 16n;
|
|
15
|
+
const TRANSFER_PENDING_FLAG = 2n;
|
|
16
|
+
const TRANSFER_POST_PENDING_FLAG = 4n;
|
|
17
|
+
const TRANSFER_VOID_PENDING_FLAG = 8n;
|
|
18
|
+
const TRANSFER_IMPORTED_FLAG = 256n;
|
|
19
|
+
const TIGERBEETLE_TIMESTAMP_MAX_EXCLUSIVE = 1n << 63n;
|
|
20
|
+
const IMPORTED_TIMESTAMP_ERROR = '--timestamp must be a decimal integer greater than 0 and less than 2^63 when --flag imported is used.';
|
|
21
|
+
const ACCOUNT_FLAG_SET = {
|
|
22
|
+
allowedMask: 63n,
|
|
23
|
+
label: 'account',
|
|
24
|
+
nameToBit: new Map([
|
|
25
|
+
['linked', LINKED_FLAG],
|
|
26
|
+
['debits_must_not_exceed_credits', 2n],
|
|
27
|
+
['credits_must_not_exceed_debits', 4n],
|
|
28
|
+
['history', 8n],
|
|
29
|
+
['imported', ACCOUNT_IMPORTED_FLAG],
|
|
30
|
+
['closed', 32n],
|
|
31
|
+
]),
|
|
32
|
+
};
|
|
33
|
+
const TRANSFER_FLAG_SET = {
|
|
34
|
+
allowedMask: 511n,
|
|
35
|
+
label: 'transfer',
|
|
36
|
+
nameToBit: new Map([
|
|
37
|
+
['linked', LINKED_FLAG],
|
|
38
|
+
['pending', TRANSFER_PENDING_FLAG],
|
|
39
|
+
['post_pending_transfer', TRANSFER_POST_PENDING_FLAG],
|
|
40
|
+
['void_pending_transfer', TRANSFER_VOID_PENDING_FLAG],
|
|
41
|
+
['balancing_debit', 16n],
|
|
42
|
+
['balancing_credit', 32n],
|
|
43
|
+
['closing_debit', 64n],
|
|
44
|
+
['closing_credit', 128n],
|
|
45
|
+
['imported', TRANSFER_IMPORTED_FLAG],
|
|
46
|
+
]),
|
|
47
|
+
};
|
|
48
|
+
const ACCOUNT_FILTER_FLAG_SET = {
|
|
49
|
+
allowedMask: 7n,
|
|
50
|
+
label: 'account filter',
|
|
51
|
+
nameToBit: new Map([
|
|
52
|
+
['debits', 1n],
|
|
53
|
+
['credits', 2n],
|
|
54
|
+
['reversed', 4n],
|
|
55
|
+
]),
|
|
56
|
+
};
|
|
57
|
+
const QUERY_FILTER_FLAG_SET = {
|
|
58
|
+
allowedMask: 1n,
|
|
59
|
+
label: 'query filter',
|
|
60
|
+
nameToBit: new Map([['reversed', 1n]]),
|
|
61
|
+
};
|
|
30
62
|
exports.tbOperationNames = [
|
|
31
63
|
'create_accounts',
|
|
32
64
|
'create_transfers',
|
|
@@ -47,12 +79,19 @@ async function resolveTbPayload(options, buildFromFlags) {
|
|
|
47
79
|
return buildFromFlags();
|
|
48
80
|
}
|
|
49
81
|
function buildCreateAccountsPayload(options) {
|
|
82
|
+
const flags = flagsBitfield(options.flags, ACCOUNT_FLAG_SET);
|
|
83
|
+
rejectLinkedSingleEvent(flags);
|
|
84
|
+
const isImported = flagIsSet(flags, ACCOUNT_IMPORTED_FLAG);
|
|
85
|
+
if (!isImported && valueIsNonZero(options.timestamp)) {
|
|
86
|
+
throw new Error('--timestamp requires --flag imported when nonzero.');
|
|
87
|
+
}
|
|
50
88
|
return [
|
|
51
89
|
omitEmpty({
|
|
52
90
|
code: requiredValue(options.code, '--code'),
|
|
53
|
-
flags
|
|
91
|
+
flags,
|
|
54
92
|
id: options.id ?? randomTbId(),
|
|
55
93
|
ledger: requiredValue(options.ledger, '--ledger'),
|
|
94
|
+
timestamp: isImported ? requiredImportedTimestamp(options.timestamp) : options.timestamp,
|
|
56
95
|
user_data_128: options.userData128,
|
|
57
96
|
user_data_32: options.userData32,
|
|
58
97
|
user_data_64: options.userData64,
|
|
@@ -60,17 +99,50 @@ function buildCreateAccountsPayload(options) {
|
|
|
60
99
|
];
|
|
61
100
|
}
|
|
62
101
|
function buildCreateTransfersPayload(options) {
|
|
102
|
+
const flags = flagsBitfield(options.flags, TRANSFER_FLAG_SET);
|
|
103
|
+
rejectLinkedSingleEvent(flags);
|
|
104
|
+
const isPending = flagIsSet(flags, TRANSFER_PENDING_FLAG);
|
|
105
|
+
const isPostPending = flagIsSet(flags, TRANSFER_POST_PENDING_FLAG);
|
|
106
|
+
const isVoidPending = flagIsSet(flags, TRANSFER_VOID_PENDING_FLAG);
|
|
107
|
+
const isImported = flagIsSet(flags, TRANSFER_IMPORTED_FLAG);
|
|
108
|
+
const lifecycleModeCount = Number(isPending) + Number(isPostPending) + Number(isVoidPending);
|
|
109
|
+
if (lifecycleModeCount > 1) {
|
|
110
|
+
throw new Error('The pending, post_pending_transfer, and void_pending_transfer flags cannot be combined.');
|
|
111
|
+
}
|
|
112
|
+
const resolvesPending = isPostPending || isVoidPending;
|
|
113
|
+
if (!isImported && valueIsNonZero(options.timestamp)) {
|
|
114
|
+
throw new Error('--timestamp requires --flag imported when nonzero.');
|
|
115
|
+
}
|
|
116
|
+
if (valueIsNonZero(options.timeout)) {
|
|
117
|
+
if (isImported) {
|
|
118
|
+
throw new Error('--timeout must be zero or omitted with --flag imported.');
|
|
119
|
+
}
|
|
120
|
+
if (!isPending) {
|
|
121
|
+
throw new Error('--timeout requires --flag pending when nonzero.');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!resolvesPending && valueIsNonZero(options.pendingId)) {
|
|
125
|
+
throw new Error('--pending-id requires --flag post_pending_transfer or --flag void_pending_transfer when nonzero.');
|
|
126
|
+
}
|
|
63
127
|
return [
|
|
64
128
|
omitEmpty({
|
|
65
|
-
amount: requiredValue(options.amount, '--amount'),
|
|
66
|
-
code: requiredValue(options.code, '--code'),
|
|
67
|
-
credit_account_id:
|
|
68
|
-
|
|
69
|
-
|
|
129
|
+
amount: isVoidPending ? valueOrZero(options.amount) : requiredValue(options.amount, '--amount'),
|
|
130
|
+
code: resolvesPending ? valueOrZero(options.code) : requiredValue(options.code, '--code'),
|
|
131
|
+
credit_account_id: resolvesPending
|
|
132
|
+
? valueOrZero(options.creditAccountId)
|
|
133
|
+
: requiredValue(options.creditAccountId, '--to'),
|
|
134
|
+
debit_account_id: resolvesPending
|
|
135
|
+
? valueOrZero(options.debitAccountId)
|
|
136
|
+
: requiredValue(options.debitAccountId, '--from'),
|
|
137
|
+
flags,
|
|
70
138
|
id: options.id ?? randomTbId(),
|
|
71
|
-
ledger: requiredValue(options.ledger, '--ledger'),
|
|
72
|
-
pending_id: options.pendingId,
|
|
139
|
+
ledger: resolvesPending ? valueOrZero(options.ledger) : requiredValue(options.ledger, '--ledger'),
|
|
140
|
+
pending_id: resolvesPending ? requiredValue(options.pendingId, '--pending-id') : options.pendingId,
|
|
141
|
+
timestamp: isImported ? requiredImportedTimestamp(options.timestamp) : options.timestamp,
|
|
73
142
|
timeout: options.timeout,
|
|
143
|
+
user_data_128: options.userData128,
|
|
144
|
+
user_data_32: options.userData32,
|
|
145
|
+
user_data_64: options.userData64,
|
|
74
146
|
}),
|
|
75
147
|
];
|
|
76
148
|
}
|
|
@@ -85,7 +157,7 @@ function buildAccountFilterPayload(options) {
|
|
|
85
157
|
return omitEmpty({
|
|
86
158
|
account_id: requiredValue(options.accountId, '--account-id'),
|
|
87
159
|
code: options.code,
|
|
88
|
-
flags: flagsBitfield(options.flags),
|
|
160
|
+
flags: flagsBitfield(options.flags, ACCOUNT_FILTER_FLAG_SET),
|
|
89
161
|
limit: requiredValue(options.limit, '--limit'),
|
|
90
162
|
timestamp_max: options.timestampMax,
|
|
91
163
|
timestamp_min: options.timestampMin,
|
|
@@ -97,7 +169,7 @@ function buildAccountFilterPayload(options) {
|
|
|
97
169
|
function buildQueryPayload(options) {
|
|
98
170
|
return omitEmpty({
|
|
99
171
|
code: options.code,
|
|
100
|
-
flags: flagsBitfield(options.flags),
|
|
172
|
+
flags: flagsBitfield(options.flags, QUERY_FILTER_FLAG_SET),
|
|
101
173
|
ledger: options.ledger,
|
|
102
174
|
limit: requiredValue(options.limit, '--limit'),
|
|
103
175
|
timestamp_max: options.timestampMax,
|
|
@@ -107,29 +179,40 @@ function buildQueryPayload(options) {
|
|
|
107
179
|
user_data_64: options.userData64,
|
|
108
180
|
});
|
|
109
181
|
}
|
|
110
|
-
function flagsBitfield(flags) {
|
|
182
|
+
function flagsBitfield(flags, flagSet) {
|
|
111
183
|
if (!flags || flags.length === 0) {
|
|
112
184
|
return '0';
|
|
113
185
|
}
|
|
114
|
-
let bitfield =
|
|
186
|
+
let bitfield = 0n;
|
|
115
187
|
for (const flag of flags) {
|
|
116
188
|
const normalized = flag.trim();
|
|
117
189
|
if (!normalized) {
|
|
118
190
|
continue;
|
|
119
191
|
}
|
|
120
|
-
|
|
121
|
-
|
|
192
|
+
if (DECIMAL_INTEGER_REGEX.test(normalized)) {
|
|
193
|
+
const direct = BigInt(normalized);
|
|
194
|
+
if ((direct & ~flagSet.allowedMask) !== 0n) {
|
|
195
|
+
throw new Error(`Unsupported ${flagSet.label} flag bitfield: ${flag}`);
|
|
196
|
+
}
|
|
122
197
|
bitfield |= direct;
|
|
123
198
|
continue;
|
|
124
199
|
}
|
|
125
|
-
const bit =
|
|
200
|
+
const bit = flagSet.nameToBit.get(normalized);
|
|
126
201
|
if (bit === undefined) {
|
|
127
|
-
throw new Error(`Unsupported flag: ${flag}`);
|
|
202
|
+
throw new Error(`Unsupported ${flagSet.label} flag: ${flag}`);
|
|
128
203
|
}
|
|
129
204
|
bitfield |= bit;
|
|
130
205
|
}
|
|
131
206
|
return bitfield.toString();
|
|
132
207
|
}
|
|
208
|
+
function flagIsSet(flags, flag) {
|
|
209
|
+
return (BigInt(flags) & flag) !== 0n;
|
|
210
|
+
}
|
|
211
|
+
function rejectLinkedSingleEvent(flags) {
|
|
212
|
+
if (flagIsSet(flags, LINKED_FLAG)) {
|
|
213
|
+
throw new Error('--flag linked requires --payload or --file with a batch of events.');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
133
216
|
function omitEmpty(input) {
|
|
134
217
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value.trim().length > 0));
|
|
135
218
|
}
|
|
@@ -142,6 +225,28 @@ function requiredValue(value, flagName) {
|
|
|
142
225
|
}
|
|
143
226
|
return value.trim();
|
|
144
227
|
}
|
|
228
|
+
function requiredImportedTimestamp(value) {
|
|
229
|
+
const timestamp = requiredValue(value, '--timestamp');
|
|
230
|
+
if (!DECIMAL_INTEGER_REGEX.test(timestamp)) {
|
|
231
|
+
throw new Error(IMPORTED_TIMESTAMP_ERROR);
|
|
232
|
+
}
|
|
233
|
+
const parsed = BigInt(timestamp);
|
|
234
|
+
if (parsed === 0n || parsed >= TIGERBEETLE_TIMESTAMP_MAX_EXCLUSIVE) {
|
|
235
|
+
throw new Error(IMPORTED_TIMESTAMP_ERROR);
|
|
236
|
+
}
|
|
237
|
+
return timestamp;
|
|
238
|
+
}
|
|
239
|
+
function valueOrZero(value) {
|
|
240
|
+
const trimmed = value?.trim();
|
|
241
|
+
return trimmed || '0';
|
|
242
|
+
}
|
|
243
|
+
function valueIsNonZero(value) {
|
|
244
|
+
const trimmed = value?.trim();
|
|
245
|
+
if (!trimmed) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return !DECIMAL_INTEGER_REGEX.test(trimmed) || BigInt(trimmed) !== 0n;
|
|
249
|
+
}
|
|
145
250
|
function splitIds(value) {
|
|
146
251
|
if (!value) {
|
|
147
252
|
return [];
|
|
@@ -10,6 +10,7 @@ export declare function buildCreateAccountsPayload(options: {
|
|
|
10
10
|
flags?: string[];
|
|
11
11
|
id?: string;
|
|
12
12
|
ledger?: string;
|
|
13
|
+
timestamp?: string;
|
|
13
14
|
userData128?: string;
|
|
14
15
|
userData32?: string;
|
|
15
16
|
userData64?: string;
|
|
@@ -25,7 +26,11 @@ export declare function buildCreateTransfersPayload(options: {
|
|
|
25
26
|
id?: string;
|
|
26
27
|
ledger?: string;
|
|
27
28
|
pendingId?: string;
|
|
29
|
+
timestamp?: string;
|
|
28
30
|
timeout?: string;
|
|
31
|
+
userData128?: string;
|
|
32
|
+
userData32?: string;
|
|
33
|
+
userData64?: string;
|
|
29
34
|
}): {
|
|
30
35
|
[k: string]: string | undefined;
|
|
31
36
|
}[];
|
|
@@ -10,6 +10,7 @@ export declare function buildCreateAccountsPayload(options: {
|
|
|
10
10
|
flags?: string[];
|
|
11
11
|
id?: string;
|
|
12
12
|
ledger?: string;
|
|
13
|
+
timestamp?: string;
|
|
13
14
|
userData128?: string;
|
|
14
15
|
userData32?: string;
|
|
15
16
|
userData64?: string;
|
|
@@ -25,7 +26,11 @@ export declare function buildCreateTransfersPayload(options: {
|
|
|
25
26
|
id?: string;
|
|
26
27
|
ledger?: string;
|
|
27
28
|
pendingId?: string;
|
|
29
|
+
timestamp?: string;
|
|
28
30
|
timeout?: string;
|
|
31
|
+
userData128?: string;
|
|
32
|
+
userData32?: string;
|
|
33
|
+
userData64?: string;
|
|
29
34
|
}): {
|
|
30
35
|
[k: string]: string | undefined;
|
|
31
36
|
}[];
|
package/dist/lib/tb-payloads.js
CHANGED
|
@@ -1,23 +1,55 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
const SPLIT_IDS_REGEX = /[\s,]+/;
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
]
|
|
3
|
+
const DECIMAL_INTEGER_REGEX = /^\d+$/;
|
|
4
|
+
const LINKED_FLAG = 1n;
|
|
5
|
+
const ACCOUNT_IMPORTED_FLAG = 16n;
|
|
6
|
+
const TRANSFER_PENDING_FLAG = 2n;
|
|
7
|
+
const TRANSFER_POST_PENDING_FLAG = 4n;
|
|
8
|
+
const TRANSFER_VOID_PENDING_FLAG = 8n;
|
|
9
|
+
const TRANSFER_IMPORTED_FLAG = 256n;
|
|
10
|
+
const TIGERBEETLE_TIMESTAMP_MAX_EXCLUSIVE = 1n << 63n;
|
|
11
|
+
const IMPORTED_TIMESTAMP_ERROR = '--timestamp must be a decimal integer greater than 0 and less than 2^63 when --flag imported is used.';
|
|
12
|
+
const ACCOUNT_FLAG_SET = {
|
|
13
|
+
allowedMask: 63n,
|
|
14
|
+
label: 'account',
|
|
15
|
+
nameToBit: new Map([
|
|
16
|
+
['linked', LINKED_FLAG],
|
|
17
|
+
['debits_must_not_exceed_credits', 2n],
|
|
18
|
+
['credits_must_not_exceed_debits', 4n],
|
|
19
|
+
['history', 8n],
|
|
20
|
+
['imported', ACCOUNT_IMPORTED_FLAG],
|
|
21
|
+
['closed', 32n],
|
|
22
|
+
]),
|
|
23
|
+
};
|
|
24
|
+
const TRANSFER_FLAG_SET = {
|
|
25
|
+
allowedMask: 511n,
|
|
26
|
+
label: 'transfer',
|
|
27
|
+
nameToBit: new Map([
|
|
28
|
+
['linked', LINKED_FLAG],
|
|
29
|
+
['pending', TRANSFER_PENDING_FLAG],
|
|
30
|
+
['post_pending_transfer', TRANSFER_POST_PENDING_FLAG],
|
|
31
|
+
['void_pending_transfer', TRANSFER_VOID_PENDING_FLAG],
|
|
32
|
+
['balancing_debit', 16n],
|
|
33
|
+
['balancing_credit', 32n],
|
|
34
|
+
['closing_debit', 64n],
|
|
35
|
+
['closing_credit', 128n],
|
|
36
|
+
['imported', TRANSFER_IMPORTED_FLAG],
|
|
37
|
+
]),
|
|
38
|
+
};
|
|
39
|
+
const ACCOUNT_FILTER_FLAG_SET = {
|
|
40
|
+
allowedMask: 7n,
|
|
41
|
+
label: 'account filter',
|
|
42
|
+
nameToBit: new Map([
|
|
43
|
+
['debits', 1n],
|
|
44
|
+
['credits', 2n],
|
|
45
|
+
['reversed', 4n],
|
|
46
|
+
]),
|
|
47
|
+
};
|
|
48
|
+
const QUERY_FILTER_FLAG_SET = {
|
|
49
|
+
allowedMask: 1n,
|
|
50
|
+
label: 'query filter',
|
|
51
|
+
nameToBit: new Map([['reversed', 1n]]),
|
|
52
|
+
};
|
|
21
53
|
export const tbOperationNames = [
|
|
22
54
|
'create_accounts',
|
|
23
55
|
'create_transfers',
|
|
@@ -38,12 +70,19 @@ export async function resolveTbPayload(options, buildFromFlags) {
|
|
|
38
70
|
return buildFromFlags();
|
|
39
71
|
}
|
|
40
72
|
export function buildCreateAccountsPayload(options) {
|
|
73
|
+
const flags = flagsBitfield(options.flags, ACCOUNT_FLAG_SET);
|
|
74
|
+
rejectLinkedSingleEvent(flags);
|
|
75
|
+
const isImported = flagIsSet(flags, ACCOUNT_IMPORTED_FLAG);
|
|
76
|
+
if (!isImported && valueIsNonZero(options.timestamp)) {
|
|
77
|
+
throw new Error('--timestamp requires --flag imported when nonzero.');
|
|
78
|
+
}
|
|
41
79
|
return [
|
|
42
80
|
omitEmpty({
|
|
43
81
|
code: requiredValue(options.code, '--code'),
|
|
44
|
-
flags
|
|
82
|
+
flags,
|
|
45
83
|
id: options.id ?? randomTbId(),
|
|
46
84
|
ledger: requiredValue(options.ledger, '--ledger'),
|
|
85
|
+
timestamp: isImported ? requiredImportedTimestamp(options.timestamp) : options.timestamp,
|
|
47
86
|
user_data_128: options.userData128,
|
|
48
87
|
user_data_32: options.userData32,
|
|
49
88
|
user_data_64: options.userData64,
|
|
@@ -51,17 +90,50 @@ export function buildCreateAccountsPayload(options) {
|
|
|
51
90
|
];
|
|
52
91
|
}
|
|
53
92
|
export function buildCreateTransfersPayload(options) {
|
|
93
|
+
const flags = flagsBitfield(options.flags, TRANSFER_FLAG_SET);
|
|
94
|
+
rejectLinkedSingleEvent(flags);
|
|
95
|
+
const isPending = flagIsSet(flags, TRANSFER_PENDING_FLAG);
|
|
96
|
+
const isPostPending = flagIsSet(flags, TRANSFER_POST_PENDING_FLAG);
|
|
97
|
+
const isVoidPending = flagIsSet(flags, TRANSFER_VOID_PENDING_FLAG);
|
|
98
|
+
const isImported = flagIsSet(flags, TRANSFER_IMPORTED_FLAG);
|
|
99
|
+
const lifecycleModeCount = Number(isPending) + Number(isPostPending) + Number(isVoidPending);
|
|
100
|
+
if (lifecycleModeCount > 1) {
|
|
101
|
+
throw new Error('The pending, post_pending_transfer, and void_pending_transfer flags cannot be combined.');
|
|
102
|
+
}
|
|
103
|
+
const resolvesPending = isPostPending || isVoidPending;
|
|
104
|
+
if (!isImported && valueIsNonZero(options.timestamp)) {
|
|
105
|
+
throw new Error('--timestamp requires --flag imported when nonzero.');
|
|
106
|
+
}
|
|
107
|
+
if (valueIsNonZero(options.timeout)) {
|
|
108
|
+
if (isImported) {
|
|
109
|
+
throw new Error('--timeout must be zero or omitted with --flag imported.');
|
|
110
|
+
}
|
|
111
|
+
if (!isPending) {
|
|
112
|
+
throw new Error('--timeout requires --flag pending when nonzero.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!resolvesPending && valueIsNonZero(options.pendingId)) {
|
|
116
|
+
throw new Error('--pending-id requires --flag post_pending_transfer or --flag void_pending_transfer when nonzero.');
|
|
117
|
+
}
|
|
54
118
|
return [
|
|
55
119
|
omitEmpty({
|
|
56
|
-
amount: requiredValue(options.amount, '--amount'),
|
|
57
|
-
code: requiredValue(options.code, '--code'),
|
|
58
|
-
credit_account_id:
|
|
59
|
-
|
|
60
|
-
|
|
120
|
+
amount: isVoidPending ? valueOrZero(options.amount) : requiredValue(options.amount, '--amount'),
|
|
121
|
+
code: resolvesPending ? valueOrZero(options.code) : requiredValue(options.code, '--code'),
|
|
122
|
+
credit_account_id: resolvesPending
|
|
123
|
+
? valueOrZero(options.creditAccountId)
|
|
124
|
+
: requiredValue(options.creditAccountId, '--to'),
|
|
125
|
+
debit_account_id: resolvesPending
|
|
126
|
+
? valueOrZero(options.debitAccountId)
|
|
127
|
+
: requiredValue(options.debitAccountId, '--from'),
|
|
128
|
+
flags,
|
|
61
129
|
id: options.id ?? randomTbId(),
|
|
62
|
-
ledger: requiredValue(options.ledger, '--ledger'),
|
|
63
|
-
pending_id: options.pendingId,
|
|
130
|
+
ledger: resolvesPending ? valueOrZero(options.ledger) : requiredValue(options.ledger, '--ledger'),
|
|
131
|
+
pending_id: resolvesPending ? requiredValue(options.pendingId, '--pending-id') : options.pendingId,
|
|
132
|
+
timestamp: isImported ? requiredImportedTimestamp(options.timestamp) : options.timestamp,
|
|
64
133
|
timeout: options.timeout,
|
|
134
|
+
user_data_128: options.userData128,
|
|
135
|
+
user_data_32: options.userData32,
|
|
136
|
+
user_data_64: options.userData64,
|
|
65
137
|
}),
|
|
66
138
|
];
|
|
67
139
|
}
|
|
@@ -76,7 +148,7 @@ export function buildAccountFilterPayload(options) {
|
|
|
76
148
|
return omitEmpty({
|
|
77
149
|
account_id: requiredValue(options.accountId, '--account-id'),
|
|
78
150
|
code: options.code,
|
|
79
|
-
flags: flagsBitfield(options.flags),
|
|
151
|
+
flags: flagsBitfield(options.flags, ACCOUNT_FILTER_FLAG_SET),
|
|
80
152
|
limit: requiredValue(options.limit, '--limit'),
|
|
81
153
|
timestamp_max: options.timestampMax,
|
|
82
154
|
timestamp_min: options.timestampMin,
|
|
@@ -88,7 +160,7 @@ export function buildAccountFilterPayload(options) {
|
|
|
88
160
|
export function buildQueryPayload(options) {
|
|
89
161
|
return omitEmpty({
|
|
90
162
|
code: options.code,
|
|
91
|
-
flags: flagsBitfield(options.flags),
|
|
163
|
+
flags: flagsBitfield(options.flags, QUERY_FILTER_FLAG_SET),
|
|
92
164
|
ledger: options.ledger,
|
|
93
165
|
limit: requiredValue(options.limit, '--limit'),
|
|
94
166
|
timestamp_max: options.timestampMax,
|
|
@@ -98,29 +170,40 @@ export function buildQueryPayload(options) {
|
|
|
98
170
|
user_data_64: options.userData64,
|
|
99
171
|
});
|
|
100
172
|
}
|
|
101
|
-
function flagsBitfield(flags) {
|
|
173
|
+
function flagsBitfield(flags, flagSet) {
|
|
102
174
|
if (!flags || flags.length === 0) {
|
|
103
175
|
return '0';
|
|
104
176
|
}
|
|
105
|
-
let bitfield =
|
|
177
|
+
let bitfield = 0n;
|
|
106
178
|
for (const flag of flags) {
|
|
107
179
|
const normalized = flag.trim();
|
|
108
180
|
if (!normalized) {
|
|
109
181
|
continue;
|
|
110
182
|
}
|
|
111
|
-
|
|
112
|
-
|
|
183
|
+
if (DECIMAL_INTEGER_REGEX.test(normalized)) {
|
|
184
|
+
const direct = BigInt(normalized);
|
|
185
|
+
if ((direct & ~flagSet.allowedMask) !== 0n) {
|
|
186
|
+
throw new Error(`Unsupported ${flagSet.label} flag bitfield: ${flag}`);
|
|
187
|
+
}
|
|
113
188
|
bitfield |= direct;
|
|
114
189
|
continue;
|
|
115
190
|
}
|
|
116
|
-
const bit =
|
|
191
|
+
const bit = flagSet.nameToBit.get(normalized);
|
|
117
192
|
if (bit === undefined) {
|
|
118
|
-
throw new Error(`Unsupported flag: ${flag}`);
|
|
193
|
+
throw new Error(`Unsupported ${flagSet.label} flag: ${flag}`);
|
|
119
194
|
}
|
|
120
195
|
bitfield |= bit;
|
|
121
196
|
}
|
|
122
197
|
return bitfield.toString();
|
|
123
198
|
}
|
|
199
|
+
function flagIsSet(flags, flag) {
|
|
200
|
+
return (BigInt(flags) & flag) !== 0n;
|
|
201
|
+
}
|
|
202
|
+
function rejectLinkedSingleEvent(flags) {
|
|
203
|
+
if (flagIsSet(flags, LINKED_FLAG)) {
|
|
204
|
+
throw new Error('--flag linked requires --payload or --file with a batch of events.');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
124
207
|
function omitEmpty(input) {
|
|
125
208
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value.trim().length > 0));
|
|
126
209
|
}
|
|
@@ -133,6 +216,28 @@ function requiredValue(value, flagName) {
|
|
|
133
216
|
}
|
|
134
217
|
return value.trim();
|
|
135
218
|
}
|
|
219
|
+
function requiredImportedTimestamp(value) {
|
|
220
|
+
const timestamp = requiredValue(value, '--timestamp');
|
|
221
|
+
if (!DECIMAL_INTEGER_REGEX.test(timestamp)) {
|
|
222
|
+
throw new Error(IMPORTED_TIMESTAMP_ERROR);
|
|
223
|
+
}
|
|
224
|
+
const parsed = BigInt(timestamp);
|
|
225
|
+
if (parsed === 0n || parsed >= TIGERBEETLE_TIMESTAMP_MAX_EXCLUSIVE) {
|
|
226
|
+
throw new Error(IMPORTED_TIMESTAMP_ERROR);
|
|
227
|
+
}
|
|
228
|
+
return timestamp;
|
|
229
|
+
}
|
|
230
|
+
function valueOrZero(value) {
|
|
231
|
+
const trimmed = value?.trim();
|
|
232
|
+
return trimmed || '0';
|
|
233
|
+
}
|
|
234
|
+
function valueIsNonZero(value) {
|
|
235
|
+
const trimmed = value?.trim();
|
|
236
|
+
if (!trimmed) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
return !DECIMAL_INTEGER_REGEX.test(trimmed) || BigInt(trimmed) !== 0n;
|
|
240
|
+
}
|
|
136
241
|
function splitIds(value) {
|
|
137
242
|
if (!value) {
|
|
138
243
|
return [];
|