Skip to content

Commit 60e616f

Browse files
authored
ser-des work (#92)
* unficiation of codec types * snakeCaseInterop => keyTransformer * some unit tests on ser-des options
1 parent 04af5e3 commit 60e616f

File tree

31 files changed

+849
-464
lines changed

31 files changed

+849
-464
lines changed

etc/astra-db-ts.api.md

Lines changed: 88 additions & 118 deletions
Large diffs are not rendered by default.

examples/serdes/src/collections/class-mapping.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Author {
88
constructor(public fullName: string, public birthdate: Date) {}
99
}
1010

11-
export default async function CollClassMappingExample(name: string, db: Db) {
11+
export async function CollectionClassMappingExample(name: string, db: Db) {
1212
const coll = await db.createCollection<Book>(name, {
1313
serdes: {
1414
mutateInPlace: true,
@@ -26,13 +26,16 @@ export default async function CollClassMappingExample(name: string, db: Db) {
2626
console.log(inserted);
2727

2828
// Find a document
29-
const found = await coll.findOne({ _id: '123-4-567-89012-3' });
29+
const found = await coll.findOne({ isbn: '123-4-567-89012-3' });
3030
console.dir(found, { depth: null });
3131
}
3232

3333
const BookCodec = CollCodecs.forPath([], {
3434
serialize: (_, value: unknown, ctx) => {
3535
if (!(value instanceof Book)) {
36+
if (value && typeof value === 'object' && 'isbn' in value) {
37+
return ctx.recurse({ ...value, _id: value.isbn, isbn: undefined });
38+
}
3639
return ctx.continue();
3740
}
3841

@@ -46,15 +49,15 @@ const BookCodec = CollCodecs.forPath([], {
4649
},
4750
})
4851
},
52+
deserializeGuard(_, ctx) {
53+
return !ctx.parsingInsertedId;
54+
},
4955
deserialize: (_, value: SomeDoc, ctx) => {
50-
if (ctx.parsingInsertedId) {
51-
return ctx.continue();
52-
}
53-
54-
return ctx.done(new Book(
56+
const book = new Book(
5557
value.title,
5658
new Author(`${value.author.lastName}, ${value.author.firstName}`, new Date(value.author.birthdate)),
5759
value._id,
58-
));
60+
);
61+
return ctx.done(book);
5962
},
6063
});

examples/serdes/src/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { DataAPIClient } from '@datastax/astra-db-ts';
22
import 'dotenv/config';
3+
import { TableCustomDatatypesExample } from '@/src/tables/custom-datatypes';
4+
import { CollectionClassMappingExample } from '@/src/collections/class-mapping';
35

46
const client = new DataAPIClient(process.env.CLIENT_DB_TOKEN);
57
const db = client.db(process.env.CLIENT_DB_URL!);
@@ -11,15 +13,14 @@ const cleanup = () => db.dropCollection(NAME);
1113
(async () => {
1214
await cleanup();
1315

14-
const files = [
15-
'./tables/custom-datatypes',
16-
'./collections/class-mapping',
16+
const functions = [
17+
TableCustomDatatypesExample,
18+
CollectionClassMappingExample,
1719
];
1820

19-
for (const file of files) {
21+
for (const func of functions) {
2022
await using _ = { [Symbol.asyncDispose]: cleanup };
21-
const example = await import(file);
22-
console.log(`Running ${file}...`);
23-
await example.default(NAME, db);
23+
console.log(`Running ${func.name}...`);
24+
await func(NAME, db);
2425
}
2526
})();

examples/serdes/src/tables/custom-datatypes.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
$DeserializeForTable,
33
$SerializeForTable,
44
Db,
5+
Camel2SnakeCase,
56
SomeDoc,
67
TableCodec,
78
TableCodecs,
@@ -16,7 +17,7 @@ interface User {
1617
milestones: AssocList<string, Date>,
1718
}
1819

19-
export default async function TableCustomDatatypesExample(name: string, db: Db) {
20+
export async function TableCustomDatatypesExample(name: string, db: Db) {
2021
const table = await db.createTable<User>(name, {
2122
definition: {
2223
columns: {
@@ -28,8 +29,8 @@ export default async function TableCustomDatatypesExample(name: string, db: Db)
2829
primaryKey: 'user_id',
2930
},
3031
serdes: {
31-
mutateInPlace: true, // Optimization for serialization if you don't need to use the doc again once you pass it in
32-
snakeCaseInterop: true, // Convert camelCase to/from snake_case for column names
32+
mutateInPlace: true, // Optimization for serialization if you don't need to use the doc again once you pass it in
33+
keyTransformer: new Camel2SnakeCase(), // Convert camelCase to/from snake_case for column names
3334
codecs: [
3435
DateCodec,
3536
UserIDCodec,
@@ -64,7 +65,7 @@ class UserID {
6465

6566
const UserIDCodec = TableCodecs.forName('userId', {
6667
serialize: (_, value: UserID, ctx) => ctx.done(value.uuid),
67-
deserialize: (value: string, ctx) => ctx.done(new UserID(value)),
68+
deserialize: (_, value: string, ctx) => ctx.done(new UserID(value)),
6869
});
6970

7071
// Demonstrates validation logic through codecs
@@ -76,15 +77,15 @@ const FullNameCodec = TableCodecs.forName('fullName', {
7677
}
7778
return ctx.done(value);
7879
},
79-
deserialize: (value: string, ctx) => ctx.done(value),
80+
deserialize: (_, value: string, ctx) => ctx.done(value),
8081
});
8182

8283
// Example of retrofitting a type you don't own (or don't want to pollute with serdes logic)
8384

8485
const DateCodec = TableCodecs.forType('timestamp', {
8586
serializeClass: Date,
8687
serialize: (_, value: Date, ctx) => ctx.done(value.toISOString()),
87-
deserialize: (value: string, ctx) => ctx.done(new Date(value)),
88+
deserialize: (_, value: string, ctx) => ctx.done(new Date(value)),
8889
});
8990

9091
// Association list for demonstrating codec composition
@@ -96,12 +97,12 @@ class AssocList<K, V> implements TableCodec<typeof AssocList> {
9697
return ctx.recurse(Object.fromEntries(this.unwrap));
9798
}
9899

99-
public static [$DeserializeForTable](obj: Record<string, unknown>[], ctx: TableDesCtx, def: SomeDoc) {
100+
public static [$DeserializeForTable](_: unknown, obj: Record<string, unknown>[], ctx: TableDesCtx, def: SomeDoc) {
100101
const values = Object.entries(obj);
101102

102103
for (let i = 0; i < values.length; i++) {
103-
values[i][0] = ctx.codecs.type[def.keyType]?.deserialize(values[i][0], ctx, def)[1] ?? values[i][0];
104-
values[i][1] = ctx.codecs.type[def.valueType]?.deserialize(values[i][1], ctx, def)[1] ?? values[i][1];
104+
values[i][0] = ctx.codecs.type[def.keyType]?.deserialize(undefined, values[i][0], ctx, def)[1] ?? values[i][0];
105+
values[i][1] = ctx.codecs.type[def.valueType]?.deserialize(undefined, values[i][1], ctx, def)[1] ?? values[i][1];
105106
}
106107

107108
return ctx.done(new AssocList(values));

scripts/update-example-client-dep.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,22 @@ local)
2222
npm run build
2323
npm pack
2424

25+
dirs="$2"
26+
27+
if [ -z "$dirs" ]; then
28+
dirs="examples/*"
29+
fi
30+
2531
# Does said installation
26-
for dir in examples/*; do
32+
for dir in $dirs; do
33+
if [ ! -d "$dir" ]; then
34+
echo "Directory $dir does not exist"
35+
continue
36+
fi
2737
cd "$cwd/$dir" || exit 1
2838
npm i "${tarball_dir}"/datastax-astra-db-ts-*.tgz
2939
npm i @datastax/astra-db-ts
40+
cd "$cwd" || exit 1
3041
done
3142

3243
# Cleanup (tarball no longer required)

src/documents/collections/ser-des/codecs.ts

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import { UUID } from '@/src/documents/datatypes/uuid';
1717
import { ObjectId } from '@/src/documents/datatypes/object-id';
1818
import { DataAPIVector } from '@/src/documents/datatypes/vector';
1919
import { DataAPITimestamp } from '@/src/documents/datatypes/dates';
20-
import { CollDesCtx, CollSerCtx, TableSerCtx } from '@/src/documents';
21-
import { CodecHolder } from '@/src/lib/api/ser-des/codecs';
20+
import { CollDesCtx, CollSerCtx } from '@/src/documents';
2221
import { EmptyObj, SerDesFn } from '@/src/lib';
22+
import { CodecOpts, RawCodec } from '@/src/lib/api/ser-des/codecs';
2323
import { $DeserializeForCollection, $SerializeForCollection } from '@/src/documents/collections/ser-des/constants';
2424

2525
/**
@@ -46,19 +46,7 @@ export type CollCodec<_Class extends CollCodecClass> = EmptyObj;
4646
/**
4747
* @public
4848
*/
49-
export class CollCodecs implements CodecHolder<CollCodecSerDesFns> {
50-
/**
51-
* @internal
52-
*/
53-
public readonly get: CodecHolder<CollCodecSerDesFns>['get'];
54-
55-
/**
56-
* @internal
57-
*/
58-
public constructor(state: typeof this.get) {
59-
this.get = state;
60-
}
61-
49+
export class CollCodecs {
6250
public static Defaults = {
6351
$date: CollCodecs.forType('$date', {
6452
serializeClass: Date,
@@ -74,45 +62,32 @@ export class CollCodecs implements CodecHolder<CollCodecSerDesFns> {
7462
$objectId: CollCodecs.forType('$objectId', ObjectId),
7563
};
7664

77-
public static Overrides = {
78-
USE_DATA_API_TIMESTAMPS_OVER_DATES: CollCodecs.forType('$date', DataAPITimestamp),
79-
// USE_NUMBER_ARRAYS_FOR_VECTORS:
80-
};
81-
82-
public static forPath(path: string[], clazz: CollCodecClass): CollCodecs
83-
84-
public static forPath(path: string[], opts: CollCodecSerDesFns): CollCodecs
65+
public static USE_DATA_API_TIMESTAMPS_FOR_DATES = CollCodecs.forType('$date', DataAPITimestamp);
8566

86-
public static forPath(path: string[], clazzOrOpts: CollCodecClass | CollCodecSerDesFns): CollCodecs {
87-
if ($DeserializeForCollection in clazzOrOpts) {
88-
return new CollCodecs({ codecType: 'path', path, deserialize: clazzOrOpts[$DeserializeForCollection] });
89-
}
90-
return new CollCodecs({ codecType: 'path', path, ...clazzOrOpts });
67+
public static forPath(path: string[], optsOrClass: CodecOpts<CollCodecSerDesFns, CollSerCtx, CollDesCtx> | CollCodecClass): RawCodec<CollCodecSerDesFns> {
68+
return {
69+
path,
70+
...($DeserializeForCollection in optsOrClass)
71+
? { deserialize: optsOrClass[$DeserializeForCollection] }
72+
: optsOrClass,
73+
};
9174
}
9275

93-
public static forName(name: string, clazz: CollCodecClass): CollCodecs
94-
95-
public static forName(name: string, opts: CollCodecSerDesFns): CollCodecs
96-
97-
public static forName(name: string, clazzOrOpts: CollCodecClass | CollCodecSerDesFns): CollCodecs {
98-
if ($DeserializeForCollection in clazzOrOpts) {
99-
return new CollCodecs({ codecType: 'name', name, deserialize: clazzOrOpts[$DeserializeForCollection] });
100-
}
101-
return new CollCodecs({ codecType: 'name', name, ...clazzOrOpts });
76+
public static forName(name: string, optsOrClass: CodecOpts<CollCodecSerDesFns, CollSerCtx, CollDesCtx> | CollCodecClass): RawCodec<CollCodecSerDesFns> {
77+
return {
78+
name,
79+
...($DeserializeForCollection in optsOrClass)
80+
? { deserialize: optsOrClass[$DeserializeForCollection] }
81+
: optsOrClass,
82+
};
10283
}
10384

104-
public static forType(type: string, clazz: CollCodecClass): CollCodecs;
105-
106-
public static forType(type: string, opts: CollCodecSerDesFns & { serializeGuard: (value: unknown, ctx: TableSerCtx) => boolean }): CollCodecs;
107-
108-
public static forType(type: string, opts: CollCodecSerDesFns & { serializeClass: new (...args: any[]) => any }): CollCodecs;
109-
110-
public static forType(type: string, opts: CollCodecSerDesFns & { deserializeOnly: true }): CollCodecs;
111-
112-
public static forType(type: string, clazzOrOpts: CollCodecClass | CollCodecSerDesFns): CollCodecs {
113-
if ($DeserializeForCollection in clazzOrOpts) {
114-
return new CollCodecs({ codecType: 'type', type, deserialize: clazzOrOpts[$DeserializeForCollection] });
115-
}
116-
return new CollCodecs({ codecType: 'type', type, ...clazzOrOpts });
85+
public static forType(type: string, optsOrClass: CodecOpts<CollCodecSerDesFns, CollSerCtx, CollDesCtx> | CollCodecClass): RawCodec<CollCodecSerDesFns> {
86+
return {
87+
type,
88+
...($DeserializeForCollection in optsOrClass)
89+
? { deserialize: optsOrClass[$DeserializeForCollection] }
90+
: optsOrClass,
91+
};
11792
}
11893
}

src/documents/collections/ser-des/ser-des.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ export interface CollDesCtx extends BaseDesCtx<CollCodecSerDesFns> {
3636
/**
3737
* @public
3838
*/
39-
export interface CollectionSerDesConfig extends BaseSerDesConfig<CollCodecs, CollCodecSerDesFns, CollSerCtx, CollDesCtx> {
39+
export interface CollectionSerDesConfig extends BaseSerDesConfig<CollCodecSerDesFns, CollSerCtx, CollDesCtx> {
4040
enableBigNumbers?: boolean,
41-
codecs?: CollCodecs[],
4241
}
4342

4443
/**
@@ -94,6 +93,7 @@ const DefaultCollectionSerDesCfg = {
9493
}
9594
}
9695

96+
9797
if (typeof value === 'object' && value !== null) {
9898
if (value[$SerializeForCollection]) {
9999
if ((resp = value[$SerializeForCollection](ctx))[0] !== CONTINUE) {
@@ -143,7 +143,7 @@ const DefaultCollectionSerDesCfg = {
143143
}
144144
}
145145

146-
if (ctx.keys?.length === 1 && (ctx.keys[0] in codecs.type)) {
146+
if (ctx.keys?.length === 1 && ctx.keys[0] in codecs.type) {
147147
if ((resp = codecs.type[ctx.keys[0]].deserialize?.(key, value, ctx) ?? ctx.continue())[0] !== CONTINUE) {
148148
return resp;
149149
}

0 commit comments

Comments
 (0)