@instantdb/core 0.22.92-experimental.btnfix.20348927905.1 → 0.22.92-experimental.drewh-ssr.20347788876.1

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.
Files changed (56) hide show
  1. package/__tests__/src/serializeSchema.test.ts +123 -0
  2. package/dist/commonjs/Reactor.d.ts +13 -1
  3. package/dist/commonjs/Reactor.d.ts.map +1 -1
  4. package/dist/commonjs/Reactor.js +71 -5
  5. package/dist/commonjs/Reactor.js.map +1 -1
  6. package/dist/commonjs/createRouteHandler.d.ts +6 -0
  7. package/dist/commonjs/createRouteHandler.d.ts.map +1 -0
  8. package/dist/commonjs/createRouteHandler.js +66 -0
  9. package/dist/commonjs/createRouteHandler.js.map +1 -0
  10. package/dist/commonjs/framework.d.ts +77 -0
  11. package/dist/commonjs/framework.d.ts.map +1 -0
  12. package/dist/commonjs/framework.js +210 -0
  13. package/dist/commonjs/framework.js.map +1 -0
  14. package/dist/commonjs/index.d.ts +5 -1
  15. package/dist/commonjs/index.d.ts.map +1 -1
  16. package/dist/commonjs/index.js +7 -1
  17. package/dist/commonjs/index.js.map +1 -1
  18. package/dist/commonjs/parseSchemaFromJSON.d.ts +3 -0
  19. package/dist/commonjs/parseSchemaFromJSON.d.ts.map +1 -0
  20. package/dist/commonjs/parseSchemaFromJSON.js +148 -0
  21. package/dist/commonjs/parseSchemaFromJSON.js.map +1 -0
  22. package/dist/commonjs/reactorTypes.d.ts +5 -4
  23. package/dist/commonjs/reactorTypes.d.ts.map +1 -1
  24. package/dist/commonjs/reactorTypes.js.map +1 -1
  25. package/dist/esm/Reactor.d.ts +13 -1
  26. package/dist/esm/Reactor.d.ts.map +1 -1
  27. package/dist/esm/Reactor.js +71 -5
  28. package/dist/esm/Reactor.js.map +1 -1
  29. package/dist/esm/createRouteHandler.d.ts +6 -0
  30. package/dist/esm/createRouteHandler.d.ts.map +1 -0
  31. package/dist/esm/createRouteHandler.js +62 -0
  32. package/dist/esm/createRouteHandler.js.map +1 -0
  33. package/dist/esm/framework.d.ts +77 -0
  34. package/dist/esm/framework.d.ts.map +1 -0
  35. package/dist/esm/framework.js +170 -0
  36. package/dist/esm/framework.js.map +1 -0
  37. package/dist/esm/index.d.ts +5 -1
  38. package/dist/esm/index.d.ts.map +1 -1
  39. package/dist/esm/index.js +5 -2
  40. package/dist/esm/index.js.map +1 -1
  41. package/dist/esm/parseSchemaFromJSON.d.ts +3 -0
  42. package/dist/esm/parseSchemaFromJSON.d.ts.map +1 -0
  43. package/dist/esm/parseSchemaFromJSON.js +144 -0
  44. package/dist/esm/parseSchemaFromJSON.js.map +1 -0
  45. package/dist/esm/reactorTypes.d.ts +5 -4
  46. package/dist/esm/reactorTypes.d.ts.map +1 -1
  47. package/dist/esm/reactorTypes.js.map +1 -1
  48. package/dist/standalone/index.js +2706 -2373
  49. package/dist/standalone/index.umd.cjs +3 -3
  50. package/package.json +2 -2
  51. package/src/Reactor.js +86 -6
  52. package/src/createRouteHandler.ts +56 -0
  53. package/src/framework.ts +295 -0
  54. package/src/index.ts +9 -0
  55. package/src/parseSchemaFromJSON.ts +176 -0
  56. package/src/reactorTypes.ts +5 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instantdb/core",
