couchset 0.2.7 → 0.4.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +292 -152
  3. package/dist/connection/connection.d.ts +68 -0
  4. package/dist/connection/connection.js +495 -0
  5. package/dist/connection/connection.js.map +1 -0
  6. package/dist/connection/env.d.ts +2 -0
  7. package/dist/connection/env.js +16 -0
  8. package/dist/connection/env.js.map +1 -0
  9. package/dist/connection/index.d.ts +3 -0
  10. package/dist/connection/index.js +25 -0
  11. package/dist/connection/index.js.map +1 -0
  12. package/dist/connection/types.d.ts +25 -0
  13. package/dist/connection/types.js +3 -0
  14. package/dist/connection/types.js.map +1 -0
  15. package/dist/database.d.ts +9 -0
  16. package/dist/database.js +196 -0
  17. package/dist/database.js.map +1 -0
  18. package/dist/index.d.ts +20 -6
  19. package/dist/index.js +55 -8
  20. package/dist/index.js.map +1 -1
  21. package/dist/legacy/connection.d.ts +2 -0
  22. package/dist/legacy/connection.js +24 -0
  23. package/dist/legacy/connection.js.map +1 -0
  24. package/dist/legacy/index.d.ts +10 -0
  25. package/dist/legacy/index.js +93 -0
  26. package/dist/legacy/index.js.map +1 -0
  27. package/dist/legacy/model.d.ts +55 -0
  28. package/dist/legacy/model.js +282 -0
  29. package/dist/legacy/model.js.map +1 -0
  30. package/dist/legacy/pagination/index.d.ts +1 -0
  31. package/dist/legacy/pagination/index.js +18 -0
  32. package/dist/legacy/pagination/index.js.map +1 -0
  33. package/dist/legacy/pagination/pagination.d.ts +12 -0
  34. package/dist/legacy/pagination/pagination.js +85 -0
  35. package/dist/legacy/pagination/pagination.js.map +1 -0
  36. package/dist/legacy/search/customQuery.d.ts +13 -0
  37. package/dist/legacy/search/customQuery.js +73 -0
  38. package/dist/legacy/search/customQuery.js.map +1 -0
  39. package/dist/legacy/search/index.d.ts +1 -0
  40. package/dist/legacy/search/index.js +18 -0
  41. package/dist/legacy/search/index.js.map +1 -0
  42. package/dist/model/default-scope.d.ts +10 -0
  43. package/dist/model/default-scope.js +47 -0
  44. package/dist/model/default-scope.js.map +1 -0
  45. package/dist/model/hydrated-document.d.ts +30 -0
  46. package/dist/model/hydrated-document.js +118 -0
  47. package/dist/model/hydrated-document.js.map +1 -0
  48. package/dist/model/include.d.ts +33 -0
  49. package/dist/model/include.js +250 -0
  50. package/dist/model/include.js.map +1 -0
  51. package/dist/model/index.d.ts +192 -52
  52. package/dist/model/index.js +646 -172
  53. package/dist/model/index.js.map +1 -1
  54. package/dist/model/indexes.d.ts +18 -0
  55. package/dist/model/indexes.js +155 -0
  56. package/dist/model/indexes.js.map +1 -0
  57. package/dist/model/keyspace.d.ts +9 -0
  58. package/dist/model/keyspace.js +28 -0
  59. package/dist/model/keyspace.js.map +1 -0
  60. package/dist/model/read-helpers.d.ts +36 -0
  61. package/dist/model/read-helpers.js +275 -0
  62. package/dist/model/read-helpers.js.map +1 -0
  63. package/dist/model/safe-query.d.ts +23 -0
  64. package/dist/model/safe-query.js +138 -0
  65. package/dist/model/safe-query.js.map +1 -0
  66. package/dist/model/ttl.d.ts +9 -0
  67. package/dist/model/ttl.js +73 -0
  68. package/dist/model/ttl.js.map +1 -0
  69. package/dist/model/validation.d.ts +5 -0
  70. package/dist/model/validation.js +104 -0
  71. package/dist/model/validation.js.map +1 -0
  72. package/dist/model/write-helpers.d.ts +39 -0
  73. package/dist/model/write-helpers.js +245 -0
  74. package/dist/model/write-helpers.js.map +1 -0
  75. package/dist/next.d.ts +204 -0
  76. package/dist/next.js +1190 -0
  77. package/dist/next.js.map +1 -0
  78. package/dist/next.types.d.ts +1 -0
  79. package/dist/next.types.js +38 -0
  80. package/dist/next.types.js.map +1 -0
  81. package/dist/pagination/index.d.ts +1 -0
  82. package/dist/pagination/index.js +1 -0
  83. package/dist/pagination/index.js.map +1 -1
  84. package/dist/pagination/pagination.d.ts +1 -8
  85. package/dist/pagination/pagination.js +16 -29
  86. package/dist/pagination/pagination.js.map +1 -1
  87. package/dist/pagination/safe-pagination.d.ts +15 -0
  88. package/dist/pagination/safe-pagination.js +221 -0
  89. package/dist/pagination/safe-pagination.js.map +1 -0
  90. package/dist/pagination/types.d.ts +11 -0
  91. package/dist/pagination/types.js +3 -0
  92. package/dist/pagination/types.js.map +1 -0
  93. package/dist/search/customQuery.d.ts +10 -3
  94. package/dist/search/customQuery.js +17 -18
  95. package/dist/search/customQuery.js.map +1 -1
  96. package/dist/shared/common.model.js +2 -2
  97. package/dist/shared/common.model.js.map +1 -1
  98. package/dist/timeseries/index.d.ts +1 -0
  99. package/dist/timeseries/index.js +18 -0
  100. package/dist/timeseries/index.js.map +1 -0
  101. package/dist/timeseries/time-series-model.d.ts +60 -0
  102. package/dist/timeseries/time-series-model.js +267 -0
  103. package/dist/timeseries/time-series-model.js.map +1 -0
  104. package/dist/utils/awaitTo.d.ts +1 -1
  105. package/dist/utils/awaitTo.js +1 -1
  106. package/dist/utils/awaitTo.js.map +1 -1
  107. package/docs/beta-migration.md +175 -0
  108. package/docs/document-modeling.md +133 -0
  109. package/docs/next-primitives.md +81 -0
  110. package/legacy/index.js +1 -0
  111. package/legacy/package.json +4 -0
  112. package/next/index.js +1 -0
  113. package/next/package.json +4 -0
  114. package/package.json +18 -10
  115. package/dist/connection.d.ts +0 -42
  116. package/dist/connection.js +0 -156
  117. package/dist/connection.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 - 2026-05-18
