@primate/postgresql 0.1.2 → 0.2.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,23 @@
1
+ import type TypedArray from "@rcompat/type/TypedArray";
2
+ type Param = Array<unknown> | bigint | boolean | Date | null | number | object | string | TypedArray;
3
+ type Validate<T extends {
4
+ [K in keyof T]: Param;
5
+ }> = T;
6
+ type ColumnTypes = Validate<{
7
+ BIGINT: string;
8
+ BOOLEAN: boolean;
9
+ BYTEA: TypedArray;
10
+ FLOAT8: number;
11
+ INTEGER: number;
12
+ "NUMERIC(20, 0)": string;
13
+ "NUMERIC(39, 0)": string;
14
+ REAL: number;
15
+ "SERIAL PRIMARY KEY": number;
16
+ SMALLINT: number;
17
+ TEXT: string;
18
+ TIME: string;
19
+ TIMESTAMP: Date;
20
+ UUID: string;
21
+ }>;
22
+ export type { ColumnTypes as default };
23
+ //# sourceMappingURL=ColumnTypes.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ColumnTypes.js.map
@@ -0,0 +1,46 @@
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
+ import type StoreSchema from "pema/StoreSchema";
7
+ declare const schema: import("pema").ObjectType<{
8
+ database: import("pema/string").StringType;
9
+ host: import("pema").DefaultType<import("pema/string").StringType, "localhost">;
10
+ password: import("pema").OptionalType<import("pema/string").StringType>;
11
+ port: import("pema").DefaultType<import("pema/uint").UintType<"u32">, 5432>;
12
+ username: import("pema").OptionalType<import("pema/string").StringType>;
13
+ }>;
14
+ export default class PostgreSQLDatabase extends Database {
15
+ #private;
16
+ static config: typeof schema.input;
17
+ constructor(config?: typeof schema.input);
18
+ get typemap(): TypeMap<Dict>;
19
+ close(): Promise<void>;
20
+ get schema(): {
21
+ create: (name: string, store: StoreSchema) => Promise<void>;
22
+ delete: (name: string) => Promise<void>;
23
+ };
24
+ create<O extends Dict>(as: As, args: {
25
+ record: DataDict;
26
+ }): Promise<O>;
27
+ read(as: As, args: {
28
+ count: true;
29
+ criteria: DataDict;
30
+ }): Promise<number>;
31
+ read(as: As, args: {
32
+ criteria: DataDict;
33
+ fields?: string[];
34
+ limit?: number;
35
+ sort?: Dict<"asc" | "desc">;
36
+ }): Promise<Dict[]>;
37
+ update(as: As, args: {
38
+ changes: DataDict;
39
+ criteria: DataDict;
40
+ }): Promise<any>;
41
+ delete(as: As, args: {
42
+ criteria: DataDict;
43
+ }): Promise<any>;
44
+ }
45
+ export {};
46
+ //# sourceMappingURL=Database.d.ts.map
@@ -0,0 +1,166 @@
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 pema from "pema";
6
+ import string from "pema/string";
7
+ import uint from "pema/uint";
8
+ import postgres from "postgres";
9
+ const schema = pema({
10
+ database: string,
11
+ host: string.default("localhost"),
12
+ password: string.optional(),
13
+ port: uint.port().default(5432),
14
+ username: string.optional(),
15
+ });
16
+ export default class PostgreSQLDatabase extends Database {
17
+ static config;
18
+ #factory;
19
+ #client;
20
+ constructor(config) {
21
+ super();
22
+ const parsed = schema.parse(config);
23
+ this.#factory = () => postgres({
24
+ db: parsed.database,
25
+ host: parsed.host,
26
+ pass: parsed.password,
27
+ port: parsed.port,
28
+ user: parsed.username,
29
+ });
30
+ }
31
+ #sql() {
32
+ if (this.#client === undefined) {
33
+ this.#client = this.#factory();
34
+ }
35
+ return this.#client;
36
+ }
37
+ #join(parts, sep) {
38
+ const sql = this.#sql();
39
+ if (parts.length === 0)
40
+ return sql ``;
41
+ return parts.slice(1).reduce((acc, p) => sql `${acc}${sep}${p}`, parts[0]);
42
+ }
43
+ async #new(name, store) {
44
+ const sql = this.#sql();
45
+ const table = sql(name);
46
+ const body = Object.entries(store).map(([k, v]) => sql `${sql(k)} ${sql.unsafe(this.column(v.datatype))}`);
47
+ await sql `CREATE TABLE IF NOT EXISTS ${table}
48
+ (${this.#join(body, sql `, `)})`;
49
+ }
50
+ async #drop(name) {
51
+ const sql = this.#sql();
52
+ await sql `DROP TABLE IF EXISTS ${sql(name)};`;
53
+ }
54
+ get typemap() {
55
+ return typemap;
56
+ }
57
+ async close() {
58
+ await this.#sql().end();
59
+ }
60
+ get schema() {
61
+ return {
62
+ create: this.#new.bind(this),
63
+ delete: this.#drop.bind(this),
64
+ };
65
+ }
66
+ async create(as, args) {
67
+ const sql = this.#sql();
68
+ const columns = Object.keys(args.record);
69
+ const binds = await this.bind(as.types, args.record);
70
+ const [result] = await sql `INSERT INTO
71
+ ${sql(as.name)}
72
+ ${columns.length > 0
73
+ ? sql `(${sql(columns)}) VALUES ${sql(binds)}`
74
+ : sql.unsafe("DEFAULT VALUES")}
75
+ RETURNING id;
76
+ `;
77
+ return this.unbind(as.types, { ...args.record, id: result.id });
78
+ }
79
+ #sort(types, sort) {
80
+ maybe(sort).object();
81
+ // validate
82
+ this.toSort(types, sort);
83
+ const sql = this.#sql();
84
+ if (!sort)
85
+ return sql ``;
86
+ const entries = Object.entries(sort);
87
+ if (entries.length === 0)
88
+ return sql ``;
89
+ const items = entries.map(([field, direction]) => sql `${sql(field)} ${sql.unsafe(direction.toUpperCase())}`);
90
+ return sql ` ORDER BY ${this.#join(items, sql `, `)}`;
91
+ }
92
+ #select(types, fields) {
93
+ // validate
94
+ this.toSelect(types, fields);
95
+ const sql = this.#sql();
96
+ if (fields === undefined)
97
+ return sql.unsafe("*");
98
+ return sql(fields);
99
+ }
100
+ #limit(limit) {
101
+ maybe(limit).usize();
102
+ const sql = this.#sql();
103
+ return limit === undefined ? sql `` : sql ` LIMIT ${limit}`;
104
+ }
105
+ #where(types, criteria, nonnull) {
106
+ this.toWhere(types, criteria); // validate
107
+ const sql = this.#sql();
108
+ const entries = Object.entries(criteria);
109
+ if (entries.length === 0)
110
+ return sql ``;
111
+ const clauses = entries.map(([key, raw]) => {
112
+ if (raw === null)
113
+ return sql `${sql(key)} IS NULL`;
114
+ const value = nonnull[key];
115
+ // Handle operator objects
116
+ if (typeof raw === "object") {
117
+ if ("$like" in raw)
118
+ return sql `${sql(key)} LIKE ${value}`;
119
+ // if ("$gte" in raw) return sql`${sql(key)} >= ${nonnull[key]}`;
120
+ }
121
+ return sql `${sql(key)} = ${value}`;
122
+ });
123
+ return sql `WHERE ${this.#join(clauses, sql ` AND `)}`;
124
+ }
125
+ async read(as, args) {
126
+ const sql = this.#sql();
127
+ const table = sql(as.name);
128
+ const criteria = await this.bindCriteria(as.types, args.criteria);
129
+ const where = this.#where(as.types, args.criteria, criteria);
130
+ if (args.count === true) {
131
+ const [{ n }] = await sql `SELECT COUNT(*) AS n FROM ${table} ${where}`;
132
+ return Number(n);
133
+ }
134
+ const sort = this.#sort(as.types, args.sort);
135
+ const limit = this.#limit(args.limit);
136
+ const select = this.#select(as.types, args.fields);
137
+ const records = await sql `
138
+ SELECT ${select}
139
+ FROM ${table}
140
+ ${where}
141
+ ${sort}
142
+ ${limit}
143
+ `;
144
+ return records.map(record => this.unbind(as.types, record));
145
+ }
146
+ async update(as, args) {
147
+ assert(Object.keys(args.criteria).length > 0, "update: no criteria");
148
+ const sql = this.#sql();
149
+ const table = sql(as.name);
150
+ const criteria = await this.bindCriteria(as.types, args.criteria);
151
+ const set_binds = await this.bind(as.types, args.changes);
152
+ const where = this.#where(as.types, args.criteria, criteria);
153
+ const set = sql({ ...set_binds });
154
+ return (await sql `UPDATE ${table} SET ${set} ${where} RETURNING 1;`).length;
155
+ }
156
+ async delete(as, args) {
157
+ assert(Object.keys(args.criteria).length > 0, "delete: no criteria");
158
+ const sql = this.#sql();
159
+ const criteria = await this.bindCriteria(as.types, args.criteria);
160
+ const where = this.#where(as.types, args.criteria, criteria);
161
+ const table = sql(as.name);
162
+ return (await sql `DELETE FROM ${table} ${where} RETURNING 1;`).length;
163
+ }
164
+ ;
165
+ }
166
+ //# 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,105 @@
1
+ function identity(column) {
2
+ return {
3
+ bind: value => value,
4
+ column,
5
+ unbind: value => value,
6
+ };
7
+ }
8
+ function number(column) {
9
+ return {
10
+ bind: value => value,
11
+ column,
12
+ unbind: value => Number(value),
13
+ };
14
+ }
15
+ const typemap = {
16
+ blob: {
17
+ async bind(value) {
18
+ const arrayBuffer = await value.arrayBuffer();
19
+ return new Uint8Array(arrayBuffer);
20
+ },
21
+ column: "BYTEA",
22
+ unbind(value) {
23
+ return new Blob([value], { type: "application/octet-stream" });
24
+ },
25
+ },
26
+ boolean: identity("BOOLEAN"),
27
+ datetime: identity("TIMESTAMP"),
28
+ f32: identity("REAL"),
29
+ f64: identity("FLOAT8"),
30
+ i128: {
31
+ bind(value) {
32
+ return String(value);
33
+ },
34
+ column: "NUMERIC(39, 0)",
35
+ unbind(value) {
36
+ return BigInt(value);
37
+ },
38
+ },
39
+ i16: number("SMALLINT"),
40
+ i32: number("INTEGER"),
41
+ i64: {
42
+ bind(value) {
43
+ return String(value);
44
+ },
45
+ column: "BIGINT",
46
+ unbind(value) {
47
+ return BigInt(value);
48
+ },
49
+ },
50
+ i8: number("SMALLINT"),
51
+ primary: {
52
+ bind(value) {
53
+ if (typeof value === "string" && Number.isInteger(+value)) {
54
+ return Number(value);
55
+ }
56
+ throw new Error(`\`${value}\` is not a valid primary key value`);
57
+ },
58
+ column: "SERIAL PRIMARY KEY",
59
+ unbind(value) {
60
+ return String(value);
61
+ },
62
+ },
63
+ string: identity("TEXT"),
64
+ time: identity("TIME"),
65
+ u128: {
66
+ bind(value) {
67
+ return String(value);
68
+ },
69
+ column: "NUMERIC(39, 0)",
70
+ unbind(value) {
71
+ return BigInt(value);
72
+ },
73
+ },
74
+ u16: number("INTEGER"),
75
+ u32: {
76
+ bind(value) {
77
+ return String(value);
78
+ },
79
+ column: "BIGINT",
80
+ unbind(value) {
81
+ return Number(value);
82
+ },
83
+ },
84
+ u64: {
85
+ bind(value) {
86
+ return String(value);
87
+ },
88
+ column: "NUMERIC(20, 0)",
89
+ unbind(value) {
90
+ return BigInt(value);
91
+ },
92
+ },
93
+ u8: number("SMALLINT"),
94
+ url: {
95
+ bind(value) {
96
+ return value.toString();
97
+ },
98
+ column: "TEXT",
99
+ unbind(value) {
100
+ return new URL(value);
101
+ },
102
+ },
103
+ };
104
+ export default typemap;
105
+ //# 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,44 @@
1
1
  {
2
2
  "name": "@primate/postgresql",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Primate PostgreSQL database",
5
- "homepage": "https://primatejs.com/modules/postgresql",
6
- "bugs": "https://github.com/primatejs/primate/issues",
5
+ "homepage": "https://primate.run/docs/database/postgresql",
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/postgresql"
16
17
  },
17
18
  "dependencies": {
18
- "@rcompat/invariant": "^0.5.0",
19
- "@rcompat/object": "^0.5.0",
20
- "postgres": "^3.4.4",
21
- "@primate/core": "^0.1.4",
22
- "@primate/store": "^0.25.1"
19
+ "@rcompat/assert": "^0.3.1",
20
+ "@rcompat/record": "^0.9.1",
21
+ "pema": "",
22
+ "postgres": "^3.4.7",
23
+ "@primate/core": "^0.2.0"
23
24
  },
24
25
  "type": "module",
25
26
  "imports": {
26
27
  "#*": {
27
- "@primate/lt": "./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"
33
43
  }
34
44
  }
package/src/default.js DELETED
@@ -1,12 +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
- username,
9
- password,
10
- } = {}) => ({
11
- serve: serve({ host, port, database, username, password }),
12
- });
@@ -1,97 +0,0 @@
1
- import typemap from "#typemap";
2
- import make_sort from "@primate/store/sql/make-sort";
3
- import filter from "@rcompat/object/filter";
4
- import valmap from "@rcompat/object/valmap";
5
-
6
- const filter_null = object => filter(object, ([, value]) => value !== null);
7
- const filter_nulls = objects => objects.map(object => filter_null(object));
8
-
9
- export default class Connection {
10
- schema = {
11
- create: async (name, description) => {
12
- const { connection } = this;
13
- const body =
14
- Object.entries(valmap(description, value => typemap(value.base)))
15
- .map(([column, dataType]) => `"${column}" ${dataType}`).join(",");
16
- await connection`
17
- create table if not exists
18
- ${connection(name)} (${connection.unsafe(body)})
19
- `;
20
- },
21
- delete: async name => {
22
- const { connection } = this;
23
- await connection`drop table if exists ${connection(name)};`;
24
- },
25
- };
26
-
27
- constructor(connection) {
28
- this.connection = connection;
29
- }
30
-
31
- async find(collection, criteria = {}, projection = [], options = {}) {
32
- const { connection } = this;
33
- const rest = make_sort(options);
34
- const select = projection.length === 0 ? "*" : projection.join(", ");
35
- return filter_nulls(await connection`
36
- select ${connection.unsafe(select)}
37
- from ${connection(collection)}
38
- where ${Object.entries(criteria).reduce((acc, [key, value]) =>
39
- connection`${acc} and ${connection(key)} = ${value}`, connection`true`)}
40
- ${connection.unsafe(rest)}`);
41
- }
42
-
43
- async count(collection, criteria = {}) {
44
- const { connection } = this;
45
- const [{ count }] = await connection`
46
- select count(*)
47
- from ${connection(collection)}
48
- where ${Object.entries(criteria).reduce((acc, [key, value]) =>
49
- connection`${acc} and ${connection(key)} = ${value}`, connection`true`)}
50
- `;
51
- return Number(count);
52
- }
53
-
54
- async get(collection, primary, value) {
55
- const { connection } = this;
56
- const [result] = await connection`
57
- select *
58
- from ${connection(collection)}
59
- where ${connection(primary)}=${value};
60
- `;
61
- return result === undefined ? result : filter_null(result);
62
- }
63
-
64
- async insert(collection, primary, document) {
65
- const { connection } = this;
66
- const columns = Object.keys(document);
67
- const [result] = await this.connection`insert into
68
- ${connection(collection)}
69
- ${columns.length > 0
70
- ? connection`(${connection(columns)}) values ${connection(document)}`
71
- : connection.unsafe("default values")}
72
- returning *;
73
- `;
74
- return filter_null(result);
75
- }
76
-
77
- async update(collection, criteria = {}, delta = {}) {
78
- const { connection } = this;
79
- return (await connection`
80
- update ${connection(collection)}
81
- set ${connection({ ...delta })}
82
- where ${Object.entries(criteria).reduce((acc, [key, value]) =>
83
- connection`${acc} and ${connection(key)} = ${value}`, connection`true`)}
84
- returning *;
85
- `).length;
86
- }
87
-
88
- async delete(collection, criteria = {}) {
89
- const { connection } = this;
90
- return (await connection`
91
- delete from ${connection(collection)}
92
- where ${Object.entries(criteria).reduce((acc, [key, value]) =>
93
- connection`${acc} and ${connection(key)} = ${value}`, connection`true`)}
94
- returning *;
95
- `).length;
96
- }
97
- }
@@ -1,10 +0,0 @@
1
- import Driver from "postgres";
2
-
3
- export default ({ host, port, database, username, password }) =>
4
- new Driver({
5
- host,
6
- port,
7
- db: database,
8
- user: username,
9
- pass: password,
10
- });
@@ -1,4 +0,0 @@
1
- export default {
2
- host: "localhost",
3
- port: 5432,
4
- };
@@ -1,63 +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 numeric from "@rcompat/invariant/numeric";
6
-
7
- export default options => async () => {
8
- const client = connect(options);
9
-
10
- const types = {
11
- primary: {
12
- validate(value) {
13
- if (typeof value === "number" || numeric(value)) {
14
- return Number(value);
15
- }
16
- throw new Error(`\`${value}\` is not a valid primary key value`);
17
- },
18
- ...ident,
19
- },
20
- object: {
21
- in(value) {
22
- return JSON.stringify(value);
23
- },
24
- out(value) {
25
- return JSON.parse(value);
26
- },
27
- },
28
- number: ident,
29
- bigint: {
30
- in(value) {
31
- return value.toString();
32
- },
33
- out(value) {
34
- return BigInt(value);
35
- },
36
- },
37
- boolean: ident,
38
- date: {
39
- in(value) {
40
- return value;
41
- },
42
- out(value) {
43
- return new Date(value);
44
- },
45
- },
46
- string: ident,
47
- };
48
-
49
- return {
50
- name: "@primate/postgresql",
51
- types,
52
- async transact(stores) {
53
- return (others, next) =>
54
- client.begin(async connection => {
55
- const facade = new Facade(connection);
56
- return next([
57
- ...others, ...stores.map(([name, store]) =>
58
- [name, wrap(store, facade, types)]),
59
- ]);
60
- });
61
- },
62
- };
63
- };
@@ -1,21 +0,0 @@
1
- const types = {
2
- /* array */
3
- blob: "bytea",
4
- boolean: "boolean",
5
- datetime: "timestamp",
6
- embedded: "text",
7
- f64: "real",
8
- i8: "smallint",
9
- i16: "integer",
10
- i32: "bigint",
11
- i64: "decimal",
12
- json: "json",
13
- primary: "serial primary key",
14
- string: "text",
15
- time: "time",
16
- u8: "smallint",
17
- u16: "integer",
18
- u32: "integer",
19
- };
20
-
21
- export default value => types[value];