lingcode-js 1.0.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,125 @@
1
+ # lingcode-js
2
+
3
+ Official client SDK for a [LingCode](https://lingcode.dev) managed backend — database, auth, realtime, storage, serverless functions, vector search, and push, in one zero-dependency client.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install lingcode-js
9
+ ```
10
+
11
+ Or drop it in with a `<script>` tag (no build step):
12
+
13
+ ```html
14
+ <script src="https://lingcode.dev/sdk/lingcode-v1.js"></script>
15
+ <script>
16
+ const lingcode = LingCode.createClient(BACKEND_URL, ANON_KEY);
17
+ </script>
18
+ ```
19
+
20
+ > In apps built with LingCode `/try`, a ready `window.lingcode` is **already injected** — you can skip `createClient` entirely.
21
+
22
+ ## Quick start
23
+
24
+ ```js
25
+ import { createClient } from "lingcode-js";
26
+
27
+ const lingcode = createClient(
28
+ "https://lingcode.dev/api/cloud/be/<your-backend-id>",
29
+ "<your-anon-key>"
30
+ );
31
+ ```
32
+
33
+ ## Database
34
+
35
+ Supabase-style query builder — filters first, terminal op last.
36
+
37
+ ```js
38
+ // Select
39
+ const { data, error } = await lingcode
40
+ .from("todos")
41
+ .eq("done", false)
42
+ .order("created_at", { ascending: false })
43
+ .limit(50)
44
+ .select();
45
+
46
+ // Insert
47
+ await lingcode.from("todos").insert({ title: "Buy milk" });
48
+
49
+ // Update / delete (a filter is REQUIRED)
50
+ await lingcode.from("todos").eq("id", 1).update({ done: true });
51
+ await lingcode.from("todos").eq("id", 1).delete();
52
+ ```
53
+
54
+ Filters: `.eq .neq .gt .gte .lt .lte .like .ilike .in(col, [...]) .is(col, null | "not_null") .match({ ... })`.
55
+
56
+ ## Realtime
57
+
58
+ ```js
59
+ const off = lingcode.from("todos").subscribe(({ type, row }) => {
60
+ // type: "INSERT" | "UPDATE" | "DELETE" — patch your UI
61
+ });
62
+ // later: off();
63
+ ```
64
+
65
+ Server-side RLS means a signed-in user only ever receives their own rows.
66
+
67
+ ## Auth
68
+
69
+ The SDK persists the session and auto-attaches it to later calls.
70
+
71
+ ```js
72
+ await lingcode.auth.signUp({ email, password });
73
+ await lingcode.auth.signIn({ email, password });
74
+
75
+ // Passwordless (the SDK finalizes the link/redirect automatically)
76
+ await lingcode.auth.sendMagicLink({ email });
77
+ await lingcode.ready; // wait for redirect-session consumption on load
78
+ lingcode.auth.getUser(); // { id, email } | null
79
+
80
+ // Social (only render buttons whose provider is available)
81
+ const providers = await lingcode.auth.getProviders();
82
+ if (providers.google?.available) lingcode.auth.signInWithOAuth("google");
83
+
84
+ // Email code
85
+ await lingcode.auth.sendOtp({ email });
86
+ await lingcode.auth.verifyOtp({ email, code });
87
+
88
+ await lingcode.auth.signOut();
89
+ ```
90
+
91
+ ## Storage
92
+
93
+ ```js
94
+ const { data } = await lingcode.storage.from("public").upload("avatars/me.png", file);
95
+ const url = lingcode.storage.from("public").getPublicUrl("avatars/me.png");
96
+ await lingcode.storage.from("public").remove("avatars/me.png");
97
+ ```
98
+
99
+ ## Functions
100
+
101
+ ```js
102
+ const { data } = await lingcode.functions.invoke("send-email", { to, subject, html });
103
+ ```
104
+
105
+ ## Vector search
106
+
107
+ ```js
108
+ const { data } = await lingcode.vector.search({
109
+ table: "docs", column: "embedding", embedding: queryVec, limit: 5, metric: "cosine",
110
+ });
111
+ // Optional managed embeddings:
112
+ const { data: e } = await lingcode.vector.embed("some text"); // e.embedding
113
+ ```
114
+
115
+ ## Push notifications (Web Push)
116
+
117
+ ```js
118
+ await lingcode.push.subscribe(); // registers the service worker + subscribes
119
+ ```
120
+
121
+ The owner sends notifications from the LingCode Cloud console (or the backend API). For apps served on their own origin, host the service worker at your origin and pass `{ serviceWorker: "/lingcode-sw.js" }`.
122
+
123
+ ## License
124
+
125
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,166 @@
1
+ // Type definitions for lingcode-js
2
+ // Project: https://lingcode.dev
3
+
4
+ export interface LingCodeError extends Error {
5
+ /** Server error code, e.g. "where_required", "object_not_found", or null. */
6
+ code: string | null;
7
+ /** HTTP status (0 for client-side errors). */
8
+ status: number;
9
+ }
10
+
11
+ /** Supabase-style result envelope: check `error` before using `data`. */
12
+ export interface Result<T = any> {
13
+ data: T | null;
14
+ error: LingCodeError | null;
15
+ }
16
+
17
+ export interface User {
18
+ id: string | null;
19
+ email: string | null;
20
+ }
21
+
22
+ export interface Session {
23
+ user: User | null;
24
+ token: string;
25
+ }
26
+
27
+ /** A live row-change event delivered to `.subscribe()`. */
28
+ export interface ChangeEvent<T = any> {
29
+ table: string;
30
+ type: "INSERT" | "UPDATE" | "DELETE";
31
+ row: T;
32
+ }
33
+
34
+ /** Cancels a realtime subscription. */
35
+ export type Unsubscribe = () => void;
36
+
37
+ /**
38
+ * Chainable query builder. Add filters (`.eq`, `.in`, …) and modifiers
39
+ * (`.order`, `.limit`) before a terminal op (`.select`, `.insert`,
40
+ * `.update`, `.delete`). `.update()`/`.delete()` REQUIRE a filter.
41
+ */
42
+ export interface QueryBuilder<T = any> {
43
+ eq(column: string, value: any): this;
44
+ neq(column: string, value: any): this;
45
+ gt(column: string, value: any): this;
46
+ gte(column: string, value: any): this;
47
+ lt(column: string, value: any): this;
48
+ lte(column: string, value: any): this;
49
+ like(column: string, value: string): this;
50
+ ilike(column: string, value: string): this;
51
+ in(column: string, values: any[]): this;
52
+ /** `.is(col, null)` → IS NULL; `.is(col, "not_null")` → IS NOT NULL. */
53
+ is(column: string, value: null | "not_null"): this;
54
+ /** Merge several equality filters at once. */
55
+ match(filters: Record<string, any>): this;
56
+ order(column: string, opts?: { ascending?: boolean }): this;
57
+ limit(n: number): this;
58
+ range(from: number, to: number): this;
59
+
60
+ select(): Promise<Result<T[]>>;
61
+ insert(row: Partial<T> | Partial<T>[]): Promise<Result<T[]>>;
62
+ /** Requires a filter (.eq/.match). */
63
+ update(patch: Partial<T>): Promise<Result<T[]>>;
64
+ /** Requires a filter (.eq/.match). */
65
+ delete(): Promise<Result<T[]>>;
66
+
67
+ /** Subscribe to live INSERT/UPDATE/DELETE events (RLS-filtered). */
68
+ subscribe(
69
+ onChange: (event: ChangeEvent<T>) => void,
70
+ onError?: (e: Event) => void
71
+ ): Unsubscribe;
72
+ }
73
+
74
+ export interface ProviderInfo {
75
+ available: boolean;
76
+ source?: string | null;
77
+ }
78
+
79
+ export interface AuthApi {
80
+ signUp(creds: { email: string; password: string }): Promise<Result<Session>>;
81
+ signIn(creds: { email: string; password: string }): Promise<Result<Session>>;
82
+ signInWithPassword(creds: { email: string; password: string }): Promise<Result<Session>>;
83
+ /** Top-level navigation to the provider; on return the session is auto-stored. */
84
+ signInWithOAuth(provider: "google" | "github" | "apple" | string, opts?: { redirectTo?: string }): void;
85
+ /** Which OAuth providers are enabled for this backend. */
86
+ getProviders(): Promise<Record<string, ProviderInfo>>;
87
+ sendMagicLink(opts: { email: string; redirectTo?: string }): Promise<Result<{ sent: boolean }>>;
88
+ verifyMagicLink(token: string): Promise<Result<Session>>;
89
+ sendOtp(opts: { email: string }): Promise<Result<{ sent: boolean }>>;
90
+ verifyOtp(opts: { email: string; code: string }): Promise<Result<Session>>;
91
+ getUser(): User | null;
92
+ getToken(): string | null;
93
+ /** OAuth error code from the last redirect, if any. */
94
+ lastError(): string | null;
95
+ signOut(): Promise<{ error: null }>;
96
+ }
97
+
98
+ export interface StorageBucket {
99
+ upload(path: string, file: Blob | File | ArrayBuffer | string, opts?: { contentType?: string }): Promise<Result<{ bucket: string; path: string; bytes: number; url: string }>>;
100
+ download(path: string): Promise<Result<Blob>>;
101
+ getPublicUrl(path: string): string;
102
+ remove(path: string): Promise<Result<{ removed: boolean }>>;
103
+ }
104
+
105
+ export interface StorageApi {
106
+ from(bucket: string): StorageBucket;
107
+ }
108
+
109
+ export interface FunctionsApi {
110
+ invoke<T = any>(slug: string, body?: any): Promise<Result<T>>;
111
+ }
112
+
113
+ export interface VectorApi {
114
+ search<T = any>(query: {
115
+ table: string;
116
+ column: string;
117
+ embedding: number[];
118
+ limit?: number;
119
+ metric?: "cosine" | "l2" | "ip";
120
+ }): Promise<Result<T[]>>;
121
+ embed(input: string | string[]): Promise<Result<{ embedding: number[]; embeddings: number[][]; model: string; dimensions: number }>>;
122
+ }
123
+
124
+ export interface PushApi {
125
+ isSupported(): boolean;
126
+ /** Registers the service worker + subscribes via PushManager, then stores the subscription. */
127
+ subscribe(opts?: { serviceWorker?: string }): Promise<Result<any>>;
128
+ }
129
+
130
+ export interface LingCodeClient {
131
+ readonly url: string;
132
+ readonly anonKey: string;
133
+ readonly backendId: string;
134
+ /** Resolves after any auth redirect in the URL is consumed. */
135
+ readonly ready: Promise<LingCodeClient>;
136
+ from<T = any>(table: string): QueryBuilder<T>;
137
+ auth: AuthApi;
138
+ storage: StorageApi;
139
+ functions: FunctionsApi;
140
+ vector: VectorApi;
141
+ push: PushApi;
142
+ }
143
+
144
+ export interface CreateClientOptions {
145
+ /** Set false to skip reading ?lc_session/?lc_magic from the URL on construct. */
146
+ detectSessionInUrl?: boolean;
147
+ }
148
+
149
+ export function createClient(url: string, anonKey: string, options?: CreateClientOptions): LingCodeClient;
150
+ export const version: string;
151
+
152
+ declare const LingCode: {
153
+ createClient: typeof createClient;
154
+ version: string;
155
+ };
156
+ export default LingCode;
157
+
158
+ declare global {
159
+ interface Window {
160
+ LingCode: typeof LingCode;
161
+ /** Pre-injected in LingCode /try preview & published apps. */
162
+ lingcode?: LingCodeClient;
163
+ LINGCODE_BACKEND_URL?: string;
164
+ LINGCODE_BACKEND_ANON_KEY?: string;
165
+ }
166
+ }
package/index.mjs ADDED
@@ -0,0 +1,9 @@
1
+ // ESM entry for lingcode-js. The implementation lives in the UMD file
2
+ // lingcode-v1.js (one source of truth, also served at lingcode.dev/sdk and used
3
+ // as the browser <script> build). Node ESM + bundlers interop the CJS default
4
+ // export; this shim re-exports it as named + default ESM bindings.
5
+ import LingCode from './lingcode-v1.js';
6
+
7
+ export const createClient = LingCode.createClient;
8
+ export const version = LingCode.version;
9
+ export default LingCode;
package/lingcode-sw.js ADDED
@@ -0,0 +1,46 @@
1
+ /*!
2
+ * lingcode-sw.js — service worker for LingCode Web Push.
3
+ *
4
+ * Registered by the SDK's client.push.subscribe(). Renders incoming push
5
+ * payloads as notifications and focuses/opens the app on click. The push SEND
6
+ * path (per-backend VAPID keys, /push/subscribe + /push/send routes) lands with
7
+ * the notifications update — this worker is the stable client half, shipped with
8
+ * the SDK so the subscribe() flow has something to register.
9
+ *
10
+ * Note on scope: a service worker only controls pages under the path it's served
11
+ * from. Apps served on their own origin should host this file at their origin
12
+ * root (or pass { serviceWorker } to push.subscribe) so it can claim the app.
13
+ */
14
+ 'use strict';
15
+
16
+ self.addEventListener('install', function () { self.skipWaiting(); });
17
+ self.addEventListener('activate', function (event) { event.waitUntil(self.clients.claim()); });
18
+
19
+ self.addEventListener('push', function (event) {
20
+ var payload = {};
21
+ try { payload = event.data ? event.data.json() : {}; } catch (_) {
22
+ try { payload = { body: event.data ? event.data.text() : '' }; } catch (__) { payload = {}; }
23
+ }
24
+ var title = payload.title || 'Notification';
25
+ var options = {
26
+ body: payload.body || '',
27
+ icon: payload.icon,
28
+ badge: payload.badge,
29
+ data: { url: payload.url || '/' },
30
+ tag: payload.tag,
31
+ };
32
+ event.waitUntil(self.registration.showNotification(title, options));
33
+ });
34
+
35
+ self.addEventListener('notificationclick', function (event) {
36
+ event.notification.close();
37
+ var target = (event.notification.data && event.notification.data.url) || '/';
38
+ event.waitUntil(
39
+ self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (list) {
40
+ for (var i = 0; i < list.length; i++) {
41
+ if (list[i].url === target && 'focus' in list[i]) return list[i].focus();
42
+ }
43
+ if (self.clients.openWindow) return self.clients.openWindow(target);
44
+ })
45
+ );
46
+ });
package/lingcode-v1.js ADDED
@@ -0,0 +1,343 @@
1
+ /*!
2
+ * lingcode-js v1 — the official client SDK for a LingCode managed backend.
3
+ *
4
+ * Zero dependencies, no build step. Wraps the gateway REST endpoints
5
+ * (/api/cloud/be/<id>/*) in a Supabase/Firebase-shaped client so apps write
6
+ * client.from('todos').eq('done', false).select()
7
+ * instead of hand-rolled fetch().
8
+ *
9
+ * Loaded into generated/published apps via a <script> tag; the preview also
10
+ * pre-injects `window.lingcode` already wired to the app's backend. Keeping the
11
+ * two globals (window.LINGCODE_BACKEND_URL / _ANON_KEY) means raw-fetch apps
12
+ * keep working — the SDK is purely additive.
13
+ *
14
+ * Versioned filename (lingcode-v1.js): bump to -v2 only on a BREAKING change so
15
+ * the 4h CDN edge cache never strands an app on an incompatible build.
16
+ */
17
+ (function (global, factory) {
18
+ if (typeof module === 'object' && module.exports) module.exports = factory();
19
+ else global.LingCode = factory();
20
+ })(typeof self !== 'undefined' ? self : this, function () {
21
+ 'use strict';
22
+
23
+ // ── small helpers ────────────────────────────────────────────────────
24
+ function trimSlash(s) { return String(s || '').replace(/\/+$/, ''); }
25
+ function lastSegment(u) { var p = trimSlash(u).split('/'); return p[p.length - 1] || ''; }
26
+
27
+ function makeError(message, code, status) {
28
+ var e = new Error(message || 'Request failed');
29
+ e.code = code || null; e.status = status || 0; e.name = 'LingCodeError';
30
+ return e;
31
+ }
32
+
33
+ // Base64-decode a JWT payload (no verification — purely to read sub/email
34
+ // for getUser() after an OAuth round-trip where we only receive the token).
35
+ function decodeJwt(token) {
36
+ try {
37
+ var part = String(token).split('.')[1];
38
+ if (!part) return null;
39
+ var b64 = part.replace(/-/g, '+').replace(/_/g, '/');
40
+ while (b64.length % 4) b64 += '=';
41
+ return JSON.parse(decodeURIComponent(escape(atob(b64))));
42
+ } catch (_) { return null; }
43
+ }
44
+
45
+ // Turn a File/Blob/ArrayBuffer/string into base64 for storage uploads.
46
+ function toBase64(input) {
47
+ function bytesToB64(bytes) {
48
+ var binary = '', chunk = 0x8000;
49
+ for (var i = 0; i < bytes.length; i += chunk) {
50
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
51
+ }
52
+ return btoa(binary);
53
+ }
54
+ if (typeof input === 'string') return Promise.resolve(btoa(unescape(encodeURIComponent(input))));
55
+ if (input instanceof ArrayBuffer) return Promise.resolve(bytesToB64(new Uint8Array(input)));
56
+ if (input && typeof input.arrayBuffer === 'function') {
57
+ return input.arrayBuffer().then(function (buf) { return bytesToB64(new Uint8Array(buf)); });
58
+ }
59
+ return Promise.reject(makeError('upload() needs a File, Blob, ArrayBuffer or string', 'invalid_input'));
60
+ }
61
+
62
+ function lsGet(key) { try { return JSON.parse(localStorage.getItem(key) || 'null'); } catch (_) { return null; } }
63
+ function lsSet(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch (_) {} }
64
+ function lsDel(key) { try { localStorage.removeItem(key); } catch (_) {} }
65
+
66
+ // ── the client ───────────────────────────────────────────────────────
67
+ function createClient(url, anonKey, options) {
68
+ return new LingCodeClient(url, anonKey, options || {});
69
+ }
70
+
71
+ function LingCodeClient(url, anonKey, options) {
72
+ this.url = trimSlash(url);
73
+ this.anonKey = String(anonKey || '');
74
+ this.backendId = lastSegment(this.url);
75
+ this._sessionKey = 'lingcode.session.' + this.backendId;
76
+ this._session = lsGet(this._sessionKey); // { user, token } | null
77
+
78
+ this.auth = new AuthApi(this);
79
+ this.storage = new StorageNamespace(this);
80
+ this.functions = new FunctionsApi(this);
81
+ this.vector = new VectorApi(this);
82
+ this.push = new PushApi(this);
83
+
84
+ // Finalize any OAuth / magic-link redirect sitting in the URL. `ready`
85
+ // resolves once that's done so apps can `await client.ready` before reading
86
+ // getUser(). Sync sources (lc_session) resolve immediately.
87
+ this.ready = (options.detectSessionInUrl === false)
88
+ ? Promise.resolve(this)
89
+ : this._consumeUrlSession();
90
+ }
91
+
92
+ // The bearer to send: the signed-in user's JWT when present, else the anon key.
93
+ LingCodeClient.prototype._token = function () {
94
+ return (this._session && this._session.token) || this.anonKey;
95
+ };
96
+
97
+ LingCodeClient.prototype._setSession = function (data) {
98
+ if (!data || !data.token) return data;
99
+ this._session = { user: data.user || decodeUserFromToken(data.token), token: data.token };
100
+ lsSet(this._sessionKey, this._session);
101
+ return data;
102
+ };
103
+
104
+ // Core POST → unwrap { ok, data } → return data, or throw a normalized error.
105
+ LingCodeClient.prototype._req = function (op, body) {
106
+ var self = this;
107
+ return fetch(self.url + '/' + op, {
108
+ method: 'POST',
109
+ headers: { 'content-type': 'application/json', authorization: 'Bearer ' + self._token() },
110
+ body: JSON.stringify(body || {}),
111
+ }).then(function (res) {
112
+ return res.json().catch(function () { return null; }).then(function (json) {
113
+ if (!res.ok || !json || json.ok === false) {
114
+ throw makeError((json && (json.message || json.error)) || ('HTTP ' + res.status),
115
+ json && json.error, res.status);
116
+ }
117
+ return json.data;
118
+ });
119
+ });
120
+ };
121
+
122
+ LingCodeClient.prototype._get = function (path) {
123
+ var self = this;
124
+ return fetch(self.url + path, { headers: { authorization: 'Bearer ' + self._token() } })
125
+ .then(function (res) { return res.json().catch(function () { return null; }).then(function (j) { return { res: res, json: j }; }); });
126
+ };
127
+
128
+ LingCodeClient.prototype.from = function (table) { return new Query(this, table); };
129
+
130
+ // Read the URL for a session handed back by an auth redirect, finalize it,
131
+ // and strip the params so a refresh doesn't re-trigger.
132
+ LingCodeClient.prototype._consumeUrlSession = function () {
133
+ var self = this;
134
+ if (typeof location === 'undefined' || typeof URLSearchParams === 'undefined') return Promise.resolve(self);
135
+ var qs = new URLSearchParams(location.search);
136
+ var done = function () {
137
+ ['lc_session', 'lc_magic', 'lc_error'].forEach(function (k) { qs.delete(k); });
138
+ try {
139
+ var rest = qs.toString();
140
+ var clean = location.pathname + (rest ? '?' + rest : '') + location.hash;
141
+ history.replaceState(null, '', clean);
142
+ } catch (_) {}
143
+ return self;
144
+ };
145
+ var sess = qs.get('lc_session');
146
+ if (sess) { self._setSession({ token: sess }); return Promise.resolve(done()); }
147
+ var magic = qs.get('lc_magic');
148
+ if (magic) {
149
+ return self._req('auth/magiclink/verify', { token: magic })
150
+ .then(function (d) { self._setSession(d); }).catch(function () {})
151
+ .then(done);
152
+ }
153
+ // lc_error: leave it for the app to read via client.auth.lastError(), strip later.
154
+ if (qs.get('lc_error')) { self._authError = qs.get('lc_error'); return Promise.resolve(done()); }
155
+ return Promise.resolve(self);
156
+ };
157
+
158
+ function decodeUserFromToken(token) {
159
+ var c = decodeJwt(token);
160
+ if (!c) return null;
161
+ return { id: c.sub || null, email: c.email || null };
162
+ }
163
+
164
+ // ── query builder: filters first, terminal op last ───────────────────
165
+ // client.from('t').eq('done', false).order('created_at',{ascending:false}).limit(50).select()
166
+ // client.from('t').eq('id', 1).update({ done: true }) // where is required
167
+ // client.from('t').eq('id', 1).delete()
168
+ // client.from('t').insert({ ... }) // or insert([ ...rows ])
169
+ function Query(client, table) {
170
+ this.c = client; this.table = table;
171
+ this._where = {}; this._order = null; this._limit = null; this._offset = null;
172
+ }
173
+ function relOp(name) {
174
+ return function (col, val) { this._where[col] = makeRel(this._where[col], name, val); return this; };
175
+ }
176
+ function makeRel(existing, op, val) {
177
+ var node = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
178
+ node[op] = val; return node;
179
+ }
180
+ Query.prototype.eq = function (col, val) { this._where[col] = val; return this; };
181
+ Query.prototype.neq = relOp('neq');
182
+ Query.prototype.gt = relOp('gt');
183
+ Query.prototype.gte = relOp('gte');
184
+ Query.prototype.lt = relOp('lt');
185
+ Query.prototype.lte = relOp('lte');
186
+ Query.prototype.like = relOp('like');
187
+ Query.prototype.ilike = relOp('ilike');
188
+ Query.prototype.in = function (col, arr) { this._where[col] = { in: arr }; return this; };
189
+ // .is(col, null) → IS NULL ; .is(col, 'not_null') → IS NOT NULL
190
+ Query.prototype.is = function (col, val) { this._where[col] = (val === null) ? null : { is: val }; return this; };
191
+ // Merge a plain object of equality filters (Supabase-style .match()).
192
+ Query.prototype.match = function (obj) {
193
+ for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) this._where[k] = obj[k];
194
+ return this;
195
+ };
196
+ Query.prototype.order = function (col, opts) {
197
+ this._order = { column: col, ascending: opts ? opts.ascending !== false : true }; return this;
198
+ };
199
+ Query.prototype.limit = function (n) { this._limit = n; return this; };
200
+ Query.prototype.range = function (from, to) { this._offset = from; this._limit = (to - from + 1); return this; };
201
+
202
+ function settle(promise) {
203
+ return promise.then(function (data) { return { data: data, error: null }; },
204
+ function (error) { return { data: null, error: error }; });
205
+ }
206
+ function hasWhere(w) { for (var k in w) if (Object.prototype.hasOwnProperty.call(w, k)) return true; return false; }
207
+
208
+ Query.prototype.select = function () {
209
+ return settle(this.c._req('select', {
210
+ table: this.table, where: this._where, order: this._order,
211
+ limit: this._limit, offset: this._offset,
212
+ }));
213
+ };
214
+ Query.prototype.insert = function (rowOrRows) {
215
+ return settle(this.c._req('insert', { table: this.table, row: rowOrRows }));
216
+ };
217
+ Query.prototype.update = function (patch) {
218
+ if (!hasWhere(this._where)) return Promise.resolve({ data: null, error: makeError('update() requires a filter (.eq/.match) — refusing an unscoped update', 'where_required') });
219
+ return settle(this.c._req('update', { table: this.table, where: this._where, patch: patch }));
220
+ };
221
+ Query.prototype.delete = function () {
222
+ if (!hasWhere(this._where)) return Promise.resolve({ data: null, error: makeError('delete() requires a filter (.eq/.match) — refusing an unscoped delete', 'where_required') });
223
+ return settle(this.c._req('delete', { table: this.table, where: this._where }));
224
+ };
225
+
226
+ // Live updates: client.from('todos').subscribe(cb) → returns an unsubscribe fn.
227
+ // Each cb receives { table, type: 'INSERT'|'UPDATE'|'DELETE', row }. RLS is
228
+ // enforced server-side, so a signed-in user only ever sees their own rows.
229
+ Query.prototype.subscribe = function (cb, onError) {
230
+ var es = new EventSource(this.c.url + '/realtime?table=' + encodeURIComponent(this.table)
231
+ + '&apikey=' + encodeURIComponent(this.c._token()));
232
+ es.addEventListener('change', function (e) {
233
+ try { cb(JSON.parse(e.data)); } catch (_) {}
234
+ });
235
+ if (onError) es.onerror = onError; // EventSource auto-reconnects
236
+ return function unsubscribe() { try { es.close(); } catch (_) {} };
237
+ };
238
+
239
+ // ── auth ─────────────────────────────────────────────────────────────
240
+ function AuthApi(client) { this.c = client; }
241
+ AuthApi.prototype.signUp = function (creds) { return settle(this._post('auth/signup', creds)); };
242
+ AuthApi.prototype.signIn = AuthApi.prototype.signInWithPassword = function (creds) { return settle(this._post('auth/signin', creds)); };
243
+ AuthApi.prototype._post = function (op, body) {
244
+ var self = this;
245
+ return this.c._req(op, body).then(function (d) { self.c._setSession(d); return d; });
246
+ };
247
+ // Social login is a TOP-LEVEL navigation (not fetch); on return the SDK reads
248
+ // ?lc_session= and stores it automatically (see _consumeUrlSession).
249
+ AuthApi.prototype.signInWithOAuth = function (provider, opts) {
250
+ var redirect = (opts && opts.redirectTo) || location.href;
251
+ location.href = this.c.url + '/auth/oauth/' + encodeURIComponent(provider)
252
+ + '/start?redirect_url=' + encodeURIComponent(redirect);
253
+ };
254
+ // Which OAuth buttons to render — only show providers whose .available is true.
255
+ AuthApi.prototype.getProviders = function () {
256
+ return this.c._get('/auth/providers').then(function (r) {
257
+ if (!r.res.ok || !r.json) throw makeError('providers probe failed', null, r.res.status);
258
+ return r.json.providers || {};
259
+ });
260
+ };
261
+ AuthApi.prototype.sendMagicLink = function (opts) {
262
+ return settle(this.c._req('auth/magiclink/request', {
263
+ email: opts.email, redirect_url: (opts && opts.redirectTo) || location.href,
264
+ }));
265
+ };
266
+ AuthApi.prototype.verifyMagicLink = function (token) { return settle(this._post('auth/magiclink/verify', { token: token })); };
267
+ AuthApi.prototype.sendOtp = function (opts) { return settle(this.c._req('auth/otp/request', { email: opts.email })); };
268
+ AuthApi.prototype.verifyOtp = function (opts) { return settle(this._post('auth/otp/verify', { email: opts.email, code: opts.code })); };
269
+ AuthApi.prototype.getUser = function () { return (this.c._session && this.c._session.user) || null; };
270
+ AuthApi.prototype.getToken = function () { return (this.c._session && this.c._session.token) || null; };
271
+ AuthApi.prototype.lastError = function () { return this.c._authError || null; };
272
+ AuthApi.prototype.signOut = function () { this.c._session = null; lsDel(this.c._sessionKey); return Promise.resolve({ error: null }); };
273
+
274
+ // ── storage ──────────────────────────────────────────────────────────
275
+ function StorageNamespace(client) { this.c = client; }
276
+ StorageNamespace.prototype.from = function (bucket) { return new Bucket(this.c, bucket || 'public'); };
277
+
278
+ function Bucket(client, bucket) { this.c = client; this.bucket = bucket; }
279
+ Bucket.prototype.upload = function (path, file, opts) {
280
+ var self = this;
281
+ var contentType = (opts && opts.contentType) || (file && file.type) || 'application/octet-stream';
282
+ return settle(toBase64(file).then(function (data_b64) {
283
+ return self.c._req('storage/upload', { bucket: self.bucket, path: path, content_type: contentType, data_b64: data_b64 });
284
+ }));
285
+ };
286
+ Bucket.prototype.getPublicUrl = function (path) {
287
+ return this.c.url + '/storage/object?bucket=' + encodeURIComponent(this.bucket) + '&path=' + encodeURIComponent(path);
288
+ };
289
+ Bucket.prototype.download = function (path) {
290
+ return fetch(this.getPublicUrl(path)).then(function (res) {
291
+ if (!res.ok) return { data: null, error: makeError('object not found', 'object_not_found', res.status) };
292
+ return res.blob().then(function (blob) { return { data: blob, error: null }; });
293
+ });
294
+ };
295
+ // remove() lands with the S3/Spaces storage update; calls the route now so the
296
+ // surface is stable (returns { error } until that route ships).
297
+ Bucket.prototype.remove = function (path) {
298
+ return settle(this.c._req('storage/remove', { bucket: this.bucket, path: path }));
299
+ };
300
+
301
+ // ── functions ────────────────────────────────────────────────────────
302
+ function FunctionsApi(client) { this.c = client; }
303
+ // The gateway reads req.body.input, so wrap the caller's payload.
304
+ FunctionsApi.prototype.invoke = function (slug, body) { return settle(this.c._req('functions/' + slug, { input: body })); };
305
+
306
+ // ── vector / semantic search ─────────────────────────────────────────
307
+ function VectorApi(client) { this.c = client; }
308
+ VectorApi.prototype.search = function (q) {
309
+ return settle(this.c._req('vector/search', {
310
+ table: q.table, column: q.column, embedding: q.embedding, limit: q.limit, metric: q.metric,
311
+ }));
312
+ };
313
+ VectorApi.prototype.embed = function (input) { return settle(this.c._req('vector/embed', { input: input })); };
314
+
315
+ // ── push (Web Push) — server lands with the notifications update ──────
316
+ function PushApi(client) { this.c = client; }
317
+ PushApi.prototype.isSupported = function () {
318
+ return typeof navigator !== 'undefined' && 'serviceWorker' in navigator
319
+ && typeof window !== 'undefined' && 'PushManager' in window;
320
+ };
321
+ PushApi.prototype.subscribe = function (opts) {
322
+ var self = this;
323
+ if (!this.isSupported()) return Promise.resolve({ data: null, error: makeError('push not supported in this browser', 'unsupported') });
324
+ var swUrl = (opts && opts.serviceWorker) || 'https://lingcode.dev/sdk/lingcode-sw.js';
325
+ return navigator.serviceWorker.register(swUrl).then(function (reg) {
326
+ return self.c._get('/push/vapid-public').then(function (r) {
327
+ if (!r.res.ok || !r.json || !r.json.data) throw makeError('push not enabled for this backend', 'push_not_configured', r.res.status);
328
+ return reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(r.json.data.key || r.json.data) });
329
+ });
330
+ }).then(function (sub) {
331
+ return self.c._req('push/subscribe', { subscription: sub.toJSON ? sub.toJSON() : sub });
332
+ }).then(function (d) { return { data: d, error: null }; }, function (e) { return { data: null, error: e }; });
333
+ };
334
+ function urlBase64ToUint8Array(base64) {
335
+ var pad = '='.repeat((4 - base64.length % 4) % 4);
336
+ var b64 = (base64 + pad).replace(/-/g, '+').replace(/_/g, '/');
337
+ var raw = atob(b64), out = new Uint8Array(raw.length);
338
+ for (var i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
339
+ return out;
340
+ }
341
+
342
+ return { createClient: createClient, version: '1.0.0' };
343
+ });
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "lingcode-js",
3
+ "version": "1.0.0",
4
+ "description": "Official client SDK for a LingCode managed backend — database, auth, realtime, storage, serverless functions, vector search, and push.",
5
+ "main": "lingcode-v1.js",
6
+ "module": "index.mjs",
7
+ "browser": "lingcode-v1.js",
8
+ "unpkg": "lingcode-v1.js",
9
+ "jsdelivr": "lingcode-v1.js",
10
+ "types": "index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "import": "./index.mjs",
15
+ "require": "./lingcode-v1.js",
16
+ "default": "./lingcode-v1.js"
17
+ },
18
+ "./sw": "./lingcode-sw.js",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "files": [
22
+ "lingcode-v1.js",
23
+ "lingcode-sw.js",
24
+ "index.mjs",
25
+ "index.d.ts",
26
+ "README.md"
27
+ ],
28
+ "sideEffects": false,
29
+ "keywords": [
30
+ "lingcode",
31
+ "backend",
32
+ "baas",
33
+ "database",
34
+ "postgres",
35
+ "auth",
36
+ "realtime",
37
+ "storage",
38
+ "vector-search",
39
+ "push",
40
+ "supabase",
41
+ "firebase"
42
+ ],
43
+ "homepage": "https://lingcode.dev",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/Xavierhuang/LingCode.git",
47
+ "directory": "website/sdk"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/Xavierhuang/LingCode/issues"
51
+ },
52
+ "license": "MIT",
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }