Skip to content

Commit 0c8d7ea

Browse files
committed
Docs work + update internal parsing & tooling (#99)
* test.sh.md * check to ensure that test suite names follow convention * some stargate script updates + docs * check.sh.md * repl.sh.md * logs only to stderr by default instead of split between stderr and stdout * more dev docs updates * set-example-client-deps.sh * scripts/playgound.sh * client?: "default" => client: "fetch-h2" * migrate options parsing to 3rd party ts lib * monoids * tests passing again after more refactors (i love monoids) * update dev deps * majorly improve composability of options handlers * split ctx.continue() into ctx.replace() and ctx.nevermind(); made deser codecs run backwards * BaseSerDesCtx.target
1 parent 8d82e34 commit 0c8d7ea

File tree

155 files changed

+4543
-3549
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+4543
-3549
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,6 @@ vectorize_test_spec.json
141141
etc/test-reports/
142142
etc/playgrounds/
143143
tmp-lib-check
144+
!examples/astra-db-ts.tgz
144145

145146
.direnv

DEVGUIDE.md

Lines changed: 6 additions & 314 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 102 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,41 @@ const db = client.db(process.env.CLIENT_DB_URL!, { token: process.env.CLIENT_DB_
3636

3737
// The `VectorDoc` interface adds `$vector?: DataAPIVector` as a field to the collection type
3838
interface Dream extends VectorDoc {
39-
_id: ObjectId, // Uses an `astra-db-ts` provided type here (NOT the `bson` version)
39+
_id: ObjectId,
4040
summary: string,
41-
tags?: string[], // No sets/maps available without creating custom ser/des rules
41+
tags?: string[],
4242
}
4343

4444
(async () => {
45-
// Create the table using our helper function.
46-
// The _id should be an `ObjectId` type, as specified by `defaultId.type`
45+
// Create the collection with a custom default ID type
4746
const collection = await db.createCollection<Dream>('dreams', {
4847
defaultId: { type: 'objectId' },
4948
});
5049

51-
// Batch-insert some rows into the table
50+
// Batch-insert some rows into the table.
5251
// _id can be optionally provided, or be auto-generated @ the server side
5352
await collection.insertMany([{
5453
summary: 'A dinner on the Moon',
55-
$vector: vector([0.2, -0.3, -0.5]), // Shorthand for `new DataAPIVector([0.2, -0.3, -0.5])`
54+
$vector: vector([0.2, -0.3, -0.5]),
5655
}, {
5756
summary: 'Riding the waves',
5857
$vector: vector([0, 0.2, 1]),
5958
tags: ['sport'],
6059
}, {
61-
_id: oid('674f0f5c1c162131319fa09e'), // Shorthand for `new ObjectId('674f0f5c1c162131319fa09e')`
60+
_id: oid('674f0f5c1c162131319fa09e'),
6261
summary: 'Meeting Beethoven at the dentist',
6362
$vector: vector([0.2, 0.6, 0]),
6463
}]);
6564

6665
// Hm, changed my mind
67-
await collection.updateOne({ id: 103 }, { $set: { summary: 'Surfers\' paradise' } });
66+
await collection.updateOne({ _id: oid('674f0f5c1c162131319fa09e') }, {
67+
$set: { summary: 'Surfers\' paradise' }
68+
});
6869

69-
// Let's see what we've got
70+
// Let's see what we've got, by performing a vector search
7071
const cursor = collection.find({})
71-
.sort({ vector: vector([0, 0.2, 0.4]) }) // Performing a vector search
72-
.includeSimilarity(true) // The found doc is inferred to have `$similarity` as a property now
72+
.sort({ vector: vector([0, 0.2, 0.4]) })
73+
.includeSimilarity(true)
7374
.limit(2);
7475

7576
// This would print:
@@ -87,44 +88,42 @@ interface Dream extends VectorDoc {
8788
### Tables
8889

8990
```typescript
90-
import { DataAPIClient, InferTableSchema, vector } from '@datastax/astra-db-ts';
91+
import { DataAPIClient, InferTableSchema, Table, vector } from '@datastax/astra-db-ts';
9192

9293
// Connect to the db
9394
const client = new DataAPIClient({ logging: 'all' });
9495
const db = client.db(process.env.CLIENT_DB_URL!, { token: process.env.CLIENT_DB_TOKEN! });
9596

96-
// Create a table through the Data API if it does not yet exist.
97-
// Returns the created table through a function so we can use the inferred type of the table ourselves
98-
// (instead of having to manually define it)
99-
const mkDreamsTable = async () => await db.createTable('dreams', {
100-
definition: {
101-
columns: {
102-
id: 'int', // Shorthand notation for { type: 'int' }
103-
summary: 'text',
104-
tags: { type: 'set', valueType: 'text' }, // Collection types require additional type information
105-
vector: { type: 'vector', dimension: 3 }, // Auto-embedding-generation can be enabled through a `service` block
106-
},
107-
primaryKey: 'id', // Shorthand for { partitionBy: ['id'] }
97+
// Define the table's schema so we can infer the type of the table automatically (TS v5.0+)
98+
const DreamsTableSchema = Table.schema({
99+
columns: {
100+
id: 'int',
101+
summary: 'text',
102+
tags: { type: 'set', valueType: 'text' },
103+
vector: { type: 'vector', dimension: 3 },
108104
},
109-
ifNotExists: true, // If any table with the same name exists, do nothing
110-
}); // (note that this does not check if the tables are the same)
105+
primaryKey: 'id',
106+
});
111107

112108
// Infer the TS-equivalent type from the table definition (like zod or arktype). Equivalent to:
113109
//
114110
// interface TableSchema {
115-
// id: number, -- A primary key component, so it's required
116-
// summary?: string | null, -- Not a primary key, so it's optional and may return as null when found
117-
// tags?: Set<string>, -- Sets/maps/lists are optional to insert, but will actually be returned as empty collections instead of null
118-
// vector?: DataAPIVector | null, -- Vectors, however, may be null.
111+
// id: number,
112+
// summary?: string | null,
113+
// tags?: Set<string>,
114+
// vector?: DataAPIVector | null,
119115
// }
120-
type Dream = InferTableSchema<typeof mkDreamsTable>;
116+
type Dream = InferTableSchema<typeof DreamsTableSchema>;
121117

122118
(async () => {
123-
// Create the table using our helper function.
119+
// Create the table if it doesn't already exist
124120
// Table will be typed as `Table<Dream, { id: number }>`, where the former is the schema, and the latter is the primary key
125-
const table = await mkDreamsTable();
121+
const table = await db.createTable('dreams', {
122+
definition: DreamsTableSchema,
123+
ifNotExists: true,
124+
});
126125

127-
// Enables vector search on the table (on the 'vector' column)
126+
// Create a vector index on the vector column so we can perform ANN searches on the table
128127
await table.createVectorIndex('dreams_vector_idx', 'vector', {
129128
options: { metric: 'cosine' },
130129
ifNotExists: true,
@@ -134,12 +133,12 @@ type Dream = InferTableSchema<typeof mkDreamsTable>;
134133
const rows: Dream[] = [{
135134
id: 102,
136135
summary: 'A dinner on the Moon',
137-
vector: vector([0.2, -0.3, -0.5]), // Shorthand for `new DataAPIVector([0.2, -0.3, -0.5])`
136+
vector: vector([0.2, -0.3, -0.5]),
138137
}, {
139138
id: 103,
140139
summary: 'Riding the waves',
141140
vector: vector([0, 0.2, 1]),
142-
tags: new Set(['sport']), // Collection types use native JS collections
141+
tags: new Set(['sport']),
143142
}, {
144143
id: 37,
145144
summary: 'Meeting Beethoven at the dentist',
@@ -148,12 +147,14 @@ type Dream = InferTableSchema<typeof mkDreamsTable>;
148147
await table.insertMany(rows);
149148

150149
// Hm, changed my mind
151-
await table.updateOne({ id: 103 }, { $set: { summary: 'Surfers\' paradise' } });
150+
await table.updateOne({ id: 103 }, {
151+
$set: { summary: 'Surfers\' paradise' }
152+
});
152153

153-
// Let's see what we've got
154+
// Let's see what we've got, by performing a vector search
154155
const cursor = table.find({})
155-
.sort({ vector: vector([0, 0.2, 0.4]) }) // Performing a vector search
156-
.includeSimilarity(true) // The found doc is inferred to have `$similarity` as a property now
156+
.sort({ vector: vector([0, 0.2, 0.4]) })
157+
.includeSimilarity(true)
157158
.limit(2);
158159

159160
// This would print:
@@ -168,6 +169,56 @@ type Dream = InferTableSchema<typeof mkDreamsTable>;
168169
})();
169170
```
170171

172+
<details>
173+
<summary><i>Inferring the table schema pre-TS v5.0</i></summary>
174+
175+
Before TypeScript 5.0, there was no support for "const type parameters" (e.g. `f<const T>(t: T): T`) which `Table.schema` relies on.
176+
177+
No worries though—if you're using TypeScript 4.x or below, you can still infer the schema automatically, albeit with less language server support.
178+
179+
Schema object type errors may be non-local and harder to debug, but the code will still work as expected.
180+
181+
```ts
182+
const DreamsTableSchema = <const>{
183+
columns: {
184+
id: 'int',
185+
summary: 'text',
186+
tags: { type: 'set', valueType: 'text' },
187+
vector: { type: 'vector', dimension: 3 },
188+
},
189+
primaryKey: 'id',
190+
};
191+
192+
// Still works, but you need to ensure DreamsTableSchema is a properly typed const object
193+
type Dream = InferTableSchema<typeof DreamsTableSchema>;
194+
type DreamPK = InferTablePrimaryKey<typeof DreamsTableSchema>;
195+
196+
(async () => {
197+
// Necessary to explicitly set the type of the table schema and primary key here
198+
const table = await db.createTable<Dream, DreamPK>('dreams', {
199+
definition: DreamsTableSchema,
200+
ifNotExists: true,
201+
});
202+
})();
203+
```
204+
205+
If you're using TypeScript 4.9, you can at least use the `satisfies` operator to localize any definition type errors.
206+
207+
```ts
208+
const DreamsTableSchema = <const>{
209+
columns: {
210+
id: 'int',
211+
summary: 'text',
212+
tags: { type: 'set', valueType: 'text' },
213+
vector: { type: 'vector', dimension: 3 },
214+
},
215+
primaryKey: 'id',
216+
} satisfies CreateTableDefinition;
217+
218+
type Dream = InferTableSchema<typeof DreamsTableSchema>;
219+
```
220+
</details>
221+
171222
### Next steps
172223

173224
- More info and usage patterns are given in the ts-doc of classes and methods
@@ -250,11 +301,9 @@ const tp = new UsernamePasswordTokenProvider('*USERNAME*', '*PASSWORD*');
250301
const client = new DataAPIClient(tp, { environment: 'dse' });
251302
const db = client.db('*ENDPOINT*');
252303

253-
// You'll also need to pass it to db.admin() when not using Astra for typing purposes
254-
// If the environment does not match, an error will be thrown as a reminder
255-
// `environment: 'dse'` makes the return type be `DataAPIDbAdmin`
304+
// A common idiom may be to use `dbAdmin.createKeyspace` with `updateDbKeyspace` to initialize the keyspace when necessary
256305
const dbAdmin = db.admin({ environment: 'dse' });
257-
dbAdmin.createNamespace('...');
306+
dbAdmin.createKeyspace('...', { updateDbKeyspace: true });
258307
```
259308

260309
The `TokenProvider` class is an extensible concept to allow you to create or even refresh your tokens
@@ -268,9 +317,9 @@ as necessary, depending on the Data API backend. Tokens may even be omitted if n
268317

269318
## Non-standard environment support
270319

271-
`astra-db-ts` is designed foremost to work in Node.js environments.
320+
`astra-db-ts` is designed first and foremost to work in Node.js environments.
272321

273-
It will work in edge runtimes and other non-node environments as well, though it'll use the native `fetch` API for HTTP
322+
However, it will work in edge runtimes and other non-node environments as well, though it may use the native `fetch` API for HTTP
274323
requests, as opposed to `fetch-h2` which provides extended HTTP/2 and HTTP/1.1 support for performance.
275324

276325
By default, it'll attempt to use `fetch-h2` if available, and fall back to `fetch` if not available in that environment.
@@ -331,12 +380,13 @@ to the native fetch implementation instead if importing fails.
331380

332381
### Browser support
333382

334-
The Data API itself does not natively support browsers, so `astra-db-ts` isn't technically supported in browsers either.
383+
`astra-db-ts` is designed to work in server-side environments, but it can technically work in the browser as well.
335384

336-
However, if, for some reason, you really want to use this in a browser, you can probably do so by installing the
337-
`events` polyfill and setting up a [CORS proxy](https://github.com/Rob--W/cors-anywhere) to forward requests to the Data API.
385+
However, if, for some reason, you really want to use this in a browser, you may need to install the `events` polyfill,
386+
and possibly set up a CORS proxy (such as [CORS Anywhere](https://github.com/Rob--W/cors-anywhere)) to forward requests
387+
to the Data API.
338388

339-
But keep in mind that this is not officially supported, and may be very insecure if you're encoding sensitive
340-
data into the browser client.
389+
But keep in mind that this may be very insecure, especially if you're hardcoding sensitive data into your client-side
390+
code, as it's trivial for anyone to inspect the code and extract the token (through XSS attacks or otherwise).
341391

342-
(See `examples/browser` for a full example of this workaround in action.)
392+
(See `examples/browser` for a full example of browser usage in action.)

0 commit comments

Comments
 (0)