4
+
5
+ ### Compatibility
6
+
7
+ - Kept the default `couchset` entrypoint on the legacy API so existing projects can upgrade without rewriting model imports.
8
+ - Kept `couchset/legacy` as an explicit legacy alias.
9
+ - Added `couchset/next` for the modern API.
10
+ - Shared one Couchbase connection singleton across legacy and modern models, allowing both APIs to run side by side during gradual migrations.
11
+
12
+ ### Modern API
13
+
14
+ - Added strict modern model helpers: `insert`, `upsert`, `getById`, `replaceById`, `patchById`, `deleteById`, `findMany`, `findOne`, `page`, `exists`, and `count`.
15
+ - Removed old model method names from the modern model surface so the new API can evolve independently.
16
+ - Added hydrated document helpers for reload/save/patch/delete workflows.
17
+ - Added soft-delete/default-scope helpers, TTL helpers, validation/parse hooks, scoped collection support, include helpers, and declarative index helpers.
18
+ - Added safe SQL++ query helpers with named/positional parameters: `queryRows`, `queryOne`, and `queryPage`.
19
+ - Added safer keyspace helpers: `Model.bucket()` and `Model.from(alias?)`.
20
+
21
+ ### Connection Lifecycle
22
+
23
+ - Added lazy model binding so models can be constructed before `couchset()` connects.
24
+ - Added lifecycle helpers: `ready`, `ping`, `health`, `shutdown`, and `ensureIndexes`.
25
+ - Added reconnect support that is enabled by default, with `COUCHSET_RECONNECT` and `COUCHSET_RECONNECT_INTERVAL_MS` environment controls.
26
+ - Added retry/rebind behavior for reconnectable operation failures.
27
+
28
+ ### Starters
29
+
30
+ - Added modern env-based starter helpers on `couchset/next`: `getConnectionOptions`, `connectionOptions`, `startCouchbase`, and `startCouchbaseServerless`.
31
+ - Starter helpers read `COUCHBASE_URL`, `COUCHBASE_BUCKET`, `COUCHBASE_USERNAME`, `COUCHBASE_PASSWORD`, and `COUCHBASE_PROXY`.
32
+
33
+ ### Documentation
34
+
35
+ - Updated the README for the legacy-default and modern-next import paths.
36
+ - Added beta migration notes for running legacy and modern models together.
37
+
38
+ ### Maintenance
39
+
40
+ - Cleared high and critical `npm audit` findings.
41
+ - Updated the publish workflow to the two-job version bump and npm publish flow.
package/README.md CHANGED
@@ -1,236 +1,376 @@
1
-
2
1
  <p align="center">
