@ragestudio/scylla-odm 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -0
- package/_virtual/_rolldown/runtime.js +1 -0
- package/client.d.ts +6 -5
- package/client.js +1 -117
- package/cql_gen/create_keyspace.d.ts +11 -0
- package/cql_gen/create_keyspace.js +1 -0
- package/cql_gen/create_table.d.ts +1 -2
- package/cql_gen/create_table.js +1 -38
- package/document/index.d.ts +3 -4
- package/document/index.js +1 -63
- package/driver/index.d.ts +304 -0
- package/driver/index.js +1 -0
- package/driver/lib/auth/base-dse-authenticator.js +1 -0
- package/driver/lib/auth/dse-gssapi-auth-provider.js +1 -0
- package/driver/lib/auth/dse-plain-text-auth-provider.js +1 -0
- package/driver/lib/auth/gssapi-client.js +1 -0
- package/driver/lib/auth/index.d.ts +46 -0
- package/driver/lib/auth/index.js +1 -0
- package/driver/lib/auth/no-auth-provider.js +1 -0
- package/driver/lib/auth/plain-text-auth-provider.js +1 -0
- package/driver/lib/auth/provider.js +1 -0
- package/driver/lib/client-options.js +1 -0
- package/driver/lib/client.js +1 -0
- package/driver/lib/concurrent/index.d.ts +25 -0
- package/driver/lib/concurrent/index.js +1 -0
- package/driver/lib/connection.js +1 -0
- package/driver/lib/control-connection.js +1 -0
- package/driver/lib/datastax/graph/complex-type-helper.js +1 -0
- package/driver/lib/datastax/graph/custom-type-serializers.js +1 -0
- package/driver/lib/datastax/graph/graph-executor.js +1 -0
- package/driver/lib/datastax/graph/graph-serializer.js +1 -0
- package/driver/lib/datastax/graph/index.d.ts +63 -0
- package/driver/lib/datastax/graph/index.js +1 -0
- package/driver/lib/datastax/graph/options.js +1 -0
- package/driver/lib/datastax/graph/result-set.js +1 -0
- package/driver/lib/datastax/graph/structure.js +1 -0
- package/driver/lib/datastax/graph/type-serializers.js +1 -0
- package/driver/lib/datastax/graph/wrappers.js +1 -0
- package/driver/lib/datastax/index.d.ts +7 -0
- package/driver/lib/datastax/search/date-range.js +1 -0
- package/driver/lib/datastax/search/index.d.ts +49 -0
- package/driver/lib/datastax/search/index.js +1 -0
- package/driver/lib/encoder.js +1 -0
- package/driver/lib/errors.js +1 -0
- package/driver/lib/execution-options.js +1 -0
- package/driver/lib/execution-profile.js +1 -0
- package/driver/lib/geometry/geometry.js +1 -0
- package/driver/lib/geometry/index.d.ts +49 -0
- package/driver/lib/geometry/index.js +1 -0
- package/driver/lib/geometry/line-string.js +1 -0
- package/driver/lib/geometry/point.js +1 -0
- package/driver/lib/geometry/polygon.js +1 -0
- package/driver/lib/host-connection-pool.js +1 -0
- package/driver/lib/host.js +1 -0
- package/driver/lib/insights-client.js +1 -0
- package/driver/lib/mapping/cache.js +1 -0
- package/driver/lib/mapping/doc-info-adapter.js +1 -0
- package/driver/lib/mapping/index.d.ts +142 -0
- package/driver/lib/mapping/index.js +1 -0
- package/driver/lib/mapping/mapper.js +1 -0
- package/driver/lib/mapping/mapping-handler.js +1 -0
- package/driver/lib/mapping/model-batch-item.js +1 -0
- package/driver/lib/mapping/model-batch-mapper.js +1 -0
- package/driver/lib/mapping/model-mapper.js +1 -0
- package/driver/lib/mapping/model-mapping-info.js +1 -0
- package/driver/lib/mapping/object-selector.js +1 -0
- package/driver/lib/mapping/q.js +1 -0
- package/driver/lib/mapping/query-generator.js +9 -0
- package/driver/lib/mapping/result-mapper.js +4 -0
- package/driver/lib/mapping/result.js +1 -0
- package/driver/lib/mapping/table-mappings.js +1 -0
- package/driver/lib/mapping/tree.js +1 -0
- package/driver/lib/metadata/aggregate.js +1 -0
- package/driver/lib/metadata/client-state.js +1 -0
- package/driver/lib/metadata/data-collection.js +1 -0
- package/driver/lib/metadata/event-debouncer.js +1 -0
- package/driver/lib/metadata/index.d.ts +184 -0
- package/driver/lib/metadata/index.js +1 -0
- package/driver/lib/metadata/materialized-view.js +1 -0
- package/driver/lib/metadata/schema-function.js +1 -0
- package/driver/lib/metadata/schema-index.js +1 -0
- package/driver/lib/metadata/schema-parser.js +1 -0
- package/driver/lib/metadata/table-metadata.js +1 -0
- package/driver/lib/metrics/client-metrics.js +1 -0
- package/driver/lib/metrics/default-metrics.js +1 -0
- package/driver/lib/metrics/index.d.ts +44 -0
- package/driver/lib/metrics/index.js +1 -0
- package/driver/lib/operation-state.js +1 -0
- package/driver/lib/policies/address-resolution.js +1 -0
- package/driver/lib/policies/index.d.ts +135 -0
- package/driver/lib/policies/index.js +1 -0
- package/driver/lib/policies/load-balancing.js +1 -0
- package/driver/lib/policies/reconnection.js +1 -0
- package/driver/lib/policies/retry.js +1 -0
- package/driver/lib/policies/speculative-execution.js +1 -0
- package/driver/lib/policies/timestamp-generation.js +1 -0
- package/driver/lib/prepare-handler.js +1 -0
- package/driver/lib/promise-utils.js +1 -0
- package/driver/lib/readers.js +1 -0
- package/driver/lib/request-execution.js +1 -0
- package/driver/lib/request-handler.js +1 -0
- package/driver/lib/requests.js +1 -0
- package/driver/lib/stream-id-stack.js +1 -0
- package/driver/lib/streams.js +1 -0
- package/driver/lib/token.js +1 -0
- package/driver/lib/tokenizer.js +1 -0
- package/driver/lib/tracker/index.d.ts +45 -0
- package/driver/lib/tracker/index.js +1 -0
- package/driver/lib/tracker/request-logger.js +1 -0
- package/driver/lib/tracker/request-tracker.js +1 -0
- package/driver/lib/types/big-decimal.js +1 -0
- package/driver/lib/types/duration.js +1 -0
- package/driver/lib/types/index.d.ts +320 -0
- package/driver/lib/types/index.js +1 -0
- package/driver/lib/types/inet-address.js +1 -0
- package/driver/lib/types/integer.js +1 -0
- package/driver/lib/types/local-date.js +1 -0
- package/driver/lib/types/local-time.js +1 -0
- package/driver/lib/types/mutable-long.js +1 -0
- package/driver/lib/types/protocol-version.js +1 -0
- package/driver/lib/types/result-set.js +1 -0
- package/driver/lib/types/result-stream.js +1 -0
- package/driver/lib/types/row.js +1 -0
- package/driver/lib/types/time-uuid.js +1 -0
- package/driver/lib/types/tuple.js +1 -0
- package/driver/lib/types/uuid.js +1 -0
- package/driver/lib/types/vector.js +1 -0
- package/driver/lib/types/version-number.js +1 -0
- package/driver/lib/utils.js +4 -0
- package/driver/lib/writers.js +1 -0
- package/index.js +1 -11
- package/logger/index.d.ts +9 -0
- package/logger/index.js +1 -0
- package/model/index.d.ts +2 -3
- package/model/index.js +1 -44
- package/operations/countAll.d.ts +1 -2
- package/operations/countAll.js +1 -16
- package/operations/delete.d.ts +3 -4
- package/operations/delete.js +1 -11
- package/operations/find.d.ts +1 -2
- package/operations/find.js +1 -22
- package/operations/findOne.d.ts +1 -2
- package/operations/findOne.js +1 -17
- package/operations/sync.d.ts +1 -2
- package/operations/sync.js +1 -16
- package/operations/tableExists.d.ts +1 -2
- package/operations/tableExists.js +2 -15
- package/operations/update.d.ts +1 -2
- package/operations/update.js +1 -16
- package/package.js +1 -0
- package/package.json +7 -4
- package/schema/index.d.ts +1 -2
- package/schema/index.js +1 -16
- package/types.d.ts +3 -3
- package/types.js +1 -34
- package/utils/buildMapper.d.ts +1 -2
- package/utils/buildMapper.js +1 -13
- package/utils/delay.d.ts +4 -0
- package/utils/delay.js +1 -0
- package/utils/fillDefaults.d.ts +1 -2
- package/utils/fillDefaults.js +1 -18
- package/utils/loadModels.d.ts +1 -2
- package/utils/loadModels.js +1 -30
- package/utils/queryParser.d.ts +1 -2
- package/utils/queryParser.js +1 -94
- package/utils/typeChecker.d.ts +1 -2
- package/utils/typeChecker.js +1 -64
- package/client.js.map +0 -1
- package/cql_gen/create_table.js.map +0 -1
- package/document/index.js.map +0 -1
- package/index.js.map +0 -1
- package/model/index.js.map +0 -1
- package/operations/countAll.js.map +0 -1
- package/operations/delete.js.map +0 -1
- package/operations/find.js.map +0 -1
- package/operations/findOne.js.map +0 -1
- package/operations/sync.js.map +0 -1
- package/operations/tableExists.js.map +0 -1
- package/operations/update.js.map +0 -1
- package/schema/index.js.map +0 -1
- package/types.js.map +0 -1
- package/utils/buildMapper.js.map +0 -1
- package/utils/fillDefaults.js.map +0 -1
- package/utils/loadModels.js.map +0 -1
- package/utils/queryParser.js.map +0 -1
- package/utils/typeChecker.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# @ragestudio/scylla-odm
|
|
2
|
+
|
|
3
|
+
> A lightweight ODM for ScyllaDB and Cassandra.
|
|
4
|
+
|
|
5
|
+
> **Warning:** under active development, not production-ready.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## why this exists
|
|
10
|
+
|
|
11
|
+
Most of (not existent) Node.js ODMs for ScyllaDB/Cassandra rely on the official `cassandra-driver`.
|
|
12
|
+
|
|
13
|
+
The official `cassandra-driver` ships a built-in mapper, but it has no
|
|
14
|
+
TypeScript support or a friendly interface.
|
|
15
|
+
You define your columns with plain strings and get
|
|
16
|
+
plain objects back — no autocomplete, no type checking, no safety net.
|
|
17
|
+
|
|
18
|
+
This ODM is built on top of a modified `cassandra-driver` and adds:
|
|
19
|
+
|
|
20
|
+
- **Type-safe schemas** — your column types are inferred, so `find`,
|
|
21
|
+
`create`, and `save` all know the shape of your data
|
|
22
|
+
- **ODM Methods** — easier & friendly API for interacting with your data
|
|
23
|
+
- **Full TypeScript support** — autocomplete, type checking, and inference
|
|
24
|
+
from schema to model to document
|
|
25
|
+
- **MongoDB-style query operators** — `$gt`, `$in`, `$lte`, etc., all typed
|
|
26
|
+
|
|
27
|
+
We also stripped out everything you probably never use (DataStax Insights,
|
|
28
|
+
metrics, heavy dependencies, and other services) and applied a bunch of performance patches on
|
|
29
|
+
top of the driver itself.
|
|
30
|
+
|
|
31
|
+
The result is a leaner driver with less overhead, fewer timers, and better
|
|
32
|
+
memory usage.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @ragestudio/scylla-odm
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## quick start
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import Client, { Schema, Model, ColumnTypes } from "@ragestudio/scylla-odm";
|
|
48
|
+
import type { Column } from "@ragestudio/scylla-odm/types";
|
|
49
|
+
|
|
50
|
+
// 1. define a schema
|
|
51
|
+
const userSchema = new Schema(
|
|
52
|
+
{
|
|
53
|
+
table_name: "users",
|
|
54
|
+
keys: ["id"], // partition key and clustering keys
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: { type: ColumnTypes.Int, required: true } as Column<number>,
|
|
58
|
+
name: { type: ColumnTypes.Text, required: true } as Column<string>,
|
|
59
|
+
email: { type: ColumnTypes.Text, required: true } as Column<string>,
|
|
60
|
+
age: { type: ColumnTypes.Int } as Column<number>,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// 2. create a model
|
|
65
|
+
const User = new Model("User", userSchema);
|
|
66
|
+
|
|
67
|
+
// 3. create a client
|
|
68
|
+
const client = new Client({
|
|
69
|
+
contactPoints: ["127.0.0.1"],
|
|
70
|
+
localDataCenter: "datacenter1",
|
|
71
|
+
keyspace: "myapp",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 4. initialize and connect
|
|
75
|
+
await client.initialize();
|
|
76
|
+
|
|
77
|
+
// 4. create and save documents
|
|
78
|
+
const user = User.create({
|
|
79
|
+
id: 1234,
|
|
80
|
+
name: "Alice",
|
|
81
|
+
email: "alice@example.com",
|
|
82
|
+
age: 30,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await user.save();
|
|
86
|
+
|
|
87
|
+
// 5. query
|
|
88
|
+
const found = await User.findOne({ id: 1234 });
|
|
89
|
+
console.log(found?.name); // "Alice"
|
|
90
|
+
|
|
91
|
+
// 6. shutdown
|
|
92
|
+
await client.shutdown();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## schema
|
|
98
|
+
|
|
99
|
+
A schema describes a table: its name, primary keys, and columns.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const productSchema = new Schema(
|
|
103
|
+
{
|
|
104
|
+
table_name: "products",
|
|
105
|
+
keys: [["category"], "id"], // compound partition key
|
|
106
|
+
clustering_order: { created_at: "desc" }, // optional
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
category: { type: ColumnTypes.Text, required: true } as Column<string>,
|
|
110
|
+
id: { type: ColumnTypes.Uuid, required: true } as Column<string>,
|
|
111
|
+
title: { type: ColumnTypes.Text, required: true } as Column<string>,
|
|
112
|
+
price: { type: ColumnTypes.Double, required: true } as Column<number>,
|
|
113
|
+
created_at: { type: ColumnTypes.Timestamp } as Column<Date>,
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## model
|
|
121
|
+
|
|
122
|
+
A model wraps a schema and provides the query interface.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const Product = new Model("Product", productSchema);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### methods
|
|
129
|
+
|
|
130
|
+
| method | description |
|
|
131
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
132
|
+
| `create(data)` / `obj()` | create a new document (not persisted yet) |
|
|
133
|
+
| `find(query, options?)` | find documents matching the query, returns an array of documents |
|
|
134
|
+
| `findOne(query, options?)` | find a single document, if no document is found, returns `null`. Due the mapper limitation, note that all partition and clustering keys must be defined in order to use this method. |
|
|
135
|
+
| |
|
|
136
|
+
| `update(query)` | update documents matching the query |
|
|
137
|
+
| `delete(query)` | delete documents matching the query |
|
|
138
|
+
| `countAll()` | count all documents in the table |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## document
|
|
143
|
+
|
|
144
|
+
Documents are the objects you work with. They carry the data and a few helpers.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const product = Product.create({
|
|
148
|
+
category: "books",
|
|
149
|
+
id: uuid(),
|
|
150
|
+
title: "ODM 101",
|
|
151
|
+
price: 29.99,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await product.save(); // persist to ScyllaDB
|
|
155
|
+
await product.delete(); // delete from ScyllaDB
|
|
156
|
+
product.toRaw(); // plain object without internal fields
|
|
157
|
+
product.getChangedFields(original); // diff between current and original data
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## query operators
|
|
163
|
+
|
|
164
|
+
Use MongoDB-style operators in your queries:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// exact match
|
|
168
|
+
await User.find({ name: "Alice" });
|
|
169
|
+
|
|
170
|
+
// comparison operators
|
|
171
|
+
await Product.find({ price: { $gt: 10, $lte: 50 } });
|
|
172
|
+
|
|
173
|
+
// list matching
|
|
174
|
+
await User.find({ age: { $in: [25, 30, 35] } });
|
|
175
|
+
|
|
176
|
+
// sorting
|
|
177
|
+
await Product.find({ category: "books" }, { $orderby: { created_at: "desc" } });
|
|
178
|
+
|
|
179
|
+
// raw results (skip document wrapping)
|
|
180
|
+
const raw = await Product.find({ category: "books" }, { raw: true });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## configuration
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const client = new Client({
|
|
189
|
+
modelsPath: "./db",
|
|
190
|
+
contactPoints: ["127.0.0.1"],
|
|
191
|
+
localDataCenter: "datacenter1",
|
|
192
|
+
keyspace: "myapp",
|
|
193
|
+
port: 9042,
|
|
194
|
+
maxRetries: 3,
|
|
195
|
+
retryDelay: 1000,
|
|
196
|
+
pooling: {
|
|
197
|
+
coreConnectionsPerHost: {
|
|
198
|
+
local: 2,
|
|
199
|
+
},
|
|
200
|
+
maxRequestsPerConnection: 1024,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
You can also configure via environment variables:
|
|
206
|
+
|
|
207
|
+
| variable | default |
|
|
208
|
+
| -------------------------- | ------------------ |
|
|
209
|
+
| `SCYLLA_CONTACT_POINTS` | `127.0.0.1` |
|
|
210
|
+
| `SCYLLA_LOCAL_DATA_CENTER` | `datacenter1` |
|
|
211
|
+
| `SCYLLA_KEYSPACE` | `default_keyspace` |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## about the driver
|
|
216
|
+
|
|
217
|
+
This package ships a modified version of the `cassandra-driver`
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## license
|
|
222
|
+
|
|
223
|
+
This project is licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
|
224
|
+
|
|
225
|
+
The bundled `cassandra-driver` is licensed under the
|
|
226
|
+
[Apache License, Version 2.0](src/driver/LICENSE.txt).
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
Apache Cassandra NodeJS Driver
|
|
230
|
+
Copyright 2013 The Apache Software Foundation
|
|
231
|
+
|
|
232
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
233
|
+
you may not use this file except in compliance with the License.
|
|
234
|
+
You may obtain a copy of the License at
|
|
235
|
+
|
|
236
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
237
|
+
|
|
238
|
+
Unless required by applicable law or agreed to in writing, software
|
|
239
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
240
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
241
|
+
See the License for the specific language governing permissions and
|
|
242
|
+
limitations under the License.
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
See [`src/driver/LICENSE.txt`](src/driver/LICENSE.txt) and
|
|
246
|
+
[`src/driver/NOTICE.txt`](src/driver/NOTICE.txt) for the full legal text.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createRequire as e}from"node:module";var t=Object.create,n=Object.defineProperty,r=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getPrototypeOf,o=Object.prototype.hasOwnProperty,s=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),c=(e,t,a,s)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var c=i(t),l=0,u=c.length,d;l<u;l++)d=c[l],!o.call(e,d)&&d!==a&&n(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(s=r(t,d))||s.enumerable});return e},l=(e,r,i)=>(i=e==null?{}:t(a(e)),c(r||!e||!e.__esModule?n(i,`default`,{value:e,enumerable:!0}):i,e)),u=e(import.meta.url);export{s as __commonJSMin,u as __require,l as __toESM};
|
package/client.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { mapping } from "./driver/lib/mapping/index.js";
|
|
2
|
+
import { Client as Client$1 } from "./driver/index.js";
|
|
1
3
|
import { Model } from "./model/index.js";
|
|
2
4
|
import { ClientConfig } from "./types.js";
|
|
3
|
-
import {
|
|
5
|
+
import { Logger } from "./logger/index.js";
|
|
4
6
|
|
|
5
7
|
//#region src/client.d.ts
|
|
6
8
|
declare class Client {
|
|
@@ -10,15 +12,14 @@ declare class Client {
|
|
|
10
12
|
mapper: mapping.Mapper;
|
|
11
13
|
models: Map<string, Model<any>>;
|
|
12
14
|
model(name: string): Model<any> | undefined;
|
|
15
|
+
logger: Logger;
|
|
13
16
|
initialize(options?: {
|
|
14
17
|
sync?: boolean;
|
|
15
18
|
}): Promise<void>;
|
|
16
|
-
private connectWithRetry;
|
|
17
|
-
private delay;
|
|
18
19
|
shutdown(): Promise<void>;
|
|
20
|
+
private connectWithRetry;
|
|
19
21
|
executeWithRetry<T>(operation: () => Promise<T>, operationName?: string): Promise<T>;
|
|
20
22
|
private isRetryableError;
|
|
21
23
|
}
|
|
22
24
|
//#endregion
|
|
23
|
-
export { Client, Client as default };
|
|
24
|
-
//# sourceMappingURL=client.d.ts.map
|
|
25
|
+
export { Client, Client as default };
|
package/client.js
CHANGED
|
@@ -1,117 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import buildMapper_default from "./utils/buildMapper.js";
|
|
3
|
-
import { Model } from "./model/index.js";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import Cassandra from "cassandra-driver";
|
|
6
|
-
//#region src/client.ts
|
|
7
|
-
const DEFAULT_MAX_RETRIES = 3;
|
|
8
|
-
const DEFAULT_RETRY_DELAY = 1e3;
|
|
9
|
-
const { SCYLLA_CONTACT_POINTS, SCYLLA_LOCAL_DATA_CENTER, SCYLLA_KEYSPACE } = process.env;
|
|
10
|
-
var Client = class {
|
|
11
|
-
constructor(config = {}) {
|
|
12
|
-
if (globalThis.__scylla_client) throw new Error("An instance of Scylla Client is already initialized");
|
|
13
|
-
this.config = {
|
|
14
|
-
modelsPath: path.resolve(process.cwd(), "db"),
|
|
15
|
-
contactPoints: config.contactPoints ?? SCYLLA_CONTACT_POINTS?.split(",") ?? ["127.0.0.1"],
|
|
16
|
-
localDataCenter: config.localDataCenter ?? SCYLLA_LOCAL_DATA_CENTER ?? "datacenter1",
|
|
17
|
-
keyspace: config.keyspace ?? SCYLLA_KEYSPACE ?? "default",
|
|
18
|
-
port: 9042,
|
|
19
|
-
maxRetries: DEFAULT_MAX_RETRIES,
|
|
20
|
-
retryDelay: DEFAULT_RETRY_DELAY,
|
|
21
|
-
...config
|
|
22
|
-
};
|
|
23
|
-
const clientOptions = {
|
|
24
|
-
contactPoints: this.config.contactPoints,
|
|
25
|
-
localDataCenter: this.config.localDataCenter,
|
|
26
|
-
keyspace: this.config.keyspace,
|
|
27
|
-
protocolOptions: { port: this.config.port }
|
|
28
|
-
};
|
|
29
|
-
if (this.config.pooling) clientOptions.pooling = this.config.pooling;
|
|
30
|
-
this.driver = new Cassandra.Client(clientOptions);
|
|
31
|
-
}
|
|
32
|
-
config;
|
|
33
|
-
driver;
|
|
34
|
-
mapper;
|
|
35
|
-
models = /* @__PURE__ */ new Map();
|
|
36
|
-
model(name) {
|
|
37
|
-
return this.models.get(name);
|
|
38
|
-
}
|
|
39
|
-
async initialize(options = {}) {
|
|
40
|
-
let models;
|
|
41
|
-
try {
|
|
42
|
-
models = await loadModels_default(this.config.modelsPath);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
throw new Error(`Failed to load models: ${error.message}`);
|
|
45
|
-
}
|
|
46
|
-
models = models.filter((schema) => schema instanceof Model);
|
|
47
|
-
this.mapper = new Cassandra.mapping.Mapper(this.driver, { models: buildMapper_default(models) });
|
|
48
|
-
globalThis.__scylla_client = this;
|
|
49
|
-
for (let model of models) {
|
|
50
|
-
this.models.set(model.name, model);
|
|
51
|
-
if (options?.sync === true) await model._sync();
|
|
52
|
-
}
|
|
53
|
-
console.log("Connecting to ScyllaDB");
|
|
54
|
-
await this.connectWithRetry();
|
|
55
|
-
console.log("ScyllaDB Connected");
|
|
56
|
-
}
|
|
57
|
-
async connectWithRetry() {
|
|
58
|
-
let lastError = null;
|
|
59
|
-
for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) try {
|
|
60
|
-
await this.driver.connect();
|
|
61
|
-
return;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
lastError = error;
|
|
64
|
-
console.warn(`Connection attempt ${attempt} failed: ${error.message}`);
|
|
65
|
-
if (attempt < this.config.maxRetries) {
|
|
66
|
-
console.log(`Retrying in ${this.config.retryDelay}ms...`);
|
|
67
|
-
await this.delay(this.config.retryDelay);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
throw new Error(`Failed to connect to ScyllaDB after ${this.config.maxRetries} attempts: ${lastError?.message}`);
|
|
71
|
-
}
|
|
72
|
-
delay(ms) {
|
|
73
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
74
|
-
}
|
|
75
|
-
async shutdown() {
|
|
76
|
-
try {
|
|
77
|
-
await this.driver.shutdown();
|
|
78
|
-
console.log("ScyllaDB connection closed");
|
|
79
|
-
delete globalThis.__scylla_client;
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error("Error shutting down ScyllaDB connection:", error);
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async executeWithRetry(operation, operationName = "operation") {
|
|
86
|
-
let lastError = null;
|
|
87
|
-
for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) try {
|
|
88
|
-
return await operation();
|
|
89
|
-
} catch (error) {
|
|
90
|
-
lastError = error;
|
|
91
|
-
if (this.isRetryableError(error) && attempt < this.config.maxRetries) {
|
|
92
|
-
console.warn(`Operation ${operationName} attempt ${attempt} failed: ${error.message}`);
|
|
93
|
-
console.log(`Retrying in ${this.config.retryDelay}ms...`);
|
|
94
|
-
await this.delay(this.config.retryDelay);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
throw new Error(`Operation ${operationName} failed after ${this.config.maxRetries} attempts: ${lastError?.message}`);
|
|
100
|
-
}
|
|
101
|
-
isRetryableError(error) {
|
|
102
|
-
const retryableMessages = [
|
|
103
|
-
"timeout",
|
|
104
|
-
"connection",
|
|
105
|
-
"network",
|
|
106
|
-
"unavailable",
|
|
107
|
-
"overloaded",
|
|
108
|
-
"no hosts available"
|
|
109
|
-
];
|
|
110
|
-
const errorMessage = error.message?.toLowerCase() || "";
|
|
111
|
-
return retryableMessages.some((msg) => errorMessage.includes(msg));
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
//#endregion
|
|
115
|
-
export { Client, Client as default };
|
|
116
|
-
|
|
117
|
-
//# sourceMappingURL=client.js.map
|
|
1
|
+
import{__toESM as e}from"./_virtual/_rolldown/runtime.js";import{require_driver as t}from"./driver/index.js";import{Model as n}from"./model/index.js";import{Logger as r}from"./logger/index.js";import i from"./utils/loadModels.js";import a from"./utils/buildMapper.js";import o from"./utils/delay.js";import s from"node:path";var c=e(t(),1);const{SCYLLA_CONTACT_POINTS:l,SCYLLA_LOCAL_DATA_CENTER:u,SCYLLA_KEYSPACE:d}=process.env;var f=class{constructor(e={}){if(globalThis.__scylla_client)throw Error(`An instance of Scylla Client is already initialized`);this.config={modelsPath:s.resolve(process.cwd(),`db`),contactPoints:e.contactPoints??l?.split(`,`)??[`127.0.0.1`],localDataCenter:e.localDataCenter??u??`datacenter1`,keyspace:e.keyspace??d??`default_keyspace`,port:9042,maxRetries:3,retryDelay:1e3,...e};let t={contactPoints:this.config.contactPoints,localDataCenter:this.config.localDataCenter,keyspace:this.config.keyspace,protocolOptions:{port:this.config.port}};this.config.pooling&&(t.pooling=this.config.pooling),this.driver=new c.Client(t)}config;driver;mapper;models=new Map;model(e){return this.models.get(e)}logger=new r;async initialize(e={}){let t;try{t=await i(this.config.modelsPath)}catch(e){throw Error(`Failed to load models: ${e.message}`)}t=t.filter(e=>e instanceof n),this.mapper=new c.mapping.Mapper(this.driver,{models:a(t)}),globalThis.__scylla_client=this,this.logger.log(`Connecting...`),await this.connectWithRetry(),this.logger.log(`Connected`);for(let n of t)this.models.set(n.name,n),e?.sync===!0&&await n._sync()}async shutdown(){try{await this.driver.shutdown(),this.logger.log(`connection closed`),delete globalThis.__scylla_client}catch(e){throw this.logger.error(`error shutting down connection:`,e),e}}async connectWithRetry(){let e=null;for(let t=1;t<=this.config.maxRetries;t++)try{await this.driver.connect();return}catch(n){e=n,this.logger.warn(`Connection attempt ${t} failed: ${n.message}`),t<this.config.maxRetries&&(this.logger.log(`Retrying in ${this.config.retryDelay}ms...`),await o(this.config.retryDelay))}throw Error(`Failed to connect to ScyllaDB after ${this.config.maxRetries} attempts: ${e?.message}`)}async executeWithRetry(e,t=`operation`){let n=null;for(let r=1;r<=this.config.maxRetries;r++)try{return await e()}catch(e){if(n=e,this.isRetryableError(e)&&r<this.config.maxRetries){this.logger.warn(`Operation ${t} attempt ${r} failed: ${e.message}`),this.logger.log(`Retrying in ${this.config.retryDelay}ms...`),await o(this.config.retryDelay);continue}throw e}throw Error(`Operation ${t} failed after ${this.config.maxRetries} attempts: ${n?.message}`)}isRetryableError(e){let t=[`timeout`,`connection`,`network`,`unavailable`,`overloaded`,`no hosts available`],n=e.message?.toLowerCase()||``;return t.some(e=>n.includes(e))}};export{f as Client,f as default};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/cql_gen/create_keyspace.d.ts
|
|
2
|
+
type KeyspaceCreateOptions = {
|
|
3
|
+
keyspace: string;
|
|
4
|
+
replication?: {
|
|
5
|
+
strategy: string;
|
|
6
|
+
options: Record<string, string>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
declare function createKeyspace(options: KeyspaceCreateOptions): string;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { createKeyspace as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){let t={};return t=e.replication?e.replication:{strategy:`SimpleStrategy`,options:{replication_factor:`1`}},`CREATE KEYSPACE IF NOT EXISTS ${e.keyspace} WITH REPLICATION = ${JSON.stringify(t)};`}export{e as default};
|
|
@@ -3,5 +3,4 @@ import { Model } from "../model/index.js";
|
|
|
3
3
|
//#region src/cql_gen/create_table.d.ts
|
|
4
4
|
declare function export_default(model: Model): string;
|
|
5
5
|
//#endregion
|
|
6
|
-
export { export_default as default };
|
|
7
|
-
//# sourceMappingURL=create_table.d.ts.map
|
|
6
|
+
export { export_default as default };
|
package/cql_gen/create_table.js
CHANGED
|
@@ -1,38 +1 @@
|
|
|
1
|
-
|
|
2
|
-
function create_table_default(model) {
|
|
3
|
-
const desc = model.schema;
|
|
4
|
-
const tableName = desc.table_name;
|
|
5
|
-
const keyspace = model.client.config.keyspace;
|
|
6
|
-
const fields = desc.fields;
|
|
7
|
-
const key = desc.keys;
|
|
8
|
-
const clusteringOrder = desc.clustering_order;
|
|
9
|
-
let columnsDef = "";
|
|
10
|
-
for (const fieldName in fields) {
|
|
11
|
-
const field = fields[fieldName];
|
|
12
|
-
const typeStr = typeof field === "string" ? field : field?.type;
|
|
13
|
-
if (!typeStr) throw new Error(`Invalid field type for "${fieldName}" in model "${tableName}"`);
|
|
14
|
-
columnsDef += `"${fieldName}" ${typeStr.toUpperCase()}, `;
|
|
15
|
-
}
|
|
16
|
-
let pkDef = "";
|
|
17
|
-
if (typeof key === "string") pkDef = `"${key}"`;
|
|
18
|
-
else if (Array.isArray(key) && key.length > 0) {
|
|
19
|
-
const first = key[0];
|
|
20
|
-
if (Array.isArray(first)) pkDef = `(${first.map((k) => `"${k}"`).join(", ")})`;
|
|
21
|
-
else pkDef = `"${first}"`;
|
|
22
|
-
for (let i = 1; i < key.length; i++) pkDef += `, "${key[i]}"`;
|
|
23
|
-
} else throw new Error(`Missing or invalid primary key in model "${tableName}"`);
|
|
24
|
-
let clusterClause = "";
|
|
25
|
-
if (clusteringOrder) {
|
|
26
|
-
let orderDef = "";
|
|
27
|
-
for (const col in clusteringOrder) {
|
|
28
|
-
if (orderDef !== "") orderDef += ", ";
|
|
29
|
-
orderDef += `"${col}" ${clusteringOrder[col].toUpperCase()}`;
|
|
30
|
-
}
|
|
31
|
-
if (orderDef !== "") clusterClause = ` WITH CLUSTERING ORDER BY (${orderDef})`;
|
|
32
|
-
}
|
|
33
|
-
return `CREATE TABLE IF NOT EXISTS ${keyspace}.${tableName} (${columnsDef}PRIMARY KEY (${pkDef}))${clusterClause}`;
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
export { create_table_default as default };
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=create_table.js.map
|
|
1
|
+
function e(e){let t=e.schema,n=t.table_name,r=e.client.config.keyspace,i=t.fields,a=t.keys,o=t.clustering_order,s=``;for(let e in i){let t=i[e],r=typeof t==`string`?t:t?.type;if(!r)throw Error(`Invalid field type for "${e}" in model "${n}"`);s+=`"${e}" ${r.toUpperCase()}, `}let c=``;if(typeof a==`string`)c=`"${a}"`;else if(Array.isArray(a)&&a.length>0){let e=a[0];c=Array.isArray(e)?`(${e.map(e=>`"${e}"`).join(`, `)})`:`"${e}"`;for(let e=1;e<a.length;e++)c+=`, "${a[e]}"`}else throw Error(`Missing or invalid primary key in model "${n}"`);let l=``;if(o){let e=``;for(let t in o)e!==``&&(e+=`, `),e+=`"${t}" ${o[t].toUpperCase()}`;e!==``&&(l=` WITH CLUSTERING ORDER BY (${e})`)}return`CREATE TABLE IF NOT EXISTS ${r}.${n} (${s}PRIMARY KEY (${c}))${l}`}export{e as default};
|
package/document/index.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
+
import { mapping } from "../driver/lib/mapping/index.js";
|
|
1
2
|
import { Model } from "../model/index.js";
|
|
2
3
|
import { Doc } from "../types.js";
|
|
3
|
-
import * as _$cassandra_driver0 from "cassandra-driver";
|
|
4
4
|
|
|
5
5
|
//#region src/document/index.d.ts
|
|
6
6
|
declare class Document<TDoc = any> {
|
|
7
7
|
constructor(data: TDoc, model: Model<any, TDoc>);
|
|
8
8
|
_model: Model<any, TDoc>;
|
|
9
9
|
save(): Promise<Doc<TDoc>>;
|
|
10
|
-
delete(): Promise<
|
|
10
|
+
delete(): Promise<mapping.Result<any>>;
|
|
11
11
|
toRaw(): TDoc;
|
|
12
12
|
isValid(): boolean;
|
|
13
13
|
getChangedFields(original: Partial<TDoc>): (keyof TDoc)[];
|
|
14
14
|
}
|
|
15
15
|
//#endregion
|
|
16
|
-
export { Document, Document as default };
|
|
17
|
-
//# sourceMappingURL=index.d.ts.map
|
|
16
|
+
export { Document, Document as default };
|
package/document/index.js
CHANGED
|
@@ -1,63 +1 @@
|
|
|
1
|
-
|
|
2
|
-
var Document = class {
|
|
3
|
-
constructor(data, model) {
|
|
4
|
-
if (data == null) throw new Error("Cannot create Document with null or undefined data");
|
|
5
|
-
if (typeof data !== "object" || Array.isArray(data)) throw new Error("Document data must be an object");
|
|
6
|
-
Object.assign(this, data);
|
|
7
|
-
Object.defineProperty(this, "_model", {
|
|
8
|
-
value: model,
|
|
9
|
-
enumerable: false,
|
|
10
|
-
writable: false,
|
|
11
|
-
configurable: false
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
_model;
|
|
15
|
-
async save() {
|
|
16
|
-
try {
|
|
17
|
-
const data = this.toRaw();
|
|
18
|
-
return await this._model.update(data);
|
|
19
|
-
} catch (error) {
|
|
20
|
-
throw new Error(`Failed to save document: ${error.message}`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async delete() {
|
|
24
|
-
try {
|
|
25
|
-
return await this._model.delete(this.toRaw());
|
|
26
|
-
} catch (error) {
|
|
27
|
-
throw new Error(`Failed to delete document: ${error.message}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
toRaw() {
|
|
31
|
-
const raw = {};
|
|
32
|
-
for (const key in this) {
|
|
33
|
-
if (key === "_model") continue;
|
|
34
|
-
if (this.propertyIsEnumerable(key)) {
|
|
35
|
-
const value = this[key];
|
|
36
|
-
try {
|
|
37
|
-
JSON.stringify(value);
|
|
38
|
-
raw[key] = value;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
raw[key] = String(value);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return raw;
|
|
45
|
-
}
|
|
46
|
-
isValid() {
|
|
47
|
-
try {
|
|
48
|
-
return true;
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
getChangedFields(original) {
|
|
54
|
-
const current = this.toRaw();
|
|
55
|
-
const changed = [];
|
|
56
|
-
for (const key in current) if (!(key in original) || current[key] !== original[key]) changed.push(key);
|
|
57
|
-
return changed;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
//#endregion
|
|
61
|
-
export { Document, Document as default };
|
|
62
|
-
|
|
63
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
var e=class{constructor(e,t){if(e==null)throw Error(`Cannot create Document with null or undefined data`);if(typeof e!=`object`||Array.isArray(e))throw Error(`Document data must be an object`);Object.assign(this,e),Object.defineProperty(this,`_model`,{value:t,enumerable:!1,writable:!1,configurable:!1})}_model;async save(){try{let e=this.toRaw();return await this._model.update(e)}catch(e){throw Error(`Failed to save document: ${e.message}`)}}async delete(){try{return await this._model.delete(this.toRaw())}catch(e){throw Error(`Failed to delete document: ${e.message}`)}}toRaw(){let e={};for(let t in this)if(t!==`_model`&&this.propertyIsEnumerable(t)){let n=this[t];try{JSON.stringify(n),e[t]=n}catch{e[t]=String(n)}}return e}isValid(){try{return!0}catch{return!1}}getChangedFields(e){let t=this.toRaw(),n=[];for(let r in t)(!(r in e)||t[r]!==e[r])&&n.push(r);return n}};export{e as Document,e as default};
|