3
- "version": "0.22.92-experimental.btnfix.20348927905.1",
3
+ "version": "0.22.92-experimental.drewh-ssr.20347788876.1",
4
4
  "description": "Instant's core local abstraction",
5
5
  "homepage": "https://github.com/instantdb/instant/tree/main/client/packages/core",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  "dependencies": {
54
54
  "mutative": "^1.0.10",
55
55
  "uuid": "^11.1.0",
56
- "@instantdb/version": "0.22.92-experimental.btnfix.20348927905.1"
56
+ "@instantdb/version": "0.22.92-experimental.drewh-ssr.20347788876.1"
57
57
  },
58
58
  "scripts": {
59
59
  "test": "vitest",
package/src/Reactor.js CHANGED
@@ -348,7 +348,17 @@ export default class Reactor {
348
348
  this._oauthCallbackResponse = this._oauthLoginInit();
349
349
 
350
350
  // kick off a request to cache it
351
- this.getCurrentUser();
351
+ this.getCurrentUser().then((userInfo) => {
352
+ this.syncUserToEndpoint(userInfo.user);
353
+ });
354
+
355
+ setInterval(
356
+ async () => {
357
+ const currentUser = await this.getCurrentUser();
358
+ this.syncUserToEndpoint(currentUser.user);
359
+ },
360
+ 1000 * 60 * 20,
361
+ );
352
362
 
353
363
  NetworkListener.getIsOnline().then((isOnline) => {
354
364
  this._isOnline = isOnline;
@@ -549,6 +559,43 @@ export default class Reactor {
549
559
  }
550
560
  }
551
561
 
562
+ /**
563
+ * Does the same thing as add-query-ok
564
+ * but called as a result of receiving query info from ssr
565
+ * @param {any} q
566
+ * @param {{ triples: any; pageInfo: any; }} result
567
+ * @param {boolean} enableCardinalityInference
568
+ */
569
+ _addQueryData(q, result, enableCardinalityInference) {
570
+ if (!this.attrs) {
571
+ throw new Error('Attrs in reactor have not been set');
572
+ }
573
+ const queryHash = weakHash(q);
574
+ const attrsStore = this.ensureAttrs();
575
+ const store = s.createStore(
576
+ this.attrs,
577
+ result.triples,
578
+ enableCardinalityInference,
579
+ this.config.useDateObjects,
580
+ );
581
+ this.querySubs.updateInPlace((prev) => {
582
+ prev[queryHash] = {
583
+ result: {
584
+ store,
585
+ attrsStore,
586
+ pageInfo: result.pageInfo,
587
+ processedTxId: undefined,
588
+ isExternal: true,
589
+ },
590
+ q,
591
+ };
592
+ });
593
+ this._cleanupPendingMutationsQueries();
594
+ this.notifyOne(queryHash);
595
+ this.notifyOneQueryOnce(queryHash);
596
+ this._cleanupPendingMutationsTimeout();
597
+ }
598
+
552
599
  _handleReceive(connId, msg) {
553
600
  // opt-out, enabled by default if schema
554
601
  const enableCardinalityInference =
@@ -1221,7 +1268,7 @@ export default class Reactor {
1221
1268
  }
1222
1269
 
1223
1270
  /** Runs instaql on a query and a store */
1224
- dataForQuery(hash) {
1271
+ dataForQuery(hash, applyOptimistic = true) {
1225
1272
  const errorMessage = this._errorMessage;
1226
1273
  if (errorMessage) {
1227
1274
  return { error: errorMessage };
@@ -1245,15 +1292,26 @@ export default class Reactor {
1245
1292
  return cached;
1246
1293
  }
1247
1294
 
1248
- const { store, attrsStore, pageInfo, aggregate, processedTxId } = result;
1295
+ let store = result.store;
1296
+ let attrsStore = result.attrsStore;
1297
+ const { pageInfo, aggregate, processedTxId } = result;
1249
1298
  const mutations = this._rewriteMutationsSorted(
1250
1299
  attrsStore,
1251
1300
  pendingMutations,
1252
1301
  );
1253
- const { store: newStore, attrsStore: newAttrsStore } =
1254
- this._applyOptimisticUpdates(store, attrsStore, mutations, processedTxId);
1302
+ if (applyOptimistic) {
1303
+ const optimisticResult = this._applyOptimisticUpdates(
1304
+ store,
1305
+ attrsStore,
1306
+ mutations,
1307
+ processedTxId,
1308
+ );
1309
+
1310
+ store = optimisticResult.store;
1311
+ attrsStore = optimisticResult.attrsStore;
1312
+ }
1255
1313
  const resp = instaql(
1256
- { store: newStore, attrsStore: newAttrsStore, pageInfo, aggregate },
1314
+ { store: store, attrsStore: attrsStore, pageInfo, aggregate },
1257
1315
  q,
1258
1316
  );
1259
1317
 
@@ -1989,7 +2047,29 @@ export default class Reactor {
1989
2047
  }
1990
2048
  }
1991
2049
 
2050
+ async syncUserToEndpoint(user) {
2051
+ if (this.config.firstPartyPath) {
2052
+ try {
2053
+ fetch(this.config.firstPartyPath + '/', {
2054
+ method: 'POST',
2055
+ body: JSON.stringify({
2056
+ type: 'sync-user',
2057
+ appId: this.config.appId,
2058
+ user: user,
2059
+ }),
2060
+ headers: {
2061
+ 'Content-Type': 'application/json',
2062
+ },
2063
+ });
2064
+ } catch (error) {
2065
+ console.error('Error syncing user with external endpoint', error);
2066
+ }
2067
+ }
2068
+ }
2069
+
1992
2070
  updateUser(newUser) {
2071
+ this.syncUserToEndpoint(newUser);
2072
+
1993
2073
  const newV = { error: undefined, user: newUser };
1994
2074
  this._currentUserCached = { isLoading: false, ...newV };
1995
2075
  this._dataForQueryCache = {};
@@ -0,0 +1,56 @@
1
+ import type { User } from './clientTypes.js';
2
+
3
+ export const createInstantRouteHandler = (config: { appId: string }) => {
4
+ function createUserSyncResponse(user: User | null) {
5
+ if (user && user.refresh_token) {
6
+ return new Response(JSON.stringify({ ok: true }), {
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ // 7 day expiry
10
+ 'Set-Cookie': `instant_user_${config.appId}=${JSON.stringify(user)}; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=604800`,
11
+ },
12
+ });
13
+ } else {
14
+ return new Response(JSON.stringify({ ok: true }), {
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ // remove the cookie (some browsers)
18
+ 'Set-Cookie': `instant_user_${config.appId}=; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=-1`,
19
+ },
20
+ });
21
+ }
22
+ }
23
+
24
+ function errorResponse(status: number, message: string) {
25
+ return new Response(JSON.stringify({ ok: false, error: message }), {
26
+ status,
27
+ headers: { 'Content-Type': 'application/json' },
28
+ });
29
+ }
30
+
31
+ return {
32
+ POST: async (req: Request) => {
33
+ let body: { type?: string; appId?: string; user?: User | null };
34
+ try {
35
+ body = await req.json();
36
+ } catch {
37
+ return errorResponse(400, 'Invalid JSON body');
38
+ }
39
+
40
+ if (!body.type) {
41
+ return errorResponse(400, 'Missing "type" field');
42
+ }
43
+
44
+ if (body.appId !== config.appId) {
45
+ return errorResponse(403, 'App ID mismatch');
46
+ }
47
+
48
+ switch (body.type) {
49
+ case 'sync-user':
50
+ return createUserSyncResponse(body.user ?? null);
51
+ default:
52
+ return errorResponse(400, `Unknown type: ${body.type}`);
53
+ }
54
+ },
55
+ };
56
+ };
@@ -0,0 +1,295 @@
1
+ import {
2
+ coerceQuery,
3
+ InstantCoreDatabase,
4
+ InstantDBAttr,
5
+ weakHash,
6
+ } from './index.ts';
7
+ import * as s from './store.js';
8
+ import instaql from './instaql.js';
9
+ import { RuleParams } from './schemaTypes.ts';
10
+ import { createLinkIndex } from './utils/linkIndex.ts';
11
+
12
+ export const isServer = typeof window === 'undefined' || 'Deno' in globalThis;
13
+
14
+ export type FrameworkConfig = {
15
+ token?: string | null;
16
+ db: InstantCoreDatabase<any, any>;
17
+ };
18
+
19
+ type QueryPromise =
20
+ | {
21
+ type: 'http';
22
+ triples: any;
23
+ attrs: any;
24
+ queryHash: any;
25
+ query: any;
26
+ pageInfo?: any;
27
+ }
28
+ | {
29
+ type: 'session';
30
+ queryResult: any;
31
+ };
32
+
33
+ export class FrameworkClient {
34
+ private params: FrameworkConfig;
35
+ private db: InstantCoreDatabase<any, any>;
36
+ public resultMap: Map<
37
+ string,
38
+ {
39
+ status: 'pending' | 'success' | 'error';
40
+ type: 'http' | 'session';
41
+ promise?: Promise<QueryPromise> | null;
42
+ data?: any;
43
+ error?: any;
44
+ }
45
+ > = new Map();
46
+
47
+ private queryResolvedCallbacks: ((result: {
48
+ triples: any;
49
+ attrs: any;
50
+ queryHash: any;
51
+ query: any;
52
+ pageInfo?: any;
53
+ }) => void)[] = [];
54
+
55
+ constructor(params: FrameworkConfig) {
56
+ this.params = params;
57
+ this.db = params.db;
58
+ this.resultMap = new Map<
59
+ string,
60
+ {
61
+ type: 'http' | 'session';
62
+ status: 'pending' | 'success' | 'error';
63
+ promise?: Promise<QueryPromise>;
64
+ data?: any;
65
+ error?: any;
66
+ }
67
+ >();
68
+ }
69
+
70
+ public subscribe = (
71
+ callback: (result: {
72
+ triples: any;
73
+ attrs: any;
74
+ queryHash: string;
75
+ pageInfo?: any;
76
+ }) => void,
77
+ ) => {
78
+ this.queryResolvedCallbacks.push(callback);
79
+ };
80
+
81
+ // Runs on the client when ssr gets html script tags
82
+ public addQueryResult = (queryKey: string, value: any) => {
83
+ this.resultMap.set(queryKey, {
84
+ type: value.type,
85
+ status: 'success',
86
+ data: value,
87
+ promise: null,
88
+ error: null,
89
+ });
90
+ // send the result to the client
91
+ if (!isServer) {
92
+ // make sure the attrs are there to create stores
93
+ if (!this.db._reactor.attrs) {
94
+ this.db._reactor._setAttrs(value.attrs);
95
+ }
96
+ this.db._reactor._addQueryData(
97
+ value.query,
98
+ value,
99
+ !!this.db._reactor.config.schema,
100
+ );
101
+ }
102
+ };
103
+
104
+ public query = (
105
+ _query: any,
106
+ opts?: {
107
+ ruleParams: RuleParams;
108
+ },
109
+ ): {
110
+ type: 'http' | 'session';
111
+ status: 'pending' | 'success' | 'error';
112
+ promise?: Promise<QueryPromise>;
113
+ data?: any;
114
+ error?: any;
115
+ } => {
116
+ const { hash, query } = this.hashQuery(_query, opts);
117
+
118
+ if (this.db._reactor.status === 'authenticated') {
119
+ const promise = this.db.queryOnce(_query, opts);
120
+ let entry = {
121
+ status: 'pending' as 'pending' | 'success' | 'error',
122
+ type: 'session' as 'http' | 'session',
123
+ data: undefined as any,
124
+ error: undefined as any,
125
+ promise: promise as any,
126
+ };
127
+ promise.then((result) => {
128
+ entry.status = 'success';
129
+ entry.data = result;
130
+ entry.promise = null;
131
+ });
132
+ promise.catch((error) => {
133
+ entry.status = 'error';
134
+ entry.error = error;
135
+ entry.promise = null;
136
+ });
137
+ this.resultMap.set(hash, entry);
138
+ return entry as any;
139
+ }
140
+
141
+ const promise = this.getTriplesAndAttrsForQuery(query);
142
+ let entry = {
143
+ status: 'pending' as 'pending' | 'success' | 'error',
144
+ type: 'http' as 'http' | 'session',
145
+ data: undefined as any,
146
+ error: undefined as any,
147
+ promise: promise as any,
148
+ };
149
+
150
+ promise.then((result) => {
151
+ entry.status = 'success';
152
+ entry.data = result;
153
+ entry.promise = null;
154
+ });
155
+ promise.catch((error) => {
156
+ entry.status = 'error';
157
+ entry.error = error;
158
+ entry.promise = null;
159
+ });
160
+
161
+ promise.then((result) => {
162
+ this.queryResolvedCallbacks.forEach((callback) => {
163
+ callback({
164
+ queryHash: hash,
165
+ query: query,
166
+ attrs: result.attrs,
167
+ triples: result.triples,
168
+ pageInfo: result.pageInfo,
169
+ });
170
+ });
171
+ });
172
+
173
+ this.resultMap.set(hash, entry);
174
+ return entry;
175
+ };
176
+
177
+ public getExistingResultForQuery = (
178
+ _query: any,
179
+ opts?: {
180
+ ruleParams: RuleParams;
181
+ },
182
+ ) => {
183
+ const { hash } = this.hashQuery(_query, opts);
184
+ return this.resultMap.get(hash);
185
+ };
186
+
187
+ public completeIsomorphic = (
188
+ query: any,
189
+ triples: any[],
190
+ attrs: InstantDBAttr[],
191
+ pageInfo?: any,
192
+ ) => {
193
+ const attrMap = {};
194
+ attrs.forEach((attr) => {
195
+ attrMap[attr.id] = attr;
196
+ });
197
+
198
+ const enableCardinalityInference =
199
+ Boolean(this.db?._reactor?.config?.schema) &&
200
+ ('cardinalityInference' in this.db?._reactor?.config
201
+ ? Boolean(this.db?._reactor.config?.cardinalityInference)
202
+ : true);
203
+
204
+ const attrsStore = new s.AttrsStoreClass(
205
+ attrs.reduce((acc, attr) => {
206
+ acc[attr.id] = attr;
207
+ return acc;
208
+ }, {}),
209
+ createLinkIndex(this.db?._reactor.config.schema),
210
+ );
211
+
212
+ const store = s.createStore(
213
+ attrsStore,
214
+ triples,
215
+ enableCardinalityInference,
216
+ this.params.db._reactor.config.useDateObjects || false,
217
+ );
218
+ const resp = instaql(
219
+ {
220
+ store: store,
221
+ attrsStore: attrsStore,
222
+ pageInfo: pageInfo,
223
+ aggregate: undefined,
224
+ },
225
+ query,
226
+ );
227
+ return resp;
228
+ };
229
+
230
+ public hashQuery = (
231
+ _query: any,
232
+ opts?: {
233
+ ruleParams: RuleParams;
234
+ },
235
+ ): { hash: string; query: any } => {
236
+ if (_query && opts && 'ruleParams' in opts) {
237
+ _query = { $$ruleParams: opts['ruleParams'], ..._query };
238
+ }
239
+ const query = _query ? coerceQuery(_query) : null;
240
+ return { hash: weakHash(query), query: query };
241
+ };
242
+
243
+ public getTriplesAndAttrsForQuery = async (
244
+ query: any,
245
+ ): Promise<{
246
+ triples: any[];
247
+ attrs: InstantDBAttr[];
248
+ query: any;
249
+ queryHash: string;
250
+ type: 'http';
251
+ pageInfo?: any;
252
+ }> => {
253
+ const response = await fetch(
254
+ `${this.db._reactor.config.apiURI}/runtime/framework/query`,
255
+ {
256
+ method: 'POST',
257
+ headers: {
258
+ 'app-id': this.params.db._reactor.config.appId,
259
+ 'Content-Type': 'application/json',
260
+ Authorization: this.params.token
261
+ ? `Bearer ${this.params.token}`
262
+ : undefined,
263
+ } as Record<string, string>,
264
+ body: JSON.stringify({
265
+ query: query,
266
+ }),
267
+ },
268
+ );
269
+
270
+ if (!response.ok) {
271
+ throw new Error('Error getting triples from server');
272
+ }
273
+
274
+ const data = await response.json();
275
+
276
+ const attrs = data?.attrs;
277
+ if (!attrs) {
278
+ throw new Error('No attrs');
279
+ }
280
+
281
+ // TODO: make safer
282
+ const triples = data.result?.[0].data?.['datalog-result']?.['join-rows'][0];
283
+
284
+ const pageInfo = data.result?.[0]?.data?.['page-info'];
285
+
286
+ return {
287
+ attrs,
288
+ triples,
289
+ type: 'http',
290
+ queryHash: this.hashQuery(query).hash,
291
+ query,
292
+ pageInfo,
293
+ };
294
+ };
295
+ }
package/src/index.ts CHANGED
@@ -20,10 +20,13 @@ import {
20
20
  validateTransactions,
21
21
  TransactionValidationError,
22
22
  } from './transactionValidation.ts';
23
+
23
24
  import {
24
25
  StorageInterface,
25
26
  type StorageInterfaceStoreName,
26
27
  } from './utils/PersistedObject.ts';
28
+ import { createInstantRouteHandler } from './createRouteHandler.ts';
29
+ import { parseSchemaFromJSON } from './parseSchemaFromJSON.ts';
27
30
 
28
31
  import type {
29
32
  PresenceOpts,
@@ -103,6 +106,7 @@ import type {
103
106
  } from './schemaTypes.ts';
104
107
  import type { InstantRules } from './rulesTypes.ts';
105
108
  import type { UploadFileResponse, DeleteFileResponse } from './StorageAPI.ts';
109
+ import { FrameworkClient, type FrameworkConfig } from './framework.ts';
106
110
 
107
111
  import type {
108
112
  ExchangeCodeForTokenParams,
@@ -153,6 +157,7 @@ export type InstantConfig<
153
157
  appId: string;
154
158
  schema?: S;
155
159
  websocketURI?: string;
160
+ firstPartyPath?: string;
156
161
  apiURI?: string;
157
162
  devtool?: boolean | DevtoolConfig;
158
163
  verbose?: boolean;
@@ -903,7 +908,9 @@ export {
903
908
  validateQuery,
904
909
  QueryValidationError,
905
910
  validateTransactions,
911
+ parseSchemaFromJSON,
906
912
  TransactionValidationError,
913
+ FrameworkClient,
907
914
 
908
915
  // error
909
916
  InstantAPIError,
@@ -1019,6 +1026,7 @@ export {
1019
1026
 
1020
1027
  // SSE
1021
1028
  type EventSourceType,
1029
+ type FrameworkConfig,
1022
1030
 
1023
1031
  // sync table types
1024
1032
  type SyncTableCallback,
@@ -1035,4 +1043,5 @@ export {
1035
1043
  // storage (e.g. indexeddb) interface
1036
1044
  StorageInterface,
1037
1045
  type StorageInterfaceStoreName,
1046
+ createInstantRouteHandler,
1038
1047
  };