kinetic-sql 1.0.0 → 1.0.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.
- package/README.md +0 -1
- package/dist/cli/generate.cjs +11 -11
- package/dist/cli/generate.js +9 -9
- package/dist/index.cjs +21 -6
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +18 -3
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
Kinetic SQL is a next-gen Node.js client that wraps **PostgreSQL** and **MySQL** with a developer experience similar to Supabase, but for your own backend.
|
|
7
7
|
|
|
8
|
-

|
|
9
8
|
## ✨ Features
|
|
10
9
|
|
|
11
10
|
- **🔮 Invisible Type Safety:** Run `npx k-sql gen` and your entire database schema is auto-injected into your client. No manual interfaces.
|
package/dist/cli/generate.cjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
2
|
+
"use strict";var N=Object.create;var y=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var O=Object.getPrototypeOf,T=Object.prototype.hasOwnProperty;var A=(e,n,t,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of M(n))!T.call(e,r)&&r!==t&&y(e,r,{get:()=>n[r],enumerable:!(s=S(n,r))||s.enumerable});return e};var u=(e,n,t)=>(t=e!=null?N(O(e)):{},A(n||!e||!e.__esModule?y(t,"default",{value:e,enumerable:!0}):t,e));var w=()=>typeof document>"u"?new URL(`file:${__filename}`).href:document.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?document.currentScript.src:new URL("main.js",document.baseURI).href,g=w();var f=u(require("postgres"),1),_=u(require("mysql2/promise"),1),E=u(require("better-sqlite3"),1),p=u(require("fs"),1),l=u(require("path"),1),R=require("url"),$=(0,R.fileURLToPath)(g),L={text:"string",varchar:"string",char:"string",uuid:"string",int2:"number",int4:"number",int8:"number",float4:"number",float8:"number",numeric:"number",bool:"boolean",json:"any",jsonb:"any",date:"string",timestamp:"string",timestamptz:"string",bytea:"Buffer",_text:"string[]",_int4:"number[]"},I={varchar:"string",char:"string",text:"string",longtext:"string",int:"number",tinyint:"number",smallint:"number",mediumint:"number",bigint:"number",float:"number",double:"number",decimal:"number",datetime:"string",date:"string",timestamp:"string",json:"any",blob:"Buffer",longblob:"Buffer",tinyint1:"boolean"},P={integer:"number",int:"number",smallint:"number",bigint:"number",text:"string",varchar:"string",char:"string",clob:"string",blob:"Buffer",real:"number",double:"number",float:"number",numeric:"number",boolean:"boolean",bool:"boolean"};function C(){let e=process.argv.slice(2),n={type:"pg"};for(let t=0;t<e.length;t++){let s=e[t];if(s.startsWith("--")){let r=s.replace(/^--/,"");if(r.includes("=")){let[o,i]=r.split("=");n[o]=i}else e[t+1]&&!e[t+1].startsWith("--")&&(n[r]=e[t+1],t++)}}return n}function x(e){let n=e;for(;n!==l.default.parse(n).root;){if(p.default.existsSync(l.default.join(n,"package.json")))return n;n=l.default.dirname(n)}return e}async function U(e){let n;e.connection?n=(0,f.default)(e.connection,{max:1}):n=(0,f.default)({host:e.host||"localhost",port:Number(e.port)||5432,user:e.user||"postgres",password:e.password||"",database:e.db||"postgres",max:1});try{let t=await n`
|
|
3
3
|
SELECT table_name, column_name, udt_name, is_nullable
|
|
4
4
|
FROM information_schema.columns
|
|
5
5
|
WHERE table_schema = 'public'
|
|
6
|
-
ORDER BY table_name, ordinal_position;`,
|
|
6
|
+
ORDER BY table_name, ordinal_position;`,s=await n`
|
|
7
7
|
SELECT p.proname as function_name,
|
|
8
8
|
pg_get_function_arguments(p.oid) as args_raw,
|
|
9
9
|
t.typname as return_type
|
|
10
10
|
FROM pg_proc p
|
|
11
11
|
JOIN pg_type t ON p.prorettype = t.oid
|
|
12
12
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
13
|
-
WHERE n.nspname = 'public';`;return{columns:
|
|
13
|
+
WHERE n.nspname = 'public';`;return{columns:t,functions:s,typeMap:L}}finally{await n.end()}}async function q(e){let n=await _.default.createConnection({host:e.host||"localhost",user:e.user||"root",password:e.password||"",database:e.db,port:Number(e.port)||3306});try{let[t]=await n.execute(`
|
|
14
14
|
SELECT TABLE_NAME as table_name, COLUMN_NAME as column_name, DATA_TYPE as udt_name, IS_NULLABLE as is_nullable
|
|
15
15
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
16
16
|
WHERE TABLE_SCHEMA = ?
|
|
17
|
-
ORDER BY TABLE_NAME, ORDINAL_POSITION`,[
|
|
17
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION`,[e.db]),[s]=await n.execute(`
|
|
18
18
|
SELECT ROUTINE_NAME as function_name
|
|
19
19
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
20
20
|
WHERE ROUTINE_SCHEMA = ?
|
|
21
|
-
AND ROUTINE_TYPE = 'PROCEDURE'`,[
|
|
21
|
+
AND ROUTINE_TYPE = 'PROCEDURE'`,[e.db]);return{columns:t,functions:s,typeMap:I}}finally{await n.end()}}async function j(e){let n=e.db||e.filename||e.connection;if(!n)throw new Error("\u274C Missing SQLite file path. Usage: k-sql gen --type=sqlite --db=./dev.db");let t=new E.default(n,{readonly:!0});try{let s=t.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all(),r=[];for(let o of s){let i=t.prepare(`PRAGMA table_info('${o.name}')`).all();for(let c of i){let m=(c.type||"text").split("(")[0].toLowerCase().trim();r.push({table_name:o.name,column_name:c.name,udt_name:m,is_nullable:c.notnull===1?"NO":"YES"})}}return{columns:r,functions:[],typeMap:P}}finally{t.close()}}async function v(){let e=C();console.log(`\u{1F52E} Kinetic SQL: Introspecting ${e.type||"pg"}...`);try{let n;e.type==="mysql"?n=await q(e):e.type==="sqlite"?n=await j(e):n=await U(e);let{columns:t,functions:s,typeMap:r}=n,o={};for(let a of t){o[a.table_name]||(o[a.table_name]=[]);let b=r[a.udt_name]||"any";e.type==="mysql"&&a.udt_name==="tinyint"&&(b="number");let h=a.is_nullable==="YES"?"| null":"";o[a.table_name].push(` ${a.column_name}: ${b}${h};`)}let i=`// Auto-generated by Kinetic SQL \u26A0\uFE0F Do NOT import this file manually.
|
|
22
22
|
|
|
23
23
|
import 'kinetic-sql';
|
|
24
24
|
|
|
@@ -26,18 +26,18 @@ declare module 'kinetic-sql' {
|
|
|
26
26
|
export interface Register {
|
|
27
27
|
schema: {
|
|
28
28
|
tables: {
|
|
29
|
-
`;for(let[
|
|
30
|
-
${
|
|
29
|
+
`;for(let[a,b]of Object.entries(o))i+=` ${a}: {
|
|
30
|
+
${b.join(`
|
|
31
31
|
`)}
|
|
32
32
|
};
|
|
33
|
-
`;
|
|
33
|
+
`;i+=` };
|
|
34
34
|
functions: {
|
|
35
|
-
`;for(let
|
|
35
|
+
`;for(let a of s)i+=` ${a.function_name}: {
|
|
36
36
|
args: Record<string, any>;
|
|
37
37
|
returns: any;
|
|
38
38
|
};
|
|
39
|
-
`;
|
|
39
|
+
`;i+=` };
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
`;let
|
|
43
|
+
`;let c=x(process.cwd()),m=l.default.resolve(c,"kinetic-schema","kinetic-env.d.ts"),d=l.default.dirname(m);p.default.existsSync(d)||p.default.mkdirSync(d,{recursive:!0}),p.default.writeFileSync(m,i),console.log(`\u2705 Generated types at: ${m}`)}catch(n){console.error("\u274C Generation failed:",n),process.exit(1)}}v().then(()=>console.log("Auto-complete types generated successfully \u2728"));
|
package/dist/cli/generate.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import y from"postgres";import g from"mysql2/promise";import _ from"better-sqlite3";import p from"fs";import m from"path";import{fileURLToPath as d}from"url";var v=d(import.meta.url),E={text:"string",varchar:"string",char:"string",uuid:"string",int2:"number",int4:"number",int8:"number",float4:"number",float8:"number",numeric:"number",bool:"boolean",json:"any",jsonb:"any",date:"string",timestamp:"string",timestamptz:"string",bytea:"Buffer",_text:"string[]",_int4:"number[]"},R={varchar:"string",char:"string",text:"string",longtext:"string",int:"number",tinyint:"number",smallint:"number",mediumint:"number",bigint:"number",float:"number",double:"number",decimal:"number",datetime:"string",date:"string",timestamp:"string",json:"any",blob:"Buffer",longblob:"Buffer",tinyint1:"boolean"},h={integer:"number",int:"number",smallint:"number",bigint:"number",text:"string",varchar:"string",char:"string",clob:"string",blob:"Buffer",real:"number",double:"number",float:"number",numeric:"number",boolean:"boolean",bool:"boolean"};function N(){let n=process.argv.slice(2),t={type:"pg"};for(let e=0;e<n.length;e++){let s=n[e];if(s.startsWith("--")){let i=s.replace(/^--/,"");if(i.includes("=")){let[a,o]=i.split("=");t[a]=o}else n[e+1]&&!n[e+1].startsWith("--")&&(t[i]=n[e+1],e++)}}return t}function O(n){let t=n;for(;t!==m.parse(t).root;){if(p.existsSync(m.join(t,"package.json")))return t;t=m.dirname(t)}return n}async function M(n){let t;n.connection?t=y(n.connection,{max:1}):t=y({host:n.host||"localhost",port:Number(n.port)||5432,user:n.user||"postgres",password:n.password||"",database:n.db||"postgres",max:1});try{let e=await t`
|
|
3
3
|
SELECT table_name, column_name, udt_name, is_nullable
|
|
4
4
|
FROM information_schema.columns
|
|
5
5
|
WHERE table_schema = 'public'
|
|
6
|
-
ORDER BY table_name, ordinal_position;`,
|
|
6
|
+
ORDER BY table_name, ordinal_position;`,s=await t`
|
|
7
7
|
SELECT p.proname as function_name,
|
|
8
8
|
pg_get_function_arguments(p.oid) as args_raw,
|
|
9
9
|
t.typname as return_type
|
|
10
10
|
FROM pg_proc p
|
|
11
11
|
JOIN pg_type t ON p.prorettype = t.oid
|
|
12
12
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
13
|
-
WHERE n.nspname = 'public';`;return{columns:e,functions:
|
|
13
|
+
WHERE n.nspname = 'public';`;return{columns:e,functions:s,typeMap:E}}finally{await t.end()}}async function A(n){let t=await g.createConnection({host:n.host||"localhost",user:n.user||"root",password:n.password||"",database:n.db,port:Number(n.port)||3306});try{let[e]=await t.execute(`
|
|
14
14
|
SELECT TABLE_NAME as table_name, COLUMN_NAME as column_name, DATA_TYPE as udt_name, IS_NULLABLE as is_nullable
|
|
15
15
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
16
16
|
WHERE TABLE_SCHEMA = ?
|
|
17
|
-
ORDER BY TABLE_NAME, ORDINAL_POSITION`,[n.db]),[
|
|
17
|
+
ORDER BY TABLE_NAME, ORDINAL_POSITION`,[n.db]),[s]=await t.execute(`
|
|
18
18
|
SELECT ROUTINE_NAME as function_name
|
|
19
19
|
FROM INFORMATION_SCHEMA.ROUTINES
|
|
20
20
|
WHERE ROUTINE_SCHEMA = ?
|
|
21
|
-
AND ROUTINE_TYPE = 'PROCEDURE'`,[n.db]);return{columns:e,functions:
|
|
21
|
+
AND ROUTINE_TYPE = 'PROCEDURE'`,[n.db]);return{columns:e,functions:s,typeMap:R}}finally{await t.end()}}async function T(n){let t=n.db||n.filename||n.connection;if(!t)throw new Error("\u274C Missing SQLite file path. Usage: k-sql gen --type=sqlite --db=./dev.db");let e=new _(t,{readonly:!0});try{let s=e.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all(),i=[];for(let a of s){let o=e.prepare(`PRAGMA table_info('${a.name}')`).all();for(let l of o){let c=(l.type||"text").split("(")[0].toLowerCase().trim();i.push({table_name:a.name,column_name:l.name,udt_name:c,is_nullable:l.notnull===1?"NO":"YES"})}}return{columns:i,functions:[],typeMap:h}}finally{e.close()}}async function S(){let n=N();console.log(`\u{1F52E} Kinetic SQL: Introspecting ${n.type||"pg"}...`);try{let t;n.type==="mysql"?t=await A(n):n.type==="sqlite"?t=await T(n):t=await M(n);let{columns:e,functions:s,typeMap:i}=t,a={};for(let r of e){a[r.table_name]||(a[r.table_name]=[]);let u=i[r.udt_name]||"any";n.type==="mysql"&&r.udt_name==="tinyint"&&(u="number");let f=r.is_nullable==="YES"?"| null":"";a[r.table_name].push(` ${r.column_name}: ${u}${f};`)}let o=`// Auto-generated by Kinetic SQL \u26A0\uFE0F Do NOT import this file manually.
|
|
22
22
|
|
|
23
23
|
import 'kinetic-sql';
|
|
24
24
|
|
|
@@ -26,13 +26,13 @@ declare module 'kinetic-sql' {
|
|
|
26
26
|
export interface Register {
|
|
27
27
|
schema: {
|
|
28
28
|
tables: {
|
|
29
|
-
`;for(let[
|
|
30
|
-
${
|
|
29
|
+
`;for(let[r,u]of Object.entries(a))o+=` ${r}: {
|
|
30
|
+
${u.join(`
|
|
31
31
|
`)}
|
|
32
32
|
};
|
|
33
33
|
`;o+=` };
|
|
34
34
|
functions: {
|
|
35
|
-
`;for(let
|
|
35
|
+
`;for(let r of s)o+=` ${r.function_name}: {
|
|
36
36
|
args: Record<string, any>;
|
|
37
37
|
returns: any;
|
|
38
38
|
};
|
|
@@ -40,4 +40,4 @@ ${l.join(`
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
`;let
|
|
43
|
+
`;let l=O(process.cwd()),c=m.resolve(l,"kinetic-schema","kinetic-env.d.ts"),b=m.dirname(c);p.existsSync(b)||p.mkdirSync(b,{recursive:!0}),p.writeFileSync(c,o),console.log(`\u2705 Generated types at: ${c}`)}catch(t){console.error("\u274C Generation failed:",t),process.exit(1)}}S().then(()=>console.log("Auto-complete types generated successfully \u2728"));
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var N=Object.create;var d=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,_=Object.prototype.hasOwnProperty;var L=(s,e)=>{for(var t in e)d(s,t,{get:e[t],enumerable:!0})},R=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of C(e))!_.call(s,i)&&i!==t&&d(s,i,{get:()=>e[i],enumerable:!(r=I(e,i))||r.enumerable});return s};var u=(s,e,t)=>(t=s!=null?N(D(s)):{},R(e||!s||!s.__esModule?d(t,"default",{value:s,enumerable:!0}):t,s)),$=s=>R(d({},"__esModule",{value:!0}),s);var x={};L(x,{KineticClient:()=>g,KineticError:()=>o,and:()=>a.and,asc:()=>a.asc,desc:()=>a.desc,eq:()=>a.eq,or:()=>a.or,sql:()=>a.sql});module.exports=$(x);var o=class s extends Error{code;details;constructor(e,t,r){super(t),this.name="KineticError",this.code=e,this.details=r,Object.setPrototypeOf(this,s.prototype)}};var S=u(require("better-sqlite3"),1),y=class{db;subscribers=new Map;constructor(e){let t=":memory:";typeof e.connectionString=="string"?t=e.connectionString.replace(/^sqlite:\/\//,""):e.filename&&(t=e.filename),this.db=new S.default(t,e.options||{})}get raw(){return this.db}async init(){this.db.function("kinetic_bridge",(e,t,r)=>{this.handleEvent(e,t,r)})}handleEvent(e,t,r){let i=this.subscribers.get(e);if(!(!i||i.length===0))try{let n=null;t!=="DELETE"&&(n=this.db.prepare(`SELECT * FROM ${e} WHERE rowid = ?`).get(r));let l={action:t,data:n};i.forEach(p=>p(l))}catch(n){console.error(`\u26A0\uFE0F Kinetic SQLite: Failed to bridge event for ${e}`,n)}}async subscribe(e,t){return this.subscribers.has(e)||(this.subscribers.set(e,[]),this.attachTriggers(e)),this.subscribers.get(e).push(t),{unsubscribe:()=>{let r=this.subscribers.get(e);if(r){let i=r.indexOf(t);i>-1&&r.splice(i,1)}}}}attachTriggers(e){try{this.db.exec(`
|
|
2
|
+
CREATE TRIGGER IF NOT EXISTS kinetic_${e}_insert AFTER INSERT ON ${e}
|
|
3
|
+
BEGIN
|
|
4
|
+
SELECT kinetic_bridge('${e}', 'INSERT', NEW.rowid);
|
|
5
|
+
END;
|
|
6
|
+
|
|
7
|
+
CREATE TRIGGER IF NOT EXISTS kinetic_${e}_update AFTER UPDATE ON ${e}
|
|
8
|
+
BEGIN
|
|
9
|
+
SELECT kinetic_bridge('${e}', 'UPDATE', NEW.rowid);
|
|
10
|
+
END;
|
|
11
|
+
|
|
12
|
+
CREATE TRIGGER IF NOT EXISTS kinetic_${e}_delete AFTER DELETE ON ${e}
|
|
13
|
+
BEGIN
|
|
14
|
+
SELECT kinetic_bridge('${e}', 'DELETE', OLD.rowid);
|
|
15
|
+
END;
|
|
16
|
+
`)}catch(t){console.warn(`\u26A0\uFE0F Kinetic SQLite: Could not attach triggers to ${e}. Ensure table exists.`,t)}}async rpc(e,t){try{let i=Object.keys(t||{}).map(()=>"?").join(","),n=Object.values(t||{});return{data:this.db.prepare(`SELECT ${e}(${i})`).all(...n),error:null}}catch(r){return{data:null,error:r}}}async end(){this.db.close()}};var f=u(require("mysql2/promise"),1),b=u(require("@rodrigogs/mysql-events"),1);var m=class{pool;instance=null;subscribers=new Map;config;constructor(e){this.config=e,this.pool=f.default.createPool({host:e.host,user:e.user,password:e.password,database:e.database,port:e.port||3306,waitForConnections:!0,connectionLimit:e.poolSize||10,queueLimit:0})}get raw(){return this.pool}async init(){if(!this.config.realtimeEnabled)return;let e={host:this.config.host,user:this.config.user,password:this.config.password,port:this.config.port||3306};this.instance=new b.default(e,{startAtEnd:!0,excludedSchemas:{mysql:!0,sys:!0,information_schema:!0,performance_schema:!0}}),await this.instance.start(),this.instance.addTrigger({name:"KINETIC_EVENTS",expression:"*",statement:b.default.STATEMENTS.ALL,onEvent:t=>{if(t.schema===this.config.database&&this.subscribers.has(t.table)){let r=this.subscribers.get(t.table);r&&t.rows.forEach(i=>{r({action:t.type,data:i})})}}}),this.instance.on("error",t=>console.error("MySQL Realtime Error:",t))}async subscribe(e,t){if(!this.config.realtimeEnabled)throw new o("CONFIG_ERROR","Realtime is disabled. Set { realtimeEnabled: true } in config.");return this.subscribers.set(e,t),{unsubscribe:()=>{this.subscribers.delete(e)}}}async rpc(e,t){try{let r=Object.values(t||{}),i=r.map(()=>"?").join(", "),[n]=await this.pool.execute(`CALL ${e}(${i})`,r);return{data:n,error:null}}catch(r){return{data:null,error:new o("RPC_ERROR",`MySQL RPC Failed: ${e}`,r)}}}async end(){this.instance&&await this.instance.stop(),await this.pool.end()}};var E=u(require("postgres"),1);var T=`
|
|
2
17
|
CREATE OR REPLACE FUNCTION notify_changes() RETURNS TRIGGER AS $$
|
|
3
18
|
DECLARE
|
|
4
19
|
payload json;
|
|
@@ -11,13 +26,13 @@ BEGIN
|
|
|
11
26
|
PERFORM pg_notify('table_events', payload::text);
|
|
12
27
|
RETURN NEW;
|
|
13
28
|
END;
|
|
14
|
-
$$ LANGUAGE plpgsql;`,
|
|
29
|
+
$$ LANGUAGE plpgsql;`,v=s=>`
|
|
15
30
|
DO $$
|
|
16
31
|
BEGIN
|
|
17
|
-
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'watch_${
|
|
18
|
-
CREATE TRIGGER watch_${
|
|
19
|
-
AFTER INSERT OR UPDATE OR DELETE ON "${
|
|
32
|
+
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'watch_${s}') THEN
|
|
33
|
+
CREATE TRIGGER watch_${s}
|
|
34
|
+
AFTER INSERT OR UPDATE OR DELETE ON "${s}"
|
|
20
35
|
FOR EACH ROW EXECUTE FUNCTION notify_changes();
|
|
21
36
|
END IF;
|
|
22
37
|
END
|
|
23
|
-
$$;`;var
|
|
38
|
+
$$;`;var h=class{sql;config;realtimeEnabled;constructor(e){if(this.config=e,this.realtimeEnabled=e.realtimeEnabled||!1,typeof e.connectionString=="string")this.sql=(0,E.default)(e.connectionString,{max:e.poolSize||10});else{let{type:t,realtimeEnabled:r,poolSize:i,...n}=e;this.sql=(0,E.default)({...n,max:i||10})}}get raw(){return this.sql}async init(){if(this.realtimeEnabled)try{await this.sql.unsafe(T)}catch(e){console.warn("\u26A0\uFE0F Kinetic Driver: Failed to install generic broadcast function.",e)}}async rpc(e,t){try{let r=Object.values(t||{}),n=Object.keys(t||{}).map((O,w)=>`${O} := $${w+1}`).join(", "),l=`SELECT * FROM "${e}"(${n})`;return{data:await this.sql.unsafe(l,r),error:null}}catch(r){return{data:null,error:new o("RPC_ERROR",`Failed to execute function: ${e}`,r)}}}async subscribe(e,t){if(!this.realtimeEnabled)throw new o("CONFIG_ERROR","Realtime is disabled in config.");try{await this.sql.unsafe(v(e))}catch(i){console.error(`Failed to attach trigger to ${e}`,i)}let r;if(typeof this.config.connectionString=="string")r=(0,E.default)(this.config.connectionString,{max:1});else{let{type:i,realtimeEnabled:n,poolSize:l,...p}=this.config;r=(0,E.default)({...p,max:1})}return r.listen("table_events",i=>{let n=JSON.parse(i);n.table===e&&t(n)}).catch(i=>console.error("Listener error:",i)),{unsubscribe:async()=>{await r.end()}}}async end(){await this.sql.end()}};var g=class s{constructor(e){this.config=e;if(e.type==="pg")this.driver=new h(e);else if(e.type==="mysql")this.driver=new m(e);else if(e.type==="sqlite")this.driver=new y(e);else throw new o("CONFIG_ERROR",`Unsupported DB type: ${e.type}`)}driver;static async create(e){let t=new s(e);return await t.init(),t}async init(){await this.driver.init()}async rpc(e,t){return this.driver.rpc(e,t)}async subscribe(e,t){return this.driver.subscribe(e,t)}get raw(){return this.driver.raw}};var a=require("drizzle-orm");0&&(module.exports={KineticClient,KineticError,and,asc,desc,eq,or,sql});
|
package/dist/index.d.cts
CHANGED
|
@@ -36,6 +36,11 @@ type KineticConfig = {
|
|
|
36
36
|
port?: number;
|
|
37
37
|
poolSize?: number;
|
|
38
38
|
realtimeEnabled?: boolean;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'sqlite';
|
|
41
|
+
connectionString?: string;
|
|
42
|
+
filename?: string;
|
|
43
|
+
options?: any;
|
|
39
44
|
};
|
|
40
45
|
declare class KineticClient<Schema extends KineticSchema = ResolvedDB> {
|
|
41
46
|
private config;
|
|
@@ -53,6 +58,7 @@ declare class KineticClient<Schema extends KineticSchema = ResolvedDB> {
|
|
|
53
58
|
}) => void): Promise<{
|
|
54
59
|
unsubscribe: () => void;
|
|
55
60
|
}>;
|
|
61
|
+
get raw(): any;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
type KineticErrorCode = 'CONFIG_ERROR' | 'CONNECTION_FAILED' | 'QUERY_FAILED' | 'RPC_ERROR' | 'REALTIME_ERROR' | 'INTERNAL_ERROR';
|
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ type KineticConfig = {
|
|
|
36
36
|
port?: number;
|
|
37
37
|
poolSize?: number;
|
|
38
38
|
realtimeEnabled?: boolean;
|
|
39
|
+
} | {
|
|
40
|
+
type: 'sqlite';
|
|
41
|
+
connectionString?: string;
|
|
42
|
+
filename?: string;
|
|
43
|
+
options?: any;
|
|
39
44
|
};
|
|
40
45
|
declare class KineticClient<Schema extends KineticSchema = ResolvedDB> {
|
|
41
46
|
private config;
|
|
@@ -53,6 +58,7 @@ declare class KineticClient<Schema extends KineticSchema = ResolvedDB> {
|
|
|
53
58
|
}) => void): Promise<{
|
|
54
59
|
unsubscribe: () => void;
|
|
55
60
|
}>;
|
|
61
|
+
get raw(): any;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
type KineticErrorCode = 'CONFIG_ERROR' | 'CONNECTION_FAILED' | 'QUERY_FAILED' | 'RPC_ERROR' | 'REALTIME_ERROR' | 'INTERNAL_ERROR';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
var a=class n extends Error{code;details;constructor(e,t,r){super(t),this.name="KineticError",this.code=e,this.details=r,Object.setPrototypeOf(this,n.prototype)}};import f from"better-sqlite3";var E=class{db;subscribers=new Map;constructor(e){let t=":memory:";typeof e.connectionString=="string"?t=e.connectionString.replace(/^sqlite:\/\//,""):e.filename&&(t=e.filename),this.db=new f(t,e.options||{})}get raw(){return this.db}async init(){this.db.function("kinetic_bridge",(e,t,r)=>{this.handleEvent(e,t,r)})}handleEvent(e,t,r){let i=this.subscribers.get(e);if(!(!i||i.length===0))try{let s=null;t!=="DELETE"&&(s=this.db.prepare(`SELECT * FROM ${e} WHERE rowid = ?`).get(r));let l={action:t,data:s};i.forEach(p=>p(l))}catch(s){console.error(`\u26A0\uFE0F Kinetic SQLite: Failed to bridge event for ${e}`,s)}}async subscribe(e,t){return this.subscribers.has(e)||(this.subscribers.set(e,[]),this.attachTriggers(e)),this.subscribers.get(e).push(t),{unsubscribe:()=>{let r=this.subscribers.get(e);if(r){let i=r.indexOf(t);i>-1&&r.splice(i,1)}}}}attachTriggers(e){try{this.db.exec(`
|
|
2
|
+
CREATE TRIGGER IF NOT EXISTS kinetic_${e}_insert AFTER INSERT ON ${e}
|
|
3
|
+
BEGIN
|
|
4
|
+
SELECT kinetic_bridge('${e}', 'INSERT', NEW.rowid);
|
|
5
|
+
END;
|
|
6
|
+
|
|
7
|
+
CREATE TRIGGER IF NOT EXISTS kinetic_${e}_update AFTER UPDATE ON ${e}
|
|
8
|
+
BEGIN
|
|
9
|
+
SELECT kinetic_bridge('${e}', 'UPDATE', NEW.rowid);
|
|
10
|
+
END;
|
|
11
|
+
|
|
12
|
+
CREATE TRIGGER IF NOT EXISTS kinetic_${e}_delete AFTER DELETE ON ${e}
|
|
13
|
+
BEGIN
|
|
14
|
+
SELECT kinetic_bridge('${e}', 'DELETE', OLD.rowid);
|
|
15
|
+
END;
|
|
16
|
+
`)}catch(t){console.warn(`\u26A0\uFE0F Kinetic SQLite: Could not attach triggers to ${e}. Ensure table exists.`,t)}}async rpc(e,t){try{let i=Object.keys(t||{}).map(()=>"?").join(","),s=Object.values(t||{});return{data:this.db.prepare(`SELECT ${e}(${i})`).all(...s),error:null}}catch(r){return{data:null,error:r}}}async end(){this.db.close()}};import T from"mysql2/promise";import m from"@rodrigogs/mysql-events";var d=class{pool;instance=null;subscribers=new Map;config;constructor(e){this.config=e,this.pool=T.createPool({host:e.host,user:e.user,password:e.password,database:e.database,port:e.port||3306,waitForConnections:!0,connectionLimit:e.poolSize||10,queueLimit:0})}get raw(){return this.pool}async init(){if(!this.config.realtimeEnabled)return;let e={host:this.config.host,user:this.config.user,password:this.config.password,port:this.config.port||3306};this.instance=new m(e,{startAtEnd:!0,excludedSchemas:{mysql:!0,sys:!0,information_schema:!0,performance_schema:!0}}),await this.instance.start(),this.instance.addTrigger({name:"KINETIC_EVENTS",expression:"*",statement:m.STATEMENTS.ALL,onEvent:t=>{if(t.schema===this.config.database&&this.subscribers.has(t.table)){let r=this.subscribers.get(t.table);r&&t.rows.forEach(i=>{r({action:t.type,data:i})})}}}),this.instance.on("error",t=>console.error("MySQL Realtime Error:",t))}async subscribe(e,t){if(!this.config.realtimeEnabled)throw new a("CONFIG_ERROR","Realtime is disabled. Set { realtimeEnabled: true } in config.");return this.subscribers.set(e,t),{unsubscribe:()=>{this.subscribers.delete(e)}}}async rpc(e,t){try{let r=Object.values(t||{}),i=r.map(()=>"?").join(", "),[s]=await this.pool.execute(`CALL ${e}(${i})`,r);return{data:s,error:null}}catch(r){return{data:null,error:new a("RPC_ERROR",`MySQL RPC Failed: ${e}`,r)}}}async end(){this.instance&&await this.instance.stop(),await this.pool.end()}};import u from"postgres";var h=`
|
|
2
17
|
CREATE OR REPLACE FUNCTION notify_changes() RETURNS TRIGGER AS $$
|
|
3
18
|
DECLARE
|
|
4
19
|
payload json;
|
|
@@ -11,7 +26,7 @@ BEGIN
|
|
|
11
26
|
PERFORM pg_notify('table_events', payload::text);
|
|
12
27
|
RETURN NEW;
|
|
13
28
|
END;
|
|
14
|
-
$$ LANGUAGE plpgsql;`,
|
|
29
|
+
$$ LANGUAGE plpgsql;`,b=n=>`
|
|
15
30
|
DO $$
|
|
16
31
|
BEGIN
|
|
17
32
|
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'watch_${n}') THEN
|
|
@@ -20,4 +35,4 @@ BEGIN
|
|
|
20
35
|
FOR EACH ROW EXECUTE FUNCTION notify_changes();
|
|
21
36
|
END IF;
|
|
22
37
|
END
|
|
23
|
-
$$;`;var
|
|
38
|
+
$$;`;var y=class{sql;config;realtimeEnabled;constructor(e){if(this.config=e,this.realtimeEnabled=e.realtimeEnabled||!1,typeof e.connectionString=="string")this.sql=u(e.connectionString,{max:e.poolSize||10});else{let{type:t,realtimeEnabled:r,poolSize:i,...s}=e;this.sql=u({...s,max:i||10})}}get raw(){return this.sql}async init(){if(this.realtimeEnabled)try{await this.sql.unsafe(h)}catch(e){console.warn("\u26A0\uFE0F Kinetic Driver: Failed to install generic broadcast function.",e)}}async rpc(e,t){try{let r=Object.values(t||{}),s=Object.keys(t||{}).map((R,S)=>`${R} := $${S+1}`).join(", "),l=`SELECT * FROM "${e}"(${s})`;return{data:await this.sql.unsafe(l,r),error:null}}catch(r){return{data:null,error:new a("RPC_ERROR",`Failed to execute function: ${e}`,r)}}}async subscribe(e,t){if(!this.realtimeEnabled)throw new a("CONFIG_ERROR","Realtime is disabled in config.");try{await this.sql.unsafe(b(e))}catch(i){console.error(`Failed to attach trigger to ${e}`,i)}let r;if(typeof this.config.connectionString=="string")r=u(this.config.connectionString,{max:1});else{let{type:i,realtimeEnabled:s,poolSize:l,...p}=this.config;r=u({...p,max:1})}return r.listen("table_events",i=>{let s=JSON.parse(i);s.table===e&&t(s)}).catch(i=>console.error("Listener error:",i)),{unsubscribe:async()=>{await r.end()}}}async end(){await this.sql.end()}};var g=class n{constructor(e){this.config=e;if(e.type==="pg")this.driver=new y(e);else if(e.type==="mysql")this.driver=new d(e);else if(e.type==="sqlite")this.driver=new E(e);else throw new a("CONFIG_ERROR",`Unsupported DB type: ${e.type}`)}driver;static async create(e){let t=new n(e);return await t.init(),t}async init(){await this.driver.init()}async rpc(e,t){return this.driver.rpc(e,t)}async subscribe(e,t){return this.driver.subscribe(e,t)}get raw(){return this.driver.raw}};import{sql as V,eq as Y,desc as Z,asc as ee,and as te,or as re}from"drizzle-orm";export{g as KineticClient,a as KineticError,te as and,ee as asc,Z as desc,Y as eq,re as or,V as sql};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kinetic-sql",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Zero-config, type-safe Postgres & MySQL client with Realtime subscriptions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prebuild": "rm -rf dist",
|
|
9
9
|
"build": "tsup",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
10
11
|
"dev": "tsup --watch",
|
|
11
12
|
"gen": "node dist/cli/generate.cjs"
|
|
12
13
|
},
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
],
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"@rodrigogs/mysql-events": "^0.6.0",
|
|
31
|
+
"better-sqlite3": "^12.6.2",
|
|
30
32
|
"drizzle-orm": "^0.45.1",
|
|
31
33
|
"mysql2": "^3.16.3",
|
|
32
34
|
"postgres": "^3.4.8"
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@eslint/js": "^9.39.2",
|
|
36
38
|
"@stylistic/eslint-plugin": "^5.7.1",
|
|
39
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
37
40
|
"@types/node": "^25.2.0",
|
|
38
41
|
"@typescript-eslint/parser": "^8.54.0",
|
|
39
42
|
"eslint": "^9.39.2",
|