@primate/mongodb 0.1.3 → 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.
@@ -0,0 +1,21 @@
1
+ import type TypedArray from "@rcompat/type/TypedArray";
2
+ import type { Binary, Decimal128, ObjectId } from "mongodb";
3
+ type Param = bigint | Binary | boolean | Date | Decimal128 | number | ObjectId | string | TypedArray;
4
+ type Validate<T extends {
5
+ [K in keyof T]: Param;
6
+ }> = T;
7
+ type ColumnTypes = Validate<{
8
+ BINARY: Binary;
9
+ BOOLEAN: boolean;
10
+ DATE: Date;
11
+ DECIMAL: Decimal128;
12
+ DOUBLE: number;
13
+ INT: number;
14
+ LONG: bigint;
15
+ PRIMARY: ObjectId;
16
+ STRING: string;
17
+ TIME: string;
18
+ UUID: string;
19
+ }>;
20
+ export type { ColumnTypes as default };
21
+ //# sourceMappingURL=ColumnTypes.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ColumnTypes.js.map
@@ -0,0 +1,45 @@
1
+ import Database from "@primate/core/Database";
2
+ import type As from "@primate/core/database/As";
3
+ import type DataDict from "@primate/core/database/DataDict";
4
+ import type TypeMap from "@primate/core/database/TypeMap";
5
+ import type Dict from "@rcompat/type/Dict";
6
+ declare const schema: import("pema").ObjectType<{
7
+ database: import("pema/string").StringType;
8
+ host: import("pema").DefaultType<import("pema/string").StringType, "localhost">;
9
+ password: import("pema").OptionalType<import("pema/string").StringType>;
10
+ port: import("pema").DefaultType<import("pema/uint").UintType<"u32">, 27017>;
11
+ username: import("pema").OptionalType<import("pema/string").StringType>;
12
+ }>;
13
+ export default class MongoDBDatabase extends Database {
14
+ #private;
15
+ static config: typeof schema.input;
16
+ constructor(config?: typeof schema.input);
17
+ get typemap(): TypeMap<Dict>;
18
+ close(): Promise<void>;
19
+ get schema(): {
20
+ create: () => undefined;
21
+ delete: (name: string) => Promise<void>;
22
+ };
23
+ create<O extends Dict>(as: As, args: {
24
+ record: DataDict;
25
+ }): Promise<O>;
26
+ read(as: As, args: {
27
+ count: true;
28
+ criteria: DataDict;
29
+ }): Promise<number>;
30
+ read(as: As, args: {
31
+ criteria: DataDict;
32
+ fields?: string[];
33
+ limit?: number;
34
+ sort?: Dict<"asc" | "desc">;
35
+ }): Promise<Dict[]>;
36
+ update(as: As, args: {
37
+ changes: DataDict;
38
+ criteria: DataDict;
39
+ }): Promise<number>;
40
+ delete(as: As, args: {
41
+ criteria: DataDict;
42
+ }): Promise<number>;
43
+ }
44
+ export {};
45
+ //# sourceMappingURL=Database.d.ts.map
@@ -0,0 +1,147 @@
1
+ import typemap from "#typemap";
2
+ import Database from "@primate/core/Database";
3
+ import assert from "@rcompat/assert";
4
+ import maybe from "@rcompat/assert/maybe";
5
+ import empty from "@rcompat/record/empty";
6
+ import entries from "@rcompat/record/entries";
7
+ import toQueryString from "@rcompat/record/toQueryString";
8
+ import { MongoClient } from "mongodb";
9
+ import pema from "pema";
10
+ import string from "pema/string";
11
+ import uint from "pema/uint";
12
+ const schema = pema({
13
+ database: string,
14
+ host: string.default("localhost"),
15
+ password: string.optional(),
16
+ port: uint.port().default(27017),
17
+ username: string.optional(),
18
+ });
19
+ function make_limit(limit) {
20
+ maybe(limit).usize();
21
+ if (limit === undefined) {
22
+ return 0;
23
+ }
24
+ return limit;
25
+ }
26
+ ;
27
+ const null_to_set_unset = (changes) => {
28
+ const entry_changes = entries(changes);
29
+ const $set = entry_changes.filter(([, value]) => value !== null).get();
30
+ const $unset = entry_changes.filter(([, value]) => value === null).get();
31
+ return { $set, $unset };
32
+ };
33
+ const url_params = { directConnection: "true", replicaSet: "rs0" };
34
+ export default class MongoDBDatabase extends Database {
35
+ static config;
36
+ #factory;
37
+ #name;
38
+ #client;
39
+ constructor(config) {
40
+ super();
41
+ const { database, host, port } = schema.parse(config);
42
+ const url = `mongodb://${host}:${port}?${toQueryString(url_params)}`;
43
+ const client = new MongoClient(url);
44
+ this.#name = database;
45
+ this.#factory = async () => {
46
+ await client.connect();
47
+ return client;
48
+ };
49
+ }
50
+ async #get(collection) {
51
+ if (this.#client === undefined) {
52
+ this.#client = await this.#factory();
53
+ }
54
+ return this.#client.db(this.#name).collection(collection);
55
+ }
56
+ get typemap() {
57
+ return typemap;
58
+ }
59
+ async close() {
60
+ await this.#client.close();
61
+ }
62
+ async #drop(name) {
63
+ await (await this.#get(name)).drop();
64
+ }
65
+ get schema() {
66
+ return {
67
+ // noop
68
+ create: () => undefined,
69
+ delete: this.#drop.bind(this),
70
+ };
71
+ }
72
+ async #bind(object, types) {
73
+ const prepared = Object.fromEntries(Object.entries(object)
74
+ .map(([key, value]) => {
75
+ if (value === null)
76
+ return [key, null];
77
+ if (typeof value === "object" && "$like" in value) {
78
+ const pattern = String(value.$like);
79
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
80
+ const $regex = `^${escaped.replace(/%/g, ".*").replace(/_/g, ".")}$`;
81
+ return [key, { $regex }];
82
+ }
83
+ return [key, value];
84
+ }));
85
+ const { id, ...rest } = await this.bind(types, prepared);
86
+ return id === undefined
87
+ ? rest
88
+ : { _id: id, ...rest };
89
+ }
90
+ #unbind(object, types) {
91
+ const { _id: id, ...rest } = object;
92
+ return this.unbind(types, id === undefined ? rest : { id, ...rest });
93
+ }
94
+ async create(as, args) {
95
+ const binds = await this.#bind(args.record, as.types);
96
+ const { insertedId } = await (await this.#get(as.name)).insertOne(binds);
97
+ return this.#unbind({ ...args.record, _id: insertedId }, as.types);
98
+ }
99
+ async read(as, args) {
100
+ this.toSelect(as.types, args.fields);
101
+ this.toSort(as.types, args.sort);
102
+ const binds = await this.#bind(args.criteria, as.types);
103
+ if (args.count === true) {
104
+ return (await this.#get(as.name)).countDocuments(binds);
105
+ }
106
+ const fields = args.fields ?? [];
107
+ const mapped = fields.map(f => f === "id" ? "_id" : f);
108
+ const sort = args.sort === undefined || empty(args.sort)
109
+ ? {}
110
+ : { sort: args.sort };
111
+ const select = mapped.length === 0
112
+ ? {}
113
+ : {
114
+ projection: (() => {
115
+ const out = {};
116
+ if (!mapped.includes("_id")) {
117
+ out._id = 0;
118
+ }
119
+ for (const field of mapped) {
120
+ out[field] = 1;
121
+ }
122
+ return out;
123
+ })(),
124
+ };
125
+ const options = { ...select, ...sort, useBigInt64: true };
126
+ const records = await (await this.#get(as.name))
127
+ .find(binds, options)
128
+ .limit(make_limit(args.limit))
129
+ .toArray();
130
+ return records.map(record => this.#unbind(record, as.types));
131
+ }
132
+ async update(as, args) {
133
+ assert(Object.keys(args.criteria).length > 0, "update: no criteria");
134
+ const criteria_binds = await this.#bind(args.criteria, as.types);
135
+ const changes_binds = await this.#bind(args.changes, as.types);
136
+ const collection = await this.#get(as.name);
137
+ return (await collection
138
+ .updateMany(criteria_binds, null_to_set_unset(changes_binds)))
139
+ .modifiedCount;
140
+ }
141
+ async delete(as, args) {
142
+ assert(Object.keys(args.criteria).length > 0, "delete: no criteria");
143
+ const binds = await this.#bind(args.criteria, as.types);
144
+ return (await ((await this.#get(as.name)).deleteMany(binds))).deletedCount;
145
+ }
146
+ }
147
+ //# sourceMappingURL=Database.js.map
@@ -0,0 +1,5 @@
1
+ import type ColumnTypes from "#ColumnTypes";
2
+ import type TypeMap from "@primate/core/database/TypeMap";
3
+ declare const typemap: TypeMap<ColumnTypes>;
4
+ export default typemap;
5
+ //# sourceMappingURL=typemap.d.ts.map
@@ -0,0 +1,86 @@
1
+ import { Binary, Decimal128, ObjectId } from "mongodb";
2
+ function identity(column) {
3
+ return {
4
+ bind: value => value,
5
+ column,
6
+ unbind: value => value,
7
+ };
8
+ }
9
+ function decimal() {
10
+ return {
11
+ bind(value) {
12
+ return new Decimal128(value.toString());
13
+ },
14
+ column: "DECIMAL",
15
+ unbind(value) {
16
+ return BigInt(value.toString());
17
+ },
18
+ };
19
+ }
20
+ const typemap = {
21
+ blob: {
22
+ async bind(value) {
23
+ const arrayBuffer = await value.arrayBuffer();
24
+ return new Binary(new Uint8Array(arrayBuffer));
25
+ },
26
+ column: "BINARY",
27
+ unbind(value) {
28
+ return new Blob([value.toBits()], { type: "application/octet-stream" });
29
+ },
30
+ },
31
+ boolean: identity("BOOLEAN"),
32
+ datetime: identity("DATE"),
33
+ f32: identity("DOUBLE"),
34
+ f64: identity("DOUBLE"),
35
+ i128: {
36
+ bind(value) {
37
+ return String(value);
38
+ },
39
+ column: "STRING",
40
+ unbind(value) {
41
+ return BigInt(value);
42
+ },
43
+ },
44
+ i16: identity("INT"),
45
+ i32: identity("INT"),
46
+ i64: identity("LONG"),
47
+ i8: identity("INT"),
48
+ primary: {
49
+ bind(value) {
50
+ if (typeof value === "string") {
51
+ return new ObjectId(value);
52
+ }
53
+ throw new Error(`\`${value}\` is not a valid primary key value`);
54
+ },
55
+ column: "PRIMARY",
56
+ unbind(value) {
57
+ return String(value);
58
+ },
59
+ },
60
+ string: identity("STRING"),
61
+ time: identity("TIME"),
62
+ u128: {
63
+ bind(value) {
64
+ return String(value);
65
+ },
66
+ column: "STRING",
67
+ unbind(value) {
68
+ return BigInt(value);
69
+ },
70
+ },
71
+ u16: identity("INT"),
72
+ u32: identity("INT"),
73
+ u64: decimal(),
74
+ u8: identity("INT"),
75
+ url: {
76
+ bind(value) {
77
+ return value.toString();
78
+ },
79
+ column: "STRING",
80
+ unbind(value) {
81
+ return new URL(value);
82
+ },
83
+ },
84
+ };
85
+ export default typemap;
86
+ //# sourceMappingURL=typemap.js.map
@@ -0,0 +1,4 @@
1
+ import Database from "#Database";
2
+ declare const _default: (config: typeof Database.config) => Database;
3
+ export default _default;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ import Database from "#Database";
2
+ export default (config) => new Database(config);
3
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,34 +1,45 @@
1
1
  {
2
2
  "name": "@primate/mongodb",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "Primate MongoDB database",
5
- "homepage": "https://primatejs.com/modules/mongodb",
6
- "bugs": "https://github.com/primatejs/primate/issues",
5
+ "homepage": "https://primate.run/docs/database/mongodb",
6
+ "bugs": "https://github.com/primate-run/primate/issues",
7
7
  "license": "MIT",
8
8
  "files": [
9
- "src/**/*.js",
10
- "!src/**/*.spec.js"
9
+ "/lib/**/*.js",
10
+ "/lib/**/*.d.ts",
11
+ "!/**/*.spec.*"
11
12
  ],
12
13
  "repository": {
13
14
  "type": "git",
14
- "url": "https://github.com/primatejs/primate",
15
+ "url": "https://github.com/primate-run/primate",
15
16
  "directory": "packages/mongodb"
16
17
  },
17
18
  "dependencies": {
18
- "@rcompat/invariant": "^0.5.0",
19
- "@rcompat/object": "^0.5.0",
20
- "mongodb": "^6.8.0",
21
- "@primate/core": "^0.1.9",
22
- "@primate/store": "^0.25.4"
19
+ "@rcompat/assert": "^0.3.1",
20
+ "@rcompat/record": "^0.9.1",
21
+ "mongodb": "^6.17.0",
22
+ "pema": "",
23
+ "@primate/core": "^0.3.0"
23
24
  },
24
25
  "type": "module",
25
26
  "imports": {
26
27
  "#*": {
27
- "livetypes": "./src/private/*.js",
28
- "default": "./src/private/*.js"
28
+ "apekit": "./src/private/*.ts",
29
+ "default": "./lib/private/*.js"
29
30
  }
30
31
  },
31
32
  "exports": {
32
- ".": "./src/default.js"
33
+ ".": {
34
+ "apekit": "./src/public/index.ts",
35
+ "default": "./lib/public/index.js"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "npm run clean && tsc",
40
+ "clean": "rm -rf ./lib",
41
+ "lint": "eslint .",
42
+ "test": "npm run build && npx proby",
43
+ "start:db": "sudo mongod --dbpath /var/lib/mongodb"
33
44
  }
34
45
  }
package/src/default.js DELETED
@@ -1,10 +0,0 @@
1
- import defaults from "#defaults";
2
- import serve from "#serve";
3
-
4
- export default ({
5
- host = defaults.host,
6
- port = defaults.port,
7
- database,
8
- } = {}) => ({
9
- serve: serve({ host, port, database }),
10
- });
@@ -1,89 +0,0 @@
1
- import maybe from "@rcompat/invariant/maybe";
2
- import empty from "@rcompat/object/empty";
3
- import filter from "@rcompat/object/filter";
4
- import valmap from "@rcompat/object/valmap";
5
-
6
- const toid = ({ _id, ...rest }) => ({ id: _id, ...rest });
7
- const to_id = ({ id, ...rest }) => id === undefined
8
- ? rest
9
- : { _id: id, ...rest };
10
- const cid = criteria => criteria.id === undefined ? criteria : to_id(criteria);
11
- const null_to_set_unset = delta => {
12
- const $set = filter(delta, ([, value]) => value !== null);
13
- const $unset = filter(delta, ([, value]) => value === null);
14
- return { $set, $unset };
15
- };
16
-
17
- const make_sort = ({ sort = {} } = {}) => {
18
- maybe(sort).object();
19
-
20
- return valmap(sort, value => value === "asc" ? 1 : -1);
21
- };
22
-
23
- export default class Facade {
24
- schema = {
25
- create: async _ => {
26
- // noop
27
- },
28
- delete: async name => {
29
- await this.#by(name).drop();
30
- },
31
- };
32
-
33
- constructor(connection, session) {
34
- this.connection = connection;
35
- this.session = session;
36
- }
37
-
38
- #by(name) {
39
- return this.connection.collection(name);
40
- }
41
-
42
- get #options() {
43
- return { session: this.session };
44
- }
45
-
46
- async find(name, criteria = {}, projection = [], options = {}) {
47
- const sort = make_sort(options);
48
- const $options = {
49
- ...this.#options,
50
- ...projection.length === 0
51
- ? {}
52
- : {
53
- projection: {
54
- // erase _id unless explicit in projection
55
- _id: 0,
56
- ...Object.fromEntries(projection.map(field => [field, 1])),
57
- },
58
- },
59
- ...empty(sort) ? {} : { sort },
60
- };
61
- return (await this.#by(name).find(cid(criteria), $options).toArray())
62
- .map(document => toid(document));
63
- }
64
-
65
- async count(name, criteria = {}) {
66
- return this.#by(name).countDocuments(criteria, this.#options);
67
- }
68
-
69
- async get(name, primary, value) {
70
- const result = await this.#by(name).findOne({ _id: value }, this.#options);
71
- return result === null ? undefined : toid(result);
72
- }
73
-
74
- async insert(name, primary, document) {
75
- await this.#by(name).insertOne(document, this.#options);
76
- return toid(document);
77
- }
78
-
79
- async update(name, criteria = {}, delta = {}) {
80
- return (await this.#by(name)
81
- .updateMany(cid(criteria), null_to_set_unset(delta), this.#options))
82
- .modifiedCount;
83
- }
84
-
85
- async delete(name, criteria = {}) {
86
- return (await this.#by(name).deleteMany(cid(criteria), this.#options))
87
- .deletedCount;
88
- }
89
- }
@@ -1,8 +0,0 @@
1
- import { MongoClient } from "mongodb";
2
-
3
- export default async ({ host, port }) => {
4
- const url = `mongodb://${host}:${port}?replicaSet=rs0&directConnection=true`;
5
- const client = new MongoClient(url);
6
- await client.connect();
7
- return client;
8
- };
@@ -1,4 +0,0 @@
1
- export default {
2
- host: "localhost",
3
- port: 27017,
4
- };
@@ -1,62 +0,0 @@
1
- import Facade from "#Facade";
2
- import connect from "#connect";
3
- import ident from "@primate/store/core/ident";
4
- import wrap from "@primate/store/core/wrap";
5
- import { Decimal128, ObjectId } from "mongodb";
6
-
7
- export default ({ host, port, database } = {}) => async _ => {
8
- const client = await connect({ host, port });
9
-
10
- const types = {
11
- primary: {
12
- validate(value) {
13
- /* TODO: check that has valid objectid form */
14
- if (typeof value === "string") {
15
- return value;
16
- }
17
- throw new Error(`\`${value}\` is not a valid primary key value`);
18
- },
19
- in(value) {
20
- return new ObjectId(value);
21
- },
22
- out(value) {
23
- return value.toString();
24
- },
25
- },
26
- object: ident,
27
- number: ident,
28
- bigint: {
29
- in(value) {
30
- return new Decimal128(value.toString());
31
- },
32
- out(value) {
33
- return BigInt(value.toString());
34
- },
35
- },
36
- boolean: ident,
37
- date: ident,
38
- string: ident,
39
- };
40
-
41
- return {
42
- name: "@primate/mongodb",
43
- types,
44
- async transact(stores) {
45
- return async (others, next) => {
46
- const session = client.startSession();
47
- const facade = new Facade(client.db(database), session);
48
-
49
- try {
50
- return await session.withTransaction(async () => {
51
- const response = await next([...others, ...stores.map(([_, store]) =>
52
- [_, wrap(store, facade, types)]),
53
- ]);
54
- return response;
55
- });
56
- } finally {
57
- session.endSession();
58
- }
59
- };
60
- },
61
- };
62
- };