kreo-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# kreo-client
|
|
2
|
+
|
|
3
|
+
The official **KREO** JavaScript / TypeScript client. A tiny, typed, **zero-dependency** wrapper over the KREO API — a real Postgres backend with an instant REST API, realtime, cache and file storage.
|
|
4
|
+
|
|
5
|
+
Works in the **browser, Node 18+, Bun, Deno and edge** runtimes.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i kreo-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart (5 lines)
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createClient } from "kreo-client";
|
|
17
|
+
|
|
18
|
+
const kreo = createClient({ apiKey: process.env.KREO_KEY! });
|
|
19
|
+
|
|
20
|
+
const { data, error } = await kreo.from("todos").select().limit(10);
|
|
21
|
+
console.log(data);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Get your API key in **Dashboard → API Keys**. Use a **secret key** (`kreo_live_…`) on a server, or a **public key** (`pk_live_…`, domain-locked) in the browser.
|
|
25
|
+
|
|
26
|
+
## Read
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// filter, order, paginate
|
|
30
|
+
const { data } = await kreo
|
|
31
|
+
.from("orders")
|
|
32
|
+
.select()
|
|
33
|
+
.eq("status", "paid")
|
|
34
|
+
.order("created_at", "desc")
|
|
35
|
+
.range(0, 19); // first 20 rows
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Write
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
await kreo.from("todos").insert({ title: "Buy milk", done: false });
|
|
42
|
+
await kreo.from("todos").update({ done: true }).eq("id", 42);
|
|
43
|
+
await kreo.from("todos").delete().eq("id", 42);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Typed rows
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
type Todo = { id: number; title: string; done: boolean };
|
|
50
|
+
|
|
51
|
+
const { data } = await kreo.from<Todo>("todos").select();
|
|
52
|
+
// ^? Todo[] | null
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> Tip: run `npx kreo types` (the KREO CLI) to generate exact types for **all** your tables.
|
|
56
|
+
|
|
57
|
+
## Realtime
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
kreo.channel("messages")
|
|
61
|
+
.on("INSERT", ({ row }) => console.log("new message", row))
|
|
62
|
+
.subscribe();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## File storage
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const { data } = await kreo.storage.upload(file, { bucket: "avatars", visibility: "public" });
|
|
69
|
+
console.log(data?.url);
|
|
70
|
+
|
|
71
|
+
const url = kreo.storage.publicUrl(fileId);
|
|
72
|
+
const { data: signed } = await kreo.storage.sign(fileId, 3600); // private, 1h
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Vector search
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const { data } = await kreo.from("documents").search({
|
|
79
|
+
column: "embedding",
|
|
80
|
+
vector: [/* your query embedding */],
|
|
81
|
+
metric: "cosine",
|
|
82
|
+
limit: 10,
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Raw SQL
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const { data } = await kreo.sql("SELECT plan, count(*) FROM customers GROUP BY plan");
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API shape
|
|
93
|
+
|
|
94
|
+
Every call returns `{ data, error }` (never throws on HTTP errors). Reads also return `{ count, total }`.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const { data, error } = await kreo.from("todos").select();
|
|
98
|
+
if (error) console.error(error.message, error.status);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
Docs: <https://app.kreo.work/docs> · Made by [KREO](https://kreo.work) · MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var h=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var y=(n,e)=>{for(var t in e)h(n,t,{get:e[t],enumerable:!0})},v=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of T(e))!R.call(n,r)&&r!==t&&h(n,r,{get:()=>e[r],enumerable:!(s=f(e,r))||s.enumerable});return n};var k=n=>v(h({},"__esModule",{value:!0}),n);var C={};y(C,{KreoClient:()=>c,KreoError:()=>a,createClient:()=>w,default:()=>P});module.exports=k(C);var a=class extends Error{constructor(e,t,s=null){super(e),this.name="KreoError",this.status=t,this.code=s}},K="https://app.kreo.work",d=class{constructor(e,t,s){this.url=e;this.apiKey=t;this.extra=s}headers(e=!0){let t={"x-kreo-api-key":this.apiKey,...this.extra};return e&&(t["Content-Type"]="application/json"),t}async request(e,t,s,r=!1){let i;try{i=await fetch(this.url+t,{method:e,headers:this.headers(!r&&s!==void 0),body:s===void 0?void 0:r?s:JSON.stringify(s)})}catch(l){return{data:null,error:new a(l.message||"Network error",0),raw:null}}let o=null;try{o=await i.json()}catch{}if(!i.ok){let l=o??{};return{data:null,error:new a(l.error||i.statusText,i.status,l.pgCode??null),raw:o}}return{data:o,error:null,raw:o}}},p=class{constructor(e,t){this.t=e;this.table=t;this.filters={}}eq(e,t){return this.filters[e]=String(t),this}match(e){for(let t in e)this.filters[t]=String(e[t]);return this}order(e,t="asc"){return this._order=e,this._dir=t,this}limit(e){return this._limit=e,this}offset(e){return this._offset=e,this}range(e,t){return this._offset=e,this._limit=t-e+1,this}buildQuery(){let e=new URLSearchParams;this._limit!=null&&e.set("limit",String(this._limit)),this._offset!=null&&e.set("offset",String(this._offset)),this._order&&(e.set("order",this._order),e.set("dir",this._dir??"asc"));for(let s in this.filters)e.set(s,this.filters[s]);let t=e.toString();return t?`?${t}`:""}then(e,t){return this.t.request("GET",`/api/rest/${encodeURIComponent(this.table)}${this.buildQuery()}`).then(r=>({data:r.error?null:r.data.data,error:r.error,count:r.error?null:r.data.count,total:r.error?null:r.data.total})).then(e,t)}},u=class{constructor(e,t,s,r){this.t=e;this.table=t;this.method=s;this.values=r}eq(e,t){return e==="id"&&(this.id=t),this}then(e,t){if(this.id==null)return Promise.resolve({data:null,error:new a('update()/delete() require .eq("id", value)',400)}).then(e,t);let s=`/api/rest/${encodeURIComponent(this.table)}?id=${encodeURIComponent(String(this.id))}`;return this.t.request(this.method,s,this.method==="PUT"?this.values:void 0).then(i=>({data:i.error?null:i.data?.data??i.data,error:i.error})).then(e,t)}},g=class{constructor(e,t){this.t=e;this.table=t}select(){return new p(this.t,this.table)}async insert(e){let t=await this.t.request("POST",`/api/rest/${encodeURIComponent(this.table)}`,e);return{data:t.error?null:t.data?.data??t.data,error:t.error}}update(e){return new u(this.t,this.table,"PUT",e)}delete(){return new u(this.t,this.table,"DELETE")}async search(e){let t=await this.t.request("POST",`/api/rest/${encodeURIComponent(this.table)}/search`,e);return{data:t.error?null:t.data.data,error:t.error,count:t.error?null:t.data.count,total:null}}},b=class{constructor(e){this.t=e}async upload(e,t={}){let s=new FormData;s.append("file",e,t.filename??e.name??"file"),t.bucket&&s.append("bucket",t.bucket),t.visibility&&s.append("visibility",t.visibility);let r=await this.t.request("POST","/api/storage/upload",s,!0);return{data:r.error?null:{...r.data.file,url:r.data.url},error:r.error}}async list(e={}){let t=e.bucket?`?bucket=${encodeURIComponent(e.bucket)}`:"",s=await this.t.request("GET",`/api/storage/files${t}`),r=Array.isArray(s.data)?s.data:s.data?.files??null;return{data:s.error?null:r,error:s.error}}async remove(e){return await this.t.request("DELETE",`/api/storage/files/${encodeURIComponent(e)}`)}async sign(e,t=3600){return await this.t.request("POST","/api/storage/sign",{id:e,expiresIn:t})}publicUrl(e){return`${this.t.url}/api/storage/public/${encodeURIComponent(e)}`}},m=class{constructor(e,t,s){this.url=e;this.apiKey=t;this.table=s;this.ws=null;this.handlers=[]}on(e,t){return this.handlers.push({op:e,cb:t}),this}where(e){return this.filter=e,this}subscribe(){let e=globalThis.WebSocket;if(!e)throw new a("WebSocket is not available. Use a browser, Node 22+, or a WebSocket polyfill.",0);let t=this.url.replace(/^http/,"ws")+`/api/realtime/?apiKey=${encodeURIComponent(this.apiKey)}`;return this.ws=new e(t),this.ws.onopen=()=>this.ws?.send(JSON.stringify({type:"subscribe",table:this.table,filter:this.filter})),this.ws.onmessage=s=>{let r;try{r=JSON.parse(String(s.data))}catch{return}if(!(r.type!=="change"||!r.op))for(let i of this.handlers)(i.op==="*"||i.op===r.op)&&i.cb({op:r.op,table:r.table??this.table,row:r.row??{}})},this}unsubscribe(){try{this.ws?.send(JSON.stringify({type:"unsubscribe",table:this.table}))}catch{}try{this.ws?.close()}catch{}this.ws=null}},c=class{constructor(e){if(!e?.apiKey)throw new a("createClient requires an apiKey.",0);let t=(e.url??K).replace(/\/$/,"");this.apiKey=e.apiKey,this.t=new d(t,e.apiKey,e.headers??{}),this.storage=new b(this.t)}from(e){return new g(this.t,e)}async sql(e){let t=await this.t.request("POST","/api/db/query",{query_string:e});return{data:t.error?null:t.data,error:t.error}}channel(e){return new m(this.t.url,this.apiKey,e)}};function w(n){return new c(typeof n=="string"?{apiKey:n}:n)}var P=w;0&&(module.exports={KreoClient,KreoError,createClient});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kreo-client — the official KREO JavaScript / TypeScript client.
|
|
3
|
+
*
|
|
4
|
+
* A tiny, dependency-free wrapper over the KREO public HTTP API. Works in the
|
|
5
|
+
* browser, Node 18+, Bun, Deno and edge runtimes (uses global fetch/FormData;
|
|
6
|
+
* realtime uses global WebSocket, available in browsers and Node 22+).
|
|
7
|
+
*
|
|
8
|
+
* import { createClient } from "kreo-client";
|
|
9
|
+
* const kreo = createClient({ apiKey: "kreo_live_..." });
|
|
10
|
+
* const { data } = await kreo.from("todos").select().limit(10);
|
|
11
|
+
*/
|
|
12
|
+
interface KreoClientOptions {
|
|
13
|
+
/** Your KREO API key (kreo_live_… secret, or pk_live_… public key). */
|
|
14
|
+
apiKey: string;
|
|
15
|
+
/** API base URL. Defaults to https://app.kreo.work */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** Extra headers merged into every request. */
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
declare class KreoError extends Error {
|
|
21
|
+
status: number;
|
|
22
|
+
code: string | null;
|
|
23
|
+
constructor(message: string, status: number, code?: string | null);
|
|
24
|
+
}
|
|
25
|
+
interface KreoResult<T> {
|
|
26
|
+
data: T | null;
|
|
27
|
+
error: KreoError | null;
|
|
28
|
+
}
|
|
29
|
+
interface KreoListResult<T> {
|
|
30
|
+
data: T[] | null;
|
|
31
|
+
error: KreoError | null;
|
|
32
|
+
count: number | null;
|
|
33
|
+
total: number | null;
|
|
34
|
+
}
|
|
35
|
+
type Row = Record<string, unknown>;
|
|
36
|
+
declare class Transport {
|
|
37
|
+
url: string;
|
|
38
|
+
private apiKey;
|
|
39
|
+
private extra;
|
|
40
|
+
constructor(url: string, apiKey: string, extra: Record<string, string>);
|
|
41
|
+
headers(json?: boolean): Record<string, string>;
|
|
42
|
+
request<T>(method: string, path: string, body?: unknown, isForm?: boolean): Promise<{
|
|
43
|
+
data: T | null;
|
|
44
|
+
error: KreoError | null;
|
|
45
|
+
raw: unknown;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
declare class SelectBuilder<T extends Row> implements PromiseLike<KreoListResult<T>> {
|
|
49
|
+
private t;
|
|
50
|
+
private table;
|
|
51
|
+
private filters;
|
|
52
|
+
private _limit?;
|
|
53
|
+
private _offset?;
|
|
54
|
+
private _order?;
|
|
55
|
+
private _dir?;
|
|
56
|
+
constructor(t: Transport, table: string);
|
|
57
|
+
/** Exact-match filter: WHERE column = value (combined with AND). */
|
|
58
|
+
eq(column: string, value: string | number | boolean): this;
|
|
59
|
+
/** Apply several exact-match filters at once. */
|
|
60
|
+
match(obj: Record<string, string | number | boolean>): this;
|
|
61
|
+
order(column: string, dir?: "asc" | "desc"): this;
|
|
62
|
+
limit(n: number): this;
|
|
63
|
+
offset(n: number): this;
|
|
64
|
+
/** Zero-based inclusive range, e.g. range(0, 9) → 10 rows. */
|
|
65
|
+
range(from: number, to: number): this;
|
|
66
|
+
private buildQuery;
|
|
67
|
+
then<R1 = KreoListResult<T>, R2 = never>(onfulfilled?: ((v: KreoListResult<T>) => R1 | PromiseLike<R1>) | null, onrejected?: ((r: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
68
|
+
}
|
|
69
|
+
declare class WriteBuilder<T extends Row> implements PromiseLike<KreoResult<T>> {
|
|
70
|
+
private t;
|
|
71
|
+
private table;
|
|
72
|
+
private method;
|
|
73
|
+
private values?;
|
|
74
|
+
private id?;
|
|
75
|
+
constructor(t: Transport, table: string, method: "PUT" | "DELETE", values?: Partial<T> | undefined);
|
|
76
|
+
/** Target the row by its primary key: .eq("id", 42). Required for update/delete. */
|
|
77
|
+
eq(column: string, value: string | number): this;
|
|
78
|
+
then<R1 = KreoResult<T>, R2 = never>(onfulfilled?: ((v: KreoResult<T>) => R1 | PromiseLike<R1>) | null, onrejected?: ((r: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
79
|
+
}
|
|
80
|
+
declare class KreoTable<T extends Row> {
|
|
81
|
+
private t;
|
|
82
|
+
private table;
|
|
83
|
+
constructor(t: Transport, table: string);
|
|
84
|
+
/** Read rows. Chain .eq / .order / .limit / .range, then await. */
|
|
85
|
+
select(): SelectBuilder<T>;
|
|
86
|
+
/** Insert one row. */
|
|
87
|
+
insert(values: Partial<T>): Promise<KreoResult<T>>;
|
|
88
|
+
/** Update the row matched by .eq("id", …). */
|
|
89
|
+
update(values: Partial<T>): WriteBuilder<T>;
|
|
90
|
+
/** Delete the row matched by .eq("id", …). */
|
|
91
|
+
delete(): WriteBuilder<T>;
|
|
92
|
+
/** Vector similarity search against a pgvector column. */
|
|
93
|
+
search(opts: {
|
|
94
|
+
column: string;
|
|
95
|
+
vector: number[];
|
|
96
|
+
metric?: "cosine" | "l2" | "inner_product";
|
|
97
|
+
limit?: number;
|
|
98
|
+
select?: string[];
|
|
99
|
+
}): Promise<KreoListResult<T & {
|
|
100
|
+
distance: number;
|
|
101
|
+
}>>;
|
|
102
|
+
}
|
|
103
|
+
declare class KreoStorage {
|
|
104
|
+
private t;
|
|
105
|
+
constructor(t: Transport);
|
|
106
|
+
upload(file: Blob | File, opts?: {
|
|
107
|
+
bucket?: string;
|
|
108
|
+
visibility?: "public" | "private";
|
|
109
|
+
filename?: string;
|
|
110
|
+
}): Promise<KreoResult<{
|
|
111
|
+
id: string;
|
|
112
|
+
bucket: string;
|
|
113
|
+
url?: string;
|
|
114
|
+
}>>;
|
|
115
|
+
list(opts?: {
|
|
116
|
+
bucket?: string;
|
|
117
|
+
}): Promise<KreoResult<unknown[]>>;
|
|
118
|
+
remove(id: string): Promise<KreoResult<{
|
|
119
|
+
deleted: boolean;
|
|
120
|
+
}>>;
|
|
121
|
+
/** Create a time-limited signed URL for a private file (seconds, max 7 days). */
|
|
122
|
+
sign(id: string, expiresIn?: number): Promise<KreoResult<{
|
|
123
|
+
url: string;
|
|
124
|
+
}>>;
|
|
125
|
+
/** Direct public URL for a PUBLIC file (no auth needed to open it). */
|
|
126
|
+
publicUrl(id: string): string;
|
|
127
|
+
}
|
|
128
|
+
type ChangeOp = "INSERT" | "UPDATE" | "DELETE";
|
|
129
|
+
type ChangeHandler = (payload: {
|
|
130
|
+
op: ChangeOp;
|
|
131
|
+
table: string;
|
|
132
|
+
row: Row;
|
|
133
|
+
}) => void;
|
|
134
|
+
declare class KreoChannel {
|
|
135
|
+
private url;
|
|
136
|
+
private apiKey;
|
|
137
|
+
private table;
|
|
138
|
+
private ws;
|
|
139
|
+
private handlers;
|
|
140
|
+
private filter?;
|
|
141
|
+
constructor(url: string, apiKey: string, table: string);
|
|
142
|
+
on(op: ChangeOp | "*", cb: ChangeHandler): this;
|
|
143
|
+
where(filter: Record<string, string | number | boolean>): this;
|
|
144
|
+
subscribe(): this;
|
|
145
|
+
unsubscribe(): void;
|
|
146
|
+
}
|
|
147
|
+
declare class KreoClient {
|
|
148
|
+
private t;
|
|
149
|
+
private apiKey;
|
|
150
|
+
storage: KreoStorage;
|
|
151
|
+
constructor(opts: KreoClientOptions);
|
|
152
|
+
/** Query a table. `kreo.from<MyRow>("todos")` for typed rows. */
|
|
153
|
+
from<T extends Row = Row>(table: string): KreoTable<T>;
|
|
154
|
+
/** Run raw SQL against your own schema (needs a key with the right permission). */
|
|
155
|
+
sql<T = Row>(query: string): Promise<KreoResult<{
|
|
156
|
+
rows: T[];
|
|
157
|
+
columns?: string[];
|
|
158
|
+
}>>;
|
|
159
|
+
/** Open a realtime channel for a table (requires realtime enabled on it). */
|
|
160
|
+
channel(table: string): KreoChannel;
|
|
161
|
+
}
|
|
162
|
+
/** Create a KREO client. Pass an options object or just your API key string. */
|
|
163
|
+
declare function createClient(opts: KreoClientOptions | string): KreoClient;
|
|
164
|
+
|
|
165
|
+
export { KreoClient, type KreoClientOptions, KreoError, type KreoListResult, type KreoResult, createClient, createClient as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kreo-client — the official KREO JavaScript / TypeScript client.
|
|
3
|
+
*
|
|
4
|
+
* A tiny, dependency-free wrapper over the KREO public HTTP API. Works in the
|
|
5
|
+
* browser, Node 18+, Bun, Deno and edge runtimes (uses global fetch/FormData;
|
|
6
|
+
* realtime uses global WebSocket, available in browsers and Node 22+).
|
|
7
|
+
*
|
|
8
|
+
* import { createClient } from "kreo-client";
|
|
9
|
+
* const kreo = createClient({ apiKey: "kreo_live_..." });
|
|
10
|
+
* const { data } = await kreo.from("todos").select().limit(10);
|
|
11
|
+
*/
|
|
12
|
+
interface KreoClientOptions {
|
|
13
|
+
/** Your KREO API key (kreo_live_… secret, or pk_live_… public key). */
|
|
14
|
+
apiKey: string;
|
|
15
|
+
/** API base URL. Defaults to https://app.kreo.work */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** Extra headers merged into every request. */
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
declare class KreoError extends Error {
|
|
21
|
+
status: number;
|
|
22
|
+
code: string | null;
|
|
23
|
+
constructor(message: string, status: number, code?: string | null);
|
|
24
|
+
}
|
|
25
|
+
interface KreoResult<T> {
|
|
26
|
+
data: T | null;
|
|
27
|
+
error: KreoError | null;
|
|
28
|
+
}
|
|
29
|
+
interface KreoListResult<T> {
|
|
30
|
+
data: T[] | null;
|
|
31
|
+
error: KreoError | null;
|
|
32
|
+
count: number | null;
|
|
33
|
+
total: number | null;
|
|
34
|
+
}
|
|
35
|
+
type Row = Record<string, unknown>;
|
|
36
|
+
declare class Transport {
|
|
37
|
+
url: string;
|
|
38
|
+
private apiKey;
|
|
39
|
+
private extra;
|
|
40
|
+
constructor(url: string, apiKey: string, extra: Record<string, string>);
|
|
41
|
+
headers(json?: boolean): Record<string, string>;
|
|
42
|
+
request<T>(method: string, path: string, body?: unknown, isForm?: boolean): Promise<{
|
|
43
|
+
data: T | null;
|
|
44
|
+
error: KreoError | null;
|
|
45
|
+
raw: unknown;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
declare class SelectBuilder<T extends Row> implements PromiseLike<KreoListResult<T>> {
|
|
49
|
+
private t;
|
|
50
|
+
private table;
|
|
51
|
+
private filters;
|
|
52
|
+
private _limit?;
|
|
53
|
+
private _offset?;
|
|
54
|
+
private _order?;
|
|
55
|
+
private _dir?;
|
|
56
|
+
constructor(t: Transport, table: string);
|
|
57
|
+
/** Exact-match filter: WHERE column = value (combined with AND). */
|
|
58
|
+
eq(column: string, value: string | number | boolean): this;
|
|
59
|
+
/** Apply several exact-match filters at once. */
|
|
60
|
+
match(obj: Record<string, string | number | boolean>): this;
|
|
61
|
+
order(column: string, dir?: "asc" | "desc"): this;
|
|
62
|
+
limit(n: number): this;
|
|
63
|
+
offset(n: number): this;
|
|
64
|
+
/** Zero-based inclusive range, e.g. range(0, 9) → 10 rows. */
|
|
65
|
+
range(from: number, to: number): this;
|
|
66
|
+
private buildQuery;
|
|
67
|
+
then<R1 = KreoListResult<T>, R2 = never>(onfulfilled?: ((v: KreoListResult<T>) => R1 | PromiseLike<R1>) | null, onrejected?: ((r: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
68
|
+
}
|
|
69
|
+
declare class WriteBuilder<T extends Row> implements PromiseLike<KreoResult<T>> {
|
|
70
|
+
private t;
|
|
71
|
+
private table;
|
|
72
|
+
private method;
|
|
73
|
+
private values?;
|
|
74
|
+
private id?;
|
|
75
|
+
constructor(t: Transport, table: string, method: "PUT" | "DELETE", values?: Partial<T> | undefined);
|
|
76
|
+
/** Target the row by its primary key: .eq("id", 42). Required for update/delete. */
|
|
77
|
+
eq(column: string, value: string | number): this;
|
|
78
|
+
then<R1 = KreoResult<T>, R2 = never>(onfulfilled?: ((v: KreoResult<T>) => R1 | PromiseLike<R1>) | null, onrejected?: ((r: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
79
|
+
}
|
|
80
|
+
declare class KreoTable<T extends Row> {
|
|
81
|
+
private t;
|
|
82
|
+
private table;
|
|
83
|
+
constructor(t: Transport, table: string);
|
|
84
|
+
/** Read rows. Chain .eq / .order / .limit / .range, then await. */
|
|
85
|
+
select(): SelectBuilder<T>;
|
|
86
|
+
/** Insert one row. */
|
|
87
|
+
insert(values: Partial<T>): Promise<KreoResult<T>>;
|
|
88
|
+
/** Update the row matched by .eq("id", …). */
|
|
89
|
+
update(values: Partial<T>): WriteBuilder<T>;
|
|
90
|
+
/** Delete the row matched by .eq("id", …). */
|
|
91
|
+
delete(): WriteBuilder<T>;
|
|
92
|
+
/** Vector similarity search against a pgvector column. */
|
|
93
|
+
search(opts: {
|
|
94
|
+
column: string;
|
|
95
|
+
vector: number[];
|
|
96
|
+
metric?: "cosine" | "l2" | "inner_product";
|
|
97
|
+
limit?: number;
|
|
98
|
+
select?: string[];
|
|
99
|
+
}): Promise<KreoListResult<T & {
|
|
100
|
+
distance: number;
|
|
101
|
+
}>>;
|
|
102
|
+
}
|
|
103
|
+
declare class KreoStorage {
|
|
104
|
+
private t;
|
|
105
|
+
constructor(t: Transport);
|
|
106
|
+
upload(file: Blob | File, opts?: {
|
|
107
|
+
bucket?: string;
|
|
108
|
+
visibility?: "public" | "private";
|
|
109
|
+
filename?: string;
|
|
110
|
+
}): Promise<KreoResult<{
|
|
111
|
+
id: string;
|
|
112
|
+
bucket: string;
|
|
113
|
+
url?: string;
|
|
114
|
+
}>>;
|
|
115
|
+
list(opts?: {
|
|
116
|
+
bucket?: string;
|
|
117
|
+
}): Promise<KreoResult<unknown[]>>;
|
|
118
|
+
remove(id: string): Promise<KreoResult<{
|
|
119
|
+
deleted: boolean;
|
|
120
|
+
}>>;
|
|
121
|
+
/** Create a time-limited signed URL for a private file (seconds, max 7 days). */
|
|
122
|
+
sign(id: string, expiresIn?: number): Promise<KreoResult<{
|
|
123
|
+
url: string;
|
|
124
|
+
}>>;
|
|
125
|
+
/** Direct public URL for a PUBLIC file (no auth needed to open it). */
|
|
126
|
+
publicUrl(id: string): string;
|
|
127
|
+
}
|
|
128
|
+
type ChangeOp = "INSERT" | "UPDATE" | "DELETE";
|
|
129
|
+
type ChangeHandler = (payload: {
|
|
130
|
+
op: ChangeOp;
|
|
131
|
+
table: string;
|
|
132
|
+
row: Row;
|
|
133
|
+
}) => void;
|
|
134
|
+
declare class KreoChannel {
|
|
135
|
+
private url;
|
|
136
|
+
private apiKey;
|
|
137
|
+
private table;
|
|
138
|
+
private ws;
|
|
139
|
+
private handlers;
|
|
140
|
+
private filter?;
|
|
141
|
+
constructor(url: string, apiKey: string, table: string);
|
|
142
|
+
on(op: ChangeOp | "*", cb: ChangeHandler): this;
|
|
143
|
+
where(filter: Record<string, string | number | boolean>): this;
|
|
144
|
+
subscribe(): this;
|
|
145
|
+
unsubscribe(): void;
|
|
146
|
+
}
|
|
147
|
+
declare class KreoClient {
|
|
148
|
+
private t;
|
|
149
|
+
private apiKey;
|
|
150
|
+
storage: KreoStorage;
|
|
151
|
+
constructor(opts: KreoClientOptions);
|
|
152
|
+
/** Query a table. `kreo.from<MyRow>("todos")` for typed rows. */
|
|
153
|
+
from<T extends Row = Row>(table: string): KreoTable<T>;
|
|
154
|
+
/** Run raw SQL against your own schema (needs a key with the right permission). */
|
|
155
|
+
sql<T = Row>(query: string): Promise<KreoResult<{
|
|
156
|
+
rows: T[];
|
|
157
|
+
columns?: string[];
|
|
158
|
+
}>>;
|
|
159
|
+
/** Open a realtime channel for a table (requires realtime enabled on it). */
|
|
160
|
+
channel(table: string): KreoChannel;
|
|
161
|
+
}
|
|
162
|
+
/** Create a KREO client. Pass an options object or just your API key string. */
|
|
163
|
+
declare function createClient(opts: KreoClientOptions | string): KreoClient;
|
|
164
|
+
|
|
165
|
+
export { KreoClient, type KreoClientOptions, KreoError, type KreoListResult, type KreoResult, createClient, createClient as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var a=class extends Error{constructor(e,t,r=null){super(e),this.name="KreoError",this.status=t,this.code=r}},m="https://app.kreo.work",c=class{constructor(e,t,r){this.url=e;this.apiKey=t;this.extra=r}headers(e=!0){let t={"x-kreo-api-key":this.apiKey,...this.extra};return e&&(t["Content-Type"]="application/json"),t}async request(e,t,r,s=!1){let n;try{n=await fetch(this.url+t,{method:e,headers:this.headers(!s&&r!==void 0),body:r===void 0?void 0:s?r:JSON.stringify(r)})}catch(l){return{data:null,error:new a(l.message||"Network error",0),raw:null}}let o=null;try{o=await n.json()}catch{}if(!n.ok){let l=o??{};return{data:null,error:new a(l.error||n.statusText,n.status,l.pgCode??null),raw:o}}return{data:o,error:null,raw:o}}},h=class{constructor(e,t){this.t=e;this.table=t;this.filters={}}eq(e,t){return this.filters[e]=String(t),this}match(e){for(let t in e)this.filters[t]=String(e[t]);return this}order(e,t="asc"){return this._order=e,this._dir=t,this}limit(e){return this._limit=e,this}offset(e){return this._offset=e,this}range(e,t){return this._offset=e,this._limit=t-e+1,this}buildQuery(){let e=new URLSearchParams;this._limit!=null&&e.set("limit",String(this._limit)),this._offset!=null&&e.set("offset",String(this._offset)),this._order&&(e.set("order",this._order),e.set("dir",this._dir??"asc"));for(let r in this.filters)e.set(r,this.filters[r]);let t=e.toString();return t?`?${t}`:""}then(e,t){return this.t.request("GET",`/api/rest/${encodeURIComponent(this.table)}${this.buildQuery()}`).then(s=>({data:s.error?null:s.data.data,error:s.error,count:s.error?null:s.data.count,total:s.error?null:s.data.total})).then(e,t)}},u=class{constructor(e,t,r,s){this.t=e;this.table=t;this.method=r;this.values=s}eq(e,t){return e==="id"&&(this.id=t),this}then(e,t){if(this.id==null)return Promise.resolve({data:null,error:new a('update()/delete() require .eq("id", value)',400)}).then(e,t);let r=`/api/rest/${encodeURIComponent(this.table)}?id=${encodeURIComponent(String(this.id))}`;return this.t.request(this.method,r,this.method==="PUT"?this.values:void 0).then(n=>({data:n.error?null:n.data?.data??n.data,error:n.error})).then(e,t)}},d=class{constructor(e,t){this.t=e;this.table=t}select(){return new h(this.t,this.table)}async insert(e){let t=await this.t.request("POST",`/api/rest/${encodeURIComponent(this.table)}`,e);return{data:t.error?null:t.data?.data??t.data,error:t.error}}update(e){return new u(this.t,this.table,"PUT",e)}delete(){return new u(this.t,this.table,"DELETE")}async search(e){let t=await this.t.request("POST",`/api/rest/${encodeURIComponent(this.table)}/search`,e);return{data:t.error?null:t.data.data,error:t.error,count:t.error?null:t.data.count,total:null}}},p=class{constructor(e){this.t=e}async upload(e,t={}){let r=new FormData;r.append("file",e,t.filename??e.name??"file"),t.bucket&&r.append("bucket",t.bucket),t.visibility&&r.append("visibility",t.visibility);let s=await this.t.request("POST","/api/storage/upload",r,!0);return{data:s.error?null:{...s.data.file,url:s.data.url},error:s.error}}async list(e={}){let t=e.bucket?`?bucket=${encodeURIComponent(e.bucket)}`:"",r=await this.t.request("GET",`/api/storage/files${t}`),s=Array.isArray(r.data)?r.data:r.data?.files??null;return{data:r.error?null:s,error:r.error}}async remove(e){return await this.t.request("DELETE",`/api/storage/files/${encodeURIComponent(e)}`)}async sign(e,t=3600){return await this.t.request("POST","/api/storage/sign",{id:e,expiresIn:t})}publicUrl(e){return`${this.t.url}/api/storage/public/${encodeURIComponent(e)}`}},g=class{constructor(e,t,r){this.url=e;this.apiKey=t;this.table=r;this.ws=null;this.handlers=[]}on(e,t){return this.handlers.push({op:e,cb:t}),this}where(e){return this.filter=e,this}subscribe(){let e=globalThis.WebSocket;if(!e)throw new a("WebSocket is not available. Use a browser, Node 22+, or a WebSocket polyfill.",0);let t=this.url.replace(/^http/,"ws")+`/api/realtime/?apiKey=${encodeURIComponent(this.apiKey)}`;return this.ws=new e(t),this.ws.onopen=()=>this.ws?.send(JSON.stringify({type:"subscribe",table:this.table,filter:this.filter})),this.ws.onmessage=r=>{let s;try{s=JSON.parse(String(r.data))}catch{return}if(!(s.type!=="change"||!s.op))for(let n of this.handlers)(n.op==="*"||n.op===s.op)&&n.cb({op:s.op,table:s.table??this.table,row:s.row??{}})},this}unsubscribe(){try{this.ws?.send(JSON.stringify({type:"unsubscribe",table:this.table}))}catch{}try{this.ws?.close()}catch{}this.ws=null}},b=class{constructor(e){if(!e?.apiKey)throw new a("createClient requires an apiKey.",0);let t=(e.url??m).replace(/\/$/,"");this.apiKey=e.apiKey,this.t=new c(t,e.apiKey,e.headers??{}),this.storage=new p(this.t)}from(e){return new d(this.t,e)}async sql(e){let t=await this.t.request("POST","/api/db/query",{query_string:e});return{data:t.error?null:t.data,error:t.error}}channel(e){return new g(this.t.url,this.apiKey,e)}};function w(i){return new b(typeof i=="string"?{apiKey:i}:i)}var f=w;export{b as KreoClient,a as KreoError,w as createClient,f as default};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kreo-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official KREO JavaScript / TypeScript client — a tiny, typed, dependency-free wrapper over the KREO API (Postgres + instant REST, auth, realtime, cache & storage).",
|
|
5
|
+
"keywords": ["kreo", "baas", "backend", "postgres", "rest", "realtime", "supabase-alternative", "firebase-alternative"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://kreo.work",
|
|
8
|
+
"repository": { "type": "git", "url": "https://github.com/kreo-work/kreo-js" },
|
|
9
|
+
"bugs": { "url": "mailto:contact@kreo.work" },
|
|
10
|
+
"author": "KREO",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "README.md"],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean --minify",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.4.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": { "node": ">=18" },
|
|
33
|
+
"publishConfig": { "access": "public" }
|
|
34
|
+
}
|