3
- <h1 align="center"> CouchSet - Couchbase ORM & Automatic GraphQL API generator (Resolver/Queries)</h1>
2
+ <h1 align="center">CouchSet</h1>
4
3
  </p>
5
4
 
6
-
7
5
  <div align="center">
8
-
9
- <div style="display: flex;justify-content:center;">
10
-
11
-
12
- <img alt="NPM" src="https://img.shields.io/npm/dt/couchset.svg"></img>
13
-
14
-
15
-
16
- </div>
17
-
18
- <img width="500px" src="./docs/couchset.png"></img>
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>
19
9
  </div>
20
10
 
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`.
21
12
 
22
- CouchSet is a Couchbase ORM & Automatic GraphQL API code generator (Resolver/Queries) tool
13
+ The additive client-owned primitives—typed definitions, explicit provisioning, transaction-bound models, CAS helpers, and safe index plans—are documented in [Next primitives](./docs/next-primitives.md).
23
14
 
24
15
  - [Install](#install)
16
+ - [Legacy Default](#legacy-default)
17
+ - [Modern API](#modern-api)
18
+ - [Next primitives](./docs/next-primitives.md)
19
+ - [Connection Lifecycle](#connection-lifecycle)
20
+ - [Models](#models)
21
+ - [Reads](#reads)
22
+ - [Document Modeling](./docs/document-modeling.md)
23
+ - [Writes](#writes)
24
+ - [Queries](#queries)
25
+ - [Gradual Migration](#gradual-migration)
26
+ - [Migration Notes](./docs/beta-migration.md)
27
+ - [Changelog](./CHANGELOG.md)
25
28
  - [License](#license)
26
29
 
27
- ## 1. Install
30
+ ## Install
31
+
28
32
  ```bash
29
33
  npm i couchset --save
30
34
  ```
31
35
 
32
- ## 2. Start couchset
36
+ ## Legacy Default
37
+
38
+ Existing projects can keep importing from `couchset` and continue using the old model methods while gradually migrating.
39
+
33
40
  ```ts
34
- import { couchset } from 'couchset';
41
+ import {couchset, Model} from 'couchset';
35
42
 
