@keeprer/js 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 ADDED
@@ -0,0 +1,111 @@
1
+ # @keeprer/js
2
+
3
+ Official TypeScript / JavaScript client for [Keeprer](https://keeprer.space) — The Lightweight No-Code Database & Headless Backend.
4
+
5
+ ## 📦 Features
6
+
7
+ - ⚡ **Zero Dependencies**: Powered by native `fetch`.
8
+ - 🛡️ **Full TypeScript Support**: Auto-complete and type inference.
9
+ - 🎯 **Fluent Supabase-style API**: Chain filters like `.eq()`, `.gt()`, `.sort()`, `.limit()`.
10
+ - 🔐 **Scoped API Keys**: Supports `pk_` (publishable/read-only) and `sk_` (secret/read-write).
11
+
12
+ ---
13
+
14
+ ## 🚀 Quick Start
15
+
16
+ ### Installation
17
+
18
+ ```bash
19
+ npm install @keeprer/js
20
+ # or yarn add @keeprer/js
21
+ # or pnpm add @keeprer/js
22
+ ```
23
+
24
+ ### Initialize Client
25
+
26
+ ```typescript
27
+ import { createClient } from "@keeprer/js"
28
+
29
+ const keeprer = createClient("https://keeprer.space", "pk_xxxxxxxxxxxx")
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 📖 Usage Examples
35
+
36
+ ### 1. Query Records (Select & Filter)
37
+
38
+ ```typescript
39
+ // Query products where status is active and price > 100
40
+ const { data, total, error } = await keeprer
41
+ .from("products")
42
+ .select()
43
+ .eq("status", "active")
44
+ .gt("price", 100)
45
+ .sort("-created_at")
46
+ .limit(20)
47
+
48
+ console.log(data) // [{ id: "...", name: "Shirt", price: 350, ... }]
49
+ ```
50
+
51
+ ### 2. Insert Records (Create)
52
+
53
+ ```typescript
54
+ // Insert single row
55
+ const { data: item, error } = await keeprer
56
+ .from("products")
57
+ .insert({
58
+ name: "เสื้อยืด Keeprer",
59
+ price: 350,
60
+ status: "in_stock",
61
+ })
62
+
63
+ // Batch insert up to 500 rows
64
+ const { data: items } = await keeprer
65
+ .from("products")
66
+ .insert([
67
+ { name: "Item 1", price: 100 },
68
+ { name: "Item 2", price: 200 },
69
+ ])
70
+ ```
71
+
72
+ ### 3. Update Record (Patch)
73
+
74
+ ```typescript
75
+ const { data: updated, error } = await keeprer
76
+ .from("products")
77
+ .update("row_uuid_here", {
78
+ price: 290,
79
+ })
80
+ ```
81
+
82
+ ### 4. Delete Record (Delete)
83
+
84
+ ```typescript
85
+ const { success, error } = await keeprer
86
+ .from("products")
87
+ .delete("row_uuid_here")
88
+ ```
89
+
90
+ ### 5. Inspect Schema
91
+
92
+ ```typescript
93
+ const { table, fields } = await keeprer
94
+ .from("products")
95
+ .schema()
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 🔑 Security Best Practices
101
+
102
+ - **Frontend (React, Next.js client, Vue, Mobile apps)**:
103
+ Always use a publishable key starting with `pk_...` or toggle the table to **Public** mode in Keeprer.
104
+ - **Backend (Node.js, Express, Next.js Server Actions, Python)**:
105
+ Use a secret key starting with `sk_...` kept securely in environment variables.
106
+
107
+ ---
108
+
109
+ ## 📄 License
110
+
111
+ MIT © Keeprer Team
@@ -0,0 +1,136 @@
1
+ export interface KeeprerClientOptions {
2
+ baseUrl: string;
3
+ apiKey?: string;
4
+ fetch?: typeof globalThis.fetch;
5
+ }
6
+ export interface KeeprerError {
7
+ message: string;
8
+ status?: number;
9
+ code?: string;
10
+ }
11
+ export interface QueryResponse<T = Record<string, unknown>> {
12
+ data: T[];
13
+ total: number;
14
+ hasMore: boolean;
15
+ cursor: number | null;
16
+ error: KeeprerError | null;
17
+ }
18
+ export interface SingleResponse<T = Record<string, unknown>> {
19
+ data: T | null;
20
+ error: KeeprerError | null;
21
+ }
22
+ export interface DeleteResponse {
23
+ success: boolean;
24
+ error: KeeprerError | null;
25
+ }
26
+ export interface ColumnSchema {
27
+ id: string;
28
+ name: string;
29
+ key: string;
30
+ type: string;
31
+ options?: string[];
32
+ }
33
+ export interface TableSchemaResponse {
34
+ table: {
35
+ id: string;
36
+ name: string;
37
+ is_public: boolean;
38
+ };
39
+ fields: ColumnSchema[];
40
+ error: KeeprerError | null;
41
+ }
42
+ export declare class QueryBuilder<T = Record<string, unknown>> implements PromiseLike<QueryResponse<T>> {
43
+ private client;
44
+ private tableId;
45
+ private filters;
46
+ private sortField;
47
+ private limitCount;
48
+ private offsetCount;
49
+ constructor(client: KeeprerClient, tableId: string);
50
+ select(_columns?: string): this;
51
+ eq(column: string, value: unknown): this;
52
+ neq(column: string, value: unknown): this;
53
+ contains(column: string, value: unknown): this;
54
+ gt(column: string, value: number | string): this;
55
+ gte(column: string, value: number | string): this;
56
+ lt(column: string, value: number | string): this;
57
+ lte(column: string, value: number | string): this;
58
+ sort(field: string): this;
59
+ order(field: string, options?: {
60
+ ascending?: boolean;
61
+ }): this;
62
+ limit(count: number): this;
63
+ offset(count: number): this;
64
+ execute(): Promise<QueryResponse<T>>;
65
+ then<TResult1 = QueryResponse<T>, TResult2 = never>(onfulfilled?: ((value: QueryResponse<T>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
66
+ }
67
+ export declare class TableClient<T = Record<string, unknown>> {
68
+ private client;
69
+ private tableId;
70
+ constructor(client: KeeprerClient, tableId: string);
71
+ /**
72
+ * Start a query chain.
73
+ * e.g. `client.from("products").select().eq("status", "active")`
74
+ */
75
+ select(columns?: string): QueryBuilder<T>;
76
+ /**
77
+ * Fetch a single row by its ID.
78
+ */
79
+ get(id: string): Promise<SingleResponse<T>>;
80
+ /**
81
+ * Insert one or multiple rows.
82
+ * e.g. `client.from("products").insert({ name: "Shoes", price: 500 })`
83
+ */
84
+ insert(rowOrRows: Partial<T> | Partial<T>[]): Promise<SingleResponse<T | T[]>>;
85
+ /**
86
+ * Update an existing row by ID.
87
+ * e.g. `client.from("products").update("row_123", { price: 450 })`
88
+ */
89
+ update(id: string, values: Partial<T>): Promise<SingleResponse<T>>;
90
+ /**
91
+ * Delete a row by ID.
92
+ * e.g. `client.from("products").delete("row_123")`
93
+ */
94
+ delete(id: string): Promise<DeleteResponse>;
95
+ /**
96
+ * Fetch table column schema and metadata.
97
+ */
98
+ schema(): Promise<TableSchemaResponse>;
99
+ }
100
+ export declare class KeeprerClient {
101
+ private baseUrl;
102
+ private apiKey?;
103
+ private customFetch;
104
+ constructor(options: KeeprerClientOptions | string, apiKey?: string);
105
+ /**
106
+ * Access a table by its Table ID or name.
107
+ */
108
+ from<T = Record<string, unknown>>(tableId: string): TableClient<T>;
109
+ /**
110
+ * Export the entire workspace data as JSON.
111
+ */
112
+ exportWorkspace(workspaceId: string): Promise<{
113
+ data: any;
114
+ error: KeeprerError | null;
115
+ }>;
116
+ /**
117
+ * Internal HTTP request handler.
118
+ */
119
+ request(path: string, options?: RequestInit): Promise<{
120
+ data?: any;
121
+ error: KeeprerError | null;
122
+ }>;
123
+ }
124
+ /**
125
+ * Factory function to create a new Keeprer client instance.
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * import { createClient } from "@keeprer/js"
130
+ *
131
+ * const keeprer = createClient("https://keeprer.space", "pk_xxxxxxxxxxxx")
132
+ * const { data, total } = await keeprer.from("products").select().eq("status", "active")
133
+ * ```
134
+ */
135
+ export declare function createClient(baseUrlOrOptions: string | KeeprerClientOptions, apiKey?: string): KeeprerClient;
136
+ export default createClient;
package/dist/index.js ADDED
@@ -0,0 +1,260 @@
1
+ // Keeprer Client SDK (@keeprer/js)
2
+ //
3
+ // A lightweight, zero-dependency, type-safe client library for Keeprer Headless Database.
4
+ // Designed with a fluent Supabase-style query builder interface.
5
+ //
6
+ // Usage:
7
+ // import { createClient } from "@keeprer/js"
8
+ // const keeprer = createClient("https://keeprer.space", "pk_xxxxxxxxxxxx")
9
+ // const { data, total } = await keeprer.from("products").select().eq("status", "active").limit(10)
10
+ export class QueryBuilder {
11
+ client;
12
+ tableId;
13
+ filters = [];
14
+ sortField = null;
15
+ limitCount = 100;
16
+ offsetCount = 0;
17
+ constructor(client, tableId) {
18
+ this.client = client;
19
+ this.tableId = tableId;
20
+ }
21
+ select(_columns) {
22
+ // Keeprer returns all columns by default, columns parameter reserved for projections
23
+ return this;
24
+ }
25
+ eq(column, value) {
26
+ this.filters.push(`${column}.eq.${encodeURIComponent(String(value))}`);
27
+ return this;
28
+ }
29
+ neq(column, value) {
30
+ this.filters.push(`${column}.neq.${encodeURIComponent(String(value))}`);
31
+ return this;
32
+ }
33
+ contains(column, value) {
34
+ this.filters.push(`${column}.contains.${encodeURIComponent(String(value))}`);
35
+ return this;
36
+ }
37
+ gt(column, value) {
38
+ this.filters.push(`${column}.gt.${encodeURIComponent(String(value))}`);
39
+ return this;
40
+ }
41
+ gte(column, value) {
42
+ this.filters.push(`${column}.gte.${encodeURIComponent(String(value))}`);
43
+ return this;
44
+ }
45
+ lt(column, value) {
46
+ this.filters.push(`${column}.lt.${encodeURIComponent(String(value))}`);
47
+ return this;
48
+ }
49
+ lte(column, value) {
50
+ this.filters.push(`${column}.lte.${encodeURIComponent(String(value))}`);
51
+ return this;
52
+ }
53
+ sort(field) {
54
+ this.sortField = field;
55
+ return this;
56
+ }
57
+ order(field, options) {
58
+ this.sortField = options?.ascending === false ? `-${field}` : field;
59
+ return this;
60
+ }
61
+ limit(count) {
62
+ this.limitCount = Math.max(1, Math.min(1000, count));
63
+ return this;
64
+ }
65
+ offset(count) {
66
+ this.offsetCount = Math.max(0, count);
67
+ return this;
68
+ }
69
+ async execute() {
70
+ const params = new URLSearchParams();
71
+ if (this.filters.length > 0) {
72
+ params.set("where", this.filters.join(","));
73
+ }
74
+ if (this.sortField) {
75
+ params.set("sort", this.sortField);
76
+ }
77
+ params.set("limit", String(this.limitCount));
78
+ params.set("offset", String(this.offsetCount));
79
+ const path = `/v1/t/${encodeURIComponent(this.tableId)}/rows?${params.toString()}`;
80
+ const res = await this.client.request(path, { method: "GET" });
81
+ if (res.error) {
82
+ return { data: [], total: 0, hasMore: false, cursor: null, error: res.error };
83
+ }
84
+ return {
85
+ data: res.data?.rows || [],
86
+ total: res.data?.total || 0,
87
+ hasMore: !!res.data?.hasMore,
88
+ cursor: res.data?.cursor ?? null,
89
+ error: null,
90
+ };
91
+ }
92
+ then(onfulfilled, onrejected) {
93
+ return this.execute().then(onfulfilled, onrejected);
94
+ }
95
+ }
96
+ export class TableClient {
97
+ client;
98
+ tableId;
99
+ constructor(client, tableId) {
100
+ this.client = client;
101
+ this.tableId = tableId;
102
+ }
103
+ /**
104
+ * Start a query chain.
105
+ * e.g. `client.from("products").select().eq("status", "active")`
106
+ */
107
+ select(columns) {
108
+ const q = new QueryBuilder(this.client, this.tableId);
109
+ return q.select(columns);
110
+ }
111
+ /**
112
+ * Fetch a single row by its ID.
113
+ */
114
+ async get(id) {
115
+ const res = await this.client.request(`/v1/t/${encodeURIComponent(this.tableId)}/rows/${encodeURIComponent(id)}`, {
116
+ method: "GET",
117
+ });
118
+ if (res.error)
119
+ return { data: null, error: res.error };
120
+ return { data: res.data, error: null };
121
+ }
122
+ /**
123
+ * Insert one or multiple rows.
124
+ * e.g. `client.from("products").insert({ name: "Shoes", price: 500 })`
125
+ */
126
+ async insert(rowOrRows) {
127
+ const payload = Array.isArray(rowOrRows) ? { rows: rowOrRows } : rowOrRows;
128
+ const res = await this.client.request(`/v1/t/${encodeURIComponent(this.tableId)}/rows`, {
129
+ method: "POST",
130
+ body: JSON.stringify(payload),
131
+ });
132
+ if (res.error)
133
+ return { data: null, error: res.error };
134
+ return { data: res.data, error: null };
135
+ }
136
+ /**
137
+ * Update an existing row by ID.
138
+ * e.g. `client.from("products").update("row_123", { price: 450 })`
139
+ */
140
+ async update(id, values) {
141
+ const res = await this.client.request(`/v1/t/${encodeURIComponent(this.tableId)}/rows/${encodeURIComponent(id)}`, {
142
+ method: "PATCH",
143
+ body: JSON.stringify(values),
144
+ });
145
+ if (res.error)
146
+ return { data: null, error: res.error };
147
+ return { data: res.data, error: null };
148
+ }
149
+ /**
150
+ * Delete a row by ID.
151
+ * e.g. `client.from("products").delete("row_123")`
152
+ */
153
+ async delete(id) {
154
+ const res = await this.client.request(`/v1/t/${encodeURIComponent(this.tableId)}/rows/${encodeURIComponent(id)}`, {
155
+ method: "DELETE",
156
+ });
157
+ if (res.error)
158
+ return { success: false, error: res.error };
159
+ return { success: true, error: null };
160
+ }
161
+ /**
162
+ * Fetch table column schema and metadata.
163
+ */
164
+ async schema() {
165
+ const res = await this.client.request(`/v1/t/${encodeURIComponent(this.tableId)}/schema`, {
166
+ method: "GET",
167
+ });
168
+ if (res.error) {
169
+ return { table: { id: this.tableId, name: "", is_public: false }, fields: [], error: res.error };
170
+ }
171
+ return { table: res.data.table, fields: res.data.fields, error: null };
172
+ }
173
+ }
174
+ export class KeeprerClient {
175
+ baseUrl;
176
+ apiKey;
177
+ customFetch;
178
+ constructor(options, apiKey) {
179
+ if (typeof options === "string") {
180
+ this.baseUrl = options.replace(/\/+$/, "");
181
+ this.apiKey = apiKey;
182
+ this.customFetch = globalThis.fetch.bind(globalThis);
183
+ }
184
+ else {
185
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
186
+ this.apiKey = options.apiKey || apiKey;
187
+ this.customFetch = options.fetch || globalThis.fetch.bind(globalThis);
188
+ }
189
+ }
190
+ /**
191
+ * Access a table by its Table ID or name.
192
+ */
193
+ from(tableId) {
194
+ return new TableClient(this, tableId);
195
+ }
196
+ /**
197
+ * Export the entire workspace data as JSON.
198
+ */
199
+ async exportWorkspace(workspaceId) {
200
+ const res = await this.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/export`, { method: "GET" });
201
+ return { data: res.data, error: res.error };
202
+ }
203
+ /**
204
+ * Internal HTTP request handler.
205
+ */
206
+ async request(path, options = {}) {
207
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
208
+ const headers = {
209
+ Accept: "application/json",
210
+ ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
211
+ ...options.headers,
212
+ };
213
+ if (options.body && !headers["Content-Type"]) {
214
+ headers["Content-Type"] = "application/json";
215
+ }
216
+ try {
217
+ const response = await this.customFetch(url, {
218
+ ...options,
219
+ headers,
220
+ });
221
+ if (response.status === 204) {
222
+ return { error: null };
223
+ }
224
+ const json = await response.json().catch(() => ({}));
225
+ if (!response.ok) {
226
+ return {
227
+ error: {
228
+ message: json.error || response.statusText || "Request failed",
229
+ status: response.status,
230
+ code: json.code,
231
+ },
232
+ };
233
+ }
234
+ return { data: json, error: null };
235
+ }
236
+ catch (err) {
237
+ return {
238
+ error: {
239
+ message: err.message || "Network error",
240
+ status: 0,
241
+ },
242
+ };
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Factory function to create a new Keeprer client instance.
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * import { createClient } from "@keeprer/js"
252
+ *
253
+ * const keeprer = createClient("https://keeprer.space", "pk_xxxxxxxxxxxx")
254
+ * const { data, total } = await keeprer.from("products").select().eq("status", "active")
255
+ * ```
256
+ */
257
+ export function createClient(baseUrlOrOptions, apiKey) {
258
+ return new KeeprerClient(baseUrlOrOptions, apiKey);
259
+ }
260
+ export default createClient;
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@keeprer/js",
3
+ "version": "0.1.0",
4
+ "description": "Official TypeScript & JavaScript client for Keeprer Headless Database",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc ../../lib/sdk/index.ts --declaration --target es2022 --module esnext --moduleResolution bundler --outDir dist"
22
+ },
23
+ "keywords": [
24
+ "keeprer",
25
+ "database",
26
+ "headless",
27
+ "no-code",
28
+ "supabase",
29
+ "rest",
30
+ "sdk"
31
+ ],
32
+ "author": "Keeprer Team",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/ikarisz/Keeprer"
37
+ }
38
+ }