@resvary/postgres 0.5.0-alpha.3 → 0.6.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/LICENSE +13 -13
- package/README.md +32 -32
- package/dist/cli-options.d.ts +3 -0
- package/dist/cli-options.d.ts.map +1 -0
- package/dist/cli-options.js +35 -0
- package/dist/cli-options.js.map +1 -0
- package/dist/cli.js +11 -37
- package/dist/cli.js.map +1 -1
- package/dist/credit.js +31 -31
- package/dist/health.js +1 -1
- package/dist/import-sqlite.js +2 -2
- package/dist/migrations.js +172 -172
- package/dist/receipt.js +24 -24
- package/package.json +55 -50
package/LICENSE
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Copyright 2025-2026 horn111
|
|
2
|
-
|
|
3
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License.
|
|
5
|
-
You may obtain a copy of the License at
|
|
6
|
-
|
|
7
|
-
https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
|
|
9
|
-
Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
See the License for the specific language governing permissions and
|
|
13
|
-
limitations under the License.
|
|
1
|
+
Copyright 2025-2026 horn111
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
# @resvary/postgres
|
|
2
|
-
|
|
3
|
-
Postgres `CreditStore` and `ReceiptStore` implementations for Resvary
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm install @resvary/sdk
|
|
7
|
-
DATABASE_URL=postgres://... npx resvary-postgres migrate
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
import { createPostgresCreditStore } from '@resvary/postgres';
|
|
12
|
-
|
|
13
|
-
const store = createPostgresCreditStore({ connectionString: process.env.DATABASE_URL! });
|
|
14
|
-
// Use store with CreditLedger, then close the owned pool during shutdown.
|
|
15
|
-
await store.close();
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Pass either `connectionString` or an existing `pg.Pool`. A store created from a connection string owns and closes its pool. A store given a pool never closes it.
|
|
19
|
-
|
|
20
|
-
Credit transactions and bundled receipt ledger operations run at `SERIALIZABLE` isolation with bounded retry. Schema v2 adds database checks, foreign keys, and transaction-hash uniqueness. Apply migrations before starting application or worker processes.
|
|
21
|
-
|
|
22
|
-
CLI commands:
|
|
23
|
-
|
|
24
|
-
```text
|
|
25
|
-
resvary-postgres status
|
|
26
|
-
resvary-postgres migrate
|
|
27
|
-
resvary-postgres import-sqlite --sqlite .resvary/resvary.sqlite --dry-run
|
|
28
|
-
resvary-postgres import-sqlite --sqlite .resvary/resvary.sqlite
|
|
29
|
-
resvary-postgres verify-import --sqlite .resvary/resvary.sqlite
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
SQLite import requires Node.js 24. Stop every writer before importing. Verification compares entity payloads, balances, ledger totals, and open reservations.
|
|
1
|
+
# @resvary/postgres
|
|
2
|
+
|
|
3
|
+
Postgres `CreditStore` and `ReceiptStore` implementations for Resvary multi-process deployments. Resvary supports PostgreSQL 16–18. Stores never migrate automatically.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @resvary/sdk @resvary/postgres
|
|
7
|
+
DATABASE_URL=postgres://... npx resvary-postgres migrate
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createPostgresCreditStore } from '@resvary/postgres';
|
|
12
|
+
|
|
13
|
+
const store = createPostgresCreditStore({ connectionString: process.env.DATABASE_URL! });
|
|
14
|
+
// Use store with CreditLedger, then close the owned pool during shutdown.
|
|
15
|
+
await store.close();
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Pass either `connectionString` or an existing `pg.Pool`. A store created from a connection string owns and closes its pool. A store given a pool never closes it.
|
|
19
|
+
|
|
20
|
+
Credit transactions and bundled receipt ledger operations run at `SERIALIZABLE` isolation with bounded retry. Schema v2 adds database checks, foreign keys, and transaction-hash uniqueness. Apply migrations before starting application or worker processes.
|
|
21
|
+
|
|
22
|
+
CLI commands:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
resvary-postgres status
|
|
26
|
+
resvary-postgres migrate
|
|
27
|
+
resvary-postgres import-sqlite --sqlite .resvary/resvary.sqlite --dry-run
|
|
28
|
+
resvary-postgres import-sqlite --sqlite .resvary/resvary.sqlite
|
|
29
|
+
resvary-postgres verify-import --sqlite .resvary/resvary.sqlite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
SQLite import requires Node.js 24. Stop every writer before importing. Verification compares entity payloads, balances, ledger totals, and open reservations.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-options.d.ts","sourceRoot":"","sources":["../src/cli-options.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;AAElE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,kBAAkB,CA6B1E"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function parsePostgresCliOptions(args) {
|
|
2
|
+
const valueOptions = new Set(['schema', 'sqlite']);
|
|
3
|
+
const booleanOptions = new Set(['dry-run']);
|
|
4
|
+
const options = {};
|
|
5
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6
|
+
const arg = args[index];
|
|
7
|
+
if (!arg?.startsWith('--'))
|
|
8
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
9
|
+
const separator = arg.indexOf('=');
|
|
10
|
+
const key = arg.slice(2, separator === -1 ? undefined : separator);
|
|
11
|
+
if (!valueOptions.has(key) && !booleanOptions.has(key)) {
|
|
12
|
+
throw new Error(`Unknown option: --${key}`);
|
|
13
|
+
}
|
|
14
|
+
const inlineValue = separator === -1 ? undefined : arg.slice(separator + 1);
|
|
15
|
+
const next = args[index + 1];
|
|
16
|
+
if (booleanOptions.has(key)) {
|
|
17
|
+
const candidate = inlineValue ?? (next === 'true' || next === 'false' ? next : undefined);
|
|
18
|
+
if (candidate !== undefined && candidate !== 'true' && candidate !== 'false') {
|
|
19
|
+
throw new Error(`--${key} must be true or false`);
|
|
20
|
+
}
|
|
21
|
+
options[key] = candidate === undefined ? true : candidate === 'true';
|
|
22
|
+
if (inlineValue === undefined && candidate !== undefined)
|
|
23
|
+
index += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const value = inlineValue ?? next;
|
|
27
|
+
if (!value || value.startsWith('--'))
|
|
28
|
+
throw new Error(`--${key} requires a value`);
|
|
29
|
+
options[key] = value;
|
|
30
|
+
if (inlineValue === undefined)
|
|
31
|
+
index += 1;
|
|
32
|
+
}
|
|
33
|
+
return options;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=cli-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-options.js","sourceRoot":"","sources":["../src/cli-options.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CAAC,IAAc;IACpD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;QAC3E,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,WAAW,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC7B,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,WAAW,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YAC1F,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,OAAO,EAAE,CAAC;gBAC7E,MAAM,IAAI,KAAK,CAAC,KAAK,GAAG,wBAAwB,CAAC,CAAC;YACpD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC;YACrE,IAAI,WAAW,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS;gBAAE,KAAK,IAAI,CAAC,CAAC;YACrE,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,IAAI,IAAI,CAAC;QAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,KAAK,GAAG,mBAAmB,CAAC,CAAC;QACnF,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACrB,IAAI,WAAW,KAAK,SAAS;YAAE,KAAK,IAAI,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { getPostgresMigrationStatus, migratePostgres } from './index.js';
|
|
3
|
+
import { parsePostgresCliOptions } from './cli-options.js';
|
|
3
4
|
async function main() {
|
|
4
5
|
const [command, ...rest] = process.argv.slice(2);
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
if (command === '--help' || command === '-h' || command === 'help') {
|
|
7
|
+
process.stdout.write(`${usage()}\n`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const options = parsePostgresCliOptions(rest);
|
|
11
|
+
const connectionString = process.env.DATABASE_URL;
|
|
7
12
|
if (!connectionString)
|
|
8
|
-
throw new Error('DATABASE_URL
|
|
13
|
+
throw new Error('DATABASE_URL is required');
|
|
9
14
|
const schema = optionString(options, 'schema') ?? process.env.RESVARY_POSTGRES_SCHEMA ?? 'public';
|
|
10
15
|
const base = { connectionString, schema };
|
|
11
16
|
switch (command) {
|
|
@@ -35,42 +40,11 @@ async function main() {
|
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
42
|
default:
|
|
38
|
-
throw new Error(
|
|
43
|
+
throw new Error(usage());
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
const booleanOptions = new Set(['dry-run']);
|
|
44
|
-
const options = {};
|
|
45
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
46
|
-
const arg = args[index];
|
|
47
|
-
if (!arg?.startsWith('--'))
|
|
48
|
-
throw new Error(`Unexpected argument: ${arg}`);
|
|
49
|
-
const separator = arg.indexOf('=');
|
|
50
|
-
const key = arg.slice(2, separator === -1 ? undefined : separator);
|
|
51
|
-
if (!valueOptions.has(key) && !booleanOptions.has(key)) {
|
|
52
|
-
throw new Error(`Unknown option: --${key}`);
|
|
53
|
-
}
|
|
54
|
-
const inlineValue = separator === -1 ? undefined : arg.slice(separator + 1);
|
|
55
|
-
const next = args[index + 1];
|
|
56
|
-
if (booleanOptions.has(key)) {
|
|
57
|
-
const candidate = inlineValue ?? (next === 'true' || next === 'false' ? next : undefined);
|
|
58
|
-
if (candidate !== undefined && candidate !== 'true' && candidate !== 'false') {
|
|
59
|
-
throw new Error(`--${key} must be true or false`);
|
|
60
|
-
}
|
|
61
|
-
options[key] = candidate === undefined ? true : candidate === 'true';
|
|
62
|
-
if (inlineValue === undefined && candidate !== undefined)
|
|
63
|
-
index += 1;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const value = inlineValue ?? next;
|
|
67
|
-
if (!value || value.startsWith('--'))
|
|
68
|
-
throw new Error(`--${key} requires a value`);
|
|
69
|
-
options[key] = value;
|
|
70
|
-
if (inlineValue === undefined)
|
|
71
|
-
index += 1;
|
|
72
|
-
}
|
|
73
|
-
return options;
|
|
46
|
+
function usage() {
|
|
47
|
+
return 'Usage: resvary-postgres <status|migrate|import-sqlite|verify-import> [--schema NAME] [--sqlite PATH] [--dry-run]';
|
|
74
48
|
}
|
|
75
49
|
function optionString(options, key) {
|
|
76
50
|
const value = options[key];
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/credit.js
CHANGED
|
@@ -100,39 +100,39 @@ export class PostgresCreditStore {
|
|
|
100
100
|
}
|
|
101
101
|
async claimOutboxEvents(input) {
|
|
102
102
|
const outbox = table(this.handle, 'resvary_outbox_events');
|
|
103
|
-
const result = await this.handle.pool.query(`WITH candidates AS (
|
|
104
|
-
SELECT id FROM ${outbox}
|
|
105
|
-
WHERE ($5::text IS NULL OR project_id = $5)
|
|
106
|
-
AND ((status = 'pending' AND next_attempt_at <= $1)
|
|
107
|
-
OR (status = 'processing' AND lease_expires_at <= $1))
|
|
108
|
-
ORDER BY next_attempt_at ASC, created_at ASC
|
|
109
|
-
FOR UPDATE SKIP LOCKED
|
|
110
|
-
LIMIT $2
|
|
111
|
-
)
|
|
112
|
-
UPDATE ${outbox} AS event
|
|
113
|
-
SET status = 'processing', attempt_count = event.attempt_count + 1,
|
|
114
|
-
lease_owner = $3, lease_expires_at = $1 + $4, last_attempt_at = $1
|
|
115
|
-
FROM candidates
|
|
116
|
-
WHERE event.id = candidates.id
|
|
117
|
-
RETURNING event.payload::text AS payload, event.status, event.attempt_count,
|
|
118
|
-
event.next_attempt_at::text, event.lease_owner, event.lease_expires_at::text,
|
|
103
|
+
const result = await this.handle.pool.query(`WITH candidates AS (
|
|
104
|
+
SELECT id FROM ${outbox}
|
|
105
|
+
WHERE ($5::text IS NULL OR project_id = $5)
|
|
106
|
+
AND ((status = 'pending' AND next_attempt_at <= $1)
|
|
107
|
+
OR (status = 'processing' AND lease_expires_at <= $1))
|
|
108
|
+
ORDER BY next_attempt_at ASC, created_at ASC
|
|
109
|
+
FOR UPDATE SKIP LOCKED
|
|
110
|
+
LIMIT $2
|
|
111
|
+
)
|
|
112
|
+
UPDATE ${outbox} AS event
|
|
113
|
+
SET status = 'processing', attempt_count = event.attempt_count + 1,
|
|
114
|
+
lease_owner = $3, lease_expires_at = $1 + $4, last_attempt_at = $1
|
|
115
|
+
FROM candidates
|
|
116
|
+
WHERE event.id = candidates.id
|
|
117
|
+
RETURNING event.payload::text AS payload, event.status, event.attempt_count,
|
|
118
|
+
event.next_attempt_at::text, event.lease_owner, event.lease_expires_at::text,
|
|
119
119
|
event.last_attempt_at::text, event.last_error, event.delivered_at::text`, [input.now, input.limit, input.workerId, input.leaseMs, input.projectId ?? null]);
|
|
120
120
|
return result.rows.map(parseOutboxRow);
|
|
121
121
|
}
|
|
122
122
|
async completeOutboxEvent(eventId, workerId, deliveredAt, attemptCount) {
|
|
123
|
-
const result = await this.handle.pool.query(`UPDATE ${table(this.handle, 'resvary_outbox_events')}
|
|
124
|
-
SET status = 'delivered', delivered_at = $3, next_attempt_at = $3,
|
|
125
|
-
lease_owner = NULL, lease_expires_at = NULL, last_error = NULL
|
|
126
|
-
WHERE id = $1 AND status = 'processing' AND lease_owner = $2
|
|
123
|
+
const result = await this.handle.pool.query(`UPDATE ${table(this.handle, 'resvary_outbox_events')}
|
|
124
|
+
SET status = 'delivered', delivered_at = $3, next_attempt_at = $3,
|
|
125
|
+
lease_owner = NULL, lease_expires_at = NULL, last_error = NULL
|
|
126
|
+
WHERE id = $1 AND status = 'processing' AND lease_owner = $2
|
|
127
127
|
AND ($4::integer IS NULL OR attempt_count = $4)`, [eventId, workerId, deliveredAt, attemptCount ?? null]);
|
|
128
128
|
if (result.rowCount !== 1)
|
|
129
129
|
throw new Error(`Outbox lease lost for event ${eventId}`);
|
|
130
130
|
}
|
|
131
131
|
async failOutboxEvent(input) {
|
|
132
|
-
const result = await this.handle.pool.query(`UPDATE ${table(this.handle, 'resvary_outbox_events')}
|
|
133
|
-
SET status = $4, next_attempt_at = $5, lease_owner = NULL,
|
|
134
|
-
lease_expires_at = NULL, last_error = $3
|
|
135
|
-
WHERE id = $1 AND status = 'processing' AND lease_owner = $2
|
|
132
|
+
const result = await this.handle.pool.query(`UPDATE ${table(this.handle, 'resvary_outbox_events')}
|
|
133
|
+
SET status = $4, next_attempt_at = $5, lease_owner = NULL,
|
|
134
|
+
lease_expires_at = NULL, last_error = $3
|
|
135
|
+
WHERE id = $1 AND status = 'processing' AND lease_owner = $2
|
|
136
136
|
AND ($6::integer IS NULL OR attempt_count = $6)`, [
|
|
137
137
|
input.eventId,
|
|
138
138
|
input.workerId,
|
|
@@ -148,9 +148,9 @@ export class PostgresCreditStore {
|
|
|
148
148
|
return this.listOutboxEvents({ projectId, status: 'dead_letter' });
|
|
149
149
|
}
|
|
150
150
|
async requeueOutboxEvent(eventId, now) {
|
|
151
|
-
const result = await this.handle.pool.query(`UPDATE ${table(this.handle, 'resvary_outbox_events')}
|
|
152
|
-
SET status = 'pending', attempt_count = 0, next_attempt_at = $2,
|
|
153
|
-
lease_owner = NULL, lease_expires_at = NULL, last_error = NULL, delivered_at = NULL
|
|
151
|
+
const result = await this.handle.pool.query(`UPDATE ${table(this.handle, 'resvary_outbox_events')}
|
|
152
|
+
SET status = 'pending', attempt_count = 0, next_attempt_at = $2,
|
|
153
|
+
lease_owner = NULL, lease_expires_at = NULL, last_error = NULL, delivered_at = NULL
|
|
154
154
|
WHERE id = $1 AND status = 'dead_letter'`, [eventId, now]);
|
|
155
155
|
if (result.rowCount !== 1)
|
|
156
156
|
throw new Error(`Dead-letter event not found: ${eventId}`);
|
|
@@ -416,8 +416,8 @@ function reader(db, handle) {
|
|
|
416
416
|
};
|
|
417
417
|
}
|
|
418
418
|
function outboxSelect(tableName, suffix) {
|
|
419
|
-
return `SELECT payload::text AS payload, status, attempt_count,
|
|
420
|
-
next_attempt_at::text, lease_owner, lease_expires_at::text,
|
|
419
|
+
return `SELECT payload::text AS payload, status, attempt_count,
|
|
420
|
+
next_attempt_at::text, lease_owner, lease_expires_at::text,
|
|
421
421
|
last_attempt_at::text, last_error, delivered_at::text FROM ${tableName} ${suffix}`;
|
|
422
422
|
}
|
|
423
423
|
function parseOutboxRow(row) {
|
|
@@ -448,7 +448,7 @@ async function filteredAll(db, sql, values, filter) {
|
|
|
448
448
|
async function insert(db, handle, name, columns, values, payload, conflictColumns = ['id']) {
|
|
449
449
|
const allColumns = [...columns, 'payload'];
|
|
450
450
|
const params = allColumns.map((_, index) => `$${index + 1}`).join(', ');
|
|
451
|
-
await db.query(`INSERT INTO ${table(handle, name)} (${allColumns.join(', ')}) VALUES (${params})
|
|
451
|
+
await db.query(`INSERT INTO ${table(handle, name)} (${allColumns.join(', ')}) VALUES (${params})
|
|
452
452
|
ON CONFLICT (${conflictColumns.join(', ')}) DO NOTHING`, [...values, serializeReceiptStoreValue(payload)]);
|
|
453
453
|
}
|
|
454
454
|
async function upsert(db, handle, name, columns, values, payload) {
|
|
@@ -458,7 +458,7 @@ async function upsert(db, handle, name, columns, values, payload) {
|
|
|
458
458
|
.filter((column) => column !== 'id')
|
|
459
459
|
.map((column) => `${column} = EXCLUDED.${column}`)
|
|
460
460
|
.join(', ');
|
|
461
|
-
await db.query(`INSERT INTO ${table(handle, name)} (${allColumns.join(', ')}) VALUES (${params})
|
|
461
|
+
await db.query(`INSERT INTO ${table(handle, name)} (${allColumns.join(', ')}) VALUES (${params})
|
|
462
462
|
ON CONFLICT (id) DO UPDATE SET ${updates}`, [...values, serializeReceiptStoreValue(payload)]);
|
|
463
463
|
}
|
|
464
464
|
function matchesBalanceFilter(value, filter) {
|
package/dist/health.js
CHANGED
|
@@ -6,7 +6,7 @@ export async function checkPostgresHealth(config) {
|
|
|
6
6
|
try {
|
|
7
7
|
await handle.pool.query('SELECT 1');
|
|
8
8
|
const version = await handle.pool.query(`SELECT version FROM ${table(handle, 'resvary_schema_migrations')} ORDER BY version ASC`);
|
|
9
|
-
const outbox = await handle.pool.query(`SELECT status, COUNT(*)::text AS count FROM ${table(handle, 'resvary_outbox_events')}
|
|
9
|
+
const outbox = await handle.pool.query(`SELECT status, COUNT(*)::text AS count FROM ${table(handle, 'resvary_outbox_events')}
|
|
10
10
|
WHERE status IN ('pending', 'dead_letter') GROUP BY status`);
|
|
11
11
|
const counts = new Map(outbox.rows.map((row) => [row.status, Number(row.count)]));
|
|
12
12
|
const appliedVersions = version.rows.map((row) => row.version);
|
package/dist/import-sqlite.js
CHANGED
|
@@ -295,8 +295,8 @@ async function buildReport(source, client, schema, sqliteSchemaVersion, postgres
|
|
|
295
295
|
sourceLedger.set(key, (sourceLedger.get(key) ?? 0n) + BigInt(String(entry.deltaUnits)));
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
|
-
const targetLedgerResult = await client.query(`SELECT account_id, payload->>'bucket' AS bucket, SUM(delta_units)::text AS total
|
|
299
|
-
FROM ${table({ schema }, 'resvary_ledger_entries')}
|
|
298
|
+
const targetLedgerResult = await client.query(`SELECT account_id, payload->>'bucket' AS bucket, SUM(delta_units)::text AS total
|
|
299
|
+
FROM ${table({ schema }, 'resvary_ledger_entries')}
|
|
300
300
|
GROUP BY account_id, payload->>'bucket'`);
|
|
301
301
|
const targetLedger = new Map(targetLedgerResult.rows.map((row) => [`${row.account_id}:${row.bucket}`, BigInt(row.total)]));
|
|
302
302
|
for (const key of new Set([...sourceLedger.keys(), ...targetLedger.keys()])) {
|
package/dist/migrations.js
CHANGED
|
@@ -4,9 +4,9 @@ const MIGRATION_LOCK_ID = 7_226_519_918;
|
|
|
4
4
|
export async function getPostgresMigrationStatus(config) {
|
|
5
5
|
const handle = createPostgresHandle(config);
|
|
6
6
|
try {
|
|
7
|
-
const exists = await handle.pool.query(`SELECT EXISTS (
|
|
8
|
-
SELECT 1 FROM information_schema.tables
|
|
9
|
-
WHERE table_schema = $1 AND table_name = 'resvary_schema_migrations'
|
|
7
|
+
const exists = await handle.pool.query(`SELECT EXISTS (
|
|
8
|
+
SELECT 1 FROM information_schema.tables
|
|
9
|
+
WHERE table_schema = $1 AND table_name = 'resvary_schema_migrations'
|
|
10
10
|
) AS exists`, [handle.schema]);
|
|
11
11
|
let currentVersion = 0;
|
|
12
12
|
if (exists.rows[0]?.exists) {
|
|
@@ -31,11 +31,11 @@ export async function migratePostgres(config) {
|
|
|
31
31
|
try {
|
|
32
32
|
await client.query('SELECT pg_advisory_lock($1)', [MIGRATION_LOCK_ID]);
|
|
33
33
|
await client.query(`CREATE SCHEMA IF NOT EXISTS "${handle.schema}"`);
|
|
34
|
-
await client.query(`
|
|
35
|
-
CREATE TABLE IF NOT EXISTS ${table(handle, 'resvary_schema_migrations')} (
|
|
36
|
-
version INTEGER PRIMARY KEY,
|
|
37
|
-
applied_at BIGINT NOT NULL
|
|
38
|
-
)
|
|
34
|
+
await client.query(`
|
|
35
|
+
CREATE TABLE IF NOT EXISTS ${table(handle, 'resvary_schema_migrations')} (
|
|
36
|
+
version INTEGER PRIMARY KEY,
|
|
37
|
+
applied_at BIGINT NOT NULL
|
|
38
|
+
)
|
|
39
39
|
`);
|
|
40
40
|
const current = await client.query(`SELECT version FROM ${table(handle, 'resvary_schema_migrations')} ORDER BY version ASC`);
|
|
41
41
|
const currentVersion = assertMigrationHistory(current.rows.map((row) => row.version));
|
|
@@ -70,101 +70,101 @@ export async function applyV1(client, schema) {
|
|
|
70
70
|
const t = (name) => table({ schema }, name);
|
|
71
71
|
await client.query('BEGIN');
|
|
72
72
|
try {
|
|
73
|
-
await client.query(`
|
|
74
|
-
CREATE TABLE ${t('resvary_credit_accounts')} (
|
|
75
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, customer_id TEXT NOT NULL,
|
|
76
|
-
currency TEXT NOT NULL, posted_units NUMERIC(78,0) NOT NULL,
|
|
77
|
-
reserved_units NUMERIC(78,0) NOT NULL, updated_at BIGINT NOT NULL, payload JSONB NOT NULL,
|
|
78
|
-
UNIQUE(project_id, customer_id)
|
|
79
|
-
);
|
|
80
|
-
CREATE TABLE ${t('resvary_credit_grants')} (
|
|
81
|
-
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, amount_units NUMERIC(78,0) NOT NULL,
|
|
82
|
-
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
83
|
-
);
|
|
84
|
-
CREATE INDEX resvary_credit_grants_account ON ${t('resvary_credit_grants')}(account_id);
|
|
85
|
-
CREATE TABLE ${t('resvary_meters')} (
|
|
86
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, meter_key TEXT NOT NULL, payload JSONB NOT NULL,
|
|
87
|
-
UNIQUE(project_id, meter_key)
|
|
88
|
-
);
|
|
89
|
-
CREATE TABLE ${t('resvary_price_versions')} (
|
|
90
|
-
id TEXT PRIMARY KEY, meter_id TEXT NOT NULL, version INTEGER NOT NULL,
|
|
91
|
-
created_at BIGINT NOT NULL, payload JSONB NOT NULL, UNIQUE(meter_id, version)
|
|
92
|
-
);
|
|
93
|
-
CREATE TABLE ${t('resvary_credit_reservations')} (
|
|
94
|
-
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
95
|
-
customer_id TEXT NOT NULL, status TEXT NOT NULL,
|
|
96
|
-
reserved_units NUMERIC(78,0) NOT NULL, expires_at BIGINT NOT NULL,
|
|
97
|
-
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
98
|
-
);
|
|
99
|
-
CREATE INDEX resvary_credit_reservations_open
|
|
100
|
-
ON ${t('resvary_credit_reservations')}(project_id, customer_id, status, expires_at);
|
|
101
|
-
CREATE TABLE ${t('resvary_usage_events')} (
|
|
102
|
-
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, received_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
103
|
-
);
|
|
104
|
-
CREATE TABLE ${t('resvary_usage_receipts')} (
|
|
105
|
-
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, reservation_id TEXT NOT NULL UNIQUE,
|
|
106
|
-
usage_event_id TEXT NOT NULL UNIQUE, charged_units NUMERIC(78,0) NOT NULL,
|
|
107
|
-
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
108
|
-
);
|
|
109
|
-
CREATE INDEX resvary_usage_receipts_account
|
|
110
|
-
ON ${t('resvary_usage_receipts')}(account_id, created_at);
|
|
111
|
-
CREATE TABLE ${t('resvary_ledger_entries')} (
|
|
112
|
-
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, delta_units NUMERIC(78,0) NOT NULL,
|
|
113
|
-
balance_after_units NUMERIC(78,0) NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
114
|
-
);
|
|
115
|
-
CREATE INDEX resvary_ledger_entries_account
|
|
116
|
-
ON ${t('resvary_ledger_entries')}(account_id, created_at);
|
|
117
|
-
CREATE TABLE ${t('resvary_funding_intents')} (
|
|
118
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, customer_id TEXT NOT NULL,
|
|
119
|
-
status TEXT NOT NULL, requested_units NUMERIC(78,0) NOT NULL,
|
|
120
|
-
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
121
|
-
);
|
|
122
|
-
CREATE TABLE ${t('resvary_funding_transactions')} (
|
|
123
|
-
id TEXT PRIMARY KEY, funding_intent_id TEXT NOT NULL, rail TEXT NOT NULL,
|
|
124
|
-
network TEXT NOT NULL, external_payment_id_norm TEXT NOT NULL, tx_hash_norm TEXT,
|
|
125
|
-
amount_units NUMERIC(78,0) NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL,
|
|
126
|
-
UNIQUE(rail, network, external_payment_id_norm)
|
|
127
|
-
);
|
|
128
|
-
CREATE UNIQUE INDEX resvary_funding_transactions_tx_hash
|
|
129
|
-
ON ${t('resvary_funding_transactions')}(network, tx_hash_norm)
|
|
130
|
-
WHERE tx_hash_norm IS NOT NULL;
|
|
131
|
-
CREATE TABLE ${t('resvary_idempotency_keys')} (
|
|
132
|
-
scope TEXT NOT NULL, key TEXT NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL,
|
|
133
|
-
PRIMARY KEY(scope, key)
|
|
134
|
-
);
|
|
135
|
-
CREATE TABLE ${t('resvary_outbox_events')} (
|
|
136
|
-
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, type TEXT NOT NULL, status TEXT NOT NULL,
|
|
137
|
-
created_at BIGINT NOT NULL, attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
138
|
-
next_attempt_at BIGINT NOT NULL, lease_owner TEXT, lease_expires_at BIGINT,
|
|
139
|
-
last_attempt_at BIGINT, last_error TEXT, delivered_at BIGINT, payload JSONB NOT NULL
|
|
140
|
-
);
|
|
141
|
-
CREATE INDEX resvary_outbox_events_due
|
|
142
|
-
ON ${t('resvary_outbox_events')}(project_id, status, next_attempt_at, created_at);
|
|
143
|
-
|
|
144
|
-
CREATE TABLE ${t('resvary_invoices')} (
|
|
145
|
-
id TEXT PRIMARY KEY, status TEXT NOT NULL, customer_id TEXT, amount_units NUMERIC(78,0) NOT NULL,
|
|
146
|
-
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
147
|
-
);
|
|
148
|
-
CREATE INDEX resvary_invoices_status ON ${t('resvary_invoices')}(status);
|
|
149
|
-
CREATE INDEX resvary_invoices_customer_id ON ${t('resvary_invoices')}(customer_id);
|
|
150
|
-
CREATE TABLE ${t('resvary_receipts')} (
|
|
151
|
-
id TEXT PRIMARY KEY, invoice_id TEXT NOT NULL, tx_hash_norm TEXT, status TEXT NOT NULL,
|
|
152
|
-
amount_units NUMERIC(78,0) NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
153
|
-
);
|
|
154
|
-
CREATE UNIQUE INDEX resvary_receipts_invoice_tx_hash
|
|
155
|
-
ON ${t('resvary_receipts')}(invoice_id, tx_hash_norm) WHERE tx_hash_norm IS NOT NULL;
|
|
156
|
-
CREATE TABLE ${t('arc_webhook_events')} (
|
|
157
|
-
id TEXT PRIMARY KEY, type TEXT NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
158
|
-
);
|
|
159
|
-
CREATE TABLE ${t('arc_webhook_deliveries')} (
|
|
160
|
-
id TEXT PRIMARY KEY, event_id TEXT NOT NULL, event_type TEXT NOT NULL, attempt INTEGER NOT NULL,
|
|
161
|
-
status TEXT NOT NULL, received_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
162
|
-
);
|
|
163
|
-
CREATE INDEX arc_webhook_deliveries_event_id ON ${t('arc_webhook_deliveries')}(event_id);
|
|
164
|
-
CREATE TABLE ${t('arc_watcher_cursors')} (
|
|
165
|
-
key TEXT PRIMARY KEY, network TEXT NOT NULL, invoice_id TEXT, memo_id_norm TEXT,
|
|
166
|
-
next_from_block NUMERIC(78,0) NOT NULL, updated_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
167
|
-
);
|
|
73
|
+
await client.query(`
|
|
74
|
+
CREATE TABLE ${t('resvary_credit_accounts')} (
|
|
75
|
+
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, customer_id TEXT NOT NULL,
|
|
76
|
+
currency TEXT NOT NULL, posted_units NUMERIC(78,0) NOT NULL,
|
|
77
|
+
reserved_units NUMERIC(78,0) NOT NULL, updated_at BIGINT NOT NULL, payload JSONB NOT NULL,
|
|
78
|
+
UNIQUE(project_id, customer_id)
|
|
79
|
+
);
|
|
80
|
+
CREATE TABLE ${t('resvary_credit_grants')} (
|
|
81
|
+
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, amount_units NUMERIC(78,0) NOT NULL,
|
|
82
|
+
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
83
|
+
);
|
|
84
|
+
CREATE INDEX resvary_credit_grants_account ON ${t('resvary_credit_grants')}(account_id);
|
|
85
|
+
CREATE TABLE ${t('resvary_meters')} (
|
|
86
|
+
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, meter_key TEXT NOT NULL, payload JSONB NOT NULL,
|
|
87
|
+
UNIQUE(project_id, meter_key)
|
|
88
|
+
);
|
|
89
|
+
CREATE TABLE ${t('resvary_price_versions')} (
|
|
90
|
+
id TEXT PRIMARY KEY, meter_id TEXT NOT NULL, version INTEGER NOT NULL,
|
|
91
|
+
created_at BIGINT NOT NULL, payload JSONB NOT NULL, UNIQUE(meter_id, version)
|
|
92
|
+
);
|
|
93
|
+
CREATE TABLE ${t('resvary_credit_reservations')} (
|
|
94
|
+
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, project_id TEXT NOT NULL,
|
|
95
|
+
customer_id TEXT NOT NULL, status TEXT NOT NULL,
|
|
96
|
+
reserved_units NUMERIC(78,0) NOT NULL, expires_at BIGINT NOT NULL,
|
|
97
|
+
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
98
|
+
);
|
|
99
|
+
CREATE INDEX resvary_credit_reservations_open
|
|
100
|
+
ON ${t('resvary_credit_reservations')}(project_id, customer_id, status, expires_at);
|
|
101
|
+
CREATE TABLE ${t('resvary_usage_events')} (
|
|
102
|
+
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, received_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
103
|
+
);
|
|
104
|
+
CREATE TABLE ${t('resvary_usage_receipts')} (
|
|
105
|
+
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, reservation_id TEXT NOT NULL UNIQUE,
|
|
106
|
+
usage_event_id TEXT NOT NULL UNIQUE, charged_units NUMERIC(78,0) NOT NULL,
|
|
107
|
+
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
108
|
+
);
|
|
109
|
+
CREATE INDEX resvary_usage_receipts_account
|
|
110
|
+
ON ${t('resvary_usage_receipts')}(account_id, created_at);
|
|
111
|
+
CREATE TABLE ${t('resvary_ledger_entries')} (
|
|
112
|
+
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, delta_units NUMERIC(78,0) NOT NULL,
|
|
113
|
+
balance_after_units NUMERIC(78,0) NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
114
|
+
);
|
|
115
|
+
CREATE INDEX resvary_ledger_entries_account
|
|
116
|
+
ON ${t('resvary_ledger_entries')}(account_id, created_at);
|
|
117
|
+
CREATE TABLE ${t('resvary_funding_intents')} (
|
|
118
|
+
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, customer_id TEXT NOT NULL,
|
|
119
|
+
status TEXT NOT NULL, requested_units NUMERIC(78,0) NOT NULL,
|
|
120
|
+
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
121
|
+
);
|
|
122
|
+
CREATE TABLE ${t('resvary_funding_transactions')} (
|
|
123
|
+
id TEXT PRIMARY KEY, funding_intent_id TEXT NOT NULL, rail TEXT NOT NULL,
|
|
124
|
+
network TEXT NOT NULL, external_payment_id_norm TEXT NOT NULL, tx_hash_norm TEXT,
|
|
125
|
+
amount_units NUMERIC(78,0) NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL,
|
|
126
|
+
UNIQUE(rail, network, external_payment_id_norm)
|
|
127
|
+
);
|
|
128
|
+
CREATE UNIQUE INDEX resvary_funding_transactions_tx_hash
|
|
129
|
+
ON ${t('resvary_funding_transactions')}(network, tx_hash_norm)
|
|
130
|
+
WHERE tx_hash_norm IS NOT NULL;
|
|
131
|
+
CREATE TABLE ${t('resvary_idempotency_keys')} (
|
|
132
|
+
scope TEXT NOT NULL, key TEXT NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL,
|
|
133
|
+
PRIMARY KEY(scope, key)
|
|
134
|
+
);
|
|
135
|
+
CREATE TABLE ${t('resvary_outbox_events')} (
|
|
136
|
+
id TEXT PRIMARY KEY, project_id TEXT NOT NULL, type TEXT NOT NULL, status TEXT NOT NULL,
|
|
137
|
+
created_at BIGINT NOT NULL, attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
next_attempt_at BIGINT NOT NULL, lease_owner TEXT, lease_expires_at BIGINT,
|
|
139
|
+
last_attempt_at BIGINT, last_error TEXT, delivered_at BIGINT, payload JSONB NOT NULL
|
|
140
|
+
);
|
|
141
|
+
CREATE INDEX resvary_outbox_events_due
|
|
142
|
+
ON ${t('resvary_outbox_events')}(project_id, status, next_attempt_at, created_at);
|
|
143
|
+
|
|
144
|
+
CREATE TABLE ${t('resvary_invoices')} (
|
|
145
|
+
id TEXT PRIMARY KEY, status TEXT NOT NULL, customer_id TEXT, amount_units NUMERIC(78,0) NOT NULL,
|
|
146
|
+
created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
147
|
+
);
|
|
148
|
+
CREATE INDEX resvary_invoices_status ON ${t('resvary_invoices')}(status);
|
|
149
|
+
CREATE INDEX resvary_invoices_customer_id ON ${t('resvary_invoices')}(customer_id);
|
|
150
|
+
CREATE TABLE ${t('resvary_receipts')} (
|
|
151
|
+
id TEXT PRIMARY KEY, invoice_id TEXT NOT NULL, tx_hash_norm TEXT, status TEXT NOT NULL,
|
|
152
|
+
amount_units NUMERIC(78,0) NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
153
|
+
);
|
|
154
|
+
CREATE UNIQUE INDEX resvary_receipts_invoice_tx_hash
|
|
155
|
+
ON ${t('resvary_receipts')}(invoice_id, tx_hash_norm) WHERE tx_hash_norm IS NOT NULL;
|
|
156
|
+
CREATE TABLE ${t('arc_webhook_events')} (
|
|
157
|
+
id TEXT PRIMARY KEY, type TEXT NOT NULL, created_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
158
|
+
);
|
|
159
|
+
CREATE TABLE ${t('arc_webhook_deliveries')} (
|
|
160
|
+
id TEXT PRIMARY KEY, event_id TEXT NOT NULL, event_type TEXT NOT NULL, attempt INTEGER NOT NULL,
|
|
161
|
+
status TEXT NOT NULL, received_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
162
|
+
);
|
|
163
|
+
CREATE INDEX arc_webhook_deliveries_event_id ON ${t('arc_webhook_deliveries')}(event_id);
|
|
164
|
+
CREATE TABLE ${t('arc_watcher_cursors')} (
|
|
165
|
+
key TEXT PRIMARY KEY, network TEXT NOT NULL, invoice_id TEXT, memo_id_norm TEXT,
|
|
166
|
+
next_from_block NUMERIC(78,0) NOT NULL, updated_at BIGINT NOT NULL, payload JSONB NOT NULL
|
|
167
|
+
);
|
|
168
168
|
`);
|
|
169
169
|
await client.query(`INSERT INTO ${t('resvary_schema_migrations')}(version, applied_at) VALUES (1, $1)`, [Date.now()]);
|
|
170
170
|
await client.query('COMMIT');
|
|
@@ -178,79 +178,79 @@ async function applyV2(client, schema) {
|
|
|
178
178
|
const t = (name) => table({ schema }, name);
|
|
179
179
|
await client.query('BEGIN');
|
|
180
180
|
try {
|
|
181
|
-
const duplicateReceipt = await client.query(`SELECT tx_hash_norm, COUNT(*)::text AS count
|
|
182
|
-
FROM ${t('resvary_receipts')}
|
|
183
|
-
WHERE tx_hash_norm IS NOT NULL
|
|
184
|
-
GROUP BY tx_hash_norm
|
|
185
|
-
HAVING COUNT(*) > 1
|
|
181
|
+
const duplicateReceipt = await client.query(`SELECT tx_hash_norm, COUNT(*)::text AS count
|
|
182
|
+
FROM ${t('resvary_receipts')}
|
|
183
|
+
WHERE tx_hash_norm IS NOT NULL
|
|
184
|
+
GROUP BY tx_hash_norm
|
|
185
|
+
HAVING COUNT(*) > 1
|
|
186
186
|
LIMIT 1`);
|
|
187
187
|
if (duplicateReceipt.rows[0]) {
|
|
188
188
|
throw new Error(`Cannot enforce receipt transaction uniqueness: ${duplicateReceipt.rows[0].count} receipts use ${duplicateReceipt.rows[0].tx_hash_norm}`);
|
|
189
189
|
}
|
|
190
|
-
await client.query(`
|
|
191
|
-
DROP INDEX IF EXISTS "${schema}".resvary_receipts_invoice_tx_hash;
|
|
192
|
-
CREATE UNIQUE INDEX resvary_receipts_tx_hash
|
|
193
|
-
ON ${t('resvary_receipts')}(tx_hash_norm)
|
|
194
|
-
WHERE tx_hash_norm IS NOT NULL;
|
|
195
|
-
|
|
196
|
-
ALTER TABLE ${t('resvary_credit_accounts')}
|
|
197
|
-
ADD CONSTRAINT resvary_credit_accounts_nonnegative
|
|
198
|
-
CHECK (posted_units >= 0 AND reserved_units >= 0 AND reserved_units <= posted_units);
|
|
199
|
-
ALTER TABLE ${t('resvary_credit_grants')}
|
|
200
|
-
ADD CONSTRAINT resvary_credit_grants_amount_positive CHECK (amount_units > 0),
|
|
201
|
-
ADD CONSTRAINT resvary_credit_grants_account_fk FOREIGN KEY (account_id)
|
|
202
|
-
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
203
|
-
ALTER TABLE ${t('resvary_price_versions')}
|
|
204
|
-
ADD CONSTRAINT resvary_price_versions_version_positive CHECK (version > 0),
|
|
205
|
-
ADD CONSTRAINT resvary_price_versions_meter_fk FOREIGN KEY (meter_id)
|
|
206
|
-
REFERENCES ${t('resvary_meters')}(id);
|
|
207
|
-
ALTER TABLE ${t('resvary_credit_reservations')}
|
|
208
|
-
ADD CONSTRAINT resvary_credit_reservations_status
|
|
209
|
-
CHECK (status IN ('open', 'committed', 'released', 'expired')),
|
|
210
|
-
ADD CONSTRAINT resvary_credit_reservations_units_nonnegative CHECK (reserved_units >= 0),
|
|
211
|
-
ADD CONSTRAINT resvary_credit_reservations_account_fk FOREIGN KEY (account_id)
|
|
212
|
-
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
213
|
-
ALTER TABLE ${t('resvary_usage_events')}
|
|
214
|
-
ADD CONSTRAINT resvary_usage_events_account_fk FOREIGN KEY (account_id)
|
|
215
|
-
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
216
|
-
ALTER TABLE ${t('resvary_usage_receipts')}
|
|
217
|
-
ADD CONSTRAINT resvary_usage_receipts_units_nonnegative CHECK (charged_units >= 0),
|
|
218
|
-
ADD CONSTRAINT resvary_usage_receipts_account_fk FOREIGN KEY (account_id)
|
|
219
|
-
REFERENCES ${t('resvary_credit_accounts')}(id),
|
|
220
|
-
ADD CONSTRAINT resvary_usage_receipts_reservation_fk FOREIGN KEY (reservation_id)
|
|
221
|
-
REFERENCES ${t('resvary_credit_reservations')}(id),
|
|
222
|
-
ADD CONSTRAINT resvary_usage_receipts_usage_event_fk FOREIGN KEY (usage_event_id)
|
|
223
|
-
REFERENCES ${t('resvary_usage_events')}(id);
|
|
224
|
-
ALTER TABLE ${t('resvary_ledger_entries')}
|
|
225
|
-
ADD CONSTRAINT resvary_ledger_entries_balance_nonnegative CHECK (balance_after_units >= 0),
|
|
226
|
-
ADD CONSTRAINT resvary_ledger_entries_account_fk FOREIGN KEY (account_id)
|
|
227
|
-
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
228
|
-
ALTER TABLE ${t('resvary_funding_intents')}
|
|
229
|
-
ADD CONSTRAINT resvary_funding_intents_status
|
|
230
|
-
CHECK (status IN ('pending', 'confirmed', 'failed')),
|
|
231
|
-
ADD CONSTRAINT resvary_funding_intents_units_positive CHECK (requested_units > 0);
|
|
232
|
-
ALTER TABLE ${t('resvary_funding_transactions')}
|
|
233
|
-
ADD CONSTRAINT resvary_funding_transactions_units_positive CHECK (amount_units > 0),
|
|
234
|
-
ADD CONSTRAINT resvary_funding_transactions_intent_fk FOREIGN KEY (funding_intent_id)
|
|
235
|
-
REFERENCES ${t('resvary_funding_intents')}(id);
|
|
236
|
-
ALTER TABLE ${t('resvary_outbox_events')}
|
|
237
|
-
ADD CONSTRAINT resvary_outbox_events_status
|
|
238
|
-
CHECK (status IN ('pending', 'processing', 'delivered', 'dead_letter')),
|
|
239
|
-
ADD CONSTRAINT resvary_outbox_events_attempt_nonnegative CHECK (attempt_count >= 0);
|
|
240
|
-
ALTER TABLE ${t('resvary_invoices')}
|
|
241
|
-
ADD CONSTRAINT resvary_invoices_status
|
|
242
|
-
CHECK (status IN ('open', 'observed', 'paid', 'expired', 'refunded', 'void')),
|
|
243
|
-
ADD CONSTRAINT resvary_invoices_amount_positive CHECK (amount_units > 0);
|
|
244
|
-
ALTER TABLE ${t('resvary_receipts')}
|
|
245
|
-
ADD CONSTRAINT resvary_receipts_status CHECK (status IN ('paid', 'refunded')),
|
|
246
|
-
ADD CONSTRAINT resvary_receipts_amount_positive CHECK (amount_units > 0),
|
|
247
|
-
ADD CONSTRAINT resvary_receipts_invoice_fk FOREIGN KEY (invoice_id)
|
|
248
|
-
REFERENCES ${t('resvary_invoices')}(id);
|
|
249
|
-
ALTER TABLE ${t('arc_webhook_deliveries')}
|
|
250
|
-
ADD CONSTRAINT arc_webhook_deliveries_status CHECK (status IN ('verified', 'failed')),
|
|
251
|
-
ADD CONSTRAINT arc_webhook_deliveries_attempt_positive CHECK (attempt > 0);
|
|
252
|
-
ALTER TABLE ${t('arc_watcher_cursors')}
|
|
253
|
-
ADD CONSTRAINT arc_watcher_cursors_block_nonnegative CHECK (next_from_block >= 0);
|
|
190
|
+
await client.query(`
|
|
191
|
+
DROP INDEX IF EXISTS "${schema}".resvary_receipts_invoice_tx_hash;
|
|
192
|
+
CREATE UNIQUE INDEX resvary_receipts_tx_hash
|
|
193
|
+
ON ${t('resvary_receipts')}(tx_hash_norm)
|
|
194
|
+
WHERE tx_hash_norm IS NOT NULL;
|
|
195
|
+
|
|
196
|
+
ALTER TABLE ${t('resvary_credit_accounts')}
|
|
197
|
+
ADD CONSTRAINT resvary_credit_accounts_nonnegative
|
|
198
|
+
CHECK (posted_units >= 0 AND reserved_units >= 0 AND reserved_units <= posted_units);
|
|
199
|
+
ALTER TABLE ${t('resvary_credit_grants')}
|
|
200
|
+
ADD CONSTRAINT resvary_credit_grants_amount_positive CHECK (amount_units > 0),
|
|
201
|
+
ADD CONSTRAINT resvary_credit_grants_account_fk FOREIGN KEY (account_id)
|
|
202
|
+
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
203
|
+
ALTER TABLE ${t('resvary_price_versions')}
|
|
204
|
+
ADD CONSTRAINT resvary_price_versions_version_positive CHECK (version > 0),
|
|
205
|
+
ADD CONSTRAINT resvary_price_versions_meter_fk FOREIGN KEY (meter_id)
|
|
206
|
+
REFERENCES ${t('resvary_meters')}(id);
|
|
207
|
+
ALTER TABLE ${t('resvary_credit_reservations')}
|
|
208
|
+
ADD CONSTRAINT resvary_credit_reservations_status
|
|
209
|
+
CHECK (status IN ('open', 'committed', 'released', 'expired')),
|
|
210
|
+
ADD CONSTRAINT resvary_credit_reservations_units_nonnegative CHECK (reserved_units >= 0),
|
|
211
|
+
ADD CONSTRAINT resvary_credit_reservations_account_fk FOREIGN KEY (account_id)
|
|
212
|
+
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
213
|
+
ALTER TABLE ${t('resvary_usage_events')}
|
|
214
|
+
ADD CONSTRAINT resvary_usage_events_account_fk FOREIGN KEY (account_id)
|
|
215
|
+
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
216
|
+
ALTER TABLE ${t('resvary_usage_receipts')}
|
|
217
|
+
ADD CONSTRAINT resvary_usage_receipts_units_nonnegative CHECK (charged_units >= 0),
|
|
218
|
+
ADD CONSTRAINT resvary_usage_receipts_account_fk FOREIGN KEY (account_id)
|
|
219
|
+
REFERENCES ${t('resvary_credit_accounts')}(id),
|
|
220
|
+
ADD CONSTRAINT resvary_usage_receipts_reservation_fk FOREIGN KEY (reservation_id)
|
|
221
|
+
REFERENCES ${t('resvary_credit_reservations')}(id),
|
|
222
|
+
ADD CONSTRAINT resvary_usage_receipts_usage_event_fk FOREIGN KEY (usage_event_id)
|
|
223
|
+
REFERENCES ${t('resvary_usage_events')}(id);
|
|
224
|
+
ALTER TABLE ${t('resvary_ledger_entries')}
|
|
225
|
+
ADD CONSTRAINT resvary_ledger_entries_balance_nonnegative CHECK (balance_after_units >= 0),
|
|
226
|
+
ADD CONSTRAINT resvary_ledger_entries_account_fk FOREIGN KEY (account_id)
|
|
227
|
+
REFERENCES ${t('resvary_credit_accounts')}(id);
|
|
228
|
+
ALTER TABLE ${t('resvary_funding_intents')}
|
|
229
|
+
ADD CONSTRAINT resvary_funding_intents_status
|
|
230
|
+
CHECK (status IN ('pending', 'confirmed', 'failed')),
|
|
231
|
+
ADD CONSTRAINT resvary_funding_intents_units_positive CHECK (requested_units > 0);
|
|
232
|
+
ALTER TABLE ${t('resvary_funding_transactions')}
|
|
233
|
+
ADD CONSTRAINT resvary_funding_transactions_units_positive CHECK (amount_units > 0),
|
|
234
|
+
ADD CONSTRAINT resvary_funding_transactions_intent_fk FOREIGN KEY (funding_intent_id)
|
|
235
|
+
REFERENCES ${t('resvary_funding_intents')}(id);
|
|
236
|
+
ALTER TABLE ${t('resvary_outbox_events')}
|
|
237
|
+
ADD CONSTRAINT resvary_outbox_events_status
|
|
238
|
+
CHECK (status IN ('pending', 'processing', 'delivered', 'dead_letter')),
|
|
239
|
+
ADD CONSTRAINT resvary_outbox_events_attempt_nonnegative CHECK (attempt_count >= 0);
|
|
240
|
+
ALTER TABLE ${t('resvary_invoices')}
|
|
241
|
+
ADD CONSTRAINT resvary_invoices_status
|
|
242
|
+
CHECK (status IN ('open', 'observed', 'paid', 'expired', 'refunded', 'void')),
|
|
243
|
+
ADD CONSTRAINT resvary_invoices_amount_positive CHECK (amount_units > 0);
|
|
244
|
+
ALTER TABLE ${t('resvary_receipts')}
|
|
245
|
+
ADD CONSTRAINT resvary_receipts_status CHECK (status IN ('paid', 'refunded')),
|
|
246
|
+
ADD CONSTRAINT resvary_receipts_amount_positive CHECK (amount_units > 0),
|
|
247
|
+
ADD CONSTRAINT resvary_receipts_invoice_fk FOREIGN KEY (invoice_id)
|
|
248
|
+
REFERENCES ${t('resvary_invoices')}(id);
|
|
249
|
+
ALTER TABLE ${t('arc_webhook_deliveries')}
|
|
250
|
+
ADD CONSTRAINT arc_webhook_deliveries_status CHECK (status IN ('verified', 'failed')),
|
|
251
|
+
ADD CONSTRAINT arc_webhook_deliveries_attempt_positive CHECK (attempt > 0);
|
|
252
|
+
ALTER TABLE ${t('arc_watcher_cursors')}
|
|
253
|
+
ADD CONSTRAINT arc_watcher_cursors_block_nonnegative CHECK (next_from_block >= 0);
|
|
254
254
|
`);
|
|
255
255
|
await client.query(`INSERT INTO ${t('resvary_schema_migrations')}(version, applied_at) VALUES (2, $1)`, [Date.now()]);
|
|
256
256
|
await client.query('COMMIT');
|
package/dist/receipt.js
CHANGED
|
@@ -8,11 +8,11 @@ class PostgresReceiptStoreView {
|
|
|
8
8
|
this.handle = handle;
|
|
9
9
|
}
|
|
10
10
|
async saveInvoice(value) {
|
|
11
|
-
await this.db.query(`INSERT INTO ${table(this.handle, 'resvary_invoices')}
|
|
12
|
-
(id, status, customer_id, amount_units, created_at, payload)
|
|
13
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
14
|
-
ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status,
|
|
15
|
-
customer_id = EXCLUDED.customer_id, amount_units = EXCLUDED.amount_units,
|
|
11
|
+
await this.db.query(`INSERT INTO ${table(this.handle, 'resvary_invoices')}
|
|
12
|
+
(id, status, customer_id, amount_units, created_at, payload)
|
|
13
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
14
|
+
ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status,
|
|
15
|
+
customer_id = EXCLUDED.customer_id, amount_units = EXCLUDED.amount_units,
|
|
16
16
|
created_at = EXCLUDED.created_at, payload = EXCLUDED.payload`, [
|
|
17
17
|
value.id,
|
|
18
18
|
value.status,
|
|
@@ -30,9 +30,9 @@ class PostgresReceiptStoreView {
|
|
|
30
30
|
(!filter.customerId || value.customerId === filter.customerId));
|
|
31
31
|
}
|
|
32
32
|
async saveReceipt(value) {
|
|
33
|
-
await this.db.query(`INSERT INTO ${table(this.handle, 'resvary_receipts')}
|
|
34
|
-
(id, invoice_id, tx_hash_norm, status, amount_units, created_at, payload)
|
|
35
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
33
|
+
await this.db.query(`INSERT INTO ${table(this.handle, 'resvary_receipts')}
|
|
34
|
+
(id, invoice_id, tx_hash_norm, status, amount_units, created_at, payload)
|
|
35
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
36
36
|
ON CONFLICT DO NOTHING`, [
|
|
37
37
|
value.id,
|
|
38
38
|
value.invoiceId,
|
|
@@ -47,28 +47,28 @@ class PostgresReceiptStoreView {
|
|
|
47
47
|
return this.one(`SELECT payload::text AS payload FROM ${table(this.handle, 'resvary_receipts')} WHERE id = $1`, [id]);
|
|
48
48
|
}
|
|
49
49
|
getReceiptByTxHash(txHash, invoiceId) {
|
|
50
|
-
return this.one(`SELECT payload::text AS payload FROM ${table(this.handle, 'resvary_receipts')}
|
|
50
|
+
return this.one(`SELECT payload::text AS payload FROM ${table(this.handle, 'resvary_receipts')}
|
|
51
51
|
WHERE tx_hash_norm = $1 ${invoiceId ? 'AND invoice_id = $2' : ''} LIMIT 1`, invoiceId ? [txHash.toLowerCase(), invoiceId] : [txHash.toLowerCase()]);
|
|
52
52
|
}
|
|
53
53
|
listReceipts() {
|
|
54
54
|
return this.all(`SELECT payload::text AS payload FROM ${table(this.handle, 'resvary_receipts')} ORDER BY created_at ASC`);
|
|
55
55
|
}
|
|
56
56
|
async saveWebhookEvent(value) {
|
|
57
|
-
await this.db.query(`INSERT INTO ${table(this.handle, 'arc_webhook_events')}(id, type, created_at, payload)
|
|
58
|
-
VALUES ($1, $2, $3, $4)
|
|
59
|
-
ON CONFLICT (id) DO UPDATE SET type = EXCLUDED.type,
|
|
57
|
+
await this.db.query(`INSERT INTO ${table(this.handle, 'arc_webhook_events')}(id, type, created_at, payload)
|
|
58
|
+
VALUES ($1, $2, $3, $4)
|
|
59
|
+
ON CONFLICT (id) DO UPDATE SET type = EXCLUDED.type,
|
|
60
60
|
created_at = EXCLUDED.created_at, payload = EXCLUDED.payload`, [value.id, value.type, value.createdAt, serializeReceiptStoreValue(value)]);
|
|
61
61
|
}
|
|
62
62
|
async listWebhookEvents(filter = {}) {
|
|
63
63
|
return (await this.all(`SELECT payload::text AS payload FROM ${table(this.handle, 'arc_webhook_events')} ORDER BY created_at ASC`)).filter((value) => !filter.type || value.type === filter.type);
|
|
64
64
|
}
|
|
65
65
|
async saveWebhookDelivery(value) {
|
|
66
|
-
await this.db.query(`INSERT INTO ${table(this.handle, 'arc_webhook_deliveries')}
|
|
67
|
-
(id, event_id, event_type, attempt, status, received_at, payload)
|
|
68
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
69
|
-
ON CONFLICT (id) DO UPDATE SET event_id = EXCLUDED.event_id,
|
|
70
|
-
event_type = EXCLUDED.event_type, attempt = EXCLUDED.attempt,
|
|
71
|
-
status = EXCLUDED.status, received_at = EXCLUDED.received_at,
|
|
66
|
+
await this.db.query(`INSERT INTO ${table(this.handle, 'arc_webhook_deliveries')}
|
|
67
|
+
(id, event_id, event_type, attempt, status, received_at, payload)
|
|
68
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
69
|
+
ON CONFLICT (id) DO UPDATE SET event_id = EXCLUDED.event_id,
|
|
70
|
+
event_type = EXCLUDED.event_type, attempt = EXCLUDED.attempt,
|
|
71
|
+
status = EXCLUDED.status, received_at = EXCLUDED.received_at,
|
|
72
72
|
payload = EXCLUDED.payload`, [
|
|
73
73
|
value.id,
|
|
74
74
|
value.eventId,
|
|
@@ -93,12 +93,12 @@ class PostgresReceiptStoreView {
|
|
|
93
93
|
return this.all(`SELECT payload::text AS payload FROM ${table(this.handle, 'arc_watcher_cursors')} ORDER BY updated_at ASC`);
|
|
94
94
|
}
|
|
95
95
|
async saveWatcherCursor(value) {
|
|
96
|
-
await this.db.query(`INSERT INTO ${table(this.handle, 'arc_watcher_cursors')}
|
|
97
|
-
(key, network, invoice_id, memo_id_norm, next_from_block, updated_at, payload)
|
|
98
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
99
|
-
ON CONFLICT (key) DO UPDATE SET network = EXCLUDED.network,
|
|
100
|
-
invoice_id = EXCLUDED.invoice_id, memo_id_norm = EXCLUDED.memo_id_norm,
|
|
101
|
-
next_from_block = EXCLUDED.next_from_block, updated_at = EXCLUDED.updated_at,
|
|
96
|
+
await this.db.query(`INSERT INTO ${table(this.handle, 'arc_watcher_cursors')}
|
|
97
|
+
(key, network, invoice_id, memo_id_norm, next_from_block, updated_at, payload)
|
|
98
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
99
|
+
ON CONFLICT (key) DO UPDATE SET network = EXCLUDED.network,
|
|
100
|
+
invoice_id = EXCLUDED.invoice_id, memo_id_norm = EXCLUDED.memo_id_norm,
|
|
101
|
+
next_from_block = EXCLUDED.next_from_block, updated_at = EXCLUDED.updated_at,
|
|
102
102
|
payload = EXCLUDED.payload`, [
|
|
103
103
|
value.key,
|
|
104
104
|
value.network,
|
package/package.json
CHANGED
|
@@ -1,50 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@resvary/postgres",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Postgres persistence and migration tooling for Resvary",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"resvary-postgres": "dist/cli.js"
|
|
10
|
-
},
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
|
-
"import": "./dist/index.js"
|
|
15
|
-
},
|
|
16
|
-
"./import-sqlite": {
|
|
17
|
-
"types": "./dist/import-sqlite.d.ts",
|
|
18
|
-
"import": "./dist/import-sqlite.js"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"dist",
|
|
23
|
-
"README.md",
|
|
24
|
-
"LICENSE"
|
|
25
|
-
],
|
|
26
|
-
"publishConfig": {
|
|
27
|
-
"access": "public"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
},
|
|
43
|
-
"
|
|
44
|
-
"@
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@resvary/postgres",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Postgres persistence and migration tooling for Resvary",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"resvary-postgres": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./import-sqlite": {
|
|
17
|
+
"types": "./dist/import-sqlite.d.ts",
|
|
18
|
+
"import": "./dist/import-sqlite.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"dev": "tsc --watch",
|
|
32
|
+
"clean": "node -e \"const fs=require('node:fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true})\"",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.0.0"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/horn111/resvary.git",
|
|
41
|
+
"directory": "packages/postgres"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@resvary/sdk": "0.6.1",
|
|
45
|
+
"pg": "^8.16.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.20.1",
|
|
49
|
+
"@types/pg": "^8.15.5",
|
|
50
|
+
"typescript": "^5.6.0",
|
|
51
|
+
"vitest": "^4.1.11"
|
|
52
|
+
},
|
|
53
|
+
"license": "Apache-2.0",
|
|
54
|
+
"gitHead": "10c79e7e5bdecebc8a56b735466dc6a75b4a1f43"
|
|
55
|
+
}
|