36
- const started = await couchset({
37
- connectionString: 'couchbase://localhost',
38
- username: 'admin',
39
- password: '123456',
40
- bucketName: 'stq'
41
- })
43
+ await couchset({
44
+ connectionString: process.env.COUCHBASE_URL || 'couchbase://localhost',
45
+ username: process.env.COUCHBASE_USERNAME || 'admin',
46
+ password: process.env.COUCHBASE_PASSWORD || '1234',
47
+ bucketName: process.env.COUCHBASE_BUCKET || 'dev',
48
+ });
49
+
50
+ const users = new Model('User', {schema: {createdAt: 'date'}});
51
+
52
+ const created = await users.create({
53
+ userId: 'ceddy',
54
+ email: 'ceddy@example.com',
55
+ });
56
+
57
+ const found = await users.findById(created.id);
58
+ await users.updateById(created.id, {...found, email: 'new@example.com'});
59
+ await users.delete(created.id);
42
60
  ```
43
61
 
44
- ## 3. Create a Model and start using it
62
+ ## Modern API
63
+
64
+ New code can opt into the modern API with `couchset/next`.
45
65
 
46
66
  ```ts
47
- import { Model } from 'couchset';
67
+ import {couchset, Model} from 'couchset/next';
68
+
69
+ type User = {
70
+ userId: string;
71
+ email?: string;
72
+ };
73
+
74
+ const users = new Model('User', {
75
+ schema: {
76
+ createdAt: 'date',
77
+ updatedAt: 'date',
78
+ },
79
+ indexes: [
80
+ {
81
+ name: 'idx_user_userId',
82
+ fields: ['userId'],
83
+ },
84
+ ],
85
+ });
48
86
 
49
- const userModel = new Model('User');
87
+ await couchset({
88
+ connectionString: process.env.COUCHBASE_URL || 'couchbase://localhost',
89
+ username: process.env.COUCHBASE_USERNAME || 'admin',
90
+ password: process.env.COUCHBASE_PASSWORD || '1234',
91
+ bucketName: process.env.COUCHBASE_BUCKET || 'dev',
92
+ });
50
93
 
94
+ await couchset.ready();
51
95
 
52
- // Create document
53
- const created = await userModel.create({
54
- username: 'ceddy',
55
- password: 'love couchbase',
96
+ const created = await users.insert<User>({
97
+ userId: 'ceddy',
98
+ email: 'ceddy@example.com',
56
99
  });
57
100
 
58
- // Find document
59
- const foundData = await userModel.findById(created.id);
101
+ const found = await users.getById<User>(created.id);
60
102
 
61
- // update document
62
- const updatedData = await userModel.updateById(created.id, { ...created, someValue: 'x' });
103
+ const patched = await users.patchById<User>(created.id, {
104
+ $set: {email: 'new@example.com'},
105
+ });
63
106
 
64
- // delete
65
- const deletedData = await userModel.delete(created.id);
107
+ const page = await users.page<User>({
108
+ where: {userId: {$eq: 'ceddy'}},
109
+ limit: 25,
110
+ page: 0,
111
+ });
66
112
 
113
+ await users.deleteById(created.id, {hard: true});
67
114
  ```
68
115
 
69
- ## 4. Pagination
116
+ ## Connection Lifecycle
117
+
118
+ Models can be declared before connecting. Model operations wait for the shared connection before binding to the Couchbase bucket and collection.
70
119
 
71
- All models come with a method for automatic pagination
72
120
  ```ts
73
- const paginationData = await userModel.pagination({
74
- select: ["id", "email", "phone","fullname"],
75
- where: {
76
- userId: { $eq: "ceddy" },
77
- $or: [{ userId: { $eq: "ceddy" } }, { phone: 10 }],
78
- },
79
- limit: 100,
80
- page: 0,
121
+ import {couchset, health, ping, ready, shutdown} from 'couchset/next';
122
+
123
+ await couchset({
124
+ connectionString: 'couchbase://localhost',
125
+ username: 'admin',
126
+ password: '1234',
127
+ bucketName: 'dev',
128
+ autoReconnect: true,
129
+ reconnectIntervalMs: 5000,
81
130
  });
131
+
132
+ await ready();
133
+ await ping();
134
+ console.log(health());
135
+
136
+ await shutdown();
82
137
  ```
83
138
 
84
- which translates to this query
139
+ Reconnect is enabled by default. Environment flags:
140
+
141
+ - `COUCHSET_RECONNECT`: use `false`, `0`, or `no` to disable reconnect.
142
+ - `COUCHSET_RECONNECT_INTERVAL_MS`: reconnect and health-check interval in milliseconds. Default is `5000`.
143
+
144
+ The modern entrypoint also exports app starter helpers that read Couchbase credentials from env:
145
+
146
+ ```ts
147
+ import {startCouchbase, startCouchbaseServerless} from 'couchset/next';
85
148
 
86
- ```sql
87
- SELECT * FROM `stq` WHERE _type="User" AND userId="ceddy" AND (userId="ceddy" OR phone=10) ORDER BY createdAt DESC LIMIT 100 OFFSET 0
149
+ await startCouchbase();
150
+ await startCouchbaseServerless();
88
151
  ```
89
152
 
153
+ The starters read `COUCHBASE_URL`, `COUCHBASE_BUCKET`, `COUCHBASE_USERNAME`, `COUCHBASE_PASSWORD`, and `COUCHBASE_PROXY`. You can pass any `CouchsetArgs` field as an override.
90
154
 
91
- Pagination results
155
+ ## Models
92
156
 
93
- ```js
94
- [
95
- {
96
- id: '209d3143-09b7-4b3d-bf7d-f0ccd3f98922',
97
- updatedAt: 2021-01-26T01:51:43.218Z,
98
- createdAt: 2021-01-26T01:51:43.210Z,
99
- _type: 'User',
100
- userId: 'ceddy',
101
- password: '...',
102
- someValue: 'x'
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
- ]
157
+ ```ts
158
+ const auditEvents = new Model('AuditEvent', {
159
+ scope: 'app',
160
+ collection: 'events',
161
+ softDelete: true,
162
+ defaultWhere: {tenantId: {$eq: 'tenant-1'}},
163
+ dateFields: ['profile.createdAt'],
164
+ validateCreate: (doc) => doc,
165
+ validateReplace: (doc) => doc,
166
+ parse: (doc) => doc,
167
+ });
114
168
  ```
115
169
 
170
+ Useful model helpers:
116
171
 
117
- ## 5. Custom queries & Query builder
118
- Query builder is inspired from node-ottoman, for more examples, please see https://ottomanjs.com/guides/query-builder.html#query-builder
172
+ ```ts
173
+ users.bucket(); // `dev`
174
+ users.keyspace(); // `dev` or default:`dev`.`scope`.`collection`
175
+ users.from('u'); // `dev` AS u
176
+ ```
177
+
178
+ For large objects, keep list metadata and heavy payloads in separate models.
179
+ See [Document Modeling](./docs/document-modeling.md) for the recommended read
180
+ shape.
181
+
182
+ ## Reads
119
183
 
120
184
  ```ts
121
- import { Query } from 'couchset';
185
+ await users.getById<User>('user::1');
186
+ await users.findByIdWithMeta<User>('user::1');
187
+
188
+ await users.findMany<User>({
189
+ select: ['id', 'userId', 'email'],
190
+ where: {userId: {$eq: 'ceddy'}},
191
+ orderBy: {createdAt: 'DESC'},
192
+ limit: 10,
193
+ });
122
194
 
123
- const params = {
124
- select: [
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
- };
195
+ await users.findOne<User>({where: {email: {$eq: 'ceddy@example.com'}}});
196
+ await users.exists({where: {userId: {$eq: 'ceddy'}}});
197
+ await users.count({where: {userId: {$eq: 'ceddy'}}});
161
198
 
162
- const query = new Query(params, 'travel-sample').build();
163
- console.log(query);
199
+ const result = await users.page<User>({
200
+ where: {userId: {$eq: 'ceddy'}},
201
+ limit: 10,
202
+ page: 0,
203
+ });
164
204
 
205
+ result.items;
206
+ result.hasNext;
207
+ result.pageInfo.nextPage;
165
208
  ```
166
209
 
167
- which translates to
210
+ Hydrated documents:
211
+
212
+ ```ts
213
+ const doc = await users.findDocById<User & {id: string}>('user::1');
168
214
 
169
- ```sql
170
- SELECT COUNT(type) AS odm FROM travel-sample USE KEYS ["airlineR_8093","airlineR_8094"] LET amount_val=10,size_val=20 WHERE ((price>amount_val AND price IS NOT NULL) OR auto>10 OR amount=10) AND ((price2>1.99 AND price2 IS NOT NULL) AND ((price3>1.99 AND price3 IS NOT NULL) OR id="20")) AND ANY search IN address SATISFIES address="10" END AND search IN ["address"] GROUP BY type AS sch LETTING amount_v2=10,size_v2=20 HAVING type LIKE "%hotel%" ORDER BY type DESC LIMIT 10 OFFSET 1
215
+ doc.email = 'updated@example.com';
216
+ await doc.save();
217
+ await doc.patch({$set: {verified: true}});
218
+ await doc.reload();
219
+ await doc.delete({hard: true});
171
220
  ```
172
221
 
222
+ Soft delete scopes:
173
223
 
224
+ ```ts
225
+ await users.softDeleteById<User>('user::1');
226
+ await users.restoreById<User>('user::1');
227
+
228
+ await users.withDeleted().findMany<User>();
229
+ await users.onlyDeleted().findMany<User>();
230
+ await users.withoutDefaultWhere().findMany<User>();
231
+ ```
174
232
 
175
- ### Running custom query on cluster
233
+ ## Writes
176
234
 
177
235
  ```ts
178
- import { QueryCluster } from 'couchset';
236
+ await users.insert<User>({id: 'user::1', userId: 'ceddy'});
237
+ await users.upsert<User>({id: 'user::1', userId: 'ceddy'});
238
+ await users.replaceById<User>('user::1', {userId: 'ceddy', email: 'new@example.com'});
239
+ await users.patchById<User>('user::1', {
240
+ $set: {email: 'new@example.com'},
241
+ $inc: {loginCount: 1},
242
+ $unset: ['temporaryCode'],
243
+ });
244
+ await users.incrementById<User>('user::1', 'loginCount', 1);
245
+ await users.deleteById('user::1', {hard: true});
246
+ ```
179
247
 
180
- const queryresults = await QueryCluster(queryBuilder);
181
- // queryresults = { rows: object[], meta: any}
248
+ TTL helpers:
182
249
 
250
+ ```ts
251
+ await users.insert<User>({id: 'user::1', userId: 'ceddy'}, {ttl: '2h'});
252
+ await users.upsert<User>({id: 'user::2', userId: 'ceddy'}, {ttlSeconds: 300});
183
253
  ```
184
254
 
185
- ## 6. Model Automation
255
+ ## Queries
186
256
 
187
- This is how we automate it to generate code with all methods,schema, queries
257
+ Modern query helpers pass SDK parameters correctly and throw on failures by default.
188
258
 
189
259
  ```ts
190
- const automaticUser = userModel.automate();
260
+ const rows = await users.queryRows<User>(
261
+ `SELECT u.* FROM ${users.from('u')} WHERE u.userId=$userId LIMIT $limit`,
262
+ {userId: 'ceddy', limit: 10}
263
+ );
264
+
265
+ const first = await users.queryOne<User>(
266
+ `SELECT u.* FROM ${users.from('u')} WHERE u.email=$email LIMIT 1`,
267
+ {email: 'ceddy@example.com'}
268
+ );
269
+
270
+ const page = await users.queryPage<User>(
271
+ `SELECT u.* FROM ${users.from('u')} WHERE u.userId=$userId LIMIT $limit`,
272
+ {userId: 'ceddy', limit: 10}
273
+ );
191
274
  ```
192
275
 
193
- After automating the model, `automaticUser` will come with Server-side Resolver functions, and client queries, mutations, subscriptions, like below
276
+ Model read helpers also throw by default. Use `throwOnError: false` only when an empty fallback is intentional.
194
277
 
278
+ ```ts
279
+ const rows = await users.findMany<User>({
280
+ where: {userId: {$eq: 'ceddy'}},
281
+ throwOnError: false,
282
+ });
283
+ ```
284
+
285
+ ## Indexes
195
286
 
196
287
  ```ts
197
- // Get all automatic generated resolvers and queries/fragments,mutations,subscriptions
198
- const {
199
- resolver: UserResolver, // Server resolver for building GraphQL
200
- modelKeys: UserSelectors, // for any custom queries or exporting
201
- client, // client queries,mutations,subscriptions
202
- } = automaticUser;
288
+ const users = new Model('User', {
289
+ indexes: [
290
+ {
291
+ name: 'idx_user_email',
292
+ fields: ['email'],
293
+ where: {deleted: {$isNotValued: true}},
294
+ },
295
+ ],
296
+ });
203
297
 
298
+ await users.ensureIndexes();
299
+ await couchset.ensureIndexes();
204
300
  ```
205
301
 
206
- ## 7. Write to filesystem
207
- TODO
302
+ ## Includes
208
303
 
304
+ ```ts
305
+ const posts = new Model('Post');
306
+
307
+ const rows = await posts.findMany({
308
+ where: {published: {$eq: true}},
309
+ include: [
310
+ {
311
+ as: 'author',
312
+ model: users,
313
+ key: 'authorId',
314
+ type: 'leftJoin',
315
+ },
316
+ ],
317
+ });
318
+ ```
209
319
 
320
+ ## Time Series
210
321
 
322
+ ```ts
323
+ import {TimeSeriesModel} from 'couchset/next';
211
324
 
212
- <br/>
213
- <br/>
214
- <br/>
325
+ const metrics = new TimeSeriesModel('Metric', {
326
+ keyField: 'deviceId',
327
+ timeField: 'timestamp',
328
+ values: [{field: 'temperature'}],
329
+ interval: '1m',
330
+ });
215
331
 
216
- #### Contributors needed
217
- - Create automatic pagination
218
- - Create Schema and validation/population ✅
219
- - Create static methods for models like `save`, `update`, `findMany` e.t.c ✅
220
- - Automated indexes (only couchbase enterprise) ✅
221
- - Geospatial queries ✅
222
- - FTS queries ✅
332
+ await metrics.appendChunk('device-1', [
333
+ {deviceId: 'device-1', timestamp: Date.now(), temperature: 21.5},
334
+ ]);
335
+ ```
223
336
 
337
+ ## Gradual Migration
224
338
 
339
+ 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
340
 
341
+ ```ts
342
+ import {couchset, Model} from 'couchset';
343
+ import {Model as NextModel} from 'couchset/next';
226
344
 
227
- <div align="center">
228
- <img height="300px" src="./docs/couch.png"></img>
229
- </div>
345
+ await couchset(args);
230
346
 
231
- <p align="center">
232
- <h1 align="center"> Algo Inc </h1>
233
- </p>
347
+ const legacyUsers = new Model('User');
348
+ const nextUsers = new NextModel('User');
349
+ ```
350
+
351
+ `couchset/legacy` remains available as an explicit alias for the default legacy API.
352
+
353
+ Modern replacements:
354
+
355
+ | Old method | Modern method |
356
+ | --- | --- |
357
+ | `create()` | `insert()` or `upsert()` |
358
+ | `findById()` | `getById()` |
359
+ | `updateById()` / `save()` | `replaceById()` or `patchById()` |
360
+ | `delete()` | `deleteById()` |
361
+ | `pagination()` | `findMany()` or `page()` |
362
+ | `customQuery()` | `queryRows()`, `queryOne()`, or `queryPage()` |
363
+
364
+ ## Local Tests
365
+
366
+ ```bash
367
+ npm run build
368
+ npm test
369
+ npm run test:serverless
370
+ ```
371
+
372
+ Set `COUCHBASE_URL`, `COUCHBASE_BUCKET`, `COUCHBASE_USERNAME`, and `COUCHBASE_PASSWORD` to point the integration tests at a local Couchbase instance.
373
+
374
+ ## License
234
375
 
235
- ## License
236
- Couchset is [MIT licensed](./LICENSE).
376
+ 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;