couchset 0.2.7 → 0.3.0
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 +284 -153
- package/dist/connection/connection.d.ts +68 -0
- package/dist/connection/connection.js +485 -0
- package/dist/connection/connection.js.map +1 -0
- package/dist/connection/env.d.ts +2 -0
- package/dist/connection/env.js +16 -0
- package/dist/connection/env.js.map +1 -0
- package/dist/connection/index.d.ts +3 -0
- package/dist/connection/index.js +25 -0
- package/dist/connection/index.js.map +1 -0
- package/dist/connection/types.d.ts +25 -0
- package/dist/connection/types.js +3 -0
- package/dist/connection/types.js.map +1 -0
- package/dist/database.d.ts +9 -0
- package/dist/database.js +196 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +53 -8
- package/dist/index.js.map +1 -1
- package/dist/legacy/connection.d.ts +2 -0
- package/dist/legacy/connection.js +24 -0
- package/dist/legacy/connection.js.map +1 -0
- package/dist/legacy/index.d.ts +10 -0
- package/dist/legacy/index.js +93 -0
- package/dist/legacy/index.js.map +1 -0
- package/dist/legacy/model.d.ts +55 -0
- package/dist/legacy/model.js +282 -0
- package/dist/legacy/model.js.map +1 -0
- package/dist/legacy/pagination/index.d.ts +1 -0
- package/dist/legacy/pagination/index.js +18 -0
- package/dist/legacy/pagination/index.js.map +1 -0
- package/dist/legacy/pagination/pagination.d.ts +12 -0
- package/dist/legacy/pagination/pagination.js +85 -0
- package/dist/legacy/pagination/pagination.js.map +1 -0
- package/dist/legacy/search/customQuery.d.ts +13 -0
- package/dist/legacy/search/customQuery.js +73 -0
- package/dist/legacy/search/customQuery.js.map +1 -0
- package/dist/legacy/search/index.d.ts +1 -0
- package/dist/legacy/search/index.js +18 -0
- package/dist/legacy/search/index.js.map +1 -0
- package/dist/model/default-scope.d.ts +10 -0
- package/dist/model/default-scope.js +47 -0
- package/dist/model/default-scope.js.map +1 -0
- package/dist/model/hydrated-document.d.ts +30 -0
- package/dist/model/hydrated-document.js +118 -0
- package/dist/model/hydrated-document.js.map +1 -0
- package/dist/model/include.d.ts +33 -0
- package/dist/model/include.js +250 -0
- package/dist/model/include.js.map +1 -0
- package/dist/model/index.d.ts +137 -51
- package/dist/model/index.js +519 -174
- package/dist/model/index.js.map +1 -1
- package/dist/model/indexes.d.ts +16 -0
- package/dist/model/indexes.js +155 -0
- package/dist/model/indexes.js.map +1 -0
- package/dist/model/keyspace.d.ts +9 -0
- package/dist/model/keyspace.js +28 -0
- package/dist/model/keyspace.js.map +1 -0
- package/dist/model/read-helpers.d.ts +36 -0
- package/dist/model/read-helpers.js +275 -0
- package/dist/model/read-helpers.js.map +1 -0
- package/dist/model/safe-query.d.ts +23 -0
- package/dist/model/safe-query.js +138 -0
- package/dist/model/safe-query.js.map +1 -0
- package/dist/model/ttl.d.ts +9 -0
- package/dist/model/ttl.js +73 -0
- package/dist/model/ttl.js.map +1 -0
- package/dist/model/validation.d.ts +5 -0
- package/dist/model/validation.js +104 -0
- package/dist/model/validation.js.map +1 -0
- package/dist/model/write-helpers.d.ts +31 -0
- package/dist/model/write-helpers.js +220 -0
- package/dist/model/write-helpers.js.map +1 -0
- package/dist/pagination/index.d.ts +1 -0
- package/dist/pagination/index.js +1 -0
- package/dist/pagination/index.js.map +1 -1
- package/dist/pagination/pagination.d.ts +1 -8
- package/dist/pagination/pagination.js +16 -29
- package/dist/pagination/pagination.js.map +1 -1
- package/dist/pagination/safe-pagination.d.ts +15 -0
- package/dist/pagination/safe-pagination.js +221 -0
- package/dist/pagination/safe-pagination.js.map +1 -0
- package/dist/pagination/types.d.ts +11 -0
- package/dist/pagination/types.js +3 -0
- package/dist/pagination/types.js.map +1 -0
- package/dist/search/customQuery.d.ts +10 -3
- package/dist/search/customQuery.js +17 -18
- package/dist/search/customQuery.js.map +1 -1
- package/dist/shared/common.model.js +2 -2
- package/dist/shared/common.model.js.map +1 -1
- package/dist/timeseries/index.d.ts +1 -0
- package/dist/timeseries/index.js +18 -0
- package/dist/timeseries/index.js.map +1 -0
- package/dist/timeseries/time-series-model.d.ts +60 -0
- package/dist/timeseries/time-series-model.js +267 -0
- package/dist/timeseries/time-series-model.js.map +1 -0
- package/dist/utils/awaitTo.d.ts +1 -1
- package/dist/utils/awaitTo.js +1 -1
- package/dist/utils/awaitTo.js.map +1 -1
- package/docs/beta-migration.md +175 -0
- package/legacy/index.js +1 -0
- package/legacy/package.json +4 -0
- package/next/index.js +1 -0
- package/next/package.json +4 -0
- package/package.json +13 -8
- package/dist/connection.d.ts +0 -42
- package/dist/connection.js +0 -156
- package/dist/connection.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,236 +1,367 @@
|
|
|
1
|
-
|
|
2
1
|
<p align="center">
|
|
3
|
-
<h1 align="center">
|
|
2
|
+
<h1 align="center">CouchSet</h1>
|
|
4
3
|
</p>
|
|
5
4
|
|
|
6
|
-
|
|
7
5
|
<div align="center">
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<img alt="NPM" src="https://img.shields.io/npm/dt/couchset.svg"></img>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
<img alt="NPM" src="https://img.shields.io/npm/dt/couchset.svg"></img>
|
|
7
|
+
<br />
|
|
8
|
+
<img width="500px" src="./docs/couchset.png"></img>
|
|
16
9
|
</div>
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
CouchSet is a Couchbase ORM & Automatic GraphQL API code generator (Resolver/Queries) tool
|
|
11
|
+
CouchSet is a Couchbase model layer for TypeScript and Node.js. The default `couchset` entrypoint keeps the legacy API for safe upgrades; the modern API is available from `couchset/next`.
|
|
23
12
|
|
|
24
13
|
- [Install](#install)
|
|
14
|
+
- [Legacy Default](#legacy-default)
|
|
15
|
+
- [Modern API](#modern-api)
|
|
16
|
+
- [Connection Lifecycle](#connection-lifecycle)
|
|
17
|
+
- [Models](#models)
|
|
18
|
+
- [Reads](#reads)
|
|
19
|
+
- [Writes](#writes)
|
|
20
|
+
- [Queries](#queries)
|
|
21
|
+
- [Gradual Migration](#gradual-migration)
|
|
22
|
+
- [Migration Notes](./docs/beta-migration.md)
|
|
25
23
|
- [License](#license)
|
|
26
24
|
|
|
27
|
-
##
|
|
25
|
+
## Install
|
|
26
|
+
|
|
28
27
|
```bash
|
|
29
28
|
npm i couchset --save
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
##
|
|
31
|
+
## Legacy Default
|
|
32
|
+
|
|
33
|
+
Existing projects can keep importing from `couchset` and continue using the old model methods while gradually migrating.
|
|
34
|
+
|
|
33
35
|
```ts
|
|
34
|
-
import {
|
|
36
|
+
import {couchset, Model} from 'couchset';
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
connectionString: 'couchbase://localhost',
|
|
38
|
-
username: 'admin',
|
|
39
|
-
password: '
|
|
40
|
-
bucketName: '
|
|
41
|
-
|
|
38
|
+
await couchset({
|
|
39
|
+
connectionString: process.env.COUCHBASE_URL || 'couchbase://localhost',
|
|
40
|
+
username: process.env.COUCHBASE_USERNAME || 'admin',
|
|
41
|
+
password: process.env.COUCHBASE_PASSWORD || '1234',
|
|
42
|
+
bucketName: process.env.COUCHBASE_BUCKET || 'dev',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const users = new Model('User', {schema: {createdAt: 'date'}});
|
|
46
|
+
|
|
47
|
+
const created = await users.create({
|
|
48
|
+
userId: 'ceddy',
|
|
49
|
+
email: 'ceddy@example.com',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const found = await users.findById(created.id);
|
|
53
|
+
await users.updateById(created.id, {...found, email: 'new@example.com'});
|
|
54
|
+
await users.delete(created.id);
|
|
42
55
|
```
|
|
43
56
|
|
|
44
|
-
##
|
|
57
|
+
## Modern API
|
|
58
|
+
|
|
59
|
+
New code can opt into the modern API with `couchset/next`.
|
|
45
60
|
|
|
46
61
|
```ts
|
|
47
|
-
import { Model
|
|
62
|
+
import {couchset, Model} from 'couchset/next';
|
|
63
|
+
|
|
64
|
+
type User = {
|
|
65
|
+
userId: string;
|
|
66
|
+
email?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const users = new Model('User', {
|
|
70
|
+
schema: {
|
|
71
|
+
createdAt: 'date',
|
|
72
|
+
updatedAt: 'date',
|
|
73
|
+
},
|
|
74
|
+
indexes: [
|
|
75
|
+
{
|
|
76
|
+
name: 'idx_user_userId',
|
|
77
|
+
fields: ['userId'],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
});
|
|
48
81
|
|
|
49
|
-
|
|
82
|
+
await couchset({
|
|
83
|
+
connectionString: process.env.COUCHBASE_URL || 'couchbase://localhost',
|
|
84
|
+
username: process.env.COUCHBASE_USERNAME || 'admin',
|
|
85
|
+
password: process.env.COUCHBASE_PASSWORD || '1234',
|
|
86
|
+
bucketName: process.env.COUCHBASE_BUCKET || 'dev',
|
|
87
|
+
});
|
|
50
88
|
|
|
89
|
+
await couchset.ready();
|
|
51
90
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
password: 'love couchbase',
|
|
91
|
+
const created = await users.insert<User>({
|
|
92
|
+
userId: 'ceddy',
|
|
93
|
+
email: 'ceddy@example.com',
|
|
56
94
|
});
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
const foundData = await userModel.findById(created.id);
|
|
96
|
+
const found = await users.getById<User>(created.id);
|
|
60
97
|
|
|
61
|
-
|
|
62
|
-
|
|
98
|
+
const patched = await users.patchById<User>(created.id, {
|
|
99
|
+
$set: {email: 'new@example.com'},
|
|
100
|
+
});
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
const page = await users.page<User>({
|
|
103
|
+
where: {userId: {$eq: 'ceddy'}},
|
|
104
|
+
limit: 25,
|
|
105
|
+
page: 0,
|
|
106
|
+
});
|
|
66
107
|
|
|
108
|
+
await users.deleteById(created.id, {hard: true});
|
|
67
109
|
```
|
|
68
110
|
|
|
69
|
-
##
|
|
111
|
+
## Connection Lifecycle
|
|
112
|
+
|
|
113
|
+
Models can be declared before connecting. Model operations wait for the shared connection before binding to the Couchbase bucket and collection.
|
|
70
114
|
|
|
71
|
-
All models come with a method for automatic pagination
|
|
72
115
|
```ts
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
116
|
+
import {couchset, health, ping, ready, shutdown} from 'couchset/next';
|
|
117
|
+
|
|
118
|
+
await couchset({
|
|
119
|
+
connectionString: 'couchbase://localhost',
|
|
120
|
+
username: 'admin',
|
|
121
|
+
password: '1234',
|
|
122
|
+
bucketName: 'dev',
|
|
123
|
+
autoReconnect: true,
|
|
124
|
+
reconnectIntervalMs: 5000,
|
|
81
125
|
});
|
|
126
|
+
|
|
127
|
+
await ready();
|
|
128
|
+
await ping();
|
|
129
|
+
console.log(health());
|
|
130
|
+
|
|
131
|
+
await shutdown();
|
|
82
132
|
```
|
|
83
133
|
|
|
84
|
-
|
|
134
|
+
Reconnect is enabled by default. Environment flags:
|
|
135
|
+
|
|
136
|
+
- `COUCHSET_RECONNECT`: use `false`, `0`, or `no` to disable reconnect.
|
|
137
|
+
- `COUCHSET_RECONNECT_INTERVAL_MS`: reconnect and health-check interval in milliseconds. Default is `5000`.
|
|
138
|
+
|
|
139
|
+
The modern entrypoint also exports app starter helpers that read Couchbase credentials from env:
|
|
85
140
|
|
|
86
|
-
```
|
|
87
|
-
|
|
141
|
+
```ts
|
|
142
|
+
import {startCouchbase, startCouchbaseServerless} from 'couchset/next';
|
|
143
|
+
|
|
144
|
+
await startCouchbase();
|
|
145
|
+
await startCouchbaseServerless();
|
|
88
146
|
```
|
|
89
147
|
|
|
148
|
+
The starters read `COUCHBASE_URL`, `COUCHBASE_BUCKET`, `COUCHBASE_USERNAME`, `COUCHBASE_PASSWORD`, and `COUCHBASE_PROXY`. You can pass any `CouchsetArgs` field as an override.
|
|
90
149
|
|
|
91
|
-
|
|
150
|
+
## Models
|
|
92
151
|
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
id: '1392e4f6-ae1e-4e01-b7d5-103bdd0e843f',
|
|
106
|
-
updatedAt: 2021-01-26T01:51:29.591Z,
|
|
107
|
-
createdAt: 2021-01-26T01:51:29.583Z,
|
|
108
|
-
_type: 'User',
|
|
109
|
-
userId: 'ceddy',
|
|
110
|
-
password: '...',
|
|
111
|
-
someValue: 'x'
|
|
112
|
-
}
|
|
113
|
-
]
|
|
152
|
+
```ts
|
|
153
|
+
const auditEvents = new Model('AuditEvent', {
|
|
154
|
+
scope: 'app',
|
|
155
|
+
collection: 'events',
|
|
156
|
+
softDelete: true,
|
|
157
|
+
defaultWhere: {tenantId: {$eq: 'tenant-1'}},
|
|
158
|
+
dateFields: ['profile.createdAt'],
|
|
159
|
+
validateCreate: (doc) => doc,
|
|
160
|
+
validateReplace: (doc) => doc,
|
|
161
|
+
parse: (doc) => doc,
|
|
162
|
+
});
|
|
114
163
|
```
|
|
115
164
|
|
|
165
|
+
Useful model helpers:
|
|
116
166
|
|
|
117
|
-
|
|
118
|
-
|
|
167
|
+
```ts
|
|
168
|
+
users.bucket(); // `dev`
|
|
169
|
+
users.keyspace(); // `dev` or default:`dev`.`scope`.`collection`
|
|
170
|
+
users.from('u'); // `dev` AS u
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Reads
|
|
119
174
|
|
|
120
175
|
```ts
|
|
121
|
-
|
|
176
|
+
await users.getById<User>('user::1');
|
|
177
|
+
await users.findByIdWithMeta<User>('user::1');
|
|
178
|
+
|
|
179
|
+
await users.findMany<User>({
|
|
180
|
+
select: ['id', 'userId', 'email'],
|
|
181
|
+
where: {userId: {$eq: 'ceddy'}},
|
|
182
|
+
orderBy: {createdAt: 'DESC'},
|
|
183
|
+
limit: 10,
|
|
184
|
+
});
|
|
122
185
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
$count: {
|
|
127
|
-
$field: {
|
|
128
|
-
name: 'type',
|
|
129
|
-
},
|
|
130
|
-
as: 'odm',
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
let: [
|
|
135
|
-
{ key: 'amount_val', value: 10 },
|
|
136
|
-
{ key: 'size_val', value: 20 },
|
|
137
|
-
],
|
|
138
|
-
where: {
|
|
139
|
-
$or: [{ price: { $gt: 'amount_val', $isNotNull: true } }, { auto: { $gt: 10 } }, { amount: 10 }],
|
|
140
|
-
$and: [
|
|
141
|
-
{ price2: { $gt: 1.99, $isNotNull: true } },
|
|
142
|
-
{ $or: [{ price3: { $gt: 1.99, $isNotNull: true } }, { id: '20' }] },
|
|
143
|
-
],
|
|
144
|
-
$any: {
|
|
145
|
-
$expr: [{ $in: { search_expr: 'search', target_expr: 'address' } }],
|
|
146
|
-
$satisfied: { address: '10' },
|
|
147
|
-
},
|
|
148
|
-
$in: { search_expr: 'search', target_expr: ['address'] },
|
|
149
|
-
},
|
|
150
|
-
groupBy: [{ expr: 'type', as: 'sch' }],
|
|
151
|
-
letting: [
|
|
152
|
-
{ key: 'amount_v2', value: 10 },
|
|
153
|
-
{ key: 'size_v2', value: 20 },
|
|
154
|
-
],
|
|
155
|
-
having: { type: { $like: '%hotel%' } },
|
|
156
|
-
orderBy: { type: 'DESC' },
|
|
157
|
-
limit: 10,
|
|
158
|
-
offset: 1,
|
|
159
|
-
use: ['airlineR_8093', 'airlineR_8094'],
|
|
160
|
-
};
|
|
186
|
+
await users.findOne<User>({where: {email: {$eq: 'ceddy@example.com'}}});
|
|
187
|
+
await users.exists({where: {userId: {$eq: 'ceddy'}}});
|
|
188
|
+
await users.count({where: {userId: {$eq: 'ceddy'}}});
|
|
161
189
|
|
|
162
|
-
const
|
|
163
|
-
|
|
190
|
+
const result = await users.page<User>({
|
|
191
|
+
where: {userId: {$eq: 'ceddy'}},
|
|
192
|
+
limit: 10,
|
|
193
|
+
page: 0,
|
|
194
|
+
});
|
|
164
195
|
|
|
196
|
+
result.items;
|
|
197
|
+
result.hasNext;
|
|
198
|
+
result.pageInfo.nextPage;
|
|
165
199
|
```
|
|
166
200
|
|
|
167
|
-
|
|
201
|
+
Hydrated documents:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const doc = await users.findDocById<User & {id: string}>('user::1');
|
|
168
205
|
|
|
169
|
-
|
|
170
|
-
|
|
206
|
+
doc.email = 'updated@example.com';
|
|
207
|
+
await doc.save();
|
|
208
|
+
await doc.patch({$set: {verified: true}});
|
|
209
|
+
await doc.reload();
|
|
210
|
+
await doc.delete({hard: true});
|
|
171
211
|
```
|
|
172
212
|
|
|
213
|
+
Soft delete scopes:
|
|
173
214
|
|
|
215
|
+
```ts
|
|
216
|
+
await users.softDeleteById<User>('user::1');
|
|
217
|
+
await users.restoreById<User>('user::1');
|
|
218
|
+
|
|
219
|
+
await users.withDeleted().findMany<User>();
|
|
220
|
+
await users.onlyDeleted().findMany<User>();
|
|
221
|
+
await users.withoutDefaultWhere().findMany<User>();
|
|
222
|
+
```
|
|
174
223
|
|
|
175
|
-
|
|
224
|
+
## Writes
|
|
176
225
|
|
|
177
226
|
```ts
|
|
178
|
-
|
|
227
|
+
await users.insert<User>({id: 'user::1', userId: 'ceddy'});
|
|
228
|
+
await users.upsert<User>({id: 'user::1', userId: 'ceddy'});
|
|
229
|
+
await users.replaceById<User>('user::1', {userId: 'ceddy', email: 'new@example.com'});
|
|
230
|
+
await users.patchById<User>('user::1', {
|
|
231
|
+
$set: {email: 'new@example.com'},
|
|
232
|
+
$inc: {loginCount: 1},
|
|
233
|
+
$unset: ['temporaryCode'],
|
|
234
|
+
});
|
|
235
|
+
await users.incrementById<User>('user::1', 'loginCount', 1);
|
|
236
|
+
await users.deleteById('user::1', {hard: true});
|
|
237
|
+
```
|
|
179
238
|
|
|
180
|
-
|
|
181
|
-
// queryresults = { rows: object[], meta: any}
|
|
239
|
+
TTL helpers:
|
|
182
240
|
|
|
241
|
+
```ts
|
|
242
|
+
await users.insert<User>({id: 'user::1', userId: 'ceddy'}, {ttl: '2h'});
|
|
243
|
+
await users.upsert<User>({id: 'user::2', userId: 'ceddy'}, {ttlSeconds: 300});
|
|
183
244
|
```
|
|
184
245
|
|
|
185
|
-
##
|
|
246
|
+
## Queries
|
|
186
247
|
|
|
187
|
-
|
|
248
|
+
Modern query helpers pass SDK parameters correctly and throw on failures by default.
|
|
188
249
|
|
|
189
250
|
```ts
|
|
190
|
-
const
|
|
251
|
+
const rows = await users.queryRows<User>(
|
|
252
|
+
`SELECT u.* FROM ${users.from('u')} WHERE u.userId=$userId LIMIT $limit`,
|
|
253
|
+
{userId: 'ceddy', limit: 10}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const first = await users.queryOne<User>(
|
|
257
|
+
`SELECT u.* FROM ${users.from('u')} WHERE u.email=$email LIMIT 1`,
|
|
258
|
+
{email: 'ceddy@example.com'}
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const page = await users.queryPage<User>(
|
|
262
|
+
`SELECT u.* FROM ${users.from('u')} WHERE u.userId=$userId LIMIT $limit`,
|
|
263
|
+
{userId: 'ceddy', limit: 10}
|
|
264
|
+
);
|
|
191
265
|
```
|
|
192
266
|
|
|
193
|
-
|
|
267
|
+
Model read helpers also throw by default. Use `throwOnError: false` only when an empty fallback is intentional.
|
|
194
268
|
|
|
269
|
+
```ts
|
|
270
|
+
const rows = await users.findMany<User>({
|
|
271
|
+
where: {userId: {$eq: 'ceddy'}},
|
|
272
|
+
throwOnError: false,
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Indexes
|
|
195
277
|
|
|
196
278
|
```ts
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
const users = new Model('User', {
|
|
280
|
+
indexes: [
|
|
281
|
+
{
|
|
282
|
+
name: 'idx_user_email',
|
|
283
|
+
fields: ['email'],
|
|
284
|
+
where: {deleted: {$isNotValued: true}},
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
});
|
|
203
288
|
|
|
289
|
+
await users.ensureIndexes();
|
|
290
|
+
await couchset.ensureIndexes();
|
|
204
291
|
```
|
|
205
292
|
|
|
206
|
-
##
|
|
207
|
-
TODO
|
|
293
|
+
## Includes
|
|
208
294
|
|
|
295
|
+
```ts
|
|
296
|
+
const posts = new Model('Post');
|
|
297
|
+
|
|
298
|
+
const rows = await posts.findMany({
|
|
299
|
+
where: {published: {$eq: true}},
|
|
300
|
+
include: [
|
|
301
|
+
{
|
|
302
|
+
as: 'author',
|
|
303
|
+
model: users,
|
|
304
|
+
key: 'authorId',
|
|
305
|
+
type: 'leftJoin',
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
```
|
|
209
310
|
|
|
311
|
+
## Time Series
|
|
210
312
|
|
|
313
|
+
```ts
|
|
314
|
+
import {TimeSeriesModel} from 'couchset/next';
|
|
211
315
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
316
|
+
const metrics = new TimeSeriesModel('Metric', {
|
|
317
|
+
keyField: 'deviceId',
|
|
318
|
+
timeField: 'timestamp',
|
|
319
|
+
values: [{field: 'temperature'}],
|
|
320
|
+
interval: '1m',
|
|
321
|
+
});
|
|
215
322
|
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
- Automated indexes (only couchbase enterprise) ✅
|
|
221
|
-
- Geospatial queries ✅
|
|
222
|
-
- FTS queries ✅
|
|
323
|
+
await metrics.appendChunk('device-1', [
|
|
324
|
+
{deviceId: 'device-1', timestamp: Date.now(), temperature: 21.5},
|
|
325
|
+
]);
|
|
326
|
+
```
|
|
223
327
|
|
|
328
|
+
## Gradual Migration
|
|
224
329
|
|
|
330
|
+
Use `couchset` for old code and `couchset/next` for new code. Both model APIs share the same connection singleton, reconnect loop, and health state, so you can migrate one model or file at a time without opening a second Couchbase cluster connection.
|
|
225
331
|
|
|
332
|
+
```ts
|
|
333
|
+
import {couchset, Model} from 'couchset';
|
|
334
|
+
import {Model as NextModel} from 'couchset/next';
|
|
226
335
|
|
|
227
|
-
|
|
228
|
-
<img height="300px" src="./docs/couch.png"></img>
|
|
229
|
-
</div>
|
|
336
|
+
await couchset(args);
|
|
230
337
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
338
|
+
const legacyUsers = new Model('User');
|
|
339
|
+
const nextUsers = new NextModel('User');
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`couchset/legacy` remains available as an explicit alias for the default legacy API.
|
|
343
|
+
|
|
344
|
+
Modern replacements:
|
|
345
|
+
|
|
346
|
+
| Old method | Modern method |
|
|
347
|
+
| --- | --- |
|
|
348
|
+
| `create()` | `insert()` or `upsert()` |
|
|
349
|
+
| `findById()` | `getById()` |
|
|
350
|
+
| `updateById()` / `save()` | `replaceById()` or `patchById()` |
|
|
351
|
+
| `delete()` | `deleteById()` |
|
|
352
|
+
| `pagination()` | `findMany()` or `page()` |
|
|
353
|
+
| `customQuery()` | `queryRows()`, `queryOne()`, or `queryPage()` |
|
|
354
|
+
|
|
355
|
+
## Local Tests
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
npm run build
|
|
359
|
+
npm test
|
|
360
|
+
npm run test:serverless
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Set `COUCHBASE_URL`, `COUCHBASE_BUCKET`, `COUCHBASE_USERNAME`, and `COUCHBASE_PASSWORD` to point the integration tests at a local Couchbase instance.
|
|
364
|
+
|
|
365
|
+
## License
|
|
234
366
|
|
|
235
|
-
|
|
236
|
-
Couchset is [MIT licensed](./LICENSE).
|
|
367
|
+
CouchSet is [MIT licensed](./LICENSE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Bucket, Cluster, Collection } from 'couchbase';
|
|
2
|
+
import { ConnectionHealth, ConnectionState, CouchsetArgs } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* CouchbaseConnection class
|
|
5
|
+
* Only one CouchbaseConnection can exist that's why it's a singleton
|
|
6
|
+
*/
|
|
7
|
+
export declare class CouchbaseConnection implements CouchsetArgs {
|
|
8
|
+
private static _instance;
|
|
9
|
+
bucket: Bucket;
|
|
10
|
+
cluster: Cluster;
|
|
11
|
+
private connectionPromise?;
|
|
12
|
+
private connectionSettings?;
|
|
13
|
+
private reconnectPromise?;
|
|
14
|
+
private reconnectReject?;
|
|
15
|
+
private reconnectTimer?;
|
|
16
|
+
private healthTimer?;
|
|
17
|
+
private connectionState;
|
|
18
|
+
private autoReconnectEnabled;
|
|
19
|
+
private reconnectDelayMs;
|
|
20
|
+
private manuallyClosed;
|
|
21
|
+
private lastConnectionError?;
|
|
22
|
+
connectionString: string;
|
|
23
|
+
bucketName: string;
|
|
24
|
+
username: string;
|
|
25
|
+
password: string;
|
|
26
|
+
static get Instance(): CouchbaseConnection;
|
|
27
|
+
private constructor();
|
|
28
|
+
private configureReconnect;
|
|
29
|
+
private normalizeArgs;
|
|
30
|
+
private sameSettings;
|
|
31
|
+
private assignSettings;
|
|
32
|
+
private settingsError;
|
|
33
|
+
private connectionOptions;
|
|
34
|
+
private clearReconnectTimer;
|
|
35
|
+
private clearHealthTimer;
|
|
36
|
+
private scheduleTimer;
|
|
37
|
+
private rawPing;
|
|
38
|
+
private openCluster;
|
|
39
|
+
private scheduleHealthCheck;
|
|
40
|
+
private connectWithSettings;
|
|
41
|
+
private startReconnect;
|
|
42
|
+
/**
|
|
43
|
+
* start
|
|
44
|
+
*/
|
|
45
|
+
init: (args: CouchsetArgs) => Promise<CouchbaseConnection>;
|
|
46
|
+
/**
|
|
47
|
+
* start serverless start
|
|
48
|
+
*/
|
|
49
|
+
initServerless: (args: CouchsetArgs) => Promise<CouchbaseConnection>;
|
|
50
|
+
/**
|
|
51
|
+
* getCollection
|
|
52
|
+
*/
|
|
53
|
+
getCollection: (scopeName?: string, collectionName?: string) => Collection;
|
|
54
|
+
getCluster: () => Cluster;
|
|
55
|
+
getBucket: () => string;
|
|
56
|
+
isConnected: () => boolean;
|
|
57
|
+
ready: () => Promise<CouchbaseConnection>;
|
|
58
|
+
ping: () => Promise<any>;
|
|
59
|
+
state: () => ConnectionState;
|
|
60
|
+
health: () => ConnectionHealth;
|
|
61
|
+
shouldReconnect: (error: unknown) => boolean;
|
|
62
|
+
markDisconnected: (error?: unknown) => void;
|
|
63
|
+
/**
|
|
64
|
+
* shutdown cluster
|
|
65
|
+
*/
|
|
66
|
+
shutdown: () => Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export default CouchbaseConnection;
|