@owlmeans/bot-postgres 0.1.18-rc.2 → 0.1.18
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 +16 -18
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/bot-postgres/SKILL.md +19 -33
- package/build/helper.d.ts +1 -1
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +3 -7
- package/build/helper.js.map +1 -1
- package/build/resource.d.ts +5 -7
- package/build/resource.d.ts.map +1 -1
- package/build/resource.js +5 -15
- package/build/resource.js.map +1 -1
- package/build/types.d.ts +3 -6
- package/build/types.d.ts.map +1 -1
- package/package.json +13 -13
- package/src/helper.ts +3 -9
- package/src/resource.ts +8 -22
- package/src/types.ts +3 -8
- package/tests/schema.spec.ts +14 -24
- package/tests/tables.spec.ts +20 -14
package/README.md
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
# @owlmeans/bot-postgres
|
|
2
2
|
|
|
3
|
-
PostgreSQL storage for `@owlmeans/server-bot`: it registers the
|
|
4
|
-
resolves by alias — `bot-connection`, `bot-
|
|
3
|
+
PostgreSQL storage for `@owlmeans/server-bot`: it registers the three resources the `BotService`
|
|
4
|
+
resolves by alias — `bot-connection`, `bot-update` (the inbox) and `bot-activity` — with
|
|
5
5
|
the shared AJV schemas and the indexes the service reads by. A Postgres-backed server (typically a
|
|
6
6
|
generated target project) adds it next to `appendBot`.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
bun add @owlmeans/bot-postgres@^0.1.18
|
|
11
|
+
bun add @owlmeans/bot-postgres@^0.1.18
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Peer dependencies: `pg`, `ajv`.
|
|
15
15
|
|
|
16
16
|
## Concepts
|
|
17
17
|
|
|
18
|
-
- **
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
- **Three resources** — `BotConnectionResource`, `BotUpdateResource` and `BotActivityResource`, each
|
|
19
|
+
a `PostgresResource` of the matching `@owlmeans/server-bot` record, at the aliases
|
|
20
|
+
`RES_BOT_CONNECTION`, `RES_BOT_UPDATE`, `RES_BOT_ACTIVITY`. An application's keyword groups are its
|
|
21
|
+
own records, stored by its own resources — not here.
|
|
21
22
|
- **Shared schemas** — `resource.schema` is exactly `server-bot`'s record schema; this package
|
|
22
23
|
declares no schema of its own.
|
|
23
24
|
- **Column shapes** — nested objects (`identity`, the inbox `update`, the activity `target`) are one
|
|
24
|
-
`jsonb` column each; string lists (`
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
`jsonb` column each; string lists (`setup`, `credentialsSet`, `handled`) are `text[]`; timestamps
|
|
26
|
+
are ISO `text`; the sealed credentials are one `text` column; the activity's `ref` (the handler's
|
|
27
|
+
opaque reference) is a nullable `text` column.
|
|
27
28
|
- **Unique inbox key** — `dedupe` carries a unique index, so a redelivered update is one row even
|
|
28
29
|
when two deliveries race.
|
|
29
30
|
- **Ids** — records are created without one; Postgres mints it.
|
|
@@ -38,7 +39,7 @@ import { appendPostgresBot } from '@owlmeans/bot-postgres'
|
|
|
38
39
|
import { appendBot } from '@owlmeans/server-bot'
|
|
39
40
|
|
|
40
41
|
appendPostgres(context) // the Postgres connection service, cfg.dbs[...]
|
|
41
|
-
appendPostgresBot(context) // the
|
|
42
|
+
appendPostgresBot(context) // the three resources
|
|
42
43
|
appendBot(context) // the service that resolves them by alias
|
|
43
44
|
|
|
44
45
|
await context.configure().init() // resource init reconciles tables and indexes (meta.autoSync, default Full)
|
|
@@ -49,7 +50,7 @@ await context.configure().init() // resource init reconciles tables and inde
|
|
|
49
50
|
### Another database, connection service or alias set
|
|
50
51
|
|
|
51
52
|
```typescript
|
|
52
|
-
const resources = { connection: 'chat-connection',
|
|
53
|
+
const resources = { connection: 'chat-connection', update: 'chat-update', activity: 'chat-activity' }
|
|
53
54
|
|
|
54
55
|
appendPostgresBot(context, { dbAlias: 'bots', serviceAlias: 'postgres-secondary', resources })
|
|
55
56
|
appendBot(context, { resources })
|
|
@@ -72,15 +73,14 @@ export const makeResource: ResourceMaker<BotConnectionRecord, BotConnectionResou
|
|
|
72
73
|
(dbAlias, serviceAlias) => makeBotConnectionResource(dbAlias, RES_BOT_CONNECTION, serviceAlias)
|
|
73
74
|
```
|
|
74
75
|
|
|
75
|
-
`
|
|
76
|
+
`update.ts` and `activity.ts` follow the same shape.
|
|
76
77
|
|
|
77
78
|
## API
|
|
78
79
|
|
|
79
80
|
| Symbol | Kind | Purpose |
|
|
80
81
|
|---|---|---|
|
|
81
|
-
| `appendPostgresBot(ctx, opts?)` | function | Registers the
|
|
82
|
+
| `appendPostgresBot(ctx, opts?)` | function | Registers the three resources, each only if its alias is free |
|
|
82
83
|
| `makeBotConnectionResource(dbAlias?, alias?, serviceAlias?)` | function | `BotConnectionResource` |
|
|
83
|
-
| `makeBotRuleResource(dbAlias?, alias?, serviceAlias?)` | function | `BotRuleResource` |
|
|
84
84
|
| `makeBotUpdateResource(dbAlias?, alias?, serviceAlias?)` | function | `BotUpdateResource` (the inbox) |
|
|
85
85
|
| `makeBotActivityResource(dbAlias?, alias?, serviceAlias?)` | function | `BotActivityResource` |
|
|
86
86
|
| `BotPostgresOptions` | type | `{ dbAlias?, serviceAlias?, resources? }` |
|
|
@@ -93,8 +93,6 @@ Named `<table>_<suffix>`:
|
|
|
93
93
|
|---|---|---|
|
|
94
94
|
| `bot_connection` | `scope_created` | `scope`, `createdAt` |
|
|
95
95
|
| `bot_connection` | `receive` | `enabled`, `status`, `receive` |
|
|
96
|
-
| `bot_rule` | `scope` | `scope` |
|
|
97
|
-
| `bot_rule` | `scope_connection` | `scope`, `connection` |
|
|
98
96
|
| `bot_update` | `dedupe` (unique) | `dedupe` |
|
|
99
97
|
| `bot_update` | `inbox` | `status`, `receivedAt` |
|
|
100
98
|
| `bot_update` | `lease` | `status`, `claimedAt` |
|
|
@@ -106,7 +104,7 @@ Named `<table>_<suffix>`:
|
|
|
106
104
|
## Testing
|
|
107
105
|
|
|
108
106
|
`tests/schema.spec.ts` needs no database: it compiles the shared schemas and asserts the column
|
|
109
|
-
types, the
|
|
107
|
+
types, the activity's `ref` column, the declared indexes and idempotent registration.
|
|
110
108
|
`tests/conformance.spec.ts` (the `@owlmeans/server-bot/conformance` store suite, every case including
|
|
111
109
|
the unique one) and `tests/tables.spec.ts` (a second boot plans no DDL; the round trips) are
|
|
112
110
|
integration suites gated on `POSTGRES_URL`; they skip without one.
|
|
@@ -135,7 +133,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
135
133
|
your project's skill store (`.agents/skills/`):
|
|
136
134
|
|
|
137
135
|
```sh
|
|
138
|
-
npx @owlmeans/agent-skills@^0.1.18
|
|
136
|
+
npx @owlmeans/agent-skills@^0.1.18
|
|
139
137
|
```
|
|
140
138
|
|
|
141
139
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 3,
|
|
3
3
|
"package": "@owlmeans/bot-postgres",
|
|
4
|
-
"version": "0.1.18
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18",
|
|
5
|
+
"generatedAt": "2026-10-28T00:31:34.725Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bot-postgres
|
|
3
|
-
description: How to use @owlmeans/bot-postgres — the Postgres extension that registers the
|
|
3
|
+
description: How to use @owlmeans/bot-postgres — the Postgres extension that registers the three @owlmeans/server-bot resources (bot-connection, bot-update, bot-activity) for a GENERATED TARGET PROJECT, their table shapes and indexes, the unique inbox dedupe key, and the target's resource seed files. Auto-invoked when wiring bot storage into a target's Postgres backend, writing a target's resources/bot/*.ts, or touching the bot tables' shape or indexes.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,11 +8,11 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/bot-postgres
|
|
9
9
|
|
|
10
10
|
**Layer:** Infra extension
|
|
11
|
-
**Install:** `"@owlmeans/bot-postgres": "^0.1.18
|
|
11
|
+
**Install:** `"@owlmeans/bot-postgres": "^0.1.18"` in `dependencies` (peers `pg`, `ajv`)
|
|
12
12
|
|
|
13
|
-
The Postgres store of `@owlmeans/server-bot`:
|
|
13
|
+
The Postgres store of `@owlmeans/server-bot`: three `@owlmeans/postgres-resource` resources over the
|
|
14
14
|
record schemas `server-bot` declares, plus the indexes its reads need. It owns no bot behaviour — the
|
|
15
|
-
`BotService` resolves the
|
|
15
|
+
`BotService` resolves the three by alias on every call, so this package and `appendBot` are registered
|
|
16
16
|
independently, in any order.
|
|
17
17
|
|
|
18
18
|
## Key Exports
|
|
@@ -20,10 +20,9 @@ independently, in any order.
|
|
|
20
20
|
| Export | Description |
|
|
21
21
|
|--------|-------------|
|
|
22
22
|
| `makeBotConnectionResource(dbAlias?, alias?, serviceAlias?)` | `BotConnectionResource` (`PostgresResource<BotConnectionRecord>`), alias default `RES_BOT_CONNECTION` |
|
|
23
|
-
| `makeBotRuleResource(dbAlias?, alias?, serviceAlias?)` | `BotRuleResource`, default `RES_BOT_RULE` |
|
|
24
23
|
| `makeBotUpdateResource(dbAlias?, alias?, serviceAlias?)` | `BotUpdateResource` — the inbox, default `RES_BOT_UPDATE` |
|
|
25
24
|
| `makeBotActivityResource(dbAlias?, alias?, serviceAlias?)` | `BotActivityResource`, default `RES_BOT_ACTIVITY` |
|
|
26
|
-
| `appendPostgresBot(ctx, opts?)` | Registers the
|
|
25
|
+
| `appendPostgresBot(ctx, opts?)` | Registers the three, each only while its alias is free — idempotent |
|
|
27
26
|
| `BotPostgresOptions` | `{ dbAlias?, serviceAlias?, resources?: BotResourceAliases }` |
|
|
28
27
|
|
|
29
28
|
`dbAlias` is the `cfg.dbs` entry (default `postgres`), `serviceAlias` the connection service (default
|
|
@@ -32,8 +31,8 @@ independently, in any order.
|
|
|
32
31
|
|
|
33
32
|
## The schema is imported, never redeclared
|
|
34
33
|
|
|
35
|
-
Each maker assigns `BotConnectionRecordSchema` / `
|
|
36
|
-
|
|
34
|
+
Each maker assigns `BotConnectionRecordSchema` / `BotUpdateRecordSchema` / `BotActivityRecordSchema`
|
|
35
|
+
from `@owlmeans/server-bot` to `resource.schema` — the same object, never a
|
|
37
36
|
copy, and no `pg:` override. A change to a record is a change to `server-bot`'s schema; this package
|
|
38
37
|
follows it at the next boot (`PgAutoSync.Full` reconciles the table).
|
|
39
38
|
|
|
@@ -44,11 +43,11 @@ What `postgres-resource`'s default compiler makes of the shared schemas (`tests/
|
|
|
44
43
|
| Values | Column | Properties |
|
|
45
44
|
|---|---|---|
|
|
46
45
|
| nested objects | one `jsonb` column each | `identity`, the inbox `update` (open: `raw` is the platform's payload), the activity `target` |
|
|
47
|
-
| string lists | native `text[]` | `setup`, `credentialsSet`, `
|
|
46
|
+
| string lists | native `text[]` | `setup`, `credentialsSet`, `handled` |
|
|
48
47
|
| timestamps | `text` (ISO, sortable as stored — no `format`, so never `timestamptz`) | `createdAt`, `updatedAt`, `receivedAt`, `claimedAt`, `retryAt`, `processedAt`, `at` |
|
|
49
48
|
| sealed credentials | ONE `text` column, never split per credential key | `sealed` (`v1.<iv>.<tag>.<ciphertext>`) |
|
|
50
|
-
| enums | `text` + `CHECK` | `platform`, `transport`, `status`, `receive`, `
|
|
51
|
-
| `
|
|
49
|
+
| enums | `text` + `CHECK` | `platform`, `transport`, `status`, `receive`, `action` |
|
|
50
|
+
| the handler's reference | nullable `text` | the activity `ref` (opaque: the id of the app's own record a handler followed) |
|
|
52
51
|
|
|
53
52
|
Records are created without an `id` (Postgres mints `gen_random_uuid()::text`); `update`/`save` with an
|
|
54
53
|
id REPLACES the whole row. Absent optional fields come back `null`; the service reads `null` as absent.
|
|
@@ -62,8 +61,6 @@ schema never collides; a maker run twice declares each index once.
|
|
|
62
61
|
|---|---|---|---|
|
|
63
62
|
| `bot_connection` | `scope_created` | `scope`, `createdAt` | a scope's connections in order |
|
|
64
63
|
| `bot_connection` | `receive` | `enabled`, `status`, `receive` | the worker's receiver reconciliation |
|
|
65
|
-
| `bot_rule` | `scope` | `scope` | a scope's rules |
|
|
66
|
-
| `bot_rule` | `scope_connection` | `scope`, `connection` | a connection's own rules, the remove cascade |
|
|
67
64
|
| `bot_update` | `dedupe` **UNIQUE** | `dedupe` | one row per redelivered update |
|
|
68
65
|
| `bot_update` | `inbox` | `status`, `receivedAt` | pending rows, oldest first |
|
|
69
66
|
| `bot_update` | `lease` | `status`, `claimedAt` | expired `processing` claims |
|
|
@@ -78,7 +75,7 @@ the conformance suite's `unique` capability.
|
|
|
78
75
|
|
|
79
76
|
## Target wiring
|
|
80
77
|
|
|
81
|
-
**Backs:** chat-bot connections with sealed credentials,
|
|
78
|
+
**Backs:** chat-bot connections with sealed credentials, the inbound update inbox and the bots' activity log
|
|
82
79
|
|
|
83
80
|
| Sub-project | Packages |
|
|
84
81
|
|---|---|
|
|
@@ -88,7 +85,8 @@ One file per resource under `sources/backend/src/resources/bot/`: the generated
|
|
|
88
85
|
file's zero-argument `makeResource` and registers it before `init()` creates the table. The path
|
|
89
86
|
names the alias (`bot/connection.ts` → `bot-connection`), which is exactly `server-bot`'s default, so
|
|
90
87
|
`appendBot(context)` needs no `resources` option. The bot service itself, the api trees and the
|
|
91
|
-
worker runtime are `server-bot`'s wiring.
|
|
88
|
+
worker runtime are `server-bot`'s wiring. Keyword groups are NOT stored here: they are the target's
|
|
89
|
+
own generated product records, with their own resource file like any other record.
|
|
92
90
|
|
|
93
91
|
```ts file=sources/backend/src/resources/bot/connection.ts
|
|
94
92
|
import { makeBotConnectionResource } from '@owlmeans/bot-postgres'
|
|
@@ -105,18 +103,6 @@ export const makeResource: ResourceMaker<BotConnectionRecord, BotConnectionResou
|
|
|
105
103
|
(dbAlias, serviceAlias) => makeBotConnectionResource(dbAlias, RES_BOT_CONNECTION, serviceAlias)
|
|
106
104
|
```
|
|
107
105
|
|
|
108
|
-
```ts file=sources/backend/src/resources/bot/rule.ts
|
|
109
|
-
import { makeBotRuleResource } from '@owlmeans/bot-postgres'
|
|
110
|
-
import type { BotRuleResource } from '@owlmeans/bot-postgres'
|
|
111
|
-
import { RES_BOT_RULE } from '@owlmeans/server-bot'
|
|
112
|
-
import type { BotRuleRecord } from '@owlmeans/server-bot'
|
|
113
|
-
import type { ResourceMaker } from '@owlmeans/resource'
|
|
114
|
-
|
|
115
|
-
/** The keyword rules (`bot-rule`) — scope-wide or bound to one connection, kept in `order`. */
|
|
116
|
-
export const makeResource: ResourceMaker<BotRuleRecord, BotRuleResource> =
|
|
117
|
-
(dbAlias, serviceAlias) => makeBotRuleResource(dbAlias, RES_BOT_RULE, serviceAlias)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
106
|
```ts file=sources/backend/src/resources/bot/update.ts
|
|
121
107
|
import { makeBotUpdateResource } from '@owlmeans/bot-postgres'
|
|
122
108
|
import type { BotUpdateResource } from '@owlmeans/bot-postgres'
|
|
@@ -141,7 +127,7 @@ export const makeResource: ResourceMaker<BotActivityRecord, BotActivityResource>
|
|
|
141
127
|
(dbAlias, serviceAlias) => makeBotActivityResource(dbAlias, RES_BOT_ACTIVITY, serviceAlias)
|
|
142
128
|
```
|
|
143
129
|
|
|
144
|
-
All
|
|
130
|
+
All three are required: the service reads and writes every one of them. Never pass a maker to
|
|
145
131
|
`ResourceMaker` directly (`makeResource = makeBotConnectionResource`) — a caller's `serviceAlias`
|
|
146
132
|
would land in the resource-alias position.
|
|
147
133
|
|
|
@@ -153,9 +139,9 @@ import { appendPostgresBot } from '@owlmeans/bot-postgres'
|
|
|
153
139
|
import { appendBot } from '@owlmeans/server-bot'
|
|
154
140
|
|
|
155
141
|
appendPostgres(context) // the connection service, cfg.dbs[...]
|
|
156
|
-
appendPostgresBot(context) // the
|
|
142
|
+
appendPostgresBot(context) // the three resources
|
|
157
143
|
appendBot(context) // the service, which resolves them by alias per call
|
|
158
|
-
await context.configure().init() // init reconciles the
|
|
144
|
+
await context.configure().init() // init reconciles the three tables and their indexes
|
|
159
145
|
```
|
|
160
146
|
|
|
161
147
|
Custom aliases go to both sides:
|
|
@@ -163,11 +149,11 @@ Custom aliases go to both sides:
|
|
|
163
149
|
|
|
164
150
|
## Testing
|
|
165
151
|
|
|
166
|
-
Category C. `bun test ./tests`: `schema.spec.ts` needs no database (the column types, the
|
|
167
|
-
`
|
|
152
|
+
Category C. `bun test ./tests`: `schema.spec.ts` needs no database (the column types, the activity's
|
|
153
|
+
`ref` column, the declared indexes, the unique dedupe DDL, idempotent registration); `conformance.spec.ts`
|
|
168
154
|
runs `server-bot`'s store suite — `botConformanceCasesFor(['unique'])` over
|
|
169
155
|
`makeBotConformanceSubject(context)`, every case — and `tables.spec.ts` asserts a second boot plans
|
|
170
|
-
no DDL and round-trips the jsonb, `text[]` and `
|
|
156
|
+
no DDL and round-trips the jsonb, `text[]` and `ref` columns. Both are gated on `POSTGRES_URL`
|
|
171
157
|
(`gateHelper.postgresGate()`, each spec file its own throwaway schema) and skip cleanly without it.
|
|
172
158
|
With no database of this checkout's own, run them against a throwaway container
|
|
173
159
|
(`docker run --rm -d -p 127.0.0.1:<port>:5432 -e POSTGRES_PASSWORD=… postgres:<tag>`) — never another
|
package/build/helper.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BasicContext } from '@owlmeans/context';
|
|
2
2
|
import type { BotPostgresOptions } from './types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Registers the
|
|
4
|
+
* Registers the three bot resources, each only while its alias is free — a second call (a context
|
|
5
5
|
* composed in two places, a hot reload) leaves the registered ones alone. Registration order against
|
|
6
6
|
* `appendBot` is free: the service resolves its resources by alias on every call.
|
|
7
7
|
*/
|
package/build/helper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAAS,YAAY,CAAC,GAAG,CAAC,SAAQ,kBAAkB,KAAQ,IAezF,CAAA"}
|
package/build/helper.js
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { RES_BOT_ACTIVITY, RES_BOT_CONNECTION,
|
|
2
|
-
import { makeBotActivityResource, makeBotConnectionResource,
|
|
1
|
+
import { RES_BOT_ACTIVITY, RES_BOT_CONNECTION, RES_BOT_UPDATE } from '@owlmeans/server-bot';
|
|
2
|
+
import { makeBotActivityResource, makeBotConnectionResource, makeBotUpdateResource } from './resource.js';
|
|
3
3
|
/**
|
|
4
|
-
* Registers the
|
|
4
|
+
* Registers the three bot resources, each only while its alias is free — a second call (a context
|
|
5
5
|
* composed in two places, a hot reload) leaves the registered ones alone. Registration order against
|
|
6
6
|
* `appendBot` is free: the service resolves its resources by alias on every call.
|
|
7
7
|
*/
|
|
8
8
|
export const appendPostgresBot = (ctx, opts = {}) => {
|
|
9
9
|
const { dbAlias, serviceAlias, resources = {} } = opts;
|
|
10
10
|
const connection = resources.connection ?? RES_BOT_CONNECTION;
|
|
11
|
-
const rule = resources.rule ?? RES_BOT_RULE;
|
|
12
11
|
const update = resources.update ?? RES_BOT_UPDATE;
|
|
13
12
|
const activity = resources.activity ?? RES_BOT_ACTIVITY;
|
|
14
13
|
if (!ctx.hasResource(connection)) {
|
|
15
14
|
ctx.registerResource(makeBotConnectionResource(dbAlias, connection, serviceAlias));
|
|
16
15
|
}
|
|
17
|
-
if (!ctx.hasResource(rule)) {
|
|
18
|
-
ctx.registerResource(makeBotRuleResource(dbAlias, rule, serviceAlias));
|
|
19
|
-
}
|
|
20
16
|
if (!ctx.hasResource(update)) {
|
|
21
17
|
ctx.registerResource(makeBotUpdateResource(dbAlias, update, serviceAlias));
|
|
22
18
|
}
|
package/build/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE3F,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAGzG;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAsB,EAAE,IAAI,GAAuB,EAAE,EAAQ,EAAE;IAC/F,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,IAAI,CAAA;IACtD,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,IAAI,kBAAkB,CAAA;IAC7D,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,cAAc,CAAA;IACjD,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,IAAI,gBAAgB,CAAA;IAEvD,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,OAAO,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAA;IACpF,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IAC5E,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAA;IAChF,CAAC;AACH,CAAC,CAAA"}
|
package/build/resource.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BotActivityResource, BotConnectionResource,
|
|
1
|
+
import type { BotActivityResource, BotConnectionResource, BotUpdateResource } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* `bot-connection` — the connections of every scope. The schema is `server-bot`'s own, assigned, never
|
|
4
4
|
* copied: `identity` compiles to one `jsonb` column, `setup` and `credentialsSet` to `text[]`, the
|
|
@@ -6,11 +6,6 @@ import type { BotActivityResource, BotConnectionResource, BotRuleResource, BotUp
|
|
|
6
6
|
* the worker's reconciliation (enabled, connected, polling or on the gateway).
|
|
7
7
|
*/
|
|
8
8
|
export declare const makeBotConnectionResource: (dbAlias?: string, alias?: string, serviceAlias?: string) => BotConnectionResource;
|
|
9
|
-
/**
|
|
10
|
-
* `bot-rule` — keyword rules, scope-wide (`connection` null) or bound to one connection; `keywords` is
|
|
11
|
-
* `text[]`, `order` a `double precision` column (quoted wherever the store names it).
|
|
12
|
-
*/
|
|
13
|
-
export declare const makeBotRuleResource: (dbAlias?: string, alias?: string, serviceAlias?: string) => BotRuleResource;
|
|
14
9
|
/**
|
|
15
10
|
* `bot-update` — the inbox. `dedupe` is UNIQUE: two concurrent deliveries of one platform update
|
|
16
11
|
* collide here (`RecordExists`) and the service reads the stored row back. The normalized `update`
|
|
@@ -18,6 +13,9 @@ export declare const makeBotRuleResource: (dbAlias?: string, alias?: string, ser
|
|
|
18
13
|
* expired leases), a connection's newest update, and the retention purge.
|
|
19
14
|
*/
|
|
20
15
|
export declare const makeBotUpdateResource: (dbAlias?: string, alias?: string, serviceAlias?: string) => BotUpdateResource;
|
|
21
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* `bot-activity` — what every action did; `target` is one `jsonb` column, `ref` (the handler's opaque
|
|
18
|
+
* reference) a nullable `text` column. Read newest first per scope.
|
|
19
|
+
*/
|
|
22
20
|
export declare const makeBotActivityResource: (dbAlias?: string, alias?: string, serviceAlias?: string) => BotActivityResource;
|
|
23
21
|
//# sourceMappingURL=resource.d.ts.map
|
package/build/resource.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAoB/F;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,aAC1B,MAAM,UAAS,MAAM,iBAAsC,MAAM,KAC1E,qBASF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,aACtB,MAAM,UAAS,MAAM,iBAAkC,MAAM,KACtE,iBAYF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,aACxB,MAAM,UAAS,MAAM,iBAAoC,MAAM,KACxE,mBASF,CAAA"}
|
package/build/resource.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { makePostgresResource, pgDeclarationHelper, pgNameHelper } from '@owlmeans/postgres-resource';
|
|
2
|
-
import { BotActivityRecordSchema, BotConnectionRecordSchema,
|
|
2
|
+
import { BotActivityRecordSchema, BotConnectionRecordSchema, BotUpdateRecordSchema, RES_BOT_ACTIVITY, RES_BOT_CONNECTION, RES_BOT_UPDATE, } from '@owlmeans/server-bot';
|
|
3
3
|
/**
|
|
4
4
|
* Declare the indexes a maker owns, each once. Declarations are kept per alias at module scope, so a
|
|
5
5
|
* maker that runs twice (a target's file wrapping it, a spec) would otherwise declare every index
|
|
@@ -31,19 +31,6 @@ export const makeBotConnectionResource = (dbAlias, alias = RES_BOT_CONNECTION, s
|
|
|
31
31
|
]);
|
|
32
32
|
return resource;
|
|
33
33
|
};
|
|
34
|
-
/**
|
|
35
|
-
* `bot-rule` — keyword rules, scope-wide (`connection` null) or bound to one connection; `keywords` is
|
|
36
|
-
* `text[]`, `order` a `double precision` column (quoted wherever the store names it).
|
|
37
|
-
*/
|
|
38
|
-
export const makeBotRuleResource = (dbAlias, alias = RES_BOT_RULE, serviceAlias) => {
|
|
39
|
-
const resource = makePostgresResource(alias, dbAlias, serviceAlias);
|
|
40
|
-
resource.schema = BotRuleRecordSchema;
|
|
41
|
-
declareIndexes(resource, alias, [
|
|
42
|
-
['scope', { columns: ['scope'] }],
|
|
43
|
-
['scope_connection', { columns: ['scope', 'connection'] }],
|
|
44
|
-
]);
|
|
45
|
-
return resource;
|
|
46
|
-
};
|
|
47
34
|
/**
|
|
48
35
|
* `bot-update` — the inbox. `dedupe` is UNIQUE: two concurrent deliveries of one platform update
|
|
49
36
|
* collide here (`RecordExists`) and the service reads the stored row back. The normalized `update`
|
|
@@ -62,7 +49,10 @@ export const makeBotUpdateResource = (dbAlias, alias = RES_BOT_UPDATE, serviceAl
|
|
|
62
49
|
]);
|
|
63
50
|
return resource;
|
|
64
51
|
};
|
|
65
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* `bot-activity` — what every action did; `target` is one `jsonb` column, `ref` (the handler's opaque
|
|
54
|
+
* reference) a nullable `text` column. Read newest first per scope.
|
|
55
|
+
*/
|
|
66
56
|
export const makeBotActivityResource = (dbAlias, alias = RES_BOT_ACTIVITY, serviceAlias) => {
|
|
67
57
|
const resource = makePostgresResource(alias, dbAlias, serviceAlias);
|
|
68
58
|
resource.schema = BotActivityRecordSchema;
|
package/build/resource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAGrG,OAAO,EACL,uBAAuB,EAAE,yBAAyB,EAAE,
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { PostgresResource } from '@owlmeans/postgres-resource';
|
|
2
|
-
import type { BotActivityRecord, BotConnectionRecord, BotResourceAliases,
|
|
2
|
+
import type { BotActivityRecord, BotConnectionRecord, BotResourceAliases, BotUpdateRecord } from '@owlmeans/server-bot';
|
|
3
3
|
/** `bot-connection` — one row per connected bot, its credentials sealed in one text column. */
|
|
4
4
|
export interface BotConnectionResource extends PostgresResource<BotConnectionRecord> {
|
|
5
5
|
}
|
|
6
|
-
/** `bot-rule` — the keyword rules of a scope, scope-wide or bound to one connection. */
|
|
7
|
-
export interface BotRuleResource extends PostgresResource<BotRuleRecord> {
|
|
8
|
-
}
|
|
9
6
|
/** `bot-update` — the inbox: one row per inbound platform update, unique by its `dedupe` key. */
|
|
10
7
|
export interface BotUpdateResource extends PostgresResource<BotUpdateRecord> {
|
|
11
8
|
}
|
|
@@ -13,12 +10,12 @@ export interface BotUpdateResource extends PostgresResource<BotUpdateRecord> {
|
|
|
13
10
|
export interface BotActivityResource extends PostgresResource<BotActivityRecord> {
|
|
14
11
|
}
|
|
15
12
|
export interface BotPostgresOptions {
|
|
16
|
-
/** The `cfg.dbs` entry the
|
|
13
|
+
/** The `cfg.dbs` entry the three tables live in — default `postgres`. */
|
|
17
14
|
dbAlias?: string;
|
|
18
15
|
/** The Postgres connection service — default `postgres`. */
|
|
19
16
|
serviceAlias?: string;
|
|
20
17
|
/**
|
|
21
|
-
* The resources' aliases — default `bot-connection`, `bot-
|
|
18
|
+
* The resources' aliases — default `bot-connection`, `bot-update`, `bot-activity`.
|
|
22
19
|
* The bot service must be given the same ones (`appendBot(ctx, { resources })`).
|
|
23
20
|
*/
|
|
24
21
|
resources?: BotResourceAliases;
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEvH,+FAA+F;AAC/F,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB,CAAC,mBAAmB,CAAC;CAAG;AAEvF,iGAAiG;AACjG,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB,CAAC,eAAe,CAAC;CAAG;AAE/E,sFAAsF;AACtF,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB,CAAC,iBAAiB,CAAC;CAAG;AAEnF,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/bot-postgres",
|
|
3
|
-
"version": "0.1.18
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"description": "Postgres-backed resources for @owlmeans/server-bot
|
|
5
|
+
"description": "Postgres-backed resources for @owlmeans/server-bot \u2014 the connection, inbox and activity tables of a generated target project, compiled from the shared AJV schemas, with the unique inbox dedupe key.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc -b",
|
|
@@ -23,23 +23,23 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@owlmeans/context": "^0.1.18
|
|
27
|
-
"@owlmeans/postgres-resource": "^0.1.18
|
|
28
|
-
"@owlmeans/resource": "^0.1.18
|
|
29
|
-
"@owlmeans/server-bot": "^0.1.18
|
|
26
|
+
"@owlmeans/context": "^0.1.18",
|
|
27
|
+
"@owlmeans/postgres-resource": "^0.1.18",
|
|
28
|
+
"@owlmeans/resource": "^0.1.18",
|
|
29
|
+
"@owlmeans/server-bot": "^0.1.18"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"ajv": "*",
|
|
33
33
|
"pg": "*"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@owlmeans/bot": "^0.1.18
|
|
37
|
-
"@owlmeans/dep-config": "
|
|
38
|
-
"@owlmeans/log": "^0.1.18
|
|
39
|
-
"@owlmeans/postgres": "^0.1.18
|
|
40
|
-
"@owlmeans/server-context": "^0.1.18
|
|
41
|
-
"@owlmeans/test": "^0.1.18
|
|
42
|
-
"@owlmeans/test-integration": "^0.1.18
|
|
36
|
+
"@owlmeans/bot": "^0.1.18",
|
|
37
|
+
"@owlmeans/dep-config": "^0.1.18",
|
|
38
|
+
"@owlmeans/log": "^0.1.18",
|
|
39
|
+
"@owlmeans/postgres": "^0.1.18",
|
|
40
|
+
"@owlmeans/server-context": "^0.1.18",
|
|
41
|
+
"@owlmeans/test": "^0.1.18",
|
|
42
|
+
"@owlmeans/test-integration": "^0.1.18",
|
|
43
43
|
"@types/bun": "^1.4.2",
|
|
44
44
|
"@types/node": "^26.6.4",
|
|
45
45
|
"@types/pg": "^8.23.1",
|
package/src/helper.ts
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import type { BasicContext } from '@owlmeans/context'
|
|
2
|
-
import { RES_BOT_ACTIVITY, RES_BOT_CONNECTION,
|
|
2
|
+
import { RES_BOT_ACTIVITY, RES_BOT_CONNECTION, RES_BOT_UPDATE } from '@owlmeans/server-bot'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
makeBotActivityResource, makeBotConnectionResource, makeBotRuleResource, makeBotUpdateResource,
|
|
6
|
-
} from './resource.js'
|
|
4
|
+
import { makeBotActivityResource, makeBotConnectionResource, makeBotUpdateResource } from './resource.js'
|
|
7
5
|
import type { BotPostgresOptions } from './types.js'
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
|
-
* Registers the
|
|
8
|
+
* Registers the three bot resources, each only while its alias is free — a second call (a context
|
|
11
9
|
* composed in two places, a hot reload) leaves the registered ones alone. Registration order against
|
|
12
10
|
* `appendBot` is free: the service resolves its resources by alias on every call.
|
|
13
11
|
*/
|
|
14
12
|
export const appendPostgresBot = (ctx: BasicContext<any>, opts: BotPostgresOptions = {}): void => {
|
|
15
13
|
const { dbAlias, serviceAlias, resources = {} } = opts
|
|
16
14
|
const connection = resources.connection ?? RES_BOT_CONNECTION
|
|
17
|
-
const rule = resources.rule ?? RES_BOT_RULE
|
|
18
15
|
const update = resources.update ?? RES_BOT_UPDATE
|
|
19
16
|
const activity = resources.activity ?? RES_BOT_ACTIVITY
|
|
20
17
|
|
|
21
18
|
if (!ctx.hasResource(connection)) {
|
|
22
19
|
ctx.registerResource(makeBotConnectionResource(dbAlias, connection, serviceAlias))
|
|
23
20
|
}
|
|
24
|
-
if (!ctx.hasResource(rule)) {
|
|
25
|
-
ctx.registerResource(makeBotRuleResource(dbAlias, rule, serviceAlias))
|
|
26
|
-
}
|
|
27
21
|
if (!ctx.hasResource(update)) {
|
|
28
22
|
ctx.registerResource(makeBotUpdateResource(dbAlias, update, serviceAlias))
|
|
29
23
|
}
|
package/src/resource.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { makePostgresResource, pgDeclarationHelper, pgNameHelper } from '@owlmea
|
|
|
2
2
|
import type { PostgresResource } from '@owlmeans/postgres-resource'
|
|
3
3
|
import type { ResourceRecord } from '@owlmeans/resource'
|
|
4
4
|
import {
|
|
5
|
-
BotActivityRecordSchema, BotConnectionRecordSchema,
|
|
6
|
-
|
|
5
|
+
BotActivityRecordSchema, BotConnectionRecordSchema, BotUpdateRecordSchema, RES_BOT_ACTIVITY, RES_BOT_CONNECTION,
|
|
6
|
+
RES_BOT_UPDATE,
|
|
7
7
|
} from '@owlmeans/server-bot'
|
|
8
|
-
import type { BotActivityRecord, BotConnectionRecord,
|
|
8
|
+
import type { BotActivityRecord, BotConnectionRecord, BotUpdateRecord } from '@owlmeans/server-bot'
|
|
9
9
|
|
|
10
|
-
import type { BotActivityResource, BotConnectionResource,
|
|
10
|
+
import type { BotActivityResource, BotConnectionResource, BotUpdateResource } from './types.js'
|
|
11
11
|
import type { IndexList } from './types.local.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -46,23 +46,6 @@ export const makeBotConnectionResource = (
|
|
|
46
46
|
return resource
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* `bot-rule` — keyword rules, scope-wide (`connection` null) or bound to one connection; `keywords` is
|
|
51
|
-
* `text[]`, `order` a `double precision` column (quoted wherever the store names it).
|
|
52
|
-
*/
|
|
53
|
-
export const makeBotRuleResource = (
|
|
54
|
-
dbAlias?: string, alias: string = RES_BOT_RULE, serviceAlias?: string
|
|
55
|
-
): BotRuleResource => {
|
|
56
|
-
const resource = makePostgresResource<BotRuleRecord, BotRuleResource>(alias, dbAlias, serviceAlias)
|
|
57
|
-
resource.schema = BotRuleRecordSchema
|
|
58
|
-
declareIndexes(resource, alias, [
|
|
59
|
-
['scope', { columns: ['scope'] }],
|
|
60
|
-
['scope_connection', { columns: ['scope', 'connection'] }],
|
|
61
|
-
])
|
|
62
|
-
|
|
63
|
-
return resource
|
|
64
|
-
}
|
|
65
|
-
|
|
66
49
|
/**
|
|
67
50
|
* `bot-update` — the inbox. `dedupe` is UNIQUE: two concurrent deliveries of one platform update
|
|
68
51
|
* collide here (`RecordExists`) and the service reads the stored row back. The normalized `update`
|
|
@@ -85,7 +68,10 @@ export const makeBotUpdateResource = (
|
|
|
85
68
|
return resource
|
|
86
69
|
}
|
|
87
70
|
|
|
88
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* `bot-activity` — what every action did; `target` is one `jsonb` column, `ref` (the handler's opaque
|
|
73
|
+
* reference) a nullable `text` column. Read newest first per scope.
|
|
74
|
+
*/
|
|
89
75
|
export const makeBotActivityResource = (
|
|
90
76
|
dbAlias?: string, alias: string = RES_BOT_ACTIVITY, serviceAlias?: string
|
|
91
77
|
): BotActivityResource => {
|
package/src/types.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { PostgresResource } from '@owlmeans/postgres-resource'
|
|
2
|
-
import type {
|
|
3
|
-
BotActivityRecord, BotConnectionRecord, BotResourceAliases, BotRuleRecord, BotUpdateRecord,
|
|
4
|
-
} from '@owlmeans/server-bot'
|
|
2
|
+
import type { BotActivityRecord, BotConnectionRecord, BotResourceAliases, BotUpdateRecord } from '@owlmeans/server-bot'
|
|
5
3
|
|
|
6
4
|
/** `bot-connection` — one row per connected bot, its credentials sealed in one text column. */
|
|
7
5
|
export interface BotConnectionResource extends PostgresResource<BotConnectionRecord> {}
|
|
8
6
|
|
|
9
|
-
/** `bot-rule` — the keyword rules of a scope, scope-wide or bound to one connection. */
|
|
10
|
-
export interface BotRuleResource extends PostgresResource<BotRuleRecord> {}
|
|
11
|
-
|
|
12
7
|
/** `bot-update` — the inbox: one row per inbound platform update, unique by its `dedupe` key. */
|
|
13
8
|
export interface BotUpdateResource extends PostgresResource<BotUpdateRecord> {}
|
|
14
9
|
|
|
@@ -16,12 +11,12 @@ export interface BotUpdateResource extends PostgresResource<BotUpdateRecord> {}
|
|
|
16
11
|
export interface BotActivityResource extends PostgresResource<BotActivityRecord> {}
|
|
17
12
|
|
|
18
13
|
export interface BotPostgresOptions {
|
|
19
|
-
/** The `cfg.dbs` entry the
|
|
14
|
+
/** The `cfg.dbs` entry the three tables live in — default `postgres`. */
|
|
20
15
|
dbAlias?: string
|
|
21
16
|
/** The Postgres connection service — default `postgres`. */
|
|
22
17
|
serviceAlias?: string
|
|
23
18
|
/**
|
|
24
|
-
* The resources' aliases — default `bot-connection`, `bot-
|
|
19
|
+
* The resources' aliases — default `bot-connection`, `bot-update`, `bot-activity`.
|
|
25
20
|
* The bot service must be given the same ones (`appendBot(ctx, { resources })`).
|
|
26
21
|
*/
|
|
27
22
|
resources?: BotResourceAliases
|
package/tests/schema.spec.ts
CHANGED
|
@@ -4,20 +4,19 @@ import { addLogPlugin, memoryPlugin } from '@owlmeans/log'
|
|
|
4
4
|
import { pgDeclarationHelper, pgDiffHelper, pgSchemaHelper } from '@owlmeans/postgres-resource'
|
|
5
5
|
import type { TableSpec } from '@owlmeans/postgres-resource'
|
|
6
6
|
import {
|
|
7
|
-
BotActivityRecordSchema, BotConnectionRecordSchema,
|
|
8
|
-
|
|
7
|
+
BotActivityRecordSchema, BotConnectionRecordSchema, BotUpdateRecordSchema, RES_BOT_ACTIVITY, RES_BOT_CONNECTION,
|
|
8
|
+
RES_BOT_UPDATE,
|
|
9
9
|
} from '@owlmeans/server-bot'
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
appendPostgresBot, makeBotActivityResource, makeBotConnectionResource,
|
|
12
|
+
appendPostgresBot, makeBotActivityResource, makeBotConnectionResource, makeBotUpdateResource,
|
|
13
13
|
} from '../src/index.js'
|
|
14
14
|
import type { BotUpdateResource } from '../src/index.js'
|
|
15
15
|
|
|
16
|
-
const ALIASES = [RES_BOT_CONNECTION,
|
|
16
|
+
const ALIASES = [RES_BOT_CONNECTION, RES_BOT_UPDATE, RES_BOT_ACTIVITY]
|
|
17
17
|
|
|
18
18
|
const SCHEMAS: Record<string, object> = {
|
|
19
19
|
[RES_BOT_CONNECTION]: BotConnectionRecordSchema,
|
|
20
|
-
[RES_BOT_RULE]: BotRuleRecordSchema,
|
|
21
20
|
[RES_BOT_UPDATE]: BotUpdateRecordSchema,
|
|
22
21
|
[RES_BOT_ACTIVITY]: BotActivityRecordSchema,
|
|
23
22
|
}
|
|
@@ -30,7 +29,6 @@ const reset = (): void => {
|
|
|
30
29
|
|
|
31
30
|
const makeAll = (): void => {
|
|
32
31
|
makeBotConnectionResource()
|
|
33
|
-
makeBotRuleResource()
|
|
34
32
|
makeBotUpdateResource()
|
|
35
33
|
makeBotActivityResource()
|
|
36
34
|
}
|
|
@@ -59,7 +57,7 @@ const ddlOf = (spec: TableSpec): string[] =>
|
|
|
59
57
|
describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
60
58
|
test('each maker assigns server-bot\'s schema, not a copy, under the default alias', () => {
|
|
61
59
|
reset()
|
|
62
|
-
const made = [makeBotConnectionResource(),
|
|
60
|
+
const made = [makeBotConnectionResource(), makeBotUpdateResource(), makeBotActivityResource()]
|
|
63
61
|
|
|
64
62
|
expect(made.map(resource => resource.alias)).toEqual(ALIASES)
|
|
65
63
|
expect(made.map(resource => resource.schema)).toEqual(ALIASES.map(alias => SCHEMAS[alias]))
|
|
@@ -84,7 +82,6 @@ describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
|
84
82
|
reset()
|
|
85
83
|
makeAll()
|
|
86
84
|
const connection = specOf(RES_BOT_CONNECTION)
|
|
87
|
-
const rule = specOf(RES_BOT_RULE)
|
|
88
85
|
const update = specOf(RES_BOT_UPDATE)
|
|
89
86
|
const activity = specOf(RES_BOT_ACTIVITY)
|
|
90
87
|
|
|
@@ -96,7 +93,6 @@ describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
|
96
93
|
const strings = { sqlType: 'text[]', jsonb: false, array: true }
|
|
97
94
|
expect(typeOf(connection, 'setup')).toEqual({ ...strings, notNull: false })
|
|
98
95
|
expect(typeOf(connection, 'credentialsSet')).toEqual({ ...strings, notNull: true })
|
|
99
|
-
expect(typeOf(rule, 'keywords')).toEqual({ ...strings, notNull: true })
|
|
100
96
|
expect(typeOf(update, 'handled')).toEqual({ ...strings, notNull: true })
|
|
101
97
|
|
|
102
98
|
/** The sealed credentials are ONE opaque text column, never split per credential key. */
|
|
@@ -108,17 +104,15 @@ describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
|
108
104
|
])
|
|
109
105
|
})
|
|
110
106
|
|
|
111
|
-
test('the
|
|
107
|
+
test('the activity\'s ref is a nullable text column — the handler\'s opaque reference', () => {
|
|
112
108
|
reset()
|
|
113
109
|
makeAll()
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
expect(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
expect(create).toContain('"order" double precision NOT NULL')
|
|
121
|
-
expect(create).not.toMatch(/[^"]order[^"]/)
|
|
110
|
+
const activity = specOf(RES_BOT_ACTIVITY)
|
|
111
|
+
|
|
112
|
+
expect(typeOf(activity, 'ref')).toEqual({ sqlType: 'text', jsonb: false, array: false, notNull: false })
|
|
113
|
+
expect(activity.byProperty.rule).toBeUndefined()
|
|
114
|
+
const [create] = ddlOf(activity)
|
|
115
|
+
expect(create).toContain('"ref" text')
|
|
122
116
|
})
|
|
123
117
|
|
|
124
118
|
test('the inbox dedupe key is a unique index, and every index the reads need is declared', () => {
|
|
@@ -131,10 +125,6 @@ describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
|
131
125
|
['bot_connection_scope_created', ['scope', 'createdAt'], false],
|
|
132
126
|
['bot_connection_receive', ['enabled', 'status', 'receive'], false],
|
|
133
127
|
])
|
|
134
|
-
expect(names(RES_BOT_RULE)).toEqual([
|
|
135
|
-
['bot_rule_scope', ['scope'], false],
|
|
136
|
-
['bot_rule_scope_connection', ['scope', 'connection'], false],
|
|
137
|
-
])
|
|
138
128
|
expect(names(RES_BOT_UPDATE)).toEqual([
|
|
139
129
|
['bot_update_dedupe', ['dedupe'], true],
|
|
140
130
|
['bot_update_inbox', ['status', 'receivedAt'], false],
|
|
@@ -169,7 +159,7 @@ describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
|
169
159
|
}
|
|
170
160
|
})
|
|
171
161
|
|
|
172
|
-
test('appendPostgresBot registers the
|
|
162
|
+
test('appendPostgresBot registers the three once, and custom aliases name their own tables and indexes', () => {
|
|
173
163
|
const context = makeBasicContext<BasicConfig>({ ready: false, service: 'bot-pg-schema', type: AppType.Backend, services: {} })
|
|
174
164
|
appendPostgresBot(context)
|
|
175
165
|
const first = ALIASES.map(alias => context.resource(alias))
|
|
@@ -177,7 +167,7 @@ describe('@owlmeans/bot-postgres — table shapes', () => {
|
|
|
177
167
|
expect(ALIASES.map(alias => context.resource(alias))).toEqual(first)
|
|
178
168
|
first.forEach((resource, at) => expect(context.resource(ALIASES[at])).toBe(resource))
|
|
179
169
|
|
|
180
|
-
const custom = { connection: 'chat-connection',
|
|
170
|
+
const custom = { connection: 'chat-connection', update: 'chat-update', activity: 'chat-activity' }
|
|
181
171
|
appendPostgresBot(context, { resources: custom })
|
|
182
172
|
expect(Object.values(custom).map(alias => context.resource<BotUpdateResource>(alias).schema))
|
|
183
173
|
.toEqual(ALIASES.map(alias => SCHEMAS[alias]))
|
package/tests/tables.spec.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { afterAll, describe, expect, test } from 'bun:test'
|
|
2
2
|
import { makePgIntrospectHelper, pgDiffHelper } from '@owlmeans/postgres-resource'
|
|
3
3
|
import type { PostgresResource } from '@owlmeans/postgres-resource'
|
|
4
|
-
import {
|
|
4
|
+
import { BotActionKind, BotChatKind, BotPlatform, BotTransport, BotUpdateKind } from '@owlmeans/bot'
|
|
5
5
|
import { RecordExists } from '@owlmeans/resource'
|
|
6
|
-
import { BotInboxStatus, RES_BOT_ACTIVITY, RES_BOT_CONNECTION,
|
|
6
|
+
import { BotInboxStatus, RES_BOT_ACTIVITY, RES_BOT_CONNECTION, RES_BOT_UPDATE } from '@owlmeans/server-bot'
|
|
7
7
|
import type { BotUpdateRecord } from '@owlmeans/server-bot'
|
|
8
8
|
|
|
9
9
|
import { appendPostgresBot } from '../src/index.js'
|
|
10
|
-
import type {
|
|
10
|
+
import type { BotActivityResource, BotUpdateResource } from '../src/index.js'
|
|
11
11
|
import { gate, makeSuite } from './context.js'
|
|
12
12
|
|
|
13
|
-
const ALIASES = [RES_BOT_CONNECTION,
|
|
13
|
+
const ALIASES = [RES_BOT_CONNECTION, RES_BOT_UPDATE, RES_BOT_ACTIVITY]
|
|
14
14
|
|
|
15
15
|
const suite = makeSuite('tables')
|
|
16
16
|
const it = gate.skip ? test.skip : test
|
|
@@ -36,7 +36,7 @@ describe('@owlmeans/bot-postgres — tables against a real database', () => {
|
|
|
36
36
|
await suite.teardown()
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
it('a second boot reconciles nothing on any of the
|
|
39
|
+
it('a second boot reconciles nothing on any of the three tables', async () => {
|
|
40
40
|
await suite.boot()
|
|
41
41
|
const again = await suite.boot()
|
|
42
42
|
const { pool } = await again.context.resource<PostgresResource<never>>(RES_BOT_CONNECTION).db()
|
|
@@ -53,10 +53,10 @@ describe('@owlmeans/bot-postgres — tables against a real database', () => {
|
|
|
53
53
|
}
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
it('round-trips nested values, string lists and the
|
|
56
|
+
it('round-trips nested values, string lists and the activity ref; the dedupe key is unique', async () => {
|
|
57
57
|
const { context } = await suite.boot()
|
|
58
58
|
const updates = context.resource<BotUpdateResource>(RES_BOT_UPDATE)
|
|
59
|
-
const
|
|
59
|
+
const activities = context.resource<BotActivityResource>(RES_BOT_ACTIVITY)
|
|
60
60
|
|
|
61
61
|
const row = await updates.create(inboxRow('c-1:telegram:1'))
|
|
62
62
|
expect(typeof row.id).toBe('string')
|
|
@@ -64,18 +64,24 @@ describe('@owlmeans/bot-postgres — tables against a real database', () => {
|
|
|
64
64
|
expect([stored.handled, stored.update.chat, stored.claimedAt]).toEqual([[], { id: '-100', kind: BotChatKind.Group }, null])
|
|
65
65
|
await expect(updates.create(inboxRow('c-1:telegram:1'))).rejects.toThrow(RecordExists)
|
|
66
66
|
|
|
67
|
-
const
|
|
68
|
-
scope: 'tables',
|
|
69
|
-
|
|
67
|
+
const followed = await activities.create({
|
|
68
|
+
scope: 'tables', connection: 'c-1', platform: BotPlatform.Telegram, update: row.id!, action: BotActionKind.Delete,
|
|
69
|
+
target: { chat: '-100', message: '7' }, ref: 'group-spam', ok: true, transport: BotTransport.Mock,
|
|
70
|
+
at: '2026-10-26T10:00:01.000Z',
|
|
70
71
|
})
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const plain = await activities.create({
|
|
73
|
+
scope: 'tables', connection: 'c-1', platform: BotPlatform.Telegram, action: BotActionKind.Send,
|
|
74
|
+
target: { chat: '-100' }, text: 'Announcement', ok: true, transport: BotTransport.Mock, at: '2026-10-26T10:00:02.000Z',
|
|
75
|
+
})
|
|
76
|
+
const newest = await activities.list({ scope: 'tables' }, { sort: [{ field: 'at', order: 'desc' }] })
|
|
77
|
+
expect(newest.items.map(item => [item.id, item.ref, item.target])).toEqual([
|
|
78
|
+
[plain.id, null, { chat: '-100' }],
|
|
79
|
+
[followed.id, 'group-spam', { chat: '-100', message: '7' }],
|
|
74
80
|
])
|
|
75
81
|
})
|
|
76
82
|
|
|
77
83
|
it('custom aliases get tables and index names of their own', async () => {
|
|
78
|
-
const custom = { connection: 'chat-connection',
|
|
84
|
+
const custom = { connection: 'chat-connection', update: 'chat-update', activity: 'chat-activity' }
|
|
79
85
|
const { context } = await suite.boot({ bot: { resources: custom } })
|
|
80
86
|
const registered = context.resource(custom.update)
|
|
81
87
|
appendPostgresBot(context, { resources: custom })
|