@spooky-sync/client-solid 0.0.1-canary.6 → 0.0.1-canary.60

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/src/index.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import type { SyncedDbConfig } from './types';
2
2
  import {
3
- SpookyClient,
4
- AuthService,
5
- BucketHandle,
6
- type SpookyQueryResultPromise,
7
- UpdateOptions,
8
- RunOptions,
3
+ Sp00kyClient,
4
+ type Sp00kyQueryResultPromise,
5
+ type AuthService,
6
+ type BucketHandle,
7
+ type UpdateOptions,
8
+ type RunOptions,
9
9
  } from '@spooky-sync/core';
10
10
 
11
- import {
11
+ import type {
12
12
  GetTable,
13
13
  QueryBuilder,
14
14
  SchemaStructure,
@@ -25,19 +25,27 @@ import {
25
25
  RoutePayload,
26
26
  BucketNames,
27
27
  BucketDefinitionSchema,
28
+ QueryModifier,
29
+ QueryModifierBuilder,
30
+ QueryInfo,
31
+ RelationshipsMetadata,
32
+ RelationshipDefinition,
33
+ InferRelatedModelFromMetadata,
34
+ GetCardinality,
28
35
  } from '@spooky-sync/query-builder';
29
36
 
30
- import { RecordId, Uuid, Surreal } from 'surrealdb';
37
+ import { RecordId, Uuid, type Surreal } from 'surrealdb';
31
38
  export { RecordId, Uuid };
32
39
  export type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';
33
40
  export { useQuery } from './lib/use-query';
41
+ export { useCrdtField } from './lib/use-crdt-field';
34
42
  export { useFileUpload, type FileUploadResult } from './lib/use-file-upload';
35
43
  export { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';
36
- export { SpookyProvider, type SpookyProviderProps } from './lib/SpookyProvider';
44
+ export { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';
37
45
  export { useDb } from './lib/context';
38
46
 
39
47
  // export { AuthEventTypes } from "@spooky-sync/core"; // TODO: Verify if AuthEventTypes exists in core
40
- export type {};
48
+
41
49
 
42
50
  // Re-export query builder types for convenience
43
51
  export type {
@@ -52,7 +60,7 @@ export type {
52
60
  TableModel,
53
61
  TableNames,
54
62
  QueryResult,
55
- } from '@spooky-sync/query-builder';
63
+ };
56
64
 
57
65
  export type RelationshipField<
58
66
  Schema extends SchemaStructure,
@@ -94,30 +102,30 @@ export type WithRelatedMany<Field extends string, RelatedFields extends RelatedF
94
102
  };
95
103
 
96
104
  /**
97
- * SyncedDb - A thin wrapper around spooky-ts for Solid.js integration
98
- * Delegates all logic to the underlying spooky-ts instance
105
+ * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration
106
+ * Delegates all logic to the underlying sp00ky-ts instance
99
107
  */
100
108
  export class SyncedDb<S extends SchemaStructure> {
101
109
  private config: SyncedDbConfig<S>;
102
- private spooky: SpookyClient<S> | null = null;
110
+ private sp00ky: Sp00kyClient<S> | null = null;
103
111
  private _initialized = false;
104
112
 
105
113
  constructor(config: SyncedDbConfig<S>) {
106
114
  this.config = config;
107
115
  }
108
116
 
109
- public getSpooky(): SpookyClient<S> {
110
- if (!this.spooky) throw new Error('SyncedDb not initialized');
111
- return this.spooky;
117
+ public getSp00ky(): Sp00kyClient<S> {
118
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
119
+ return this.sp00ky;
112
120
  }
113
121
 
114
122
  /**
115
- * Initialize the spooky-ts instance
123
+ * Initialize the sp00ky-ts instance
116
124
  */
117
125
  async init(): Promise<void> {
118
126
  if (this._initialized) return;
119
- this.spooky = new SpookyClient<S>(this.config);
120
- await this.spooky.init();
127
+ this.sp00ky = new Sp00kyClient<S>(this.config);
128
+ await this.sp00ky.init();
121
129
  this._initialized = true;
122
130
  }
123
131
 
@@ -125,8 +133,8 @@ export class SyncedDb<S extends SchemaStructure> {
125
133
  * Create a new record in the database
126
134
  */
127
135
  async create(id: string, payload: Record<string, unknown>): Promise<void> {
128
- if (!this.spooky) throw new Error('SyncedDb not initialized');
129
- await this.spooky.create(id, payload as Record<string, unknown>);
136
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
137
+ await this.sp00ky.create(id, payload as Record<string, unknown>);
130
138
  }
131
139
 
132
140
  /**
@@ -138,8 +146,8 @@ export class SyncedDb<S extends SchemaStructure> {
138
146
  payload: Partial<TableModel<GetTable<S, TName>>>,
139
147
  options?: UpdateOptions
140
148
  ): Promise<void> {
141
- if (!this.spooky) throw new Error('SyncedDb not initialized');
142
- await this.spooky.update(
149
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
150
+ await this.sp00ky.update(
143
151
  tableName as string,
144
152
  recordId,
145
153
  payload as Record<string, unknown>,
@@ -154,10 +162,10 @@ export class SyncedDb<S extends SchemaStructure> {
154
162
  tableName: TName,
155
163
  selector: string | InnerQuery<GetTable<S, TName>, boolean>
156
164
  ): Promise<void> {
157
- if (!this.spooky) throw new Error('SyncedDb not initialized');
165
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
158
166
  if (typeof selector !== 'string')
159
167
  throw new Error('Only string ID selectors are supported currently with core');
160
- await this.spooky.delete(tableName as string, selector);
168
+ await this.sp00ky.delete(tableName as string, selector);
161
169
  }
162
170
 
163
171
  /**
@@ -165,9 +173,9 @@ export class SyncedDb<S extends SchemaStructure> {
165
173
  */
166
174
  public query<TName extends TableNames<S>>(
167
175
  table: TName
168
- ): QueryBuilder<S, TName, SpookyQueryResultPromise, {}, false> {
169
- if (!this.spooky) throw new Error('SyncedDb not initialized');
170
- return this.spooky.query(table, {});
176
+ ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {
177
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
178
+ return this.sp00ky.query(table, {});
171
179
  }
172
180
 
173
181
  /**
@@ -182,17 +190,17 @@ export class SyncedDb<S extends SchemaStructure> {
182
190
  payload: RoutePayload<S, B, R>,
183
191
  options?: RunOptions,
184
192
  ): Promise<void> {
185
- if (!this.spooky) throw new Error('SyncedDb not initialized');
186
- await this.spooky.run(backend, path, payload, options);
193
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
194
+ await this.sp00ky.run(backend, path, payload, options);
187
195
  }
188
196
 
189
197
  /**
190
198
  * Authenticate with the database
191
199
  */
192
200
  public async authenticate(token: string): Promise<RecordId<string>> {
193
- const result = await this.spooky?.authenticate(token);
194
- // SpookyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)
195
- // Wait, checked SpookyClient: return this.remote.getClient().authenticate(token);
201
+ await this.sp00ky?.authenticate(token);
202
+ // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)
203
+ // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);
196
204
  // SurrealDB authenticate returns void? or token?
197
205
  // Assuming void or token.
198
206
  return new RecordId('user', 'me'); // Placeholder or actual?
@@ -210,54 +218,54 @@ export class SyncedDb<S extends SchemaStructure> {
210
218
  * Sign out, clear session and local storage
211
219
  */
212
220
  public async signOut(): Promise<void> {
213
- if (!this.spooky) throw new Error('SyncedDb not initialized');
214
- await this.spooky.auth.signOut();
221
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
222
+ await this.sp00ky.auth.signOut();
215
223
  }
216
224
 
217
225
  /**
218
226
  * Execute a function with direct access to the remote database connection
219
227
  */
220
228
  public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {
221
- if (!this.spooky) throw new Error('SyncedDb not initialized');
222
- return await this.spooky.useRemote(fn);
229
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
230
+ return await this.sp00ky.useRemote(fn);
223
231
  }
224
232
  /**
225
233
  * Access the remote database service directly
226
234
  */
227
- get remote(): SpookyClient<S>['remoteClient'] {
228
- if (!this.spooky) throw new Error('SyncedDb not initialized');
229
- return this.spooky.remoteClient;
235
+ get remote(): Sp00kyClient<S>['remoteClient'] {
236
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
237
+ return this.sp00ky.remoteClient;
230
238
  }
231
239
 
232
240
  /**
233
241
  * Access the local database service directly
234
242
  */
235
- get local(): SpookyClient<S>['localClient'] {
236
- if (!this.spooky) throw new Error('SyncedDb not initialized');
237
- return this.spooky.localClient;
243
+ get local(): Sp00kyClient<S>['localClient'] {
244
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
245
+ return this.sp00ky.localClient;
238
246
  }
239
247
 
240
248
  /**
241
249
  * Access the auth service
242
250
  */
243
251
  get auth(): AuthService<S> {
244
- if (!this.spooky) throw new Error('SyncedDb not initialized');
245
- return this.spooky.auth;
252
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
253
+ return this.sp00ky.auth;
246
254
  }
247
255
 
248
256
  get pendingMutationCount(): number {
249
- if (!this.spooky) throw new Error('SyncedDb not initialized');
250
- return this.spooky.pendingMutationCount;
257
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
258
+ return this.sp00ky.pendingMutationCount;
251
259
  }
252
260
 
253
261
  subscribeToPendingMutations(cb: (count: number) => void): () => void {
254
- if (!this.spooky) throw new Error('SyncedDb not initialized');
255
- return this.spooky.subscribeToPendingMutations(cb);
262
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
263
+ return this.sp00ky.subscribeToPendingMutations(cb);
256
264
  }
257
265
 
258
266
  bucket<B extends BucketNames<S>>(name: B): BucketHandle {
259
- if (!this.spooky) throw new Error('SyncedDb not initialized');
260
- return this.spooky.bucket(name);
267
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
268
+ return this.sp00ky.bucket(name);
261
269
  }
262
270
 
263
271
  getBucketConfig(name: string): BucketDefinitionSchema | undefined {
@@ -1,10 +1,11 @@
1
- import { createSignal, onMount, createComponent, createMemo, JSX, mergeProps } from 'solid-js';
1
+ import type { JSX} from 'solid-js';
2
+ import { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';
2
3
  import type { SchemaStructure } from '@spooky/query-builder';
3
4
  import type { SyncedDbConfig } from '../types';
4
5
  import { SyncedDb } from '../index';
5
- import { SpookyContext } from './context';
6
+ import { Sp00kyContext } from './context';
6
7
 
7
- export interface SpookyProviderProps<S extends SchemaStructure> {
8
+ export interface Sp00kyProviderProps<S extends SchemaStructure> {
8
9
  config: SyncedDbConfig<S>;
9
10
  fallback?: JSX.Element;
10
11
  onError?: (error: Error) => void;
@@ -12,8 +13,8 @@ export interface SpookyProviderProps<S extends SchemaStructure> {
12
13
  children: JSX.Element;
13
14
  }
14
15
 
15
- export function SpookyProvider<S extends SchemaStructure>(
16
- props: SpookyProviderProps<S>
16
+ export function Sp00kyProvider<S extends SchemaStructure>(
17
+ props: Sp00kyProviderProps<S>
17
18
  ): JSX.Element {
18
19
  const merged = mergeProps(
19
20
  {
@@ -35,7 +36,8 @@ export function SpookyProvider<S extends SchemaStructure>(
35
36
  if (merged.onError) {
36
37
  merged.onError(error);
37
38
  } else {
38
- console.error('SpookyProvider: Failed to initialize database', error);
39
+ // oxlint-disable-next-line no-console
40
+ console.error('Sp00kyProvider: Failed to initialize database', error);
39
41
  }
40
42
  }
41
43
  });
@@ -43,7 +45,7 @@ export function SpookyProvider<S extends SchemaStructure>(
43
45
  const content = createMemo(() => {
44
46
  const instance = db();
45
47
  if (!instance) return merged.fallback;
46
- return createComponent(SpookyContext.Provider, {
48
+ return createComponent(Sp00kyContext.Provider, {
47
49
  value: instance,
48
50
  get children() {
49
51
  return merged.children;
@@ -2,12 +2,12 @@ import { createContext, useContext } from 'solid-js';
2
2
  import type { SchemaStructure } from '@spooky/query-builder';
3
3
  import type { SyncedDb } from '../index';
4
4
 
5
- export const SpookyContext = createContext<SyncedDb<any> | undefined>();
5
+ export const Sp00kyContext = createContext<SyncedDb<any> | undefined>();
6
6
 
7
7
  export function useDb<S extends SchemaStructure>(): SyncedDb<S> {
8
- const db = useContext(SpookyContext);
8
+ const db = useContext(Sp00kyContext);
9
9
  if (!db) {
10
- throw new Error('useDb must be used within a <SpookyProvider>. Wrap your app in <SpookyProvider config={...}>.');
10
+ throw new Error('useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.');
11
11
  }
12
12
  return db as SyncedDb<S>;
13
13
  }
package/src/lib/models.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RecordId } from 'surrealdb';
1
+ import type { RecordId } from 'surrealdb';
2
2
 
3
3
  // Re-export types from query-builder for backward compatibility
4
4
  export type { GenericModel, GenericSchema } from '@spooky/query-builder';
@@ -0,0 +1,54 @@
1
+ import { createEffect, createSignal, onCleanup, useContext, type Accessor } from 'solid-js';
2
+ import { Sp00kyContext } from './context';
3
+ import type { CrdtField } from '@spooky-sync/core';
4
+
5
+ export function useCrdtField(
6
+ table: string,
7
+ recordId: () => string | undefined,
8
+ field: string,
9
+ fallbackText?: () => string | undefined,
10
+ ): Accessor<CrdtField | null> {
11
+ const db = useContext(Sp00kyContext);
12
+ if (!db) {
13
+ throw new Error('useCrdtField must be used within a <Sp00kyProvider>');
14
+ }
15
+
16
+ const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null);
17
+ let currentId: string | undefined;
18
+ let initialized = false;
19
+
20
+ createEffect(() => {
21
+ const id = recordId();
22
+
23
+ // Skip if the ID hasn't changed (but allow the first non-undefined value through)
24
+ if (initialized && id === currentId) return;
25
+
26
+ // Close previous field
27
+ if (currentId && crdtField()) {
28
+ db.getSp00ky().closeCrdtField(table, currentId, field);
29
+ setCrdtField(null);
30
+ }
31
+
32
+ currentId = id;
33
+ initialized = true;
34
+
35
+ if (!id) return;
36
+
37
+ const sp00ky = db.getSp00ky();
38
+ const text = fallbackText?.();
39
+ sp00ky.openCrdtField(table, id, field, text).then((cf) => {
40
+ if (currentId === id) {
41
+ setCrdtField(cf);
42
+ }
43
+ });
44
+ });
45
+
46
+ onCleanup(() => {
47
+ if (currentId && crdtField()) {
48
+ db.getSp00ky().closeCrdtField(table, currentId, field);
49
+ setCrdtField(null);
50
+ }
51
+ });
52
+
53
+ return crdtField;
54
+ }
@@ -78,9 +78,8 @@ export function useDownloadFile<S extends SchemaStructure>(
78
78
 
79
79
  let currentKey: string | null = null;
80
80
  let privateUrl: string | null = null;
81
- let refetchTrigger: () => void;
82
81
  const [refetchSignal, setRefetchSignal] = createSignal(0);
83
- refetchTrigger = () => setRefetchSignal((n) => n + 1);
82
+ const refetchTrigger = () => setRefetchSignal((n) => n + 1);
84
83
 
85
84
  async function doDownload(key: string, filePath: string): Promise<string | null> {
86
85
  if (useCache) {
@@ -184,6 +183,7 @@ export function useDownloadFile<S extends SchemaStructure>(
184
183
  setUrl(result);
185
184
  setIsLoading(false);
186
185
  }
186
+ return undefined;
187
187
  },
188
188
  (err) => {
189
189
  if (!cancelled) {
@@ -33,6 +33,7 @@ export function useFileUpload<S extends SchemaStructure>(
33
33
  bucketName = dbOrBucketName as BucketNames<S>;
34
34
  } else {
35
35
  db = dbOrBucketName as SyncedDb<S>;
36
+ // oxlint-disable-next-line no-non-null-assertion
36
37
  bucketName = maybeBucketName!;
37
38
  }
38
39
 
@@ -52,7 +53,7 @@ export function useFileUpload<S extends SchemaStructure>(
52
53
  const config = db.getBucketConfig(bucketName as string);
53
54
  if (!config) return;
54
55
 
55
- if (config.maxSize != null && file.size > config.maxSize) {
56
+ if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {
56
57
  const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);
57
58
  throw new Error(`File exceeds maximum size of ${maxMB} MB.`);
58
59
  }
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  ColumnSchema,
3
3
  FinalQuery,
4
4
  SchemaStructure,
@@ -7,8 +7,8 @@ import {
7
7
  } from '@spooky-sync/query-builder';
8
8
  import { createEffect, createSignal, onCleanup, useContext } from 'solid-js';
9
9
  import { SyncedDb } from '..';
10
- import { SpookyQueryResultPromise } from '@spooky-sync/core';
11
- import { SpookyContext } from './context';
10
+ import type { Sp00kyQueryResultPromise } from '@spooky-sync/core';
11
+ import { Sp00kyContext } from './context';
12
12
 
13
13
  type QueryArg<
14
14
  S extends SchemaStructure,
@@ -17,9 +17,9 @@ type QueryArg<
17
17
  RelatedFields extends Record<string, any>,
18
18
  IsOne extends boolean,
19
19
  > =
20
- | FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>
20
+ | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>
21
21
  | (() =>
22
- | FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>
22
+ | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>
23
23
  | null
24
24
  | undefined);
25
25
 
@@ -82,11 +82,11 @@ export function useQuery<
82
82
  options = maybeOptions;
83
83
  } else {
84
84
  // Context-based overload: useQuery(query, options?)
85
- const contextDb = useContext(SpookyContext);
85
+ const contextDb = useContext(Sp00kyContext);
86
86
  if (!contextDb) {
87
87
  throw new Error(
88
- 'useQuery: No db argument provided and no SpookyContext found. ' +
89
- 'Either pass a SyncedDb instance or wrap your app in <SpookyProvider>.'
88
+ 'useQuery: No db argument provided and no Sp00kyContext found. ' +
89
+ 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.'
90
90
  );
91
91
  }
92
92
  db = contextDb as SyncedDb<S>;
@@ -100,23 +100,23 @@ export function useQuery<
100
100
  const [unsubscribe, setUnsubscribe] = createSignal<(() => void) | undefined>(undefined);
101
101
  let prevQueryString: string | undefined;
102
102
 
103
- const spooky = db.getSpooky();
103
+ const sp00ky = db.getSp00ky();
104
104
 
105
105
  const initQuery = async (
106
- query: FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>
106
+ query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>
107
107
  ) => {
108
108
  const { hash } = await query.run();
109
109
  setError(undefined);
110
110
 
111
111
  let isFirstCall = true;
112
- const unsub = await spooky.subscribe(
112
+ const unsub = await sp00ky.subscribe(
113
113
  hash,
114
114
  (e) => {
115
- const data = (query.isOne ? e[0] : e) as TData;
116
- setData(() => data);
115
+ const queryData = (query.isOne ? e[0] : e) as TData;
116
+ setData(() => queryData);
117
117
  // The first (immediate) callback with no data likely means the local DB
118
118
  // hasn't synced yet — don't mark as fetched so UI shows loading state
119
- const hasData = query.isOne ? data != null : (e as any[]).length > 0;
119
+ const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;
120
120
  if (!isFirstCall || hasData) {
121
121
  setIsFetched(true);
122
122
  }
@@ -1,7 +1,6 @@
1
- import type { Surreal } from 'surrealdb';
2
1
  import type { SyncedDb } from '../index';
3
- import { GenericSchema } from '../lib/models';
4
- import type { SpookyConfig } from '@spooky-sync/core';
2
+ import type { GenericSchema } from '../lib/models';
3
+ import type { Sp00kyConfig } from '@spooky-sync/core';
5
4
  import type { SchemaStructure, TableNames, GetTable, TableModel } from '@spooky-sync/query-builder';
6
5
 
7
6
  /**
@@ -44,7 +43,7 @@ export type InferRelationshipsFromConst<S extends SchemaStructure, Schema extend
44
43
  // Prettify helper expands types for better intellisense
45
44
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
46
45
 
47
- export type SyncedDbConfig<S extends SchemaStructure> = Prettify<SpookyConfig<S>>;
46
+ export type SyncedDbConfig<S extends SchemaStructure> = Prettify<Sp00kyConfig<S>>;
48
47
 
49
48
  // export interface LocalDbConfig {
50
49
  // name: string;