@junejs/juno 0.0.30-dev.1 → 0.0.30
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/dist/ast-BsvUEqhl.d.ts +48 -0
- package/dist/ast.d.ts +2 -0
- package/dist/ast.js +0 -0
- package/dist/batch-c_vy0gyx.d.ts +72 -0
- package/dist/batch.d.ts +2 -0
- package/dist/batch.js +88 -0
- package/dist/compiler-PkKpLl6z.d.ts +29 -0
- package/dist/compiler.d.ts +2 -0
- package/dist/compiler.js +122 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +205 -0
- package/dist/tag-B0HQho7q.d.ts +11 -0
- package/dist/tag.d.ts +2 -0
- package/dist/tag.js +60 -0
- package/dist/types-qDjFvlZu.d.ts +5 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +96 -0
- package/package.json +13 -6
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/ast.d.ts
|
|
2
|
+
type Node = SelectNode | InsertNode | UpdateNode | DeleteNode | UpsertNode;
|
|
3
|
+
type CmpOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "like";
|
|
4
|
+
type Predicate = {
|
|
5
|
+
col: string;
|
|
6
|
+
op: CmpOp;
|
|
7
|
+
} | {
|
|
8
|
+
col: string;
|
|
9
|
+
op: "in";
|
|
10
|
+
arity: number;
|
|
11
|
+
};
|
|
12
|
+
type OrderTerm = {
|
|
13
|
+
col: string;
|
|
14
|
+
dir: "asc" | "desc";
|
|
15
|
+
};
|
|
16
|
+
type SelectNode = {
|
|
17
|
+
kind: "select";
|
|
18
|
+
from: string;
|
|
19
|
+
where: Predicate[];
|
|
20
|
+
orderBy?: OrderTerm[];
|
|
21
|
+
limit?: number | "param";
|
|
22
|
+
offset?: "param";
|
|
23
|
+
};
|
|
24
|
+
type InsertNode = {
|
|
25
|
+
kind: "insert";
|
|
26
|
+
into: string;
|
|
27
|
+
columns: string[];
|
|
28
|
+
};
|
|
29
|
+
type UpdateNode = {
|
|
30
|
+
kind: "update";
|
|
31
|
+
table: string;
|
|
32
|
+
set: string[];
|
|
33
|
+
where: string[];
|
|
34
|
+
};
|
|
35
|
+
type DeleteNode = {
|
|
36
|
+
kind: "delete";
|
|
37
|
+
from: string;
|
|
38
|
+
where: string[];
|
|
39
|
+
};
|
|
40
|
+
type UpsertNode = {
|
|
41
|
+
kind: "upsert";
|
|
42
|
+
into: string;
|
|
43
|
+
columns: string[];
|
|
44
|
+
conflict: string[];
|
|
45
|
+
update: string[];
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
export { OrderTerm as a, UpdateNode as c, Node as i, UpsertNode as l, DeleteNode as n, Predicate as o, InsertNode as r, SelectNode as s, CmpOp as t };
|
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as OrderTerm, c as UpdateNode, i as Node, l as UpsertNode, n as DeleteNode, o as Predicate, r as InsertNode, s as SelectNode, t as CmpOp } from "./ast-BsvUEqhl.js";
|
|
2
|
+
export { CmpOp, DeleteNode, InsertNode, Node, OrderTerm, Predicate, SelectNode, UpdateNode, UpsertNode };
|
package/dist/ast.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import "./tag-B0HQho7q.js";
|
|
2
|
+
import "./compiler-PkKpLl6z.js";
|
|
3
|
+
import "./types-qDjFvlZu.js";
|
|
4
|
+
import { JuneDb, RunResult } from "@junejs/core/resources";
|
|
5
|
+
import { DataLayer } from "@junejs/core/config";
|
|
6
|
+
//#region src/index.d.ts
|
|
7
|
+
type Row = Record<string, unknown>;
|
|
8
|
+
interface Schema {}
|
|
9
|
+
type TableNames = keyof Schema & string;
|
|
10
|
+
type RowOf<K extends TableNames> = Schema[K] & Row;
|
|
11
|
+
type Operators = {
|
|
12
|
+
eq?: unknown;
|
|
13
|
+
ne?: unknown;
|
|
14
|
+
gt?: unknown;
|
|
15
|
+
gte?: unknown;
|
|
16
|
+
lt?: unknown;
|
|
17
|
+
lte?: unknown;
|
|
18
|
+
in?: unknown[];
|
|
19
|
+
like?: string;
|
|
20
|
+
};
|
|
21
|
+
type Where<T> = { [K in keyof T]?: T[K] | Operators; };
|
|
22
|
+
type OrderBy<T> = { [K in keyof T]?: "asc" | "desc"; };
|
|
23
|
+
declare class Table<T extends Row = Row> {
|
|
24
|
+
private readonly db;
|
|
25
|
+
private readonly name;
|
|
26
|
+
private readonly loaders;
|
|
27
|
+
constructor(db: JuneDb, name: string, loaders?: Map<string, unknown>);
|
|
28
|
+
private get compiler();
|
|
29
|
+
all(where?: Where<T>, opts?: {
|
|
30
|
+
orderBy?: OrderBy<T>;
|
|
31
|
+
limit?: number;
|
|
32
|
+
offset?: number;
|
|
33
|
+
}): Promise<T[]>;
|
|
34
|
+
findBy(where: Partial<T>): Promise<T | undefined>;
|
|
35
|
+
private ambientLoader;
|
|
36
|
+
private ambientListLoader;
|
|
37
|
+
insert(values: Partial<T>): Promise<RunResult>;
|
|
38
|
+
upsert(values: Partial<T>, opts: {
|
|
39
|
+
onConflict: string | string[];
|
|
40
|
+
}): Promise<T | undefined>;
|
|
41
|
+
update(where: Partial<T>, values: Partial<T>): Promise<RunResult>;
|
|
42
|
+
delete(where: Partial<T>): Promise<RunResult>;
|
|
43
|
+
loader(key?: string): Loader<string | number, T>;
|
|
44
|
+
}
|
|
45
|
+
type Juno = {
|
|
46
|
+
table<K extends TableNames>(name: K): Table<RowOf<K>>;
|
|
47
|
+
table<T extends Row = Row>(name: string): Table<T>;
|
|
48
|
+
db: JuneDb;
|
|
49
|
+
reads(...tables: string[]): void;
|
|
50
|
+
writes(...tables: string[]): void;
|
|
51
|
+
};
|
|
52
|
+
declare function juno(db: JuneDb): Juno;
|
|
53
|
+
declare function installDataLayer(): void;
|
|
54
|
+
declare function junoDataLayer(): DataLayer;
|
|
55
|
+
declare function table<K extends TableNames>(name: K): Table<RowOf<K>>;
|
|
56
|
+
declare function table<T extends Row = Row>(name: string): Table<T>;
|
|
57
|
+
declare function reads(...tables: string[]): void;
|
|
58
|
+
declare function writes(...tables: string[]): void;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/batch.d.ts
|
|
61
|
+
type Loader<K, V> = {
|
|
62
|
+
load(key: K): Promise<V | null>;
|
|
63
|
+
};
|
|
64
|
+
declare function createLoader<K, V>(batchFn: (keys: K[]) => Promise<V[]>, keyOf: (value: V) => K): Loader<K, V>;
|
|
65
|
+
declare function tableLoader<V extends Row = Row>(db: JuneDb, table: string, key?: string): Loader<string | number, V>;
|
|
66
|
+
type ListLoader<K, V> = {
|
|
67
|
+
load(key: K): Promise<V[]>;
|
|
68
|
+
};
|
|
69
|
+
declare function createGroupLoader<K, V>(batchFn: (keys: K[]) => Promise<V[]>, keyOf: (value: V) => K): ListLoader<K, V>;
|
|
70
|
+
declare function tableListLoader<V extends Row = Row>(db: JuneDb, table: string, key?: string): ListLoader<string | number, V>;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { juno as _, tableListLoader as a, table as b, Operators as c, RowOf as d, Schema as f, installDataLayer as g, Where as h, createLoader as i, OrderBy as l, TableNames as m, Loader as n, tableLoader as o, Table as p, createGroupLoader as r, Juno as s, ListLoader as t, Row as u, junoDataLayer as v, writes as x, reads as y };
|
package/dist/batch.d.ts
ADDED
package/dist/batch.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { dialectFor } from "./compiler.js";
|
|
2
|
+
import { recordTableRead } from "@junejs/core/instrumentation";
|
|
3
|
+
//#region src/batch.ts
|
|
4
|
+
function inQuery(db, table, key, keys) {
|
|
5
|
+
recordTableRead(table);
|
|
6
|
+
const node = {
|
|
7
|
+
kind: "select",
|
|
8
|
+
from: table,
|
|
9
|
+
where: [{
|
|
10
|
+
col: key,
|
|
11
|
+
op: "in",
|
|
12
|
+
arity: keys.length
|
|
13
|
+
}]
|
|
14
|
+
};
|
|
15
|
+
return db.query(dialectFor(db).compile(node), keys);
|
|
16
|
+
}
|
|
17
|
+
function createLoader(batchFn, keyOf) {
|
|
18
|
+
let queue = [];
|
|
19
|
+
let scheduled = false;
|
|
20
|
+
function flush() {
|
|
21
|
+
const batch = queue;
|
|
22
|
+
queue = [];
|
|
23
|
+
scheduled = false;
|
|
24
|
+
batchFn([...new Set(batch.map((b) => b.key))]).then((values) => {
|
|
25
|
+
const byKey = new Map(values.map((v) => [keyOf(v), v]));
|
|
26
|
+
for (const b of batch) b.resolve(byKey.get(b.key) ?? null);
|
|
27
|
+
}, (err) => {
|
|
28
|
+
for (const b of batch) b.reject(err);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return { load(key) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
queue.push({
|
|
34
|
+
key,
|
|
35
|
+
resolve,
|
|
36
|
+
reject
|
|
37
|
+
});
|
|
38
|
+
if (!scheduled) {
|
|
39
|
+
scheduled = true;
|
|
40
|
+
queueMicrotask(flush);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
45
|
+
function tableLoader(db, table, key = "id") {
|
|
46
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`unsafe SQL identifier: ${table}.${key}`);
|
|
47
|
+
return createLoader((keys) => inQuery(db, table, key, keys), (row) => row[key]);
|
|
48
|
+
}
|
|
49
|
+
function createGroupLoader(batchFn, keyOf) {
|
|
50
|
+
let queue = [];
|
|
51
|
+
let scheduled = false;
|
|
52
|
+
function flush() {
|
|
53
|
+
const batch = queue;
|
|
54
|
+
queue = [];
|
|
55
|
+
scheduled = false;
|
|
56
|
+
batchFn([...new Set(batch.map((b) => b.key))]).then((values) => {
|
|
57
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const v of values) {
|
|
59
|
+
const k = keyOf(v);
|
|
60
|
+
const list = byKey.get(k);
|
|
61
|
+
if (list) list.push(v);
|
|
62
|
+
else byKey.set(k, [v]);
|
|
63
|
+
}
|
|
64
|
+
for (const b of batch) b.resolve(byKey.get(b.key) ?? []);
|
|
65
|
+
}, (err) => {
|
|
66
|
+
for (const b of batch) b.reject(err);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { load(key) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
queue.push({
|
|
72
|
+
key,
|
|
73
|
+
resolve,
|
|
74
|
+
reject
|
|
75
|
+
});
|
|
76
|
+
if (!scheduled) {
|
|
77
|
+
scheduled = true;
|
|
78
|
+
queueMicrotask(flush);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
} };
|
|
82
|
+
}
|
|
83
|
+
function tableListLoader(db, table, key = "id") {
|
|
84
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`unsafe SQL identifier: ${table}.${key}`);
|
|
85
|
+
return createGroupLoader((keys) => inQuery(db, table, key, keys), (row) => row[key]);
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { createGroupLoader, createLoader, tableListLoader, tableLoader };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { i as Node } from "./ast-BsvUEqhl.js";
|
|
2
|
+
import { JuneDb } from "@junejs/core/resources";
|
|
3
|
+
//#region src/compiler.d.ts
|
|
4
|
+
declare function ident(name: string): string;
|
|
5
|
+
declare abstract class Dialect {
|
|
6
|
+
private readonly cache;
|
|
7
|
+
compile(node: Node): string;
|
|
8
|
+
protected abstract placeholder(i: number): string;
|
|
9
|
+
protected quoteId(name: string): string;
|
|
10
|
+
protected emit(node: Node): string;
|
|
11
|
+
}
|
|
12
|
+
declare class SqliteDialect extends Dialect {
|
|
13
|
+
protected placeholder(): string;
|
|
14
|
+
}
|
|
15
|
+
declare class PostgresDialect extends Dialect {
|
|
16
|
+
protected placeholder(i: number): string;
|
|
17
|
+
protected quoteId(name: string): string;
|
|
18
|
+
}
|
|
19
|
+
declare class MysqlDialect extends Dialect {
|
|
20
|
+
protected placeholder(): string;
|
|
21
|
+
protected quoteId(name: string): string;
|
|
22
|
+
protected emit(node: Node): string;
|
|
23
|
+
}
|
|
24
|
+
declare const sqlite: SqliteDialect;
|
|
25
|
+
declare const postgres: PostgresDialect;
|
|
26
|
+
declare const mysql: MysqlDialect;
|
|
27
|
+
declare function dialectFor(db: Pick<JuneDb, "dialect">): Dialect;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { dialectFor as a, postgres as c, SqliteDialect as i, sqlite as l, MysqlDialect as n, ident as o, PostgresDialect as r, mysql as s, Dialect as t };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as dialectFor, c as postgres, i as SqliteDialect, l as sqlite, n as MysqlDialect, o as ident, r as PostgresDialect, s as mysql, t as Dialect } from "./compiler-PkKpLl6z.js";
|
|
2
|
+
export { Dialect, MysqlDialect, PostgresDialect, SqliteDialect, dialectFor, ident, mysql, postgres, sqlite };
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//#region src/compiler.ts
|
|
2
|
+
const SYM = {
|
|
3
|
+
eq: "=",
|
|
4
|
+
ne: "!=",
|
|
5
|
+
gt: ">",
|
|
6
|
+
gte: ">=",
|
|
7
|
+
lt: "<",
|
|
8
|
+
lte: "<=",
|
|
9
|
+
like: "like"
|
|
10
|
+
};
|
|
11
|
+
function predicate(p, ph, q) {
|
|
12
|
+
if (p.op === "in") return `${q(p.col)} in (${Array.from({ length: p.arity }, ph).join(", ")})`;
|
|
13
|
+
return `${q(p.col)} ${SYM[p.op]} ${ph()}`;
|
|
14
|
+
}
|
|
15
|
+
function ident(name) {
|
|
16
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`unsafe SQL identifier: ${name}`);
|
|
17
|
+
return name;
|
|
18
|
+
}
|
|
19
|
+
function keyOf(node) {
|
|
20
|
+
switch (node.kind) {
|
|
21
|
+
case "select": return `s|${node.from}|` + node.where.map((p) => p.op === "in" ? `${p.col}:in${p.arity}` : `${p.col}:${p.op}`).join("&") + `|${(node.orderBy ?? []).map((o) => `${o.col} ${o.dir}`).join(",")}|${node.limit ?? ""}|${node.offset ?? ""}`;
|
|
22
|
+
case "insert": return `i|${node.into}|${node.columns.join(",")}`;
|
|
23
|
+
case "update": return `u|${node.table}|${node.set.join(",")}|${node.where.join(",")}`;
|
|
24
|
+
case "delete": return `d|${node.from}|${node.where.join(",")}`;
|
|
25
|
+
case "upsert": return `x|${node.into}|${node.columns.join(",")}|${node.conflict.join(",")}|${node.update.join(",")}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var Dialect = class {
|
|
29
|
+
cache = /* @__PURE__ */ new Map();
|
|
30
|
+
compile(node) {
|
|
31
|
+
const key = keyOf(node);
|
|
32
|
+
let sql = this.cache.get(key);
|
|
33
|
+
if (sql === void 0) {
|
|
34
|
+
sql = this.emit(node);
|
|
35
|
+
this.cache.set(key, sql);
|
|
36
|
+
}
|
|
37
|
+
return sql;
|
|
38
|
+
}
|
|
39
|
+
quoteId(name) {
|
|
40
|
+
return ident(name);
|
|
41
|
+
}
|
|
42
|
+
emit(node) {
|
|
43
|
+
const q = (name) => this.quoteId(name);
|
|
44
|
+
switch (node.kind) {
|
|
45
|
+
case "select": {
|
|
46
|
+
let i = 0;
|
|
47
|
+
const ph = () => this.placeholder(++i);
|
|
48
|
+
const where = node.where.length ? ` where ${node.where.map((p) => predicate(p, ph, q)).join(" and ")}` : "";
|
|
49
|
+
const order = node.orderBy?.length ? ` order by ${node.orderBy.map((o) => `${q(o.col)} ${o.dir === "desc" ? "desc" : "asc"}`).join(", ")}` : "";
|
|
50
|
+
const limit = node.limit === "param" ? ` limit ${ph()}` : node.limit != null ? ` limit ${node.limit}` : "";
|
|
51
|
+
const offset = node.offset === "param" ? ` offset ${ph()}` : "";
|
|
52
|
+
return `select * from ${q(node.from)}${where}${order}${limit}${offset}`;
|
|
53
|
+
}
|
|
54
|
+
case "insert": {
|
|
55
|
+
let i = 0;
|
|
56
|
+
const cols = node.columns.map(q);
|
|
57
|
+
const vals = node.columns.map(() => this.placeholder(++i));
|
|
58
|
+
return `insert into ${q(node.into)} (${cols.join(", ")}) values (${vals.join(", ")})`;
|
|
59
|
+
}
|
|
60
|
+
case "update": {
|
|
61
|
+
let i = 0;
|
|
62
|
+
const set = node.set.map((c) => `${q(c)} = ${this.placeholder(++i)}`).join(", ");
|
|
63
|
+
const where = node.where.map((c) => `${q(c)} = ${this.placeholder(++i)}`).join(" and ");
|
|
64
|
+
return `update ${q(node.table)} set ${set} where ${where}`;
|
|
65
|
+
}
|
|
66
|
+
case "delete": {
|
|
67
|
+
let i = 0;
|
|
68
|
+
const where = node.where.map((c) => `${q(c)} = ${this.placeholder(++i)}`).join(" and ");
|
|
69
|
+
return `delete from ${q(node.from)} where ${where}`;
|
|
70
|
+
}
|
|
71
|
+
case "upsert": {
|
|
72
|
+
let i = 0;
|
|
73
|
+
const cols = node.columns.map(q);
|
|
74
|
+
const vals = node.columns.map(() => this.placeholder(++i));
|
|
75
|
+
const set = node.update.map((c) => `${q(c)} = excluded.${q(c)}`).join(", ");
|
|
76
|
+
return `insert into ${q(node.into)} (${cols.join(", ")}) values (${vals.join(", ")}) on conflict (${node.conflict.map(q).join(", ")}) do update set ${set} returning *`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var SqliteDialect = class extends Dialect {
|
|
82
|
+
placeholder() {
|
|
83
|
+
return "?";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var PostgresDialect = class extends Dialect {
|
|
87
|
+
placeholder(i) {
|
|
88
|
+
return `$${i}`;
|
|
89
|
+
}
|
|
90
|
+
quoteId(name) {
|
|
91
|
+
return `"${ident(name)}"`;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var MysqlDialect = class extends Dialect {
|
|
95
|
+
placeholder() {
|
|
96
|
+
return "?";
|
|
97
|
+
}
|
|
98
|
+
quoteId(name) {
|
|
99
|
+
return `\`${ident(name)}\``;
|
|
100
|
+
}
|
|
101
|
+
emit(node) {
|
|
102
|
+
if (node.kind !== "upsert") return super.emit(node);
|
|
103
|
+
const q = (name) => this.quoteId(name);
|
|
104
|
+
const cols = node.columns.map(q);
|
|
105
|
+
const vals = node.columns.map(() => this.placeholder());
|
|
106
|
+
const set = node.update.map((c) => `${q(c)} = new.${q(c)}`).join(", ");
|
|
107
|
+
return `insert into ${q(node.into)} (${cols.join(", ")}) values (${vals.join(", ")}) as new on duplicate key update ${set}`;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const sqlite = new SqliteDialect();
|
|
111
|
+
const postgres = new PostgresDialect();
|
|
112
|
+
const mysql = new MysqlDialect();
|
|
113
|
+
const BY_DIALECT = {
|
|
114
|
+
sqlite,
|
|
115
|
+
postgres,
|
|
116
|
+
mysql
|
|
117
|
+
};
|
|
118
|
+
function dialectFor(db) {
|
|
119
|
+
return BY_DIALECT[db.dialect ?? "sqlite"];
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { Dialect, MysqlDialect, PostgresDialect, SqliteDialect, dialectFor, ident, mysql, postgres, sqlite };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { c as UpdateNode, i as Node, l as UpsertNode, n as DeleteNode, r as InsertNode, s as SelectNode } from "./ast-BsvUEqhl.js";
|
|
2
|
+
import { i as taggingDb, n as tablesFromSql, r as tagSql, t as SqlTouch } from "./tag-B0HQho7q.js";
|
|
3
|
+
import { a as dialectFor, c as postgres, i as SqliteDialect, l as sqlite, n as MysqlDialect, o as ident, r as PostgresDialect, s as mysql, t as Dialect } from "./compiler-PkKpLl6z.js";
|
|
4
|
+
import { t as emitSchemaTypes } from "./types-qDjFvlZu.js";
|
|
5
|
+
import { _ as juno, a as tableListLoader, b as table, c as Operators, d as RowOf, f as Schema, g as installDataLayer, h as Where, i as createLoader, l as OrderBy, m as TableNames, n as Loader, o as tableLoader, p as Table, r as createGroupLoader, s as Juno, t as ListLoader, u as Row, v as junoDataLayer, x as writes, y as reads } from "./batch-c_vy0gyx.js";
|
|
6
|
+
export { type DeleteNode, Dialect, type InsertNode, Juno, type ListLoader, type Loader, MysqlDialect, type Node, Operators, OrderBy, PostgresDialect, Row, RowOf, Schema, type SelectNode, type SqlTouch, SqliteDialect, Table, TableNames, type UpdateNode, type UpsertNode, Where, createGroupLoader, createLoader, dialectFor, emitSchemaTypes, ident, installDataLayer, juno, junoDataLayer, mysql, postgres, reads, sqlite, table, tableListLoader, tableLoader, tablesFromSql, tagSql, taggingDb, writes };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Dialect, MysqlDialect, PostgresDialect, SqliteDialect, dialectFor, ident, mysql, postgres, sqlite } from "./compiler.js";
|
|
2
|
+
import { createGroupLoader, createLoader, tableListLoader, tableLoader } from "./batch.js";
|
|
3
|
+
import { tablesFromSql, tagSql, taggingDb } from "./tag.js";
|
|
4
|
+
import { emitSchemaTypes } from "./types.js";
|
|
5
|
+
import { recordTableRead, recordTableWrite } from "@junejs/core/instrumentation";
|
|
6
|
+
import { db, registerSqlTagger, requestLocal } from "@junejs/db";
|
|
7
|
+
//#region src/index.ts
|
|
8
|
+
const OPS = /* @__PURE__ */ new Set([
|
|
9
|
+
"eq",
|
|
10
|
+
"ne",
|
|
11
|
+
"gt",
|
|
12
|
+
"gte",
|
|
13
|
+
"lt",
|
|
14
|
+
"lte",
|
|
15
|
+
"in",
|
|
16
|
+
"like"
|
|
17
|
+
]);
|
|
18
|
+
function isOperators(v) {
|
|
19
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
20
|
+
}
|
|
21
|
+
var Table = class {
|
|
22
|
+
db;
|
|
23
|
+
name;
|
|
24
|
+
loaders;
|
|
25
|
+
constructor(db, name, loaders = /* @__PURE__ */ new Map()) {
|
|
26
|
+
this.db = db;
|
|
27
|
+
this.name = name;
|
|
28
|
+
this.loaders = loaders;
|
|
29
|
+
}
|
|
30
|
+
get compiler() {
|
|
31
|
+
return dialectFor(this.db);
|
|
32
|
+
}
|
|
33
|
+
async all(where, opts) {
|
|
34
|
+
recordTableRead(this.name);
|
|
35
|
+
const w = where ?? {};
|
|
36
|
+
const keys = Object.keys(w);
|
|
37
|
+
const [col] = keys;
|
|
38
|
+
if (!opts && keys.length === 1 && col !== void 0 && !isOperators(w[col])) return this.ambientListLoader(col).load(w[col]);
|
|
39
|
+
const predicates = [];
|
|
40
|
+
const params = [];
|
|
41
|
+
for (const c of keys) {
|
|
42
|
+
const v = w[c];
|
|
43
|
+
if (isOperators(v)) for (const op of Object.keys(v)) {
|
|
44
|
+
if (!OPS.has(op)) throw new Error(`unknown operator: ${op}`);
|
|
45
|
+
const operand = v[op];
|
|
46
|
+
if (op === "in") {
|
|
47
|
+
const arr = operand;
|
|
48
|
+
predicates.push({
|
|
49
|
+
col: c,
|
|
50
|
+
op: "in",
|
|
51
|
+
arity: arr.length
|
|
52
|
+
});
|
|
53
|
+
params.push(...arr);
|
|
54
|
+
} else {
|
|
55
|
+
predicates.push({
|
|
56
|
+
col: c,
|
|
57
|
+
op
|
|
58
|
+
});
|
|
59
|
+
params.push(operand);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
predicates.push({
|
|
64
|
+
col: c,
|
|
65
|
+
op: "eq"
|
|
66
|
+
});
|
|
67
|
+
params.push(v);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const orderBy = opts?.orderBy ? Object.entries(opts.orderBy).map(([c, dir]) => ({
|
|
71
|
+
col: c,
|
|
72
|
+
dir
|
|
73
|
+
})) : void 0;
|
|
74
|
+
const node = {
|
|
75
|
+
kind: "select",
|
|
76
|
+
from: this.name,
|
|
77
|
+
where: predicates,
|
|
78
|
+
orderBy,
|
|
79
|
+
limit: opts?.limit != null ? "param" : void 0,
|
|
80
|
+
offset: opts?.offset != null ? "param" : void 0
|
|
81
|
+
};
|
|
82
|
+
if (opts?.limit != null) params.push(opts.limit);
|
|
83
|
+
if (opts?.offset != null) params.push(opts.offset);
|
|
84
|
+
return this.db.query(this.compiler.compile(node), params);
|
|
85
|
+
}
|
|
86
|
+
async findBy(where) {
|
|
87
|
+
recordTableRead(this.name);
|
|
88
|
+
const keys = Object.keys(where);
|
|
89
|
+
const [col] = keys;
|
|
90
|
+
if (keys.length === 1 && col !== void 0) return await this.ambientLoader(col).load(where[col]) ?? void 0;
|
|
91
|
+
const where2 = keys.map((k) => ({
|
|
92
|
+
col: k,
|
|
93
|
+
op: "eq"
|
|
94
|
+
}));
|
|
95
|
+
const sql = this.compiler.compile({
|
|
96
|
+
kind: "select",
|
|
97
|
+
from: this.name,
|
|
98
|
+
where: where2,
|
|
99
|
+
limit: 1
|
|
100
|
+
});
|
|
101
|
+
return this.db.get(sql, Object.values(where));
|
|
102
|
+
}
|
|
103
|
+
ambientLoader(col) {
|
|
104
|
+
const key = `${this.name}::${col}`;
|
|
105
|
+
let loader = this.loaders.get(key);
|
|
106
|
+
if (!loader) {
|
|
107
|
+
loader = tableLoader(this.db, this.name, col);
|
|
108
|
+
this.loaders.set(key, loader);
|
|
109
|
+
}
|
|
110
|
+
return loader;
|
|
111
|
+
}
|
|
112
|
+
ambientListLoader(col) {
|
|
113
|
+
const key = `list:${this.name}::${col}`;
|
|
114
|
+
let loader = this.loaders.get(key);
|
|
115
|
+
if (!loader) {
|
|
116
|
+
loader = tableListLoader(this.db, this.name, col);
|
|
117
|
+
this.loaders.set(key, loader);
|
|
118
|
+
}
|
|
119
|
+
return loader;
|
|
120
|
+
}
|
|
121
|
+
async insert(values) {
|
|
122
|
+
recordTableWrite(this.name);
|
|
123
|
+
const sql = this.compiler.compile({
|
|
124
|
+
kind: "insert",
|
|
125
|
+
into: this.name,
|
|
126
|
+
columns: Object.keys(values)
|
|
127
|
+
});
|
|
128
|
+
return this.db.run(sql, Object.values(values));
|
|
129
|
+
}
|
|
130
|
+
async upsert(values, opts) {
|
|
131
|
+
recordTableWrite(this.name);
|
|
132
|
+
const columns = Object.keys(values);
|
|
133
|
+
const conflict = Array.isArray(opts.onConflict) ? opts.onConflict : [opts.onConflict];
|
|
134
|
+
const updates = columns.filter((c) => !conflict.includes(c));
|
|
135
|
+
const update = updates.length ? updates : conflict;
|
|
136
|
+
const sql = this.compiler.compile({
|
|
137
|
+
kind: "upsert",
|
|
138
|
+
into: this.name,
|
|
139
|
+
columns,
|
|
140
|
+
conflict,
|
|
141
|
+
update
|
|
142
|
+
});
|
|
143
|
+
return this.db.get(sql, Object.values(values));
|
|
144
|
+
}
|
|
145
|
+
async update(where, values) {
|
|
146
|
+
recordTableWrite(this.name);
|
|
147
|
+
const sql = this.compiler.compile({
|
|
148
|
+
kind: "update",
|
|
149
|
+
table: this.name,
|
|
150
|
+
set: Object.keys(values),
|
|
151
|
+
where: Object.keys(where)
|
|
152
|
+
});
|
|
153
|
+
return this.db.run(sql, [...Object.values(values), ...Object.values(where)]);
|
|
154
|
+
}
|
|
155
|
+
async delete(where) {
|
|
156
|
+
recordTableWrite(this.name);
|
|
157
|
+
const sql = this.compiler.compile({
|
|
158
|
+
kind: "delete",
|
|
159
|
+
from: this.name,
|
|
160
|
+
where: Object.keys(where)
|
|
161
|
+
});
|
|
162
|
+
return this.db.run(sql, Object.values(where));
|
|
163
|
+
}
|
|
164
|
+
loader(key = "id") {
|
|
165
|
+
return tableLoader(this.db, this.name, key);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
function juno(db) {
|
|
169
|
+
const loaders = /* @__PURE__ */ new Map();
|
|
170
|
+
function table(name) {
|
|
171
|
+
return new Table(db, name, loaders);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
db: taggingDb(db),
|
|
175
|
+
table,
|
|
176
|
+
reads(...tables) {
|
|
177
|
+
for (const t of tables) recordTableRead(t);
|
|
178
|
+
},
|
|
179
|
+
writes(...tables) {
|
|
180
|
+
for (const t of tables) recordTableWrite(t);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const LOADERS = Symbol.for("junejs.juno.loaders");
|
|
185
|
+
function installDataLayer() {
|
|
186
|
+
registerSqlTagger(tagSql);
|
|
187
|
+
}
|
|
188
|
+
function junoDataLayer() {
|
|
189
|
+
return {
|
|
190
|
+
install: installDataLayer,
|
|
191
|
+
module: "@junejs/juno",
|
|
192
|
+
emitTypes: emitSchemaTypes
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function table(name) {
|
|
196
|
+
return new Table(db, name, requestLocal(LOADERS, () => /* @__PURE__ */ new Map()));
|
|
197
|
+
}
|
|
198
|
+
function reads(...tables) {
|
|
199
|
+
for (const t of tables) recordTableRead(t);
|
|
200
|
+
}
|
|
201
|
+
function writes(...tables) {
|
|
202
|
+
for (const t of tables) recordTableWrite(t);
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
export { Dialect, MysqlDialect, PostgresDialect, SqliteDialect, Table, createGroupLoader, createLoader, dialectFor, emitSchemaTypes, ident, installDataLayer, juno, junoDataLayer, mysql, postgres, reads, sqlite, table, tableListLoader, tableLoader, tablesFromSql, tagSql, taggingDb, writes };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JuneDb } from "@junejs/core/resources";
|
|
2
|
+
//#region src/tag.d.ts
|
|
3
|
+
type SqlTouch = {
|
|
4
|
+
kind: "read" | "write" | "other";
|
|
5
|
+
tables: string[];
|
|
6
|
+
};
|
|
7
|
+
declare function tablesFromSql(sqlRaw: string): SqlTouch;
|
|
8
|
+
declare function tagSql(sql: string): void;
|
|
9
|
+
declare function taggingDb(db: JuneDb): JuneDb;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { taggingDb as i, tablesFromSql as n, tagSql as r, SqlTouch as t };
|
package/dist/tag.d.ts
ADDED
package/dist/tag.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { recordTableRead, recordTableWrite } from "@junejs/core/instrumentation";
|
|
2
|
+
//#region src/tag.ts
|
|
3
|
+
const TABLE = "[`\"'\\[]?([A-Za-z_][\\w.]*)[`\"'\\]]?";
|
|
4
|
+
function bare(token) {
|
|
5
|
+
const dot = token.lastIndexOf(".");
|
|
6
|
+
return dot >= 0 ? token.slice(dot + 1) : token;
|
|
7
|
+
}
|
|
8
|
+
function normalize(sql) {
|
|
9
|
+
return sql.replace(/--[^\n]*/g, " ").replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\s+/g, " ").trim();
|
|
10
|
+
}
|
|
11
|
+
function collect(sql, re) {
|
|
12
|
+
const out = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const m of sql.matchAll(re)) if (m[1]) out.add(bare(m[1]));
|
|
14
|
+
return [...out];
|
|
15
|
+
}
|
|
16
|
+
function tablesFromSql(sqlRaw) {
|
|
17
|
+
const sql = normalize(sqlRaw);
|
|
18
|
+
switch ((sql.match(/^(\w+)/)?.[1] ?? "").toLowerCase()) {
|
|
19
|
+
case "select":
|
|
20
|
+
case "with": return {
|
|
21
|
+
kind: "read",
|
|
22
|
+
tables: collect(sql, new RegExp(`\\b(?:from|join)\\s+${TABLE}`, "gi"))
|
|
23
|
+
};
|
|
24
|
+
case "insert": return {
|
|
25
|
+
kind: "write",
|
|
26
|
+
tables: collect(sql, new RegExp(`\\binto\\s+${TABLE}`, "gi"))
|
|
27
|
+
};
|
|
28
|
+
case "update": return {
|
|
29
|
+
kind: "write",
|
|
30
|
+
tables: collect(sql, new RegExp(`^update\\s+${TABLE}`, "gi"))
|
|
31
|
+
};
|
|
32
|
+
case "delete": return {
|
|
33
|
+
kind: "write",
|
|
34
|
+
tables: collect(sql, new RegExp(`\\bfrom\\s+${TABLE}`, "gi"))
|
|
35
|
+
};
|
|
36
|
+
default: return {
|
|
37
|
+
kind: "other",
|
|
38
|
+
tables: []
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function tagSql(sql) {
|
|
43
|
+
const t = tablesFromSql(sql);
|
|
44
|
+
if (t.kind === "read") for (const name of t.tables) recordTableRead(name);
|
|
45
|
+
else if (t.kind === "write") for (const name of t.tables) recordTableWrite(name);
|
|
46
|
+
}
|
|
47
|
+
function taggingDb(db) {
|
|
48
|
+
const query = (sql, params) => (tagSql(sql), db.query(sql, params));
|
|
49
|
+
const get = (sql, params) => (tagSql(sql), db.get(sql, params));
|
|
50
|
+
const run = (sql, params) => (tagSql(sql), db.run(sql, params));
|
|
51
|
+
return new Proxy(db, { get(target, prop, receiver) {
|
|
52
|
+
if (prop === "query") return query;
|
|
53
|
+
if (prop === "get") return get;
|
|
54
|
+
if (prop === "run") return run;
|
|
55
|
+
const value = Reflect.get(target, prop, receiver);
|
|
56
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
57
|
+
} });
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { tablesFromSql, tagSql, taggingDb };
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
function propKey(name) {
|
|
3
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
4
|
+
}
|
|
5
|
+
const nul = (ts, nullable) => nullable ? `${ts} | null` : ts;
|
|
6
|
+
function sqliteType(declared, notnull, pk) {
|
|
7
|
+
const t = declared.toUpperCase();
|
|
8
|
+
let base;
|
|
9
|
+
if (t.includes("INT")) base = "number";
|
|
10
|
+
else if (t.includes("CHAR") || t.includes("CLOB") || t.includes("TEXT")) base = "string";
|
|
11
|
+
else if (t === "" || t.includes("BLOB")) base = "Uint8Array";
|
|
12
|
+
else if (t.includes("REAL") || t.includes("FLOA") || t.includes("DOUB")) base = "number";
|
|
13
|
+
else base = "number";
|
|
14
|
+
return nul(base, notnull === 0 && pk === 0);
|
|
15
|
+
}
|
|
16
|
+
async function introspectSqlite(db) {
|
|
17
|
+
const tables = await db.query("select name from sqlite_master where type = 'table' and name not like 'sqlite_%' and name <> '_june_migrations' order by name");
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const { name } of tables) {
|
|
20
|
+
const cols = await db.query("select name, type, \"notnull\" as nn, pk from pragma_table_info(?)", [name]);
|
|
21
|
+
out.push([name, cols.map((c) => ({
|
|
22
|
+
name: c.name,
|
|
23
|
+
ts: sqliteType(c.type, c.nn, c.pk)
|
|
24
|
+
}))]);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function postgresType(dataType, nullable) {
|
|
29
|
+
const t = dataType.toLowerCase();
|
|
30
|
+
let base;
|
|
31
|
+
if (t === "boolean") base = "boolean";
|
|
32
|
+
else if (t === "smallint" || t === "integer" || t === "real" || t === "double precision") base = "number";
|
|
33
|
+
else if (t === "bigint" || t === "numeric" || t === "decimal") base = "string";
|
|
34
|
+
else if (t === "bytea") base = "Uint8Array";
|
|
35
|
+
else if (t === "json" || t === "jsonb") base = "unknown";
|
|
36
|
+
else if (t === "date" || t.startsWith("timestamp")) base = "Date";
|
|
37
|
+
else if (t === "uuid" || t === "text" || t.startsWith("character") || t === "name" || t.startsWith("time")) base = "string";
|
|
38
|
+
else base = "unknown";
|
|
39
|
+
return nul(base, nullable);
|
|
40
|
+
}
|
|
41
|
+
function mysqlType(dataType, nullable) {
|
|
42
|
+
const t = dataType.toLowerCase();
|
|
43
|
+
let base;
|
|
44
|
+
if ([
|
|
45
|
+
"tinyint",
|
|
46
|
+
"smallint",
|
|
47
|
+
"mediumint",
|
|
48
|
+
"int",
|
|
49
|
+
"integer",
|
|
50
|
+
"bigint",
|
|
51
|
+
"float",
|
|
52
|
+
"double",
|
|
53
|
+
"year"
|
|
54
|
+
].includes(t)) base = "number";
|
|
55
|
+
else if (t === "decimal" || t === "numeric") base = "string";
|
|
56
|
+
else if ([
|
|
57
|
+
"char",
|
|
58
|
+
"varchar",
|
|
59
|
+
"text",
|
|
60
|
+
"tinytext",
|
|
61
|
+
"mediumtext",
|
|
62
|
+
"longtext",
|
|
63
|
+
"enum",
|
|
64
|
+
"set",
|
|
65
|
+
"time"
|
|
66
|
+
].includes(t)) base = "string";
|
|
67
|
+
else if (t === "date" || t === "datetime" || t === "timestamp") base = "Date";
|
|
68
|
+
else if (t === "json") base = "unknown";
|
|
69
|
+
else if (t.includes("blob") || t.includes("binary") || t === "bit") base = "Uint8Array";
|
|
70
|
+
else base = "unknown";
|
|
71
|
+
return nul(base, nullable);
|
|
72
|
+
}
|
|
73
|
+
async function introspectInfoSchema(db, ph, scope, map) {
|
|
74
|
+
const tables = await db.query(`select table_name as name from information_schema.tables where table_schema = ${scope} and table_type = 'BASE TABLE' and table_name <> '_june_migrations' order by table_name`);
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const { name } of tables) {
|
|
77
|
+
const cols = await db.query(`select column_name as name, data_type as type, is_nullable as nullable from information_schema.columns where table_schema = ${scope} and table_name = ${ph} order by ordinal_position`, [name]);
|
|
78
|
+
out.push([name, cols.map((c) => ({
|
|
79
|
+
name: c.name,
|
|
80
|
+
ts: map(c.type, c.nullable === "YES")
|
|
81
|
+
}))]);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function assemble(tables) {
|
|
86
|
+
return "// Generated by `june db types`. Do not edit — re-run after a migration.\nimport \"@junejs/juno\";\n\ndeclare module \"@junejs/juno\" {\n interface Schema {\n" + tables.map(([name, fields]) => {
|
|
87
|
+
const lines = fields.map((f) => ` ${propKey(f.name)}: ${f.ts};`).join("\n");
|
|
88
|
+
return ` ${propKey(name)}: {\n${lines}\n };`;
|
|
89
|
+
}).join("\n") + "\n }\n}\n";
|
|
90
|
+
}
|
|
91
|
+
async function emitSchemaTypes(db) {
|
|
92
|
+
const dialect = db.dialect ?? "sqlite";
|
|
93
|
+
return assemble(dialect === "postgres" ? await introspectInfoSchema(db, "$1", "current_schema()", postgresType) : dialect === "mysql" ? await introspectInfoSchema(db, "?", "database()", mysqlType) : await introspectSqlite(db));
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { emitSchemaTypes };
|
package/package.json
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/juno",
|
|
3
|
-
"version": "0.0.30
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "Juno — June's ergonomic data layer over the JuneDb contract. The default that ships with the agent-native magic (Tier 3).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://june.build",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"source": "./src/index.ts",
|
|
11
|
+
"bun": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
10
15
|
},
|
|
11
16
|
"files": [
|
|
17
|
+
"dist",
|
|
12
18
|
"src"
|
|
13
19
|
],
|
|
14
20
|
"scripts": {
|
|
15
21
|
"test": "bun test",
|
|
16
|
-
"typecheck": "tsc --noEmit"
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"build": "tsdown"
|
|
17
24
|
},
|
|
18
25
|
"dependencies": {
|
|
19
|
-
"@junejs/core": ">=0.1.0
|
|
20
|
-
"@junejs/db": ">=0.0.33
|
|
26
|
+
"@junejs/core": ">=0.1.0",
|
|
27
|
+
"@junejs/db": ">=0.0.33"
|
|
21
28
|
},
|
|
22
29
|
"devDependencies": {
|
|
23
|
-
"@junejs/server": "^0.0.
|
|
30
|
+
"@junejs/server": "^0.1.0-dev.4",
|
|
24
31
|
"@types/pg": "^8.20.0",
|
|
25
32
|
"mysql2": "^3.22.5",
|
|
26
33
|
"pg": "^8.21.0"
|