@nestcorehq/js 0.0.0-stage → 0.9.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/LICENSE +21 -0
- package/README.md +274 -2
- package/dist/index.d.mts +2088 -0
- package/dist/index.d.ts +2088 -0
- package/dist/index.global.js +18 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +17 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NestCore
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,275 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nestcorehq/js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official **browser/JavaScript SDK** for the [NestCore](https://nestcore.io)
|
|
4
|
+
platform — auth, database, a Supabase-style query builder, storage, realtime,
|
|
5
|
+
and edge functions, for web apps (React, Vue, Svelte, vanilla, …).
|
|
6
|
+
|
|
7
|
+
This is the client-side counterpart to [`@nestcorehq/node`](https://github.com/Orbitnest/nestcore_node):
|
|
8
|
+
the same API surface, packaged for the browser (ESM, CJS, and a CDN/IIFE bundle),
|
|
9
|
+
with session persistence and no Node-only dependencies.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @nestcorehq/js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or drop it in over a CDN — the global is `NestCore` (the old `OrbitNest` global
|
|
16
|
+
still works as a deprecated alias):
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/@nestcorehq/js"></script>
|
|
20
|
+
<script>
|
|
21
|
+
const nestcore = NestCore.createClient({ apiKey: 'YOUR_ANON_KEY' });
|
|
22
|
+
</script>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> Use your project's **publishable anon key** in client-side code. Never ship a
|
|
26
|
+
> service-role key to the browser — it bypasses row-level security.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createClient, localStorageAdapter } from '@nestcorehq/js';
|
|
34
|
+
|
|
35
|
+
const nestcore = createClient({
|
|
36
|
+
apiKey: import.meta.env.VITE_NESTCORE_ANON_KEY,
|
|
37
|
+
storage: localStorageAdapter, // keep the user signed in across page reloads
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Query with the fluent builder
|
|
41
|
+
const { data, error } = await nestcore.db
|
|
42
|
+
.from('posts')
|
|
43
|
+
.eq('published', true)
|
|
44
|
+
.order('created_at', { ascending: false })
|
|
45
|
+
.limit(10)
|
|
46
|
+
.select();
|
|
47
|
+
|
|
48
|
+
// Auth
|
|
49
|
+
await nestcore.auth.signIn({ email, password });
|
|
50
|
+
const user = nestcore.auth.getUser();
|
|
51
|
+
|
|
52
|
+
// Realtime
|
|
53
|
+
const channel = nestcore.channel('orders')
|
|
54
|
+
.on('postgres_changes', { event: 'INSERT', table: 'orders' }, (e) => console.log('new order', e.new))
|
|
55
|
+
.subscribe();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The project slug is read from the API key's JWT, and `baseUrl` defaults to
|
|
59
|
+
`https://api.nestcore.io` — so `apiKey` is usually all you need. Pass `baseUrl`
|
|
60
|
+
for local dev.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## What's included
|
|
65
|
+
|
|
66
|
+
`createClient()` returns a client with these namespaces:
|
|
67
|
+
|
|
68
|
+
| Namespace | What it does |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `db` | Raw SQL (`db.query`), table CRUD, RLS policy management, and the fluent query builder via `db.from(table)`. |
|
|
71
|
+
| `auth` | Email/password, email OTP, password recovery, profile, and **passkeys/WebAuthn**. Sessions persist via the configured storage adapter. |
|
|
72
|
+
| `storage` | `storage.from(bucket)` → `upload` / `download` / `list` / `remove` / `getPublicUrl`. |
|
|
73
|
+
| `realtime` / `channel()` | Postgres change subscriptions, broadcast, and presence over WebSocket. |
|
|
74
|
+
| `functions` | `functions.invoke(name, { body })` to call edge functions. |
|
|
75
|
+
| `jobs`, `env`, `logs` | Deprecated: these need a NestCore console admin token, so with a project key every call returns an `ADMIN_ONLY` error. Use the console, the CLI or the MCP server. |
|
|
76
|
+
| `analytics` | `identify` / `track` / `screen` / `crash`, with batched ingest. |
|
|
77
|
+
|
|
78
|
+
### The query builder
|
|
79
|
+
|
|
80
|
+
`db.from(table)` returns a chainable, **parameterized** builder (every value is
|
|
81
|
+
bound, never interpolated) that mirrors `@nestcorehq/node` and Supabase:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
nestcore.db.from('users')
|
|
85
|
+
.eq('role', 'admin') // also: neq, gt, gte, lt, lte, like, ilike, in, is
|
|
86
|
+
.order('created_at', { ascending: false })
|
|
87
|
+
.range(0, 19) // or .limit(n) / .page(n, size)
|
|
88
|
+
.select('id, email, role'); // → { data: { rows, total }, error }
|
|
89
|
+
|
|
90
|
+
await nestcore.db.from('users').eq('id', id).single(); // exactly one row
|
|
91
|
+
await nestcore.db.from('users').eq('id', id).maybeSingle(); // one row or null
|
|
92
|
+
|
|
93
|
+
await nestcore.db.from('posts').insert({ title: 'Hi' });
|
|
94
|
+
await nestcore.db.from('posts').update(id, { title: 'Edited' });
|
|
95
|
+
await nestcore.db.from('posts').delete(id);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Every call returns `{ data, error }` — check `error` before using `data` (the
|
|
99
|
+
SDK never throws on API errors).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Session persistence
|
|
104
|
+
|
|
105
|
+
By default the session lives only in that client instance (safe for SSR: one
|
|
106
|
+
client per request never sees another request's user). Pass a storage adapter
|
|
107
|
+
to persist it:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { createClient, localStorageAdapter, createMemoryAdapter } from '@nestcorehq/js';
|
|
111
|
+
|
|
112
|
+
createClient({ apiKey, storage: localStorageAdapter }); // survives refresh
|
|
113
|
+
createClient({ apiKey, storage: createMemoryAdapter() }); // in-memory store you can share deliberately
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`localStorageAdapter` persists nothing when `localStorage` is unavailable (SSR,
|
|
117
|
+
private mode), so it's always safe to pass — on a server each client keeps its
|
|
118
|
+
own session.
|
|
119
|
+
You can also supply your own adapter — any object with
|
|
120
|
+
`getItem` / `setItem` / `removeItem`:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import type { StorageAdapter } from '@nestcorehq/js';
|
|
124
|
+
|
|
125
|
+
const sessionStorageAdapter: StorageAdapter = {
|
|
126
|
+
getItem: (k) => sessionStorage.getItem(k),
|
|
127
|
+
setItem: (k, v) => sessionStorage.setItem(k, v),
|
|
128
|
+
removeItem: (k) => sessionStorage.removeItem(k),
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Persisted sessions are namespaced per project, so multiple clients on the same
|
|
133
|
+
origin don't collide.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### Calling auth from your server
|
|
138
|
+
|
|
139
|
+
Auth endpoints are throttled per client IP. When your server calls them for its users (a Next.js server action, an API route), every user would share your server's IP and its limit. Pass the user's IP with the **service-role key** and each user gets their own bucket; the same IP goes into the auth audit log:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const client = createClient({
|
|
143
|
+
apiKey: process.env.NESTCORE_SERVICE_ROLE_KEY!, // never ship this key to a browser
|
|
144
|
+
clientIp: userIp, // e.g. the first address of x-forwarded-for set by YOUR proxy
|
|
145
|
+
});
|
|
146
|
+
await client.auth.signIn({ email, password });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`clientIp` is sent as `X-NestCore-Client-IP` and must be a single IPv4 or IPv6 address. The API ignores it on requests made with the anon key or a user token, so a browser can't choose its own bucket. Create one client per request.
|
|
150
|
+
|
|
151
|
+
Project auth answers `429` with a `code` when a limit from the project's auth settings is reached: `OTP_RATE_LIMITED` (too many wrong codes), `OTP_REQUEST_LIMIT` (too many codes requested for one email in an hour), `ACCOUNT_LOCKED` (too many wrong passwords).
|
|
152
|
+
|
|
153
|
+
## AI
|
|
154
|
+
|
|
155
|
+
Chat, streaming, tool calls and embeddings through the project's own provider keys (Console → Project Settings → AI). Calls need a signed-in user or the service-role key.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const { data } = await client.ai.chat({ messages: [{ role: 'user', content: 'Summarise my last order' }] });
|
|
159
|
+
|
|
160
|
+
for await (const ev of client.ai.chatStream({ messages })) {
|
|
161
|
+
if (ev.type === 'delta') process.stdout.write(ev.text);
|
|
162
|
+
if (ev.type === 'error') console.error(ev.code, ev.message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { data: vectors } = await client.ai.embed(['first', 'second']); // 768 dimensions
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Tools: pass `tools: [{ name, description, parameters }]`; run each of `data.toolCalls`, then send the reply back as `{ role: 'assistant', content: null, toolCalls }` plus one `{ role: 'tool', toolCallId, content }` per call. See https://nestcore.io/docs/ai-gateway.
|
|
169
|
+
|
|
170
|
+
### Memory, collections, threads, semantic search
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// Memory: facts about the signed-in user (near-duplicates update the same memory)
|
|
174
|
+
await client.memory.add('Prefers vegetarian recipes', { kind: 'preference' });
|
|
175
|
+
const { data: mem } = await client.memory.search('what should I cook?'); // mem.degraded = keyword fallback
|
|
176
|
+
|
|
177
|
+
// Collections: documents chunked, embedded and searched (hybrid vector + keyword)
|
|
178
|
+
await admin.collections.create({ name: 'handbook' }); // service-role key
|
|
179
|
+
await admin.collections.from('handbook').ingest({ url: 'https://example.com/guide.pdf' }, { wait: true });
|
|
180
|
+
const { data: hits } = await client.collections.from('handbook').search('How do refunds work?');
|
|
181
|
+
|
|
182
|
+
// Threads: saved conversations; replies stream and are stored as they arrive
|
|
183
|
+
const { data: thread } = await client.ai.threads.create({ title: 'Trip' });
|
|
184
|
+
for await (const ev of client.ai.threads.from(thread.id).run({ input: 'Plan a weekend', memory: true, collections: ['handbook'] })) {
|
|
185
|
+
if (ev.type === 'delta') process.stdout.write(ev.text);
|
|
186
|
+
if (ev.type === 'citation') console.log(`[${ev.index}]`, ev.title);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Automatic embeddings (Console → AI → Embeddings), then search a table by meaning under RLS
|
|
190
|
+
const { data } = await client.db.from('articles').eq('published', true).semantic('body_embedding', 'reset my password', { k: 5 });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
See https://nestcore.io/docs/memory-api, /docs/collections, /docs/threads and /docs/embeddings.
|
|
194
|
+
|
|
195
|
+
## Realtime
|
|
196
|
+
|
|
197
|
+
Browsers can't set WebSocket request headers, so the API key is sent via the
|
|
198
|
+
connection subprotocol (never in the URL — it would leak into logs/history). In
|
|
199
|
+
the browser the SDK uses the native `WebSocket`; for isomorphic/SSR use on
|
|
200
|
+
Node.js < 22, pass one:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import WebSocket from 'ws';
|
|
204
|
+
createClient({ apiKey, realtimeOptions: { WebSocket } });
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const channel = nestcore.channel('room-1')
|
|
209
|
+
.on('broadcast', { event: 'cursor' }, (msg) => render(msg.payload))
|
|
210
|
+
.on('postgres_changes', { event: '*', table: 'messages' }, (e) => append(e.new))
|
|
211
|
+
.subscribe((status) => console.log(status));
|
|
212
|
+
|
|
213
|
+
channel.send('cursor', { x, y });
|
|
214
|
+
channel.presence.track({ user: 'me' });
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## TypeScript types from your schema
|
|
220
|
+
|
|
221
|
+
Generate row types from the live schema for end-to-end safety (typically run
|
|
222
|
+
server-side / in a build step, since it needs a service-role key):
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { generateTypes } from '@nestcorehq/js';
|
|
226
|
+
const ts = await generateTypes(serverClient.db); // → string of TS interfaces + a `Database` map
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> Prefer the [`nestcore` CLI](https://github.com/Orbitnest/nestcore_cli)'s
|
|
230
|
+
> `nestcore gen types` for this in projects.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Browser support
|
|
235
|
+
|
|
236
|
+
Targets ES2020 and any environment with `fetch`, `WebSocket`, `FormData`, and
|
|
237
|
+
`Blob` (all modern browsers; Node 22+ for isomorphic use). No polyfills needed.
|
|
238
|
+
|
|
239
|
+
`@nestcorehq/node` vs `@nestcorehq/js`: the Node SDK additionally ships a
|
|
240
|
+
filesystem-backed **migration runner**; the web SDK omits it (no filesystem in
|
|
241
|
+
the browser) and adds **session persistence adapters**. Everything else is the
|
|
242
|
+
same API.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npm install
|
|
250
|
+
npm run build # → dist/ : ESM (.mjs), CJS (.js), IIFE (.global.js), + .d.ts
|
|
251
|
+
npm run typecheck
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Migrating from `@orbitneststudio/js`
|
|
257
|
+
|
|
258
|
+
The SDK was renamed from OrbitNest to NestCore. Switch the dependency to
|
|
259
|
+
`@nestcorehq/js` and update your imports; nothing else has to change at once:
|
|
260
|
+
|
|
261
|
+
- The old export names still work as deprecated aliases: `OrbitNestError`
|
|
262
|
+
(same class as `NestCoreError`, so `instanceof` checks keep working),
|
|
263
|
+
`OrbitNestClient` and `OrbitNestConfig`.
|
|
264
|
+
- The default `baseUrl` is now `https://api.nestcore.io`.
|
|
265
|
+
- `clientIp` is now sent as `X-NestCore-Client-IP`.
|
|
266
|
+
- Sessions saved under the old `orbitnest.session.*` storage key and the
|
|
267
|
+
analytics ids under `orbitnest_*` are moved to the new `nestcore` keys on
|
|
268
|
+
first read, so signed-in users stay signed in.
|
|
269
|
+
- The CDN global `OrbitNest` is kept as an alias of `NestCore`.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT © NestCore
|