@raisindb/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 ADDED
@@ -0,0 +1,734 @@
1
+ # RaisinDB JavaScript/TypeScript Client SDK
2
+
3
+ A universal WebSocket client for RaisinDB that works seamlessly in browser, Node.js, and serverless environments.
4
+
5
+ ## Features
6
+
7
+ - **Universal Compatibility**: Works in browser, Node.js, and serverless environments
8
+ - **TypeScript First**: Full TypeScript support with comprehensive type definitions
9
+ - **Auto-Reconnection**: Automatic reconnection with exponential backoff
10
+ - **Event Subscriptions**: Real-time event streaming with filtered subscriptions
11
+ - **MessagePack Protocol**: Efficient binary protocol using MessagePack
12
+ - **JWT Authentication**: Built-in authentication and token management
13
+ - **Identity Auth**: Email/password login, registration, session management
14
+ - **Auth State Changes**: Firebase/Supabase-style `onAuthStateChange()` listener
15
+ - **User Home Subscriptions**: Auto-subscribe to user profile changes
16
+ - **SQL Queries**: Template literal support for safe SQL queries
17
+ - **Type-Safe**: Full type safety for all operations
18
+ - **Framework Agnostic**: Works with React, Vue, Svelte, Angular, or vanilla JS
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @raisindb/client
24
+ ```
25
+
26
+ For Node.js environments, also install the WebSocket library:
27
+
28
+ ```bash
29
+ npm install ws
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```typescript
35
+ import { RaisinClient } from '@raisindb/client';
36
+
37
+ // Create client
38
+ const client = new RaisinClient('raisin://localhost:8080/sys/default');
39
+
40
+ // Connect and authenticate
41
+ await client.connect();
42
+ await client.authenticate({
43
+ username: 'admin',
44
+ password: 'password'
45
+ });
46
+
47
+ // Get database and workspace
48
+ const db = client.database('my_repo');
49
+ const ws = db.workspace('content');
50
+
51
+ // Create a node
52
+ const node = await ws.nodes().create({
53
+ type: 'Page',
54
+ path: '/home',
55
+ properties: {
56
+ title: 'Home Page',
57
+ description: 'Welcome to our site'
58
+ }
59
+ });
60
+
61
+ console.log('Created node:', node);
62
+ ```
63
+
64
+ ## Usage Examples
65
+
66
+ ### Admin Authentication
67
+
68
+ ```typescript
69
+ import { RaisinClient, ConnectionState } from '@raisindb/client';
70
+
71
+ const client = new RaisinClient('raisin://localhost:8080/sys/default', {
72
+ connection: {
73
+ autoReconnect: true,
74
+ heartbeatInterval: 30000,
75
+ },
76
+ requestTimeout: 30000,
77
+ defaultBranch: 'main',
78
+ });
79
+
80
+ // Listen to connection state changes (public API)
81
+ const unsubscribe = client.onConnectionStateChange((state) => {
82
+ console.log('Connection state:', state);
83
+ if (state === ConnectionState.Disconnected) {
84
+ showOfflineIndicator();
85
+ }
86
+ });
87
+
88
+ // Connect and authenticate (admin user)
89
+ await client.connect();
90
+ await client.authenticate({ username: 'admin', password: 'password' });
91
+
92
+ console.log('Authenticated:', client.isAuthenticated());
93
+ ```
94
+
95
+ ### Identity Authentication (Email/Password)
96
+
97
+ For user-facing apps with email/password authentication:
98
+
99
+ ```typescript
100
+ import { RaisinClient, LocalStorageTokenStorage } from '@raisindb/client';
101
+
102
+ // Create client with persistent token storage
103
+ const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
104
+ tokenStorage: new LocalStorageTokenStorage('myapp'),
105
+ });
106
+
107
+ // Login with email/password
108
+ const user = await client.loginWithEmail('user@example.com', 'password', 'myrepo');
109
+ console.log('Logged in as:', user.email);
110
+ console.log('User home path:', user.userHome); // e.g., '/users/internal/john-at-example-com'
111
+
112
+ // Register new user
113
+ const newUser = await client.registerWithEmail(
114
+ 'newuser@example.com',
115
+ 'password123',
116
+ 'myrepo',
117
+ 'John Doe' // display name
118
+ );
119
+
120
+ // Logout (reconnects as anonymous by default)
121
+ await client.logout();
122
+
123
+ // Or fully disconnect
124
+ await client.logout({ disconnect: true });
125
+ ```
126
+
127
+ ### Session Restoration
128
+
129
+ Restore user session on app startup:
130
+
131
+ ```typescript
132
+ const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
133
+ tokenStorage: new LocalStorageTokenStorage('myapp'),
134
+ });
135
+
136
+ // Initialize session from stored tokens
137
+ const user = await client.initSession('myrepo');
138
+ if (user) {
139
+ console.log('Session restored for:', user.email);
140
+ } else {
141
+ // No stored session - redirect to login
142
+ redirectToLogin();
143
+ }
144
+ ```
145
+
146
+ ### Auth State Changes (Firebase/Supabase Pattern)
147
+
148
+ Listen reactively to authentication events:
149
+
150
+ ```typescript
151
+ import { RaisinClient, AuthEvent } from '@raisindb/client';
152
+
153
+ // Subscribe to auth state changes
154
+ const unsubscribe = client.onAuthStateChange(({ event, session }) => {
155
+ switch (event) {
156
+ case 'SIGNED_IN':
157
+ console.log('User signed in:', session.user?.email);
158
+ break;
159
+ case 'SIGNED_OUT':
160
+ console.log('User signed out');
161
+ redirectToLogin();
162
+ break;
163
+ case 'TOKEN_REFRESHED':
164
+ console.log('Token refreshed');
165
+ break;
166
+ case 'SESSION_EXPIRED':
167
+ console.log('Session expired');
168
+ break;
169
+ case 'USER_UPDATED':
170
+ console.log('User profile updated:', session.user);
171
+ break;
172
+ }
173
+ });
174
+
175
+ // Later, to stop listening:
176
+ unsubscribe();
177
+ ```
178
+
179
+ ### User Home Changes
180
+
181
+ Listen for real-time updates to the user's profile node:
182
+
183
+ ```typescript
184
+ // Auto-subscribes to user's home node when logged in
185
+ const unsubscribe = client.onUserChange(({ node, changeType }) => {
186
+ console.log('User home updated:', changeType);
187
+ console.log('New properties:', node.properties);
188
+ // Update UI with new avatar, displayName, preferences, etc.
189
+ });
190
+ ```
191
+
192
+ ### Helper Methods
193
+
194
+ ```typescript
195
+ // Get current session (sync)
196
+ const session = client.getSession();
197
+ if (session) {
198
+ console.log('User:', session.user?.email);
199
+ console.log('Token:', session.accessToken);
200
+ }
201
+
202
+ // Get current user (alias for getStoredUser)
203
+ const user = client.getUser();
204
+
205
+ // Check if user has stored token
206
+ if (client.hasStoredToken()) {
207
+ await client.initSession('myrepo');
208
+ }
209
+ ```
210
+
211
+ ### Node Operations
212
+
213
+ ```typescript
214
+ const db = client.database('my_repo');
215
+ const ws = db.workspace('content');
216
+ const nodes = ws.nodes();
217
+
218
+ // Create a node
219
+ const page = await nodes.create({
220
+ type: 'Page',
221
+ path: '/blog/my-post',
222
+ properties: {
223
+ title: 'My First Post',
224
+ author: 'John Doe',
225
+ published: true,
226
+ },
227
+ });
228
+
229
+ // Get a node by ID
230
+ const node = await nodes.get(page.id);
231
+
232
+ // Get a node by path
233
+ const nodeByPath = await nodes.getByPath('/blog/my-post');
234
+
235
+ // Update a node
236
+ const updated = await nodes.update(page.id, {
237
+ properties: {
238
+ title: 'My Updated Post',
239
+ updatedAt: new Date().toISOString(),
240
+ },
241
+ });
242
+
243
+ // Query nodes by type
244
+ const pages = await nodes.queryByType('Page', 10);
245
+
246
+ // Query nodes by property
247
+ const publishedPages = await nodes.queryByProperty('published', true);
248
+
249
+ // Get children of a node
250
+ const children = await nodes.getChildren(page.id);
251
+
252
+ // Delete a node
253
+ await nodes.delete(page.id);
254
+ ```
255
+
256
+ ### SQL Queries
257
+
258
+ ```typescript
259
+ const db = client.database('my_repo');
260
+
261
+ // Template literal queries (automatically parameterized)
262
+ const nodeType = 'Page';
263
+ const results = await db.sql`
264
+ SELECT * FROM nodes
265
+ WHERE node_type = ${nodeType}
266
+ ORDER BY created_at DESC
267
+ LIMIT 10
268
+ `;
269
+
270
+ console.log('Columns:', results.columns);
271
+ console.log('Rows:', results.rows);
272
+ console.log('Row count:', results.row_count);
273
+
274
+ // Raw SQL with explicit parameters
275
+ const sqlQuery = db.getSqlQuery();
276
+ const results2 = await sqlQuery.execute(
277
+ 'SELECT * FROM nodes WHERE node_type = $1 AND created_at > $2',
278
+ ['Page', '2024-01-01']
279
+ );
280
+
281
+ // Raw SQL without parameters (use with caution)
282
+ const results3 = await sqlQuery.raw('SELECT COUNT(*) FROM nodes');
283
+ ```
284
+
285
+ ### Event Subscriptions
286
+
287
+ ```typescript
288
+ const db = client.database('my_repo');
289
+ const ws = db.workspace('content');
290
+ const events = ws.events();
291
+
292
+ // Subscribe to all events in workspace
293
+ const subscription1 = await events.subscribe({}, (event) => {
294
+ console.log('Event:', event.event_type);
295
+ console.log('Payload:', event.payload);
296
+ });
297
+
298
+ // Subscribe to specific event types
299
+ const subscription2 = await events.subscribeToTypes(
300
+ ['node:created', 'node:updated'],
301
+ (event) => {
302
+ console.log('Node changed:', event.payload);
303
+ }
304
+ );
305
+
306
+ // Subscribe to events for a specific path pattern
307
+ const subscription3 = await events.subscribeToPath('/blog/*', (event) => {
308
+ console.log('Blog event:', event);
309
+ });
310
+
311
+ // Subscribe to events for a specific node type
312
+ const subscription4 = await events.subscribeToNodeType('Page', (event) => {
313
+ console.log('Page event:', event);
314
+ });
315
+
316
+ // Unsubscribe
317
+ await subscription1.unsubscribe();
318
+ ```
319
+
320
+ ### Workspace Management
321
+
322
+ ```typescript
323
+ const db = client.database('my_repo');
324
+ const workspaces = db.workspaces();
325
+
326
+ // Create a workspace
327
+ const workspace = await workspaces.create({
328
+ name: 'blog',
329
+ description: 'Blog content workspace',
330
+ });
331
+
332
+ // Get workspace metadata
333
+ const ws = await workspaces.get('blog');
334
+ console.log('Workspace:', ws);
335
+
336
+ // List all workspaces
337
+ const allWorkspaces = await workspaces.list();
338
+
339
+ // Update workspace
340
+ const updated = await workspaces.update('blog', {
341
+ description: 'Updated description',
342
+ allowed_node_types: ['Page', 'Post', 'Image'],
343
+ });
344
+
345
+ // Delete workspace
346
+ await workspaces.delete('blog');
347
+ ```
348
+
349
+ ### Working with Multiple Branches
350
+
351
+ ```typescript
352
+ const client = new RaisinClient('raisin://localhost:8080/sys/default');
353
+ await client.connect();
354
+ await client.authenticate({ username: 'admin', password: 'password' });
355
+
356
+ // Work on main branch
357
+ const db = client.database('my_repo');
358
+ const mainWs = db.workspace('content');
359
+ const node = await mainWs.nodes().create({
360
+ type: 'Page',
361
+ path: '/test',
362
+ properties: { title: 'Test' },
363
+ });
364
+
365
+ // Switch to feature branch
366
+ client.setBranch('feature/new-design');
367
+
368
+ // Operations now run on feature branch
369
+ const featureWs = db.workspace('content');
370
+ const featureNode = await featureWs.nodes().get(node.id);
371
+
372
+ // Switch back to main
373
+ client.setBranch('main');
374
+ ```
375
+
376
+ ### Error Handling
377
+
378
+ ```typescript
379
+ import { RaisinClient } from '@raisindb/client';
380
+
381
+ try {
382
+ const client = new RaisinClient('raisin://localhost:8080/sys/default');
383
+ await client.connect();
384
+
385
+ try {
386
+ await client.authenticate({
387
+ username: 'admin',
388
+ password: 'wrong-password'
389
+ });
390
+ } catch (error) {
391
+ console.error('Authentication failed:', error.message);
392
+ console.error('Error code:', error.code);
393
+ }
394
+
395
+ const db = client.database('my_repo');
396
+ const ws = db.workspace('content');
397
+
398
+ try {
399
+ const node = await ws.nodes().get('non-existent-id');
400
+ if (!node) {
401
+ console.log('Node not found');
402
+ }
403
+ } catch (error) {
404
+ console.error('Error fetching node:', error);
405
+ }
406
+
407
+ } catch (error) {
408
+ console.error('Connection error:', error);
409
+ }
410
+ ```
411
+
412
+ ### Custom Token Storage
413
+
414
+ By default, tokens are stored in memory. You can implement custom storage:
415
+
416
+ ```typescript
417
+ import { TokenStorage } from '@raisindb/client';
418
+
419
+ class LocalStorageTokenStorage implements TokenStorage {
420
+ getAccessToken(): string | null {
421
+ return localStorage.getItem('access_token');
422
+ }
423
+
424
+ setAccessToken(token: string): void {
425
+ localStorage.setItem('access_token', token);
426
+ }
427
+
428
+ getRefreshToken(): string | null {
429
+ return localStorage.getItem('refresh_token');
430
+ }
431
+
432
+ setRefreshToken(token: string): void {
433
+ localStorage.setItem('refresh_token', token);
434
+ }
435
+
436
+ clear(): void {
437
+ localStorage.removeItem('access_token');
438
+ localStorage.removeItem('refresh_token');
439
+ }
440
+ }
441
+
442
+ const client = new RaisinClient('raisin://localhost:8080/sys/default', {
443
+ tokenStorage: new LocalStorageTokenStorage(),
444
+ });
445
+ ```
446
+
447
+ ## Framework Integration
448
+
449
+ ### React
450
+
451
+ ```tsx
452
+ import { useEffect, useState } from 'react';
453
+ import { RaisinClient, LocalStorageTokenStorage, IdentityUser } from '@raisindb/client';
454
+
455
+ const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
456
+ tokenStorage: new LocalStorageTokenStorage('myapp'),
457
+ });
458
+
459
+ function App() {
460
+ const [user, setUser] = useState<IdentityUser | null>(null);
461
+ const [loading, setLoading] = useState(true);
462
+
463
+ useEffect(() => {
464
+ // Initialize session on mount
465
+ client.initSession('myrepo').then(setUser).finally(() => setLoading(false));
466
+
467
+ // Listen to auth state changes
468
+ const unsubscribe = client.onAuthStateChange(({ event, session }) => {
469
+ setUser(session.user);
470
+ });
471
+
472
+ return unsubscribe;
473
+ }, []);
474
+
475
+ if (loading) return <div>Loading...</div>;
476
+ if (!user) return <LoginPage />;
477
+ return <Dashboard user={user} />;
478
+ }
479
+ ```
480
+
481
+ ### SvelteKit
482
+
483
+ ```typescript
484
+ // lib/raisin.ts
485
+ import { RaisinClient, LocalStorageTokenStorage } from '@raisindb/client';
486
+ import { writable } from 'svelte/store';
487
+
488
+ export const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
489
+ tokenStorage: new LocalStorageTokenStorage('myapp'),
490
+ });
491
+
492
+ export const user = writable<IdentityUser | null>(null);
493
+
494
+ // Subscribe to auth changes
495
+ client.onAuthStateChange(({ session }) => {
496
+ user.set(session.user);
497
+ });
498
+
499
+ // lib/stores/auth.ts
500
+ export async function initSession() {
501
+ return client.initSession('myrepo');
502
+ }
503
+
504
+ export async function login(email: string, password: string) {
505
+ return client.loginWithEmail(email, password, 'myrepo');
506
+ }
507
+
508
+ export async function logout() {
509
+ return client.logout();
510
+ }
511
+ ```
512
+
513
+ ### Vue 3
514
+
515
+ ```typescript
516
+ // composables/useAuth.ts
517
+ import { ref, onMounted, onUnmounted } from 'vue';
518
+ import { RaisinClient, LocalStorageTokenStorage, IdentityUser } from '@raisindb/client';
519
+
520
+ const client = new RaisinClient('ws://localhost:8081/sys/default/myrepo', {
521
+ tokenStorage: new LocalStorageTokenStorage('myapp'),
522
+ });
523
+
524
+ export function useAuth() {
525
+ const user = ref<IdentityUser | null>(null);
526
+ const loading = ref(true);
527
+ let unsubscribe: (() => void) | null = null;
528
+
529
+ onMounted(async () => {
530
+ user.value = await client.initSession('myrepo');
531
+ loading.value = false;
532
+
533
+ unsubscribe = client.onAuthStateChange(({ session }) => {
534
+ user.value = session.user;
535
+ });
536
+ });
537
+
538
+ onUnmounted(() => unsubscribe?.());
539
+
540
+ return {
541
+ user,
542
+ loading,
543
+ login: (email: string, password: string) => client.loginWithEmail(email, password, 'myrepo'),
544
+ logout: () => client.logout(),
545
+ };
546
+ }
547
+ ```
548
+
549
+ ### Server-Side Rendering (SSR)
550
+
551
+ For SSR environments (Next.js, Nuxt, SvelteKit server routes), use the HTTP client:
552
+
553
+ ```typescript
554
+ import { RaisinClient } from '@raisindb/client';
555
+
556
+ // Create HTTP-only client for SSR
557
+ const client = RaisinClient.forSSR('http://localhost:8080', {
558
+ tenantId: 'default',
559
+ });
560
+
561
+ // Authenticate (admin or JWT)
562
+ await client.authenticate({ username: 'admin', password: 'admin' });
563
+
564
+ // Execute queries
565
+ const result = await client.executeSql('myrepo', 'SELECT * FROM nodes LIMIT 10');
566
+ ```
567
+
568
+ ## API Reference
569
+
570
+ ### RaisinClient
571
+
572
+ Main client class for connecting to RaisinDB.
573
+
574
+ #### Constructor
575
+
576
+ ```typescript
577
+ new RaisinClient(url: string, options?: ClientOptions)
578
+ ```
579
+
580
+ **Options:**
581
+ - `connection`: Connection options (auto-reconnect, heartbeat, etc.)
582
+ - `tokenStorage`: Custom token storage implementation
583
+ - `requestTimeout`: Request timeout in milliseconds (default: 30000)
584
+ - `tenantId`: Tenant ID (extracted from URL if not provided)
585
+ - `defaultBranch`: Default branch name (default: "main")
586
+
587
+ #### Methods
588
+
589
+ **Connection & Admin Auth:**
590
+ - `connect(): Promise<void>` - Connect to the server
591
+ - `authenticate(credentials: Credentials): Promise<void>` - Authenticate with credentials
592
+ - `disconnect(): void` - Disconnect from the server
593
+ - `isConnected(): boolean` - Check if connected
594
+ - `isAuthenticated(): boolean` - Check if authenticated
595
+
596
+ **Identity Auth (Email/Password):**
597
+ - `loginWithEmail(email, password, repository): Promise<IdentityUser>` - Login with email
598
+ - `registerWithEmail(email, password, repository, displayName?): Promise<IdentityUser>` - Register
599
+ - `logout(options?): Promise<void>` - Logout (options: `{ disconnect?, reconnect? }`)
600
+ - `initSession(repository): Promise<IdentityUser | null>` - Restore session from storage
601
+ - `refreshToken(): Promise<IdentityUser | null>` - Refresh access token
602
+
603
+ **Auth State Listeners:**
604
+ - `onAuthStateChange(callback): () => void` - Listen for auth events (`SIGNED_IN`, `SIGNED_OUT`, etc.)
605
+ - `onConnectionStateChange(callback): () => void` - Listen for connection state changes
606
+ - `onUserChange(callback): () => void` - Listen for user home node changes
607
+
608
+ **Session Helpers:**
609
+ - `getSession(): { user, accessToken } | null` - Get current session (sync)
610
+ - `getUser(): IdentityUser | null` - Get current user (alias)
611
+ - `getStoredUser(): IdentityUser | null` - Get stored user from localStorage
612
+ - `hasStoredToken(): boolean` - Check if token exists in storage
613
+ - `getCurrentUser(): CurrentUser | null` - Get full current user with roles/node
614
+ - `fetchUserNode(repository): Promise<UserNode | null>` - Fetch user node via SQL
615
+
616
+ **Database & Branches:**
617
+ - `database(name: string): Database` - Get database interface
618
+ - `setBranch(branch: string): void` - Set branch for subsequent requests
619
+ - `getBranch(): string` - Get current branch
620
+
621
+ ### Database
622
+
623
+ Database/repository interface.
624
+
625
+ #### Methods
626
+
627
+ - `workspace(name: string): WorkspaceClient` - Get workspace client
628
+ - `workspaces(): WorkspaceManager` - Get workspace management operations
629
+ - `sql(strings, ...values): Promise<SqlResult>` - Execute SQL query with template literals
630
+ - `executeSql(query: string, params?: unknown[]): Promise<SqlResult>` - Execute raw SQL
631
+
632
+ ### WorkspaceClient
633
+
634
+ Workspace operations interface.
635
+
636
+ #### Methods
637
+
638
+ - `nodes(): NodeOperations` - Get node operations
639
+ - `events(): EventSubscriptions` - Get event subscriptions
640
+
641
+ ### NodeOperations
642
+
643
+ Node CRUD operations.
644
+
645
+ #### Methods
646
+
647
+ - `create(options: NodeCreateOptions): Promise<Node>` - Create a node
648
+ - `update(id: string, options: NodeUpdateOptions): Promise<Node>` - Update a node
649
+ - `delete(id: string): Promise<boolean>` - Delete a node
650
+ - `get(id: string): Promise<Node | null>` - Get a node by ID
651
+ - `query(options: NodeQueryOptions): Promise<Node[]>` - Query nodes
652
+ - `getByPath(path: string): Promise<Node | null>` - Get a node by path
653
+ - `queryByProperty(name: string, value: PropertyValue, limit?: number): Promise<Node[]>` - Query by property
654
+ - `queryByType(nodeType: string, limit?: number): Promise<Node[]>` - Query by type
655
+ - `getChildren(parentId: string, limit?: number): Promise<Node[]>` - Get children
656
+
657
+ ### EventSubscriptions
658
+
659
+ Event subscription interface.
660
+
661
+ #### Methods
662
+
663
+ - `subscribe(filters: SubscriptionFilters, callback: EventCallback): Promise<Subscription>` - Subscribe to events
664
+ - `subscribeToTypes(eventTypes: string[], callback: EventCallback): Promise<Subscription>` - Subscribe to specific types
665
+ - `subscribeToPath(path: string, callback: EventCallback): Promise<Subscription>` - Subscribe to path
666
+ - `subscribeToNodeType(nodeType: string, callback: EventCallback): Promise<Subscription>` - Subscribe to node type
667
+
668
+ ## Environment Support
669
+
670
+ ### Browser
671
+
672
+ Works out of the box with native WebSocket support:
673
+
674
+ ```typescript
675
+ import { RaisinClient } from '@raisindb/client';
676
+
677
+ const client = new RaisinClient('raisin://localhost:8080/sys/default');
678
+ ```
679
+
680
+ ### Node.js
681
+
682
+ Requires the `ws` package:
683
+
684
+ ```bash
685
+ npm install ws
686
+ ```
687
+
688
+ ```typescript
689
+ import { RaisinClient } from '@raisindb/client';
690
+
691
+ const client = new RaisinClient('raisin://localhost:8080/sys/default');
692
+ ```
693
+
694
+ ### TypeScript
695
+
696
+ Full TypeScript support with type definitions included:
697
+
698
+ ```typescript
699
+ import { RaisinClient, Node, NodeCreateOptions } from '@raisindb/client';
700
+
701
+ const options: NodeCreateOptions = {
702
+ type: 'Page',
703
+ path: '/home',
704
+ properties: {
705
+ title: 'Home',
706
+ },
707
+ };
708
+ ```
709
+
710
+ ## Building
711
+
712
+ ```bash
713
+ # Install dependencies
714
+ npm install
715
+
716
+ # Build
717
+ npm run build
718
+
719
+ # Watch mode
720
+ npm run dev
721
+
722
+ # Type check
723
+ npm run typecheck
724
+ ```
725
+
726
+ ## License
727
+
728
+ BSL-1.1 - See LICENSE file for details.
729
+
730
+ ## Links
731
+
732
+ - [RaisinDB Documentation](https://raisindb.com/docs)
733
+ - [GitHub Repository](https://github.com/maravilla-labs/raisindb)
734
+ - [Issue Tracker](https://github.com/maravilla-labs/raisindb/issues)