@junejs/juno 0.0.30-dev.0 → 0.0.30-dev.2

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,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 };
@@ -0,0 +1,2 @@
1
+ import { a as tableListLoader, i as createLoader, n as Loader, o as tableLoader, r as createGroupLoader, t as ListLoader } from "./batch-c_vy0gyx.js";
2
+ export { ListLoader, Loader, createGroupLoader, createLoader, tableListLoader, tableLoader };
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 };
@@ -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 };
@@ -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
@@ -0,0 +1,2 @@
1
+ import { i as taggingDb, n as tablesFromSql, r as tagSql, t as SqlTouch } from "./tag-B0HQho7q.js";
2
+ export { SqlTouch, tablesFromSql, tagSql, taggingDb };
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 };
@@ -0,0 +1,5 @@
1
+ import { JuneDb } from "@junejs/core/resources";
2
+ //#region src/types.d.ts
3
+ declare function emitSchemaTypes(db: JuneDb): Promise<string>;
4
+ //#endregion
5
+ export { emitSchemaTypes as t };
@@ -0,0 +1,2 @@
1
+ import { t as emitSchemaTypes } from "./types-qDjFvlZu.js";
2
+ export { emitSchemaTypes };
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-dev.0",
3
+ "version": "0.0.30-dev.2",
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
- ".": "./src/index.ts"
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-dev.0",
20
- "@junejs/db": ">=0.0.33-dev.0"
26
+ "@junejs/core": ">=0.1.0-dev.4",
27
+ "@junejs/db": ">=0.0.33-dev.1"
21
28
  },
22
29
  "devDependencies": {
23
- "@junejs/server": "^0.0.47",
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"
package/src/index.ts CHANGED
@@ -67,15 +67,20 @@ function isOperators(v: unknown): v is Operators {
67
67
  }
68
68
 
69
69
  export class Table<T extends Row = Row> {
70
- constructor(
71
- private readonly db: JuneDb,
72
- private readonly name: string,
73
- // Per-request ambient-loader registry, shared across the juno() handle's
74
- // .table() calls so concurrent findBy/all coalesce. Holds both point and list
75
- // loaders (keyed apart). Optional/defaulted so a standalone `new Table(db,
76
- // name)` still works (it just batches alone).
77
- private readonly loaders: Map<string, unknown> = new Map(),
78
- ) {}
70
+ // Explicit fields, not parameter properties — parameter properties aren't
71
+ // erasable, and June ships raw .ts that consumers type-strip (erasableSyntaxOnly).
72
+ private readonly db: JuneDb;
73
+ private readonly name: string;
74
+ // Per-request ambient-loader registry, shared across the juno() handle's
75
+ // .table() calls so concurrent findBy/all coalesce. Holds both point and list
76
+ // loaders (keyed apart). Optional/defaulted so a standalone `new Table(db,
77
+ // name)` still works (it just batches alone).
78
+ private readonly loaders: Map<string, unknown>;
79
+ constructor(db: JuneDb, name: string, loaders: Map<string, unknown> = new Map()) {
80
+ this.db = db;
81
+ this.name = name;
82
+ this.loaders = loaders;
83
+ }
79
84
 
80
85
  // The compiler for THIS table's db — picked from the handle's dialect tag (sqlite
81
86
  // by default). So `table()` emits the right SQL whether db is sqlite/D1, Postgres,