@black-cape/microstore 0.0.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.
- package/LICENSE +15 -0
- package/README.md +794 -0
- package/dist/index.d.mts +88 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, MicroStore Contributors
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
# MicroStore
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**A powerful TypeScript React data normalization library that provides a single source of truth for your application state.**
|
|
6
|
+
|
|
7
|
+
MicroStore is an abstract data normalization layer for React projects that eliminates data duplication across AJAX / fetch requests and provides reactive access to normalized records. Built on [TinyBase](https://tinybase.org/), it automatically interprets REST API responses and maintains a consistent, normalized data record layer that your UI can reactively subscribe to. Each component using the reactivity layer still receives immutable copies of each record, but they will always be in sync.
|
|
8
|
+
|
|
9
|
+
## 🚀 Features
|
|
10
|
+
|
|
11
|
+
- **🎯 Single Source of Truth**: Eliminates data duplication by normalizing records across all API requests
|
|
12
|
+
- **⚡ Reactive Updates**: Components automatically re-render when normalized data changes
|
|
13
|
+
- **🔄 Automatic REST Interpretation**: Built-in support for Ember REST Adapter and fastapi-cruddy-framework response formats
|
|
14
|
+
- **🔌 Provider Agnostic**: Works with any AJAX library (ky, axios, fetch) or query cache (React Query, SWR)
|
|
15
|
+
- **🛡️ TypeScript First**: Fully typed with comprehensive schema validation
|
|
16
|
+
- **🎨 Transform System**: Flexible field and record-level data transformations
|
|
17
|
+
- **⚡ Performance Optimized**: Built on TinyBase for efficient storage and queries
|
|
18
|
+
|
|
19
|
+
## 📦 Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @black-cape/microstore
|
|
23
|
+
# or
|
|
24
|
+
yarn add @black-cape/microstore
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @black-cape/microstore
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## ⚛️ React Compatibility
|
|
30
|
+
|
|
31
|
+
MicroStore supports both **React 18** and **React 19**:
|
|
32
|
+
|
|
33
|
+
- ✅ **React 18.0+**: Full compatibility with all features
|
|
34
|
+
- ✅ **React 19.0+**: Full compatibility with latest React features
|
|
35
|
+
- 🔧 **React Compiler**: Optional support for React's experimental compiler
|
|
36
|
+
|
|
37
|
+
### Testing Compatibility
|
|
38
|
+
|
|
39
|
+
Test your specific React version:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Test with React 18
|
|
43
|
+
npm install react@^18.0.0 react-dom@^18.0.0
|
|
44
|
+
npm run test:compatibility
|
|
45
|
+
|
|
46
|
+
# Test with React 19
|
|
47
|
+
npm install react@^19.0.0 react-dom@^19.0.0
|
|
48
|
+
npm run test:compatibility
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 🏃 Quick Start
|
|
52
|
+
|
|
53
|
+
### 1. Define Your Schemas
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { MicroStore, MicroStoreProvider, useReactive } from 'microstore';
|
|
57
|
+
|
|
58
|
+
// Define your data schemas
|
|
59
|
+
const schemas = {
|
|
60
|
+
user: {
|
|
61
|
+
id: { type: 'string', primaryKey: true },
|
|
62
|
+
name: { type: 'string' },
|
|
63
|
+
email: { type: 'string' },
|
|
64
|
+
tags: { type: 'string', transform: 'json' }, // Will serialized / deserialized into and out of tinybase as JSON
|
|
65
|
+
preferences: { type: 'string', transform: 'json' } // Will serialized / deserialized into and out of tinybase as JSON
|
|
66
|
+
},
|
|
67
|
+
post: {
|
|
68
|
+
id: { type: 'string', primaryKey: true },
|
|
69
|
+
title: { type: 'string' },
|
|
70
|
+
content: { type: 'string' },
|
|
71
|
+
userId: { type: 'string' },
|
|
72
|
+
tags: { type: 'string', transform: 'json' } // Will serialized / deserialized into and out of tinybase as JSON
|
|
73
|
+
}
|
|
74
|
+
} as const;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Setup the Provider
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { MicroStore, MicroStoreProvider } from 'microstore';
|
|
81
|
+
|
|
82
|
+
const store = new MicroStore({ schemas });
|
|
83
|
+
|
|
84
|
+
function App() {
|
|
85
|
+
return (
|
|
86
|
+
<MicroStoreProvider store={store}>
|
|
87
|
+
<UserList />
|
|
88
|
+
</MicroStoreProvider>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Use with React Query (or any data fetcher)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { useQuery } from '@tanstack/react-query';
|
|
97
|
+
import { useReactive, useMicroStore } from 'microstore';
|
|
98
|
+
|
|
99
|
+
function UserList() {
|
|
100
|
+
const store = useMicroStore();
|
|
101
|
+
|
|
102
|
+
// Fetch data with React Query
|
|
103
|
+
const { data: rawUsers } = useQuery({
|
|
104
|
+
queryKey: ['users'],
|
|
105
|
+
queryFn: async () => {
|
|
106
|
+
const response = await fetch('/api/users');
|
|
107
|
+
const data = await response.json();
|
|
108
|
+
|
|
109
|
+
// Push the response into MicroStore for normalization
|
|
110
|
+
store?.pushPayload('GET', data);
|
|
111
|
+
|
|
112
|
+
return data.users; // Return the raw array for React Query
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// useReactive ensures components get the normalized, single-source-of-truth data
|
|
117
|
+
const users = useReactive('user', rawUsers || []);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ul>
|
|
121
|
+
{users.map(user => (
|
|
122
|
+
<UserItem key={user.id} user={user} />
|
|
123
|
+
))}
|
|
124
|
+
</ul>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function UserItem({ user }) {
|
|
129
|
+
// This component will automatically re-render if this user
|
|
130
|
+
// is updated anywhere else in the application!
|
|
131
|
+
return (
|
|
132
|
+
<li>
|
|
133
|
+
{user.name} - {user.email}
|
|
134
|
+
<small>Joined: {user.createdAt.toLocaleDateString()}</small>
|
|
135
|
+
</li>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 🔌 Core Classes and Hooks
|
|
141
|
+
|
|
142
|
+
### `MicroStore`
|
|
143
|
+
|
|
144
|
+
The main store class that handles data normalization, storage, and retrieval.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const store = new MicroStore({
|
|
148
|
+
schemas,
|
|
149
|
+
fieldTransforms?: FieldTransforms,
|
|
150
|
+
recordTransforms?: RecordTransforms,
|
|
151
|
+
interpreter?: MicrostoreInterpreter
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Push data from API responses
|
|
155
|
+
store.pushPayload('GET', response);
|
|
156
|
+
store.pushRecord('user', userData, 'POST');
|
|
157
|
+
store.pushRecords('user', userArray, 'GET');
|
|
158
|
+
|
|
159
|
+
// Direct data access
|
|
160
|
+
const user = store.peekRecord<User>('user', '123');
|
|
161
|
+
const allUsers = store.peekAll<User>('user');
|
|
162
|
+
|
|
163
|
+
// Data management
|
|
164
|
+
store.unloadRecord('user', '123');
|
|
165
|
+
store.unloadAll('user');
|
|
166
|
+
store.reset();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `useReactive<T>(type: string, data: T[]): T[]`
|
|
170
|
+
|
|
171
|
+
React hook that wraps an array of records to provide reactive updates from the normalized store.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
function UserList() {
|
|
175
|
+
const { data } = useQuery(['users'], fetchUsers);
|
|
176
|
+
|
|
177
|
+
// Returns normalized users that update reactively
|
|
178
|
+
// You can update individual records in MicroStore using
|
|
179
|
+
// websockets, for instance, or if you have many components
|
|
180
|
+
// querying for users on screen at different times, any REST response
|
|
181
|
+
// that has the latest information on user x would cause user x
|
|
182
|
+
// to synchronize across all components without additional queries
|
|
183
|
+
const users = useReactive('user', data?.users || []);
|
|
184
|
+
|
|
185
|
+
return <div>{users.map(user => <User key={JSON.stringify(user)} user={user} />)}</div>;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `RESTInterpreter`
|
|
190
|
+
|
|
191
|
+
Built-in interpreter for standard REST API responses. Supports:
|
|
192
|
+
|
|
193
|
+
- **[Ember REST Adapter](https://api.emberjs.com/ember-data/release/classes/restadapter) format**
|
|
194
|
+
- **[fastapi-cruddy-framework](https://github.com/mdconaway/fastapi-cruddy-framework) format**
|
|
195
|
+
- Custom pluralized resource names
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Automatically normalizes responses like:
|
|
199
|
+
{
|
|
200
|
+
"users": [
|
|
201
|
+
{ "id": "1", "name": "John" },
|
|
202
|
+
{ "id": "2", "name": "Jane" }
|
|
203
|
+
],
|
|
204
|
+
"posts": [
|
|
205
|
+
{ "id": "1", "userId": "1", "title": "Hello World" }
|
|
206
|
+
],
|
|
207
|
+
"meta": { "total": 100 }
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `MicroStoreProvider`
|
|
212
|
+
|
|
213
|
+
React context provider that makes the store available to child components.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
<MicroStoreProvider store={store}>
|
|
217
|
+
<App />
|
|
218
|
+
</MicroStoreProvider>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## 🔄 Data Flow
|
|
222
|
+
|
|
223
|
+
1. **API Request**: Use any HTTP client (fetch, ky, axios) or query library (React Query, SWR)
|
|
224
|
+
2. **Normalization**: Push response data into MicroStore via `pushPayload()`
|
|
225
|
+
3. **Storage**: Data is normalized, deduplicated, and stored in TinyBase
|
|
226
|
+
4. **Reactive Access**: Components use `useReactive()` to get live, normalized data
|
|
227
|
+
5. **Updates**: Any changes to normalized data automatically trigger component re-renders
|
|
228
|
+
|
|
229
|
+
## 🛠️ Advanced Features
|
|
230
|
+
|
|
231
|
+
### Custom Field Transforms
|
|
232
|
+
|
|
233
|
+
Transform data at the field level during serialization/deserialization:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const customTransforms = {
|
|
237
|
+
date: {
|
|
238
|
+
serialize: (value: Date) => value.toISOString(),
|
|
239
|
+
deserialize: (value: string) => new Date(value)
|
|
240
|
+
},
|
|
241
|
+
currency: {
|
|
242
|
+
serialize: (value: number) => Math.round(value * 100), // Store as cents
|
|
243
|
+
deserialize: (value: number) => value / 100 // Display as dollars
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const store = new MicroStore({
|
|
248
|
+
schemas: {
|
|
249
|
+
product: {
|
|
250
|
+
id: { type: 'string', primaryKey: true },
|
|
251
|
+
name: { type: 'string' },
|
|
252
|
+
price: { type: 'number', transform: 'currency' },
|
|
253
|
+
createdAt: { type: 'string', transform: 'date' }
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
fieldTransforms: customTransforms
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Custom Record Transforms
|
|
261
|
+
|
|
262
|
+
Transform entire records during serialization/deserialization:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const recordTransforms = {
|
|
266
|
+
user: {
|
|
267
|
+
serialize: (user: User) => ({
|
|
268
|
+
...user // you COULD omit the computed field (displayName) here, but it will be clipped out automatically by the field schema during storage
|
|
269
|
+
}),
|
|
270
|
+
deserialize: (userData: any) => ({
|
|
271
|
+
...userData,
|
|
272
|
+
displayName: userData.fullName || userData.name // Computed field for UI
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Practical Zod Integration Example
|
|
279
|
+
|
|
280
|
+
You can use record transforms to integrate [Zod](https://zod.dev/) for robust type validation and transformation:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { z } from 'zod';
|
|
284
|
+
|
|
285
|
+
// Define Zod schema for validation and type inference
|
|
286
|
+
const UserSchema = z.object({
|
|
287
|
+
id: z.string(),
|
|
288
|
+
email: z.string().email(),
|
|
289
|
+
name: z.string(),
|
|
290
|
+
createdAt: z.date(),
|
|
291
|
+
preferences: z.object({
|
|
292
|
+
theme: z.enum(['light', 'dark']),
|
|
293
|
+
notifications: z.boolean()
|
|
294
|
+
}),
|
|
295
|
+
// Computed properties available only in class instances
|
|
296
|
+
getDisplayName: z.function().returns(z.string()).optional()
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Create a User class with methods
|
|
300
|
+
class User {
|
|
301
|
+
constructor(
|
|
302
|
+
public id: string,
|
|
303
|
+
public email: string,
|
|
304
|
+
public name: string,
|
|
305
|
+
public createdAt: Date,
|
|
306
|
+
public preferences: { theme: 'light' | 'dark'; notifications: boolean }
|
|
307
|
+
) {}
|
|
308
|
+
|
|
309
|
+
getDisplayName(): string {
|
|
310
|
+
return `${this.name} (${this.email})`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
toJSON() {
|
|
314
|
+
// Convert class instance to plain object for API serialization
|
|
315
|
+
return {
|
|
316
|
+
id: this.id,
|
|
317
|
+
email: this.email,
|
|
318
|
+
name: this.name,
|
|
319
|
+
createdAt: this.createdAt,
|
|
320
|
+
preferences: this.preferences
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static fromJSON(data: any): User {
|
|
325
|
+
// Validate and create User instance from plain object
|
|
326
|
+
const validated = UserSchema.omit({ getDisplayName: true }).parse(data);
|
|
327
|
+
return new User(
|
|
328
|
+
validated.id,
|
|
329
|
+
validated.email,
|
|
330
|
+
validated.name,
|
|
331
|
+
validated.createdAt,
|
|
332
|
+
validated.preferences
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Configure record transforms with Zod validation
|
|
338
|
+
const recordTransforms = {
|
|
339
|
+
user: {
|
|
340
|
+
// serialize: receives User class instance, returns plain object for TinyBase storage
|
|
341
|
+
serialize: (user: User) => {
|
|
342
|
+
return user.toJSON(); // Convert class instance to POJO
|
|
343
|
+
},
|
|
344
|
+
// deserialize: receives POJO from TinyBase, returns User class instance for components
|
|
345
|
+
deserialize: (userData: any) => {
|
|
346
|
+
return User.fromJSON(userData); // Validate and convert to class instance
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Configure MicroStore with Zod-powered transforms
|
|
352
|
+
const store = new MicroStore({
|
|
353
|
+
schemas: {
|
|
354
|
+
user: {
|
|
355
|
+
id: { type: 'string', primaryKey: true },
|
|
356
|
+
email: { type: 'string' },
|
|
357
|
+
name: { type: 'string' },
|
|
358
|
+
createdAt: { type: 'string', transform: 'json' }, // Dates serialized as ISO strings
|
|
359
|
+
preferences: { type: 'string', transform: 'json' } // Objects serialized as JSON
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
recordTransforms
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Usage in components - you receive fully validated User class instances
|
|
366
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
367
|
+
const user = store.peekRecord<User>('user', userId);
|
|
368
|
+
|
|
369
|
+
return (
|
|
370
|
+
<div>
|
|
371
|
+
<h1>{user?.getDisplayName()}</h1> {/* Class method available */}
|
|
372
|
+
<p>Theme: {user?.preferences.theme}</p>
|
|
373
|
+
<p>Notifications: {user?.preferences.notifications ? 'On' : 'Off'}</p>
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Benefits of Zod Integration:**
|
|
380
|
+
|
|
381
|
+
- **Runtime Validation**: Ensures data integrity when deserializing from storage
|
|
382
|
+
- **Type Safety**: Full TypeScript support with inferred types
|
|
383
|
+
- **Class Methods**: Enable rich domain models with behavior, not just data
|
|
384
|
+
- **Error Handling**: Automatic validation errors for malformed data
|
|
385
|
+
- **Schema Evolution**: Easy to update schemas as your API evolves
|
|
386
|
+
|
|
387
|
+
Your `deserialize` function receives a POJO (plain old javascript object) format object _after_ it has already been run through `tinybase` field level `deserialize` functions. (So your arrays will be arrays, objects will be objects, etc) You can then take this simple POJO record and transform it into more complex types that cannot be represented in raw JSON, like class instances, dates, etc. If you create a `zod`-based deserialize function, the expectation of the correlated `serialize` method handler would be to receive a record object in its `zod` format, and to then convert it into its pure POJO format before it is then delegated to the final field-level transformers before being pushed into `tinybase` for reactivity.
|
|
388
|
+
|
|
389
|
+
### Generating Schemas with ZodSchematizer
|
|
390
|
+
|
|
391
|
+
You can automatically generate MicroStore schemas from Zod models using TinyBase's [ZodSchematizer](https://tinybase.org/api/schematizer-zod/interfaces/schematizer/zodschematizer/):
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
# Install the ZodSchematizer
|
|
395
|
+
npm install tinybase schematizer-zod zod
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
import { z } from 'zod';
|
|
400
|
+
import { createZodSchematizer } from 'schematizer-zod';
|
|
401
|
+
|
|
402
|
+
// Define your Zod models
|
|
403
|
+
const UserZodModel = z.object({
|
|
404
|
+
id: z.string(),
|
|
405
|
+
email: z.string().email(),
|
|
406
|
+
name: z.string(),
|
|
407
|
+
age: z.number().min(0).max(120),
|
|
408
|
+
isActive: z.boolean(),
|
|
409
|
+
createdAt: z.date(), // Date object in application
|
|
410
|
+
preferences: z.object({
|
|
411
|
+
theme: z.enum(['light', 'dark']),
|
|
412
|
+
notifications: z.boolean()
|
|
413
|
+
}),
|
|
414
|
+
tags: z.array(z.string())
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const PostZodModel = z.object({
|
|
418
|
+
id: z.string(),
|
|
419
|
+
title: z.string(),
|
|
420
|
+
content: z.string(),
|
|
421
|
+
userId: z.string(),
|
|
422
|
+
publishedAt: z.date().nullable(),
|
|
423
|
+
metadata: z.object({
|
|
424
|
+
readTime: z.number(),
|
|
425
|
+
wordCount: z.number()
|
|
426
|
+
})
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Create the ZodSchematizer
|
|
430
|
+
const schematizer = createZodSchematizer({
|
|
431
|
+
user: UserZodModel,
|
|
432
|
+
post: PostZodModel
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Generate base TinyBase schemas
|
|
436
|
+
const baseTinyBaseSchemas = schematizer.getTablesSchema();
|
|
437
|
+
|
|
438
|
+
// Define custom field transforms for complex types
|
|
439
|
+
const customFieldTransforms = {
|
|
440
|
+
date: {
|
|
441
|
+
// serialize: convert Date object to ISO string for TinyBase storage
|
|
442
|
+
serialize: (value: Date) => value.toISOString(),
|
|
443
|
+
// deserialize: convert ISO string back to Date object for application use
|
|
444
|
+
deserialize: (value: string) => new Date(value)
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// Convert to MicroStore schemas by adding MicroStore-specific properties
|
|
449
|
+
const microStoreSchemas = {
|
|
450
|
+
user: {
|
|
451
|
+
...baseTinyBaseSchemas.user,
|
|
452
|
+
// Override the id field to mark it as primary key
|
|
453
|
+
id: { ...baseTinyBaseSchemas.user.id, primaryKey: true },
|
|
454
|
+
// Add transforms for complex fields
|
|
455
|
+
createdAt: { type: 'string', transform: 'date' }, // Use custom date transform
|
|
456
|
+
preferences: { type: 'string', transform: 'json' },
|
|
457
|
+
tags: { type: 'string', transform: 'json' }
|
|
458
|
+
},
|
|
459
|
+
post: {
|
|
460
|
+
...baseTinyBaseSchemas.post,
|
|
461
|
+
// Override the id field to mark it as primary key
|
|
462
|
+
id: { ...baseTinyBaseSchemas.post.id, primaryKey: true },
|
|
463
|
+
// Add transforms for complex fields
|
|
464
|
+
publishedAt: { type: 'string', transform: 'date' }, // Use custom date transform
|
|
465
|
+
metadata: { type: 'string', transform: 'json' }
|
|
466
|
+
}
|
|
467
|
+
} as const;
|
|
468
|
+
|
|
469
|
+
// Create MicroStore with generated schemas and custom transforms
|
|
470
|
+
const store = new MicroStore({
|
|
471
|
+
schemas: microStoreSchemas,
|
|
472
|
+
fieldTransforms: customFieldTransforms, // Add custom field transforms
|
|
473
|
+
recordTransforms: {
|
|
474
|
+
user: {
|
|
475
|
+
// serialize: receives Zod model instance, returns POJO for TinyBase storage
|
|
476
|
+
serialize: (user: z.infer<typeof UserZodModel>) => {
|
|
477
|
+
// Convert Zod model to plain object
|
|
478
|
+
return {
|
|
479
|
+
id: user.id,
|
|
480
|
+
email: user.email,
|
|
481
|
+
name: user.name,
|
|
482
|
+
age: user.age,
|
|
483
|
+
isActive: user.isActive,
|
|
484
|
+
createdAt: user.createdAt, // Date object - will be converted by field transform
|
|
485
|
+
preferences: user.preferences,
|
|
486
|
+
tags: user.tags
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
// deserialize: receives POJO from TinyBase, returns Zod-validated model
|
|
490
|
+
deserialize: (data: any) => {
|
|
491
|
+
return UserZodModel.parse(data); // createdAt will be Date object from field transform
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
post: {
|
|
495
|
+
// serialize: receives Zod model instance, returns POJO for TinyBase storage
|
|
496
|
+
serialize: (post: z.infer<typeof PostZodModel>) => {
|
|
497
|
+
// Convert Zod model to plain object
|
|
498
|
+
return {
|
|
499
|
+
id: post.id,
|
|
500
|
+
title: post.title,
|
|
501
|
+
content: post.content,
|
|
502
|
+
userId: post.userId,
|
|
503
|
+
publishedAt: post.publishedAt, // Date object - will be converted by field transform
|
|
504
|
+
metadata: post.metadata
|
|
505
|
+
};
|
|
506
|
+
},
|
|
507
|
+
// deserialize: receives POJO from TinyBase, returns Zod-validated model
|
|
508
|
+
deserialize: (data: any) => {
|
|
509
|
+
return PostZodModel.parse(data); // publishedAt will be Date object from field transform
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Benefits of ZodSchematizer:**
|
|
517
|
+
|
|
518
|
+
- **Automatic Schema Generation**: Convert Zod models directly to TinyBase/MicroStore schemas
|
|
519
|
+
- **Type Consistency**: Ensure your validation schemas match your storage schemas
|
|
520
|
+
- **Reduced Boilerplate**: Less manual schema definition
|
|
521
|
+
- **Schema Evolution**: Update Zod models and regenerate schemas automatically
|
|
522
|
+
- **Validation Integration**: Natural integration between Zod validation and MicroStore storage
|
|
523
|
+
|
|
524
|
+
**Workflow:**
|
|
525
|
+
|
|
526
|
+
1. Define your domain models using Zod schemas
|
|
527
|
+
2. Use ZodSchematizer to generate base TinyBase schemas
|
|
528
|
+
3. Create custom field transforms for complex types (Date, etc.)
|
|
529
|
+
4. Enhance generated schemas with MicroStore properties (`primaryKey`, `transform`)
|
|
530
|
+
5. Add record transforms with serialize returning POJOs and deserialize returning validated models
|
|
531
|
+
6. Create MicroStore instance with enhanced schemas and field transforms
|
|
532
|
+
|
|
533
|
+
### Custom Interpreters
|
|
534
|
+
|
|
535
|
+
Create custom interpreters for non-standard API formats:
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
function GraphQLInterpreter(data: any, options: any) {
|
|
539
|
+
// Handle GraphQL responses, JSON:API, or any other format
|
|
540
|
+
return {
|
|
541
|
+
data: [
|
|
542
|
+
{
|
|
543
|
+
type: 'user',
|
|
544
|
+
data: data.data.users
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
meta: data.meta
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const store = new MicroStore({
|
|
552
|
+
schemas,
|
|
553
|
+
interpreter: GraphQLInterpreter
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
## 🎯 Use Cases
|
|
558
|
+
|
|
559
|
+
### ✅ Perfect For
|
|
560
|
+
|
|
561
|
+
- **React applications with multiple data sources** that need consistent state
|
|
562
|
+
- **Applications fetching the same entities** from different API endpoints
|
|
563
|
+
- **Complex UIs** where the same data appears in multiple components
|
|
564
|
+
- **Real-time applications** that need reactive updates across components or use websockets
|
|
565
|
+
- **Data-heavy applications** that need efficient normalization and deduplication
|
|
566
|
+
|
|
567
|
+
## 🔗 Integration Examples
|
|
568
|
+
|
|
569
|
+
### With React Query
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
function useUsers() {
|
|
573
|
+
const store = useMicroStore();
|
|
574
|
+
|
|
575
|
+
return useQuery({
|
|
576
|
+
queryKey: ['users'],
|
|
577
|
+
queryFn: async () => {
|
|
578
|
+
const response = await api.get('/users');
|
|
579
|
+
store?.pushPayload('GET', response.data);
|
|
580
|
+
return response.data.users;
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function UserList() {
|
|
586
|
+
const { data } = useUsers();
|
|
587
|
+
const users = useReactive('user', data || []);
|
|
588
|
+
return <div>{/* Render users */}</div>;
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### With SWR
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
function useUsers() {
|
|
596
|
+
const store = useMicroStore();
|
|
597
|
+
|
|
598
|
+
return useSWR('/api/users', async (url) => {
|
|
599
|
+
const response = await fetch(url);
|
|
600
|
+
const data = await response.json();
|
|
601
|
+
store?.pushPayload('GET', data);
|
|
602
|
+
return data.users;
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### With Ky
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
const api = ky.create({
|
|
611
|
+
hooks: {
|
|
612
|
+
afterResponse: [
|
|
613
|
+
async (request, _options, response) => {
|
|
614
|
+
const data = await response.json();
|
|
615
|
+
store.pushPayload(request.method, data);
|
|
616
|
+
return response;
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
- If you use the `afterResponse` hook in your global `ky` api, you DON'T need to manually push data into your store in your `queryFn`s! The data will automagically be in your `MicroStore` and all you need to do is pass the IDs you want to render to `useReactive` in your components! This is thanks to the `RESTInterpreter` which will digest all of your Ember RESTAdapter compliant responses automatically. If you need to see a server framework that responds with the correct format, checkout [fastapi-cruddy-framework](https://github.com/mdconaway/fastapi-cruddy-framework)
|
|
624
|
+
|
|
625
|
+
## 📚 API Reference
|
|
626
|
+
|
|
627
|
+
### MicroStore Methods
|
|
628
|
+
|
|
629
|
+
| Method | Description |
|
|
630
|
+
| ---------------------------------------------- | ------------------------------------- |
|
|
631
|
+
| `pushPayload(method, data, options?)` | Normalize and store API response data |
|
|
632
|
+
| `pushRecord(type, record, method, options?)` | Store a single record |
|
|
633
|
+
| `pushRecords(type, records, method, options?)` | Store multiple records |
|
|
634
|
+
| `peekRecord<T>(type, id)` | Get a single record by ID |
|
|
635
|
+
| `peekAll<T>(type)` | Get all records of a type |
|
|
636
|
+
| `unloadRecord(type, id)` | Remove a record from store |
|
|
637
|
+
| `unloadAll(type)` | Remove all records of a type |
|
|
638
|
+
| `reset()` | Clear entire store |
|
|
639
|
+
|
|
640
|
+
### Schema Options
|
|
641
|
+
|
|
642
|
+
| Property | Type | Description |
|
|
643
|
+
| ------------- | ----------------------------------- | -------------------------------- |
|
|
644
|
+
| `type` | `'string' \| 'number' \| 'boolean'` | Field data type |
|
|
645
|
+
| `primaryKey?` | `boolean` | Mark field as primary key |
|
|
646
|
+
| `transform?` | `string` | Apply named transform to field |
|
|
647
|
+
| `default?` | `any` | Default value for field |
|
|
648
|
+
| `allowNull?` | `boolean` | Available if using tinybase >= 7 |
|
|
649
|
+
|
|
650
|
+
## 📋 TODO
|
|
651
|
+
|
|
652
|
+
### 🔗 TinyBase Relationships Support
|
|
653
|
+
|
|
654
|
+
Add support for [TinyBase Relationships](https://tinybase.org/api/relationships/) to enable automatic relationship management between schemas:
|
|
655
|
+
|
|
656
|
+
- [ ] **Schema Relationship Definitions**: Allow defining relationships directly in schema configuration
|
|
657
|
+
- [ ] **Automatic Relationship Creation**: Auto-generate TinyBase relationships based on schema definitions
|
|
658
|
+
- [ ] **Relationship Queries**: Extend query capabilities to leverage relationships for efficient data access
|
|
659
|
+
- [ ] **Reactive Relationship Hooks**: Create hooks that reactively update when related data changes
|
|
660
|
+
- [ ] **Foreign Key Validation**: Validate and maintain referential integrity across related records
|
|
661
|
+
- [ ] **Cascade Operations**: Support cascade delete/update operations through relationships
|
|
662
|
+
|
|
663
|
+
Example future API:
|
|
664
|
+
|
|
665
|
+
### One-to-One Relationships (using belongsTo)
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
const schemas = {
|
|
669
|
+
user: {
|
|
670
|
+
id: { type: 'string', primaryKey: true },
|
|
671
|
+
name: { type: 'string' },
|
|
672
|
+
email: { type: 'string' }
|
|
673
|
+
},
|
|
674
|
+
profile: {
|
|
675
|
+
id: { type: 'string', primaryKey: true },
|
|
676
|
+
userId: { type: 'string' }, // References user.id
|
|
677
|
+
user: { belongsTo: 'user', key: 'userId' } // Virtual relationship field (feeds off of local foreign key)
|
|
678
|
+
bio: { type: 'string' },
|
|
679
|
+
avatar: { type: 'string' }
|
|
680
|
+
}
|
|
681
|
+
} as const;
|
|
682
|
+
|
|
683
|
+
// Future hooks
|
|
684
|
+
const profile = useRelationship(user, 'profile'); // Get user's profile (inverse hasOne)
|
|
685
|
+
const user = useRelationship(profile, 'user'); // Get profile's user (belongsTo)
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### One-to-Many Relationships (belongsTo + hasMany)
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
const schemas = {
|
|
692
|
+
user: {
|
|
693
|
+
id: { type: 'string', primaryKey: true },
|
|
694
|
+
name: { type: 'string' },
|
|
695
|
+
posts: { hasMany: 'post', inverse: 'user' } // Virtual field for relationship
|
|
696
|
+
},
|
|
697
|
+
post: {
|
|
698
|
+
id: { type: 'string', primaryKey: true },
|
|
699
|
+
title: { type: 'string' },
|
|
700
|
+
content: { type: 'string' },
|
|
701
|
+
userId: { type: 'string' }, // Stores actual foreign key
|
|
702
|
+
user: { belongsTo: 'user', key: 'userId' } // Virtual relationship field (feeds off of local foreign key)
|
|
703
|
+
}
|
|
704
|
+
} as const;
|
|
705
|
+
|
|
706
|
+
// Future hooks
|
|
707
|
+
const posts = useRelationship(user, 'posts'); // Get all posts for a user (hasMany)
|
|
708
|
+
const author = useRelationship(post, 'user'); // Get post's author (belongsTo)
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Many-to-Many Relationships (through junction model)
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
const schemas = {
|
|
715
|
+
user: {
|
|
716
|
+
id: { type: 'string', primaryKey: true },
|
|
717
|
+
name: { type: 'string' },
|
|
718
|
+
userTags: { hasMany: 'userTag', inverse: 'user' },
|
|
719
|
+
tags: { hasMany: 'tag', through: 'userTags.tag' } // Through relationship
|
|
720
|
+
},
|
|
721
|
+
tag: {
|
|
722
|
+
id: { type: 'string', primaryKey: true },
|
|
723
|
+
name: { type: 'string' },
|
|
724
|
+
color: { type: 'string' },
|
|
725
|
+
userTags: { hasMany: 'userTag', inverse: 'tag' },
|
|
726
|
+
users: { hasMany: 'user', through: 'userTags.user' } // Through relationship
|
|
727
|
+
},
|
|
728
|
+
userTag: {
|
|
729
|
+
id: { type: 'string', primaryKey: true },
|
|
730
|
+
userId: { type: 'string' },
|
|
731
|
+
tagId: { type: 'string' },
|
|
732
|
+
user: { belongsTo: 'user', key: 'userId' },
|
|
733
|
+
tag: { belongsTo: 'tag', key: 'tagId' },
|
|
734
|
+
createdAt: { type: 'string' } // Junction tables can have additional fields
|
|
735
|
+
}
|
|
736
|
+
} as const;
|
|
737
|
+
|
|
738
|
+
// Future hooks for many-to-many
|
|
739
|
+
const userTags = useRelationship(user, 'tags'); // Get user's tags (through userTags)
|
|
740
|
+
const tagUsers = useRelationship(tag, 'users'); // Get tag's users (through userTags)
|
|
741
|
+
const userTagJunctions = useRelationship(user, 'userTags'); // Get actual junction records
|
|
742
|
+
|
|
743
|
+
// Post tagging example
|
|
744
|
+
const schemas = {
|
|
745
|
+
post: {
|
|
746
|
+
id: { type: 'string', primaryKey: true },
|
|
747
|
+
title: { type: 'string' },
|
|
748
|
+
postTags: { hasMany: 'postTag', inverse: 'post' },
|
|
749
|
+
tags: { hasMany: 'tag', through: 'postTags.tag' }
|
|
750
|
+
},
|
|
751
|
+
tag: {
|
|
752
|
+
id: { type: 'string', primaryKey: true },
|
|
753
|
+
name: { type: 'string' },
|
|
754
|
+
postTags: { hasMany: 'postTag', inverse: 'tag' },
|
|
755
|
+
posts: { hasMany: 'post', through: 'postTags.post' }
|
|
756
|
+
},
|
|
757
|
+
postTag: {
|
|
758
|
+
id: { type: 'string', primaryKey: true },
|
|
759
|
+
postId: { type: 'string' },
|
|
760
|
+
tagId: { type: 'string' },
|
|
761
|
+
post: { belongsTo: 'post', key: 'postId' },
|
|
762
|
+
tag: { belongsTo: 'tag', key: 'tagId' }
|
|
763
|
+
}
|
|
764
|
+
} as const;
|
|
765
|
+
|
|
766
|
+
// Complex many-to-many usage
|
|
767
|
+
const tagsForPost = useRelationship(post, 'tags'); // Get all tags for a post
|
|
768
|
+
const postsForTag = useRelationship(tag, 'posts'); // Get all posts with a tag
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
## 🤝 Contributing
|
|
772
|
+
|
|
773
|
+
Contributions are welcome! Please read our [contributing guidelines](./CONTRIBUTING.md) and submit pull requests to our repository.
|
|
774
|
+
|
|
775
|
+
We especially welcome contributions in these areas:
|
|
776
|
+
|
|
777
|
+
- 🧪 **Testing**: Help us add comprehensive test coverage
|
|
778
|
+
- 📖 **Documentation**: Improve examples and API documentation
|
|
779
|
+
- 🔗 **Relationships**: Implement TinyBase relationships support
|
|
780
|
+
- 🎯 **Transformers**: Add more field and record transform types
|
|
781
|
+
- 💡 **Examples**: Create real-world usage examples
|
|
782
|
+
|
|
783
|
+
See our [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines on development workflow, code style, and pull request requirements.
|
|
784
|
+
|
|
785
|
+
## 📄 License
|
|
786
|
+
|
|
787
|
+
ISC License - see LICENSE file for details.
|
|
788
|
+
|
|
789
|
+
## 🔗 Related Projects
|
|
790
|
+
|
|
791
|
+
- [TinyBase](https://tinybase.org/) - The reactive data store powering MicroStore
|
|
792
|
+
- [fastapi-cruddy-framework](https://github.com/mdconaway/fastapi-cruddy-framework) - Compatible REST API framework
|
|
793
|
+
- [React Query](https://tanstack.com/query) - Recommended for data fetching
|
|
794
|
+
- [SWR](https://swr.vercel.app/) - Alternative data fetching solution
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CellSchema, Row } from 'tinybase/with-schemas';
|
|
2
|
+
import { Store, Queries, Row as Row$1 } from 'tinybase';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
type CallableWithReturn<T> = (data: any) => T;
|
|
7
|
+
type RecordCallableWithReturn<T> = (data: Record<string, any>) => T;
|
|
8
|
+
type RowCallableWithReturn<T> = (data: Row<any, any>) => T;
|
|
9
|
+
type InterpretableWithReturn<T> = (data: Record<string, any>, options: Record<string, any>) => T;
|
|
10
|
+
type InterpreterTypedBatch = {
|
|
11
|
+
data: Record<string, any>[];
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
type InterpreterReturnValue = {
|
|
15
|
+
data: InterpreterTypedBatch[];
|
|
16
|
+
meta: Record<string, any> | undefined;
|
|
17
|
+
};
|
|
18
|
+
type FieldTransform = {
|
|
19
|
+
serialize: CallableWithReturn<any>;
|
|
20
|
+
deserialize: CallableWithReturn<any>;
|
|
21
|
+
};
|
|
22
|
+
type RecordTransform = {
|
|
23
|
+
serialize: RecordCallableWithReturn<any>;
|
|
24
|
+
deserialize: RowCallableWithReturn<any>;
|
|
25
|
+
};
|
|
26
|
+
type MicroStoreCellSchema = CellSchema & {
|
|
27
|
+
transform?: string;
|
|
28
|
+
primaryKey?: boolean;
|
|
29
|
+
};
|
|
30
|
+
type MicrostoreInterpreter = InterpretableWithReturn<InterpreterReturnValue>;
|
|
31
|
+
type ConventionalSchema = Record<string, CellSchema>;
|
|
32
|
+
type ConventionalSchemas = Record<string, ConventionalSchema>;
|
|
33
|
+
type MicroStoreSchema = Record<string, MicroStoreCellSchema>;
|
|
34
|
+
type MicroStoreSchemas = Record<string, MicroStoreSchema>;
|
|
35
|
+
type FieldTransforms = Record<string, FieldTransform>;
|
|
36
|
+
type RecordTransforms = Record<string, RecordTransform>;
|
|
37
|
+
type RawRow = Record<string, any>;
|
|
38
|
+
type RawRecord = Record<any, any>;
|
|
39
|
+
type MethodType = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | string;
|
|
40
|
+
type MicroStoreOptions = {
|
|
41
|
+
schemas: MicroStoreSchemas;
|
|
42
|
+
recordTransforms?: RecordTransforms;
|
|
43
|
+
fieldTransforms?: FieldTransforms;
|
|
44
|
+
interpreter?: MicrostoreInterpreter;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
declare function RESTInterpreter(data: Record<string, any>, _options: Record<string, any>): InterpreterReturnValue;
|
|
48
|
+
|
|
49
|
+
declare class MicroStore {
|
|
50
|
+
schemas: MicroStoreSchemas;
|
|
51
|
+
fieldTransforms: FieldTransforms;
|
|
52
|
+
recordTransforms: RecordTransforms;
|
|
53
|
+
store: Store;
|
|
54
|
+
queries: Queries;
|
|
55
|
+
interpreter: MicrostoreInterpreter;
|
|
56
|
+
private primaryKeys;
|
|
57
|
+
constructor(options: MicroStoreOptions);
|
|
58
|
+
private transformSchemas;
|
|
59
|
+
getPrimaryKey(type: string): string | undefined;
|
|
60
|
+
getSchema(type: string): MicroStoreSchema | null;
|
|
61
|
+
getRecordTransform(type: string | undefined): RecordTransform | undefined;
|
|
62
|
+
getFieldTransform(type: string | undefined): FieldTransform | undefined;
|
|
63
|
+
serialize(row: RawRow, schema: MicroStoreSchema): RawRow;
|
|
64
|
+
deserialize(row: Row$1, schema: MicroStoreSchema): RawRow;
|
|
65
|
+
pushRecord(type: string, data: RawRecord, method: MethodType, options?: Record<string, any>): InterpreterReturnValue | undefined;
|
|
66
|
+
pushRecords(type: string, data: RawRecord[], method: MethodType, options?: Record<string, any>): InterpreterReturnValue | undefined;
|
|
67
|
+
pushPayload(method: MethodType, data: any, options?: Record<string, any>): InterpreterReturnValue | undefined;
|
|
68
|
+
peekRecord<T>(type: string, id: string): T | undefined;
|
|
69
|
+
peekAll<T>(type: string): T[];
|
|
70
|
+
unloadRecord<T>(type: string, id: string): T | undefined;
|
|
71
|
+
unloadAll(type: string): void;
|
|
72
|
+
reset(): void;
|
|
73
|
+
getStore(): Store;
|
|
74
|
+
getQueries(): Queries;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
declare function useMicroStore(): MicroStore | null;
|
|
78
|
+
type WithExistingStore = {
|
|
79
|
+
store: MicroStore;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
};
|
|
82
|
+
declare function MicroStoreProvider(props: WithExistingStore): react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
declare function useReactive<T>(type: string, data: T[]): T[];
|
|
85
|
+
|
|
86
|
+
declare const json: FieldTransform;
|
|
87
|
+
|
|
88
|
+
export { type CallableWithReturn, type ConventionalSchema, type ConventionalSchemas, type FieldTransform, type FieldTransforms, type InterpretableWithReturn, type InterpreterReturnValue, type InterpreterTypedBatch, type MethodType, MicroStore, type MicroStoreCellSchema, type MicroStoreOptions, MicroStoreProvider, type MicroStoreSchema, type MicroStoreSchemas, type MicrostoreInterpreter, RESTInterpreter, type RawRecord, type RawRow, type RecordCallableWithReturn, type RecordTransform, type RecordTransforms, type RowCallableWithReturn, json, useMicroStore, useReactive };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CellSchema, Row } from 'tinybase/with-schemas';
|
|
2
|
+
import { Store, Queries, Row as Row$1 } from 'tinybase';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
type CallableWithReturn<T> = (data: any) => T;
|
|
7
|
+
type RecordCallableWithReturn<T> = (data: Record<string, any>) => T;
|
|
8
|
+
type RowCallableWithReturn<T> = (data: Row<any, any>) => T;
|
|
9
|
+
type InterpretableWithReturn<T> = (data: Record<string, any>, options: Record<string, any>) => T;
|
|
10
|
+
type InterpreterTypedBatch = {
|
|
11
|
+
data: Record<string, any>[];
|
|
12
|
+
type: string;
|
|
13
|
+
};
|
|
14
|
+
type InterpreterReturnValue = {
|
|
15
|
+
data: InterpreterTypedBatch[];
|
|
16
|
+
meta: Record<string, any> | undefined;
|
|
17
|
+
};
|
|
18
|
+
type FieldTransform = {
|
|
19
|
+
serialize: CallableWithReturn<any>;
|
|
20
|
+
deserialize: CallableWithReturn<any>;
|
|
21
|
+
};
|
|
22
|
+
type RecordTransform = {
|
|
23
|
+
serialize: RecordCallableWithReturn<any>;
|
|
24
|
+
deserialize: RowCallableWithReturn<any>;
|
|
25
|
+
};
|
|
26
|
+
type MicroStoreCellSchema = CellSchema & {
|
|
27
|
+
transform?: string;
|
|
28
|
+
primaryKey?: boolean;
|
|
29
|
+
};
|
|
30
|
+
type MicrostoreInterpreter = InterpretableWithReturn<InterpreterReturnValue>;
|
|
31
|
+
type ConventionalSchema = Record<string, CellSchema>;
|
|
32
|
+
type ConventionalSchemas = Record<string, ConventionalSchema>;
|
|
33
|
+
type MicroStoreSchema = Record<string, MicroStoreCellSchema>;
|
|
34
|
+
type MicroStoreSchemas = Record<string, MicroStoreSchema>;
|
|
35
|
+
type FieldTransforms = Record<string, FieldTransform>;
|
|
36
|
+
type RecordTransforms = Record<string, RecordTransform>;
|
|
37
|
+
type RawRow = Record<string, any>;
|
|
38
|
+
type RawRecord = Record<any, any>;
|
|
39
|
+
type MethodType = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | string;
|
|
40
|
+
type MicroStoreOptions = {
|
|
41
|
+
schemas: MicroStoreSchemas;
|
|
42
|
+
recordTransforms?: RecordTransforms;
|
|
43
|
+
fieldTransforms?: FieldTransforms;
|
|
44
|
+
interpreter?: MicrostoreInterpreter;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
declare function RESTInterpreter(data: Record<string, any>, _options: Record<string, any>): InterpreterReturnValue;
|
|
48
|
+
|
|
49
|
+
declare class MicroStore {
|
|
50
|
+
schemas: MicroStoreSchemas;
|
|
51
|
+
fieldTransforms: FieldTransforms;
|
|
52
|
+
recordTransforms: RecordTransforms;
|
|
53
|
+
store: Store;
|
|
54
|
+
queries: Queries;
|
|
55
|
+
interpreter: MicrostoreInterpreter;
|
|
56
|
+
private primaryKeys;
|
|
57
|
+
constructor(options: MicroStoreOptions);
|
|
58
|
+
private transformSchemas;
|
|
59
|
+
getPrimaryKey(type: string): string | undefined;
|
|
60
|
+
getSchema(type: string): MicroStoreSchema | null;
|
|
61
|
+
getRecordTransform(type: string | undefined): RecordTransform | undefined;
|
|
62
|
+
getFieldTransform(type: string | undefined): FieldTransform | undefined;
|
|
63
|
+
serialize(row: RawRow, schema: MicroStoreSchema): RawRow;
|
|
64
|
+
deserialize(row: Row$1, schema: MicroStoreSchema): RawRow;
|
|
65
|
+
pushRecord(type: string, data: RawRecord, method: MethodType, options?: Record<string, any>): InterpreterReturnValue | undefined;
|
|
66
|
+
pushRecords(type: string, data: RawRecord[], method: MethodType, options?: Record<string, any>): InterpreterReturnValue | undefined;
|
|
67
|
+
pushPayload(method: MethodType, data: any, options?: Record<string, any>): InterpreterReturnValue | undefined;
|
|
68
|
+
peekRecord<T>(type: string, id: string): T | undefined;
|
|
69
|
+
peekAll<T>(type: string): T[];
|
|
70
|
+
unloadRecord<T>(type: string, id: string): T | undefined;
|
|
71
|
+
unloadAll(type: string): void;
|
|
72
|
+
reset(): void;
|
|
73
|
+
getStore(): Store;
|
|
74
|
+
getQueries(): Queries;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
declare function useMicroStore(): MicroStore | null;
|
|
78
|
+
type WithExistingStore = {
|
|
79
|
+
store: MicroStore;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
};
|
|
82
|
+
declare function MicroStoreProvider(props: WithExistingStore): react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
declare function useReactive<T>(type: string, data: T[]): T[];
|
|
85
|
+
|
|
86
|
+
declare const json: FieldTransform;
|
|
87
|
+
|
|
88
|
+
export { type CallableWithReturn, type ConventionalSchema, type ConventionalSchemas, type FieldTransform, type FieldTransforms, type InterpretableWithReturn, type InterpreterReturnValue, type InterpreterTypedBatch, type MethodType, MicroStore, type MicroStoreCellSchema, type MicroStoreOptions, MicroStoreProvider, type MicroStoreSchema, type MicroStoreSchemas, type MicrostoreInterpreter, RESTInterpreter, type RawRecord, type RawRow, type RecordCallableWithReturn, type RecordTransform, type RecordTransforms, type RowCallableWithReturn, json, useMicroStore, useReactive };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var emberInflector=require('ember-inflector'),lodashEs=require('lodash-es'),tinybase=require('tinybase'),react=require('react'),uiReact=require('tinybase/ui-react'),jsxRuntime=require('react/jsx-runtime');var K=Object.defineProperty;var z=(o,e,r)=>e in o?K(o,e,{enumerable:true,configurable:true,writable:true,value:r}):o[e]=r;var f=(o,e,r)=>z(o,typeof e!="symbol"?e+"":e,r);function l(o,e){let r={data:[],meta:void 0},s;return o!==void 0&&Object.keys(o).forEach(t=>{let i=o[t];if(t!=="meta"){let n=[],c=lodashEs.camelCase(emberInflector.singularize(t));lodashEs.isArray(i)?i.forEach(a=>{n.push(a);}):n.push(i),r.data.push({data:n,type:c});}else s=o[t];}),r.meta=s,r}var p={serialize(o){return o?JSON.stringify(o):null},deserialize(o){return o?JSON.parse(o):null}};var N=["transform","primaryKey"],q={json:p},y=class{constructor(e){f(this,"schemas");f(this,"fieldTransforms");f(this,"recordTransforms");f(this,"store");f(this,"queries");f(this,"interpreter");f(this,"primaryKeys");this.primaryKeys={},this.schemas=e.schemas?e.schemas:{},this.fieldTransforms={...e.fieldTransforms?e.fieldTransforms:{},...q},this.recordTransforms=e.recordTransforms?e.recordTransforms:{},this.interpreter=e.interpreter?e.interpreter:l,this.store=tinybase.createStore(),this.store.setTablesSchema(this.transformSchemas()),this.queries=tinybase.createQueries(this.store);}transformSchemas(){let e={};return Object.keys(this.schemas).forEach(r=>{if(e[r]={},Object.keys(this.schemas[r]).forEach(s=>{if(e[r][s]=lodashEs.omit(this.schemas[r][s],N),this.schemas[r][s].primaryKey&&this.schemas[r][s].type==="string"){if(this.primaryKeys[r])throw Error(`More than one primary key defined for schema ${r}`);this.primaryKeys[r]=s;}}),!this.getPrimaryKey(r)&&this.schemas[r].id&&this.schemas[r].id.type==="string"&&(this.primaryKeys[r]="id"),!this.getPrimaryKey(r))throw Error(`No primary key defined for schema ${r}. This schema must have an 'id' field of "type": "string", or another field of "type": "string" with "primaryKey": true.`)}),e}getPrimaryKey(e){let r=this.primaryKeys[e];return r||void 0}getSchema(e){return e&&Object.hasOwn(this.schemas,e)?this.schemas[e]:null}getRecordTransform(e){if(e)return this.recordTransforms[e]?this.recordTransforms[e]:void 0}getFieldTransform(e){if(e)return this.fieldTransforms[e]?this.fieldTransforms[e]:void 0}serialize(e,r){let s={};return Object.keys(e).forEach(t=>{let i=r[t];if(i){let n=this.getFieldTransform(i.transform);s[t]=n?n.serialize(e[t]):e[t];}}),s}deserialize(e,r){let s={};return Object.keys(e).forEach(t=>{let i=r[t];if(i){let n=i.transform&&this.fieldTransforms[i.transform]?this.fieldTransforms[i.transform]:void 0;s[t]=n?n.deserialize(e[t]):e[t];}}),s}pushRecord(e,r,s,t={}){return this.pushRecords(e,[r],s,t)}pushRecords(e,r,s,t={}){let i=this.getRecordTransform(e),n=lodashEs.camelCase(emberInflector.pluralize(e));if(!i)return this.pushPayload(s,{[n]:r},t);let c=r.map(a=>i.serialize(a));return this.pushPayload(s,{[n]:c},t)}pushPayload(e,r,s={}){try{let t=this.interpreter(r,s);return t.data.forEach(i=>{let n=this.getSchema(i.type);if(n){let c=this.getPrimaryKey(i.type)||"id";e==="DELETE"?i.data.forEach(a=>{this.store.delRow(i.type,a[c]);}):e==="PATCH"?i.data.forEach(a=>{this.store.getRow(i.type,a[c])?this.store.setPartialRow(i.type,a[c],this.serialize(a,n)):this.store.setRow(i.type,a[c],this.serialize(a,n));}):i.data.forEach(a=>{this.store.setRow(i.type,a[c],this.serialize(a,n));});}}),t}catch(t){console.warn(t);}}peekRecord(e,r){let s=this.getSchema(e),t=this.getPrimaryKey(e),i=this.getRecordTransform(e),n=i?i.deserialize:c=>c;if(s&&t){let c=this.getStore().getRow(e,r);if(c)return n(this.deserialize(c,s))}}peekAll(e){let r=this.getSchema(e),s=this.getPrimaryKey(e),t=this.getRecordTransform(e),i=t?t.deserialize:n=>n;if(r&&s){let n=this.getStore().getTable(e);return Object.entries(n).map(([c,a])=>i(this.deserialize(a,r)))}return []}unloadRecord(e,r){let s=this.peekRecord(e,r);return s&&this.getStore().delRow(e,r),s}unloadAll(e){this.getSchema(e)&&this.getStore().delTable(e);}reset(){Object.entries(this.schemas).forEach(([e,r])=>{this.unloadAll(e);});}getStore(){return this.store}getQueries(){return this.queries}};var w=react.createContext(null);function g(){return react.useContext(w)}function J(o){let e=react.useMemo(()=>o.store,[o]);return jsxRuntime.jsx(w.Provider,{value:e,children:jsxRuntime.jsx(uiReact.Provider,{store:e.getStore(),queries:e.getQueries(),children:o.children})})}function U(o,e){return o?o.map(r=>r[e]):[]}function G(o,e,r,s,t,i){t.setQueryDefinition(s,o,({select:n,where:c})=>{Object.keys(e).forEach(a=>{n(a);}),c(a=>i.includes(a(r)));});}function X(o,e){let r=g(),s=r?.getSchema(o),t=r?.getPrimaryKey(o);if(!s||!t)throw new Error(`No MicroStore schema defined for type ${o}`);let[i,n]=react.useState(crypto.randomUUID()),c=uiReact.useQueries(),a=uiReact.useResultTable(i,c),[d,E]=react.useState(""),u=react.useRef("");react.useEffect(()=>{if(c){let m=U(e,t),h=JSON.stringify(m);d!==h&&u.current!==h&&(Object.entries(a).length>0&&(u.current=h),E(h),G(o,s,t,i,c,m));}},[e,t,s,o,c,i,d,a,u]);let R=r?.getRecordTransform(o),P=R?R.deserialize:m=>m,S=[];return e.forEach(m=>{let h=m[t],T=a[h];if(T){let v=r?.deserialize(T,s);S.push(P(v));}}),S}exports.MicroStore=y;exports.MicroStoreProvider=J;exports.RESTInterpreter=l;exports.json=p;exports.useMicroStore=g;exports.useReactive=X;//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/interpreter.ts","../src/transforms.ts","../src/microstore.ts","../src/provider.tsx","../src/reactive.ts"],"names":["RESTInterpreter","data","_options","typedPayload","meta","k","content","typedData","typeName","camelCase","singularize","isArray","item","json","microStoreReservedKeys","defaultFieldTransforms","MicroStore","options","__publicField","createStore","createQueries","transformedSchemas","k2","omit","type","possiblePK","row","schema","serializedRow","field","transform","deserializedRow","method","typeKey","pluralize","rawData","batchedPayload","payload","pk","e","id","transformer","effectiveTransformer","x","table","_","record","schemaName","StoreContext","createContext","useMicroStore","useContext","MicroStoreProvider","props","store","useMemo","jsx","TinyBaseProvider","generateFilter","obj","resetQueryDefinition","queryName","queries","filter","select","where","getCell","useReactive","uniqueHookID","useState","useQueries","rows","useResultTable","lastFilter","setLastFilter","airBrake","useRef","useEffect","stringifiedFilter","returnData","identifier","possibleRow","thing"],"mappings":"0NACA,IAAA,CAAA,CAAA,MAAA,CAAA,cAAA,CAAA,IAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,IAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,UAAA,CAAA,IAAA,CAAA,YAAA,CAAA,IAAA,CAAA,QAAA,CAAA,IAAA,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,IAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,OAAA,CAAA,EAAA,QAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAIO,SAASA,CAAAA,CAAgBC,CAAAA,CAA2BC,CAAAA,CAA+B,CACxF,IAAMC,CAAAA,CAAuC,CAC3C,IAAA,CAAM,EAAC,CACP,IAAA,CAAM,MACR,EACIC,CAAAA,CAEJ,OAAIH,CAAAA,GAAS,MAAA,EACX,OAAO,IAAA,CAAKA,CAAI,CAAA,CAAE,OAAA,CAASI,GAAM,CAC/B,IAAMC,CAAAA,CAAUL,CAAAA,CAAKI,CAAC,CAAA,CACtB,GAAIA,CAAAA,GAAM,OAAQ,CAChB,IAAME,CAAAA,CAAmC,GACnCC,CAAAA,CAAWC,kBAAAA,CAAUC,0BAAAA,CAAYL,CAAC,CAAC,CAAA,CACrCM,gBAAAA,CAAQL,CAAO,CAAA,CACjBA,CAAAA,CAAQ,OAAA,CAASM,CAAAA,EAAc,CAC7BL,EAAU,IAAA,CAAKK,CAAI,EACrB,CAAC,EAEDL,CAAAA,CAAU,IAAA,CAAKD,CAAO,CAAA,CAExBH,EAAa,IAAA,CAAK,IAAA,CAAK,CACrB,IAAA,CAAMI,CAAAA,CACN,IAAA,CAAMC,CACR,CAAC,EACH,CAAA,KACEJ,CAAAA,CAAOH,CAAAA,CAAKI,CAAC,EAEjB,CAAC,CAAA,CAEHF,CAAAA,CAAa,IAAA,CAAOC,EACbD,CACT,KCjCaU,CAAAA,CAAuB,CAClC,SAAA,CAAUZ,CAAAA,CAAM,CACd,OAAOA,CAAAA,CAAO,IAAA,CAAK,UAAUA,CAAI,CAAA,CAAI,IACvC,CAAA,CACA,YAAYA,CAAAA,CAAM,CAChB,OAAOA,CAAAA,CAAO,KAAK,KAAA,CAAMA,CAAI,CAAA,CAAI,IACnC,CACF,ECUA,IAAMa,CAAAA,CAAyB,CAAC,WAAA,CAAa,YAAY,CAAA,CACnDC,CAAAA,CAAyB,CAC7B,IAAA,CAAAF,CACF,CAAA,CAEaG,CAAAA,CAAN,KAAiB,CAStB,WAAA,CAAYC,CAAAA,CAA4B,CARxCC,CAAAA,CAAA,IAAA,CAAA,SAAA,CAAA,CACAA,CAAAA,CAAA,IAAA,CAAA,iBAAA,CAAA,CACAA,EAAA,IAAA,CAAA,kBAAA,CAAA,CACAA,CAAAA,CAAA,IAAA,CAAA,OAAA,CAAA,CACAA,CAAAA,CAAA,gBACAA,CAAAA,CAAA,IAAA,CAAA,aAAA,CAAA,CACAA,CAAAA,CAAA,IAAA,CAAQ,eAGN,IAAA,CAAK,WAAA,CAAc,EAAC,CACpB,IAAA,CAAK,OAAA,CAAUD,CAAAA,CAAQ,OAAA,CAAUA,EAAQ,OAAA,CAAU,EAAC,CAEpD,IAAA,CAAK,gBAAkB,CAAE,GAAIA,CAAAA,CAAQ,eAAA,CAAkBA,EAAQ,eAAA,CAAkB,EAAC,CAAI,GAAGF,CAAuB,CAAA,CAChH,IAAA,CAAK,gBAAA,CAAmBE,EAAQ,gBAAA,CAAmBA,CAAAA,CAAQ,gBAAA,CAAmB,GAC9E,IAAA,CAAK,WAAA,CAAcA,CAAAA,CAAQ,WAAA,CAAcA,EAAQ,WAAA,CAAcjB,CAAAA,CAC/D,IAAA,CAAK,KAAA,CAAQmB,oBAAAA,EAAY,CACzB,IAAA,CAAK,KAAA,CAAM,gBAAgB,IAAA,CAAK,gBAAA,EAAkB,CAAA,CAClD,KAAK,OAAA,CAAUC,sBAAAA,CAAc,IAAA,CAAK,KAAK,EACzC,CAEQ,gBAAA,EAAmB,CAGzB,IAAMC,CAAAA,CAA0C,EAAC,CACjD,OAAA,MAAA,CAAO,KAAK,IAAA,CAAK,OAAO,CAAA,CAAE,OAAA,CAAShB,GAAM,CAgBvC,GAfAgB,CAAAA,CAAmBhB,CAAC,EAAwB,EAAC,CAC7C,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQA,CAAC,CAAC,EAAE,OAAA,CAASiB,CAAAA,EAAO,CAG3C,GAFAD,EAAmBhB,CAAC,CAAA,CAAEiB,CAAE,CAAA,CAAIC,cAAK,IAAA,CAAK,OAAA,CAAQlB,CAAC,CAAA,CAAEiB,CAAE,CAAA,CAAGR,CAAsB,CAAA,CACpC,KAAK,OAAA,CAAQT,CAAC,CAAA,CAAEiB,CAAE,EAAE,UAAA,EAC1C,IAAA,CAAK,OAAA,CAAQjB,CAAC,EAAEiB,CAAE,CAAA,CAAE,IAAA,GAAY,QAAA,CAAU,CAC1D,GAAI,IAAA,CAAK,WAAA,CAAYjB,CAAC,CAAA,CACpB,MAAM,KAAA,CAAM,CAAA,6CAAA,EAAgDA,CAAC,CAAA,CAAE,CAAA,CAEjE,IAAA,CAAK,WAAA,CAAYA,CAAC,CAAA,CAAIiB,EACxB,CACF,CAAC,CAAA,CAEG,CAAC,IAAA,CAAK,aAAA,CAAcjB,CAAC,CAAA,EAAK,IAAA,CAAK,OAAA,CAAQA,CAAC,EAAE,EAAA,EAAS,IAAA,CAAK,OAAA,CAAQA,CAAC,EAAE,EAAA,CAAM,IAAA,GAAY,QAAA,GACvF,IAAA,CAAK,WAAA,CAAYA,CAAC,CAAA,CAAI,IAAA,CAAA,CAEpB,CAAC,IAAA,CAAK,aAAA,CAAcA,CAAC,CAAA,CACvB,MAAM,KAAA,CACJ,CAAA,kCAAA,EAAqCA,CAAC,CAAA,wHAAA,CACxC,CAEJ,CAAC,CAAA,CACMgB,CACT,CAEA,aAAA,CAAcG,CAAAA,CAAc,CAC1B,IAAMC,EAAa,IAAA,CAAK,WAAA,CAAYD,CAAI,CAAA,CACxC,OAAOC,CAAAA,EAA0B,MACnC,CAEA,SAAA,CAAUD,EAAc,CACtB,OAAIA,CAAAA,EACE,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,OAAA,CAASA,CAAI,EAC3B,IAAA,CAAK,OAAA,CAAQA,CAAI,CAAA,CAGrB,IACT,CAEA,kBAAA,CAAmBA,CAAAA,CAA0B,CAC3C,GAAIA,CAAAA,CACF,OAAO,IAAA,CAAK,gBAAA,CAAiBA,CAAI,CAAA,CAAI,IAAA,CAAK,gBAAA,CAAiBA,CAAI,CAAA,CAAI,MAGvE,CAEA,iBAAA,CAAkBA,EAA0B,CAC1C,GAAIA,CAAAA,CACF,OAAO,KAAK,eAAA,CAAgBA,CAAI,CAAA,CAAI,IAAA,CAAK,eAAA,CAAgBA,CAAI,CAAA,CAAI,MAGrE,CAEA,SAAA,CAAUE,CAAAA,CAAaC,CAAAA,CAA0B,CAC/C,IAAMC,CAAAA,CAAwB,EAAC,CAC/B,OAAA,MAAA,CAAO,KAAKF,CAAG,CAAA,CAAE,OAAA,CAASrB,CAAAA,EAAM,CAC9B,IAAMwB,CAAAA,CAAQF,CAAAA,CAAOtB,CAAC,CAAA,CACtB,GAAIwB,CAAAA,CAAO,CACT,IAAMC,CAAAA,CAAY,IAAA,CAAK,iBAAA,CAAkBD,CAAAA,CAAM,SAAS,CAAA,CACxDD,CAAAA,CAAcvB,CAAC,CAAA,CAAIyB,CAAAA,CAAYA,CAAAA,CAAU,SAAA,CAAUJ,CAAAA,CAAIrB,CAAC,CAAC,CAAA,CAAIqB,CAAAA,CAAIrB,CAAC,EACpE,CACF,CAAC,CAAA,CACMuB,CACT,CAEA,WAAA,CAAYF,CAAAA,CAAUC,CAAAA,CAA0B,CAC9C,IAAMI,CAAAA,CAA0B,EAAC,CACjC,cAAO,IAAA,CAAKL,CAAG,CAAA,CAAE,OAAA,CAASrB,GAAM,CAC9B,IAAMwB,CAAAA,CAAQF,CAAAA,CAAOtB,CAAC,CAAA,CACtB,GAAIwB,CAAAA,CAAO,CACT,IAAMC,CAAAA,CACJD,CAAAA,CAAM,SAAA,EAAa,KAAK,eAAA,CAAgBA,CAAAA,CAAM,SAAS,CAAA,CAAI,KAAK,eAAA,CAAgBA,CAAAA,CAAM,SAAS,CAAA,CAAI,OACrGE,CAAAA,CAAgB1B,CAAC,CAAA,CAAIyB,CAAAA,CAAYA,CAAAA,CAAU,WAAA,CAAYJ,CAAAA,CAAIrB,CAAC,CAAC,CAAA,CAAIqB,CAAAA,CAAIrB,CAAC,EACxE,CACF,CAAC,CAAA,CACM0B,CACT,CAGA,WAAWP,CAAAA,CAAcvB,CAAAA,CAAiB+B,CAAAA,CAAoBf,CAAAA,CAA+B,EAAC,CAAG,CAC/F,OAAO,KAAK,WAAA,CAAYO,CAAAA,CAAM,CAACvB,CAAI,EAAG+B,CAAAA,CAAQf,CAAO,CACvD,CAGA,YAAYO,CAAAA,CAAcvB,CAAAA,CAAmB+B,CAAAA,CAAoBf,CAAAA,CAA+B,EAAC,CAAG,CAClG,IAAMa,EAAY,IAAA,CAAK,kBAAA,CAAmBN,CAAI,CAAA,CACxCS,EAAUxB,kBAAAA,CAAUyB,wBAAAA,CAAUV,CAAI,CAAC,EACzC,GAAI,CAACM,CAAAA,CACH,OAAO,IAAA,CAAK,WAAA,CAAYE,CAAAA,CAAQ,CAAE,CAACC,CAAO,EAAGhC,CAAK,CAAA,CAAGgB,CAAO,CAAA,CAE9D,IAAMkB,CAAAA,CAAUlC,CAAAA,CAAK,IAAKW,CAAAA,EACjBkB,CAAAA,CAAU,SAAA,CAAUlB,CAAI,CAChC,CAAA,CACD,OAAO,IAAA,CAAK,YAAYoB,CAAAA,CAAQ,CAAE,CAACC,CAAO,EAAGE,CAAQ,CAAA,CAAGlB,CAAO,CACjE,CAGA,WAAA,CAAYe,CAAAA,CAAoB/B,CAAAA,CAAWgB,CAAAA,CAA+B,EAAC,CAAG,CAC5E,GAAI,CACF,IAAMmB,CAAAA,CAAiB,IAAA,CAAK,WAAA,CAAYnC,EAAMgB,CAAO,CAAA,CACrD,OAAAmB,CAAAA,CAAe,KAAK,OAAA,CAASC,CAAAA,EAAY,CACvC,IAAMV,CAAAA,CAAS,IAAA,CAAK,SAAA,CAAUU,CAAAA,CAAQ,IAAI,CAAA,CAE1C,GAAIV,CAAAA,CAAQ,CACV,IAAMW,CAAAA,CAAK,IAAA,CAAK,aAAA,CAAcD,CAAAA,CAAQ,IAAI,CAAA,EAAK,IAAA,CAC3CL,CAAAA,GAAW,QAAA,CACbK,CAAAA,CAAQ,IAAA,CAAK,OAAA,CAASX,CAAAA,EAAQ,CAC5B,IAAA,CAAK,KAAA,CAAM,MAAA,CAAOW,CAAAA,CAAQ,KAAMX,CAAAA,CAAIY,CAAE,CAAC,EACzC,CAAC,CAAA,CACQN,CAAAA,GAAW,OAAA,CACpBK,CAAAA,CAAQ,IAAA,CAAK,OAAA,CAASX,CAAAA,EAAQ,CAExB,KAAK,KAAA,CAAM,MAAA,CAAOW,CAAAA,CAAQ,IAAA,CAAMX,EAAIY,CAAE,CAAC,CAAA,CACzC,IAAA,CAAK,MAAM,aAAA,CAAcD,CAAAA,CAAQ,IAAA,CAAMX,CAAAA,CAAIY,CAAE,CAAA,CAAG,IAAA,CAAK,SAAA,CAAUZ,EAAKC,CAAM,CAAC,CAAA,CAE3E,IAAA,CAAK,MAAM,MAAA,CAAOU,CAAAA,CAAQ,IAAA,CAAMX,CAAAA,CAAIY,CAAE,CAAA,CAAG,IAAA,CAAK,SAAA,CAAUZ,CAAAA,CAAKC,CAAM,CAAC,EAExE,CAAC,EAEDU,CAAAA,CAAQ,IAAA,CAAK,OAAA,CAASX,CAAAA,EAAQ,CAC5B,IAAA,CAAK,KAAA,CAAM,MAAA,CAAOW,CAAAA,CAAQ,KAAMX,CAAAA,CAAIY,CAAE,CAAA,CAAG,IAAA,CAAK,SAAA,CAAUZ,CAAAA,CAAKC,CAAM,CAAC,EACtE,CAAC,EAEL,CACF,CAAC,EACMS,CACT,CAAA,MAASG,CAAAA,CAAG,CACV,QAAQ,IAAA,CAAKA,CAAC,EAChB,CACF,CAEA,UAAA,CAAcf,CAAAA,CAAcgB,CAAAA,CAA2B,CACrD,IAAMb,CAAAA,CAAS,IAAA,CAAK,SAAA,CAAUH,CAAI,CAAA,CAC5Bc,CAAAA,CAAK,IAAA,CAAK,aAAA,CAAcd,CAAI,CAAA,CAC5BiB,CAAAA,CAAc,IAAA,CAAK,kBAAA,CAAmBjB,CAAI,CAAA,CAC1CkB,CAAAA,CAAuBD,CAAAA,CAAcA,EAAY,WAAA,CAAeE,CAAAA,EAAWA,CAAAA,CACjF,GAAIhB,GAAUW,CAAAA,CAAI,CAChB,IAAMZ,CAAAA,CAAM,KAAK,QAAA,EAAS,CAAE,MAAA,CAAOF,CAAAA,CAAMgB,CAAE,CAAA,CAC3C,GAAId,CAAAA,CACF,OAAOgB,CAAAA,CAAqB,IAAA,CAAK,WAAA,CAAYhB,CAAAA,CAAKC,CAAM,CAAC,CAE7D,CAEF,CAEA,QAAWH,CAAAA,CAAmB,CAC5B,IAAMG,CAAAA,CAAS,IAAA,CAAK,SAAA,CAAUH,CAAI,CAAA,CAC5Bc,EAAK,IAAA,CAAK,aAAA,CAAcd,CAAI,CAAA,CAC5BiB,EAAc,IAAA,CAAK,kBAAA,CAAmBjB,CAAI,CAAA,CAC1CkB,EAAuBD,CAAAA,CAAcA,CAAAA,CAAY,WAAA,CAAeE,CAAAA,EAAWA,CAAAA,CACjF,GAAIhB,CAAAA,EAAUW,CAAAA,CAAI,CAChB,IAAMM,CAAAA,CAAQ,IAAA,CAAK,QAAA,GAAW,QAAA,CAASpB,CAAI,CAAA,CAC3C,OAAO,OAAO,OAAA,CAAQoB,CAAK,CAAA,CAAE,GAAA,CAAI,CAAC,CAACC,CAAAA,CAAGnB,CAAG,IACzBgB,CAAAA,CAAqB,IAAA,CAAK,WAAA,CAAYhB,CAAAA,CAAKC,CAAM,CAAC,CAEjE,CACH,CACA,OAAO,EACT,CAEA,YAAA,CAAgBH,CAAAA,CAAcgB,CAAAA,CAA2B,CACvD,IAAMM,EAAS,IAAA,CAAK,UAAA,CAActB,CAAAA,CAAMgB,CAAE,EAC1C,OAAIM,CAAAA,EACF,IAAA,CAAK,QAAA,GAAW,MAAA,CAAOtB,CAAAA,CAAMgB,CAAE,CAAA,CAE1BM,CACT,CAEA,SAAA,CAAUtB,CAAAA,CAAc,CAClB,IAAA,CAAK,SAAA,CAAUA,CAAI,CAAA,EACrB,KAAK,QAAA,EAAS,CAAE,QAAA,CAASA,CAAI,EAEjC,CAEA,KAAA,EAAQ,CACN,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA,CAAE,QAAQ,CAAC,CAACuB,CAAAA,CAAYF,CAAC,IAAM,CACxD,IAAA,CAAK,SAAA,CAAUE,CAAU,EAC3B,CAAC,EACH,CAGA,QAAA,EAAW,CACT,OAAO,IAAA,CAAK,KACd,CAGA,UAAA,EAAa,CACX,OAAO,IAAA,CAAK,OACd,CACF,EC1OA,IAAMC,EAAeC,mBAAAA,CAAiC,IAAI,CAAA,CAEnD,SAASC,GAAmC,CAGjD,OAFcC,gBAAAA,CAAWH,CAAY,CAGvC,CAIO,SAASI,CAAAA,CAAmBC,EAA0B,CAC3D,IAAMC,CAAAA,CAAQC,aAAAA,CAAQ,IAAMF,CAAAA,CAAM,KAAA,CAAO,CAACA,CAAK,CAAC,CAAA,CAGhD,OACEG,cAAAA,CAACR,CAAAA,CAAa,QAAA,CAAb,CAAsB,KAAA,CAAOM,CAAAA,CAC5B,SAAAE,cAAAA,CAACC,gBAAAA,CAAA,CAAiB,KAAA,CAAOH,EAAM,QAAA,EAAS,CAAG,OAAA,CAASA,CAAAA,CAAM,YAAW,CAClE,QAAA,CAAAD,CAAAA,CAAM,QAAA,CACT,CAAA,CACF,CAEJ,CCfA,SAASK,CAAAA,CAAezD,CAAAA,CAAyBqC,CAAAA,CAAY,CAC3D,OAAOrC,EAAOA,CAAAA,CAAK,GAAA,CAAK0D,CAAAA,EAAQA,CAAAA,CAAIrB,CAAE,CAAC,CAAA,CAAI,EAC7C,CAEA,SAASsB,CAAAA,CACPpC,CAAAA,CACAG,CAAAA,CACAW,CAAAA,CACAuB,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACA,CAMAD,CAAAA,CAAQ,kBAAA,CAAmBD,CAAAA,CAAWrC,CAAAA,CAAM,CAAC,CAAE,MAAA,CAAAwC,CAAAA,CAAQ,KAAA,CAAAC,CAAM,CAAA,GAAM,CACjE,MAAA,CAAO,IAAA,CAAKtC,CAAM,CAAA,CAAE,OAAA,CAAStB,CAAAA,EAAM,CACjC2D,CAAAA,CAAO3D,CAAC,EACV,CAAC,EACD4D,CAAAA,CAAOC,CAAAA,EAAYH,CAAAA,CAAO,QAAA,CAAiBG,EAAQ5B,CAAE,CAAC,CAAC,EACzD,CAAC,EACH,CAIO,SAAS6B,EAAe3C,CAAAA,CAAcvB,CAAAA,CAAgB,CAC3D,IAAMqD,EAAQJ,CAAAA,EAAc,CACtBvB,CAAAA,CAAS2B,CAAAA,EAAO,UAAU9B,CAAI,CAAA,CAC9Bc,CAAAA,CAAKgB,CAAAA,EAAO,aAAA,CAAc9B,CAAI,CAAA,CACpC,GAAI,CAACG,CAAAA,EAAU,CAACW,CAAAA,CACd,MAAM,IAAI,KAAA,CAAM,CAAA,sCAAA,EAAyCd,CAAI,CAAA,CAAE,EAEjE,GAAM,CAAC4C,CAAAA,CAAcvB,CAAC,CAAA,CAAIwB,cAAAA,CAAS,MAAA,CAAO,UAAA,EAAY,CAAA,CAChDP,CAAAA,CAAUQ,kBAAAA,EAAW,CACrBC,EAAOC,sBAAAA,CAAeJ,CAAAA,CAAcN,CAAO,CAAA,CAC3C,CAACW,CAAAA,CAAYC,CAAa,CAAA,CAAIL,cAAAA,CAAS,EAAE,CAAA,CAGzCM,CAAAA,CAAWC,YAAAA,CAAO,EAAE,CAAA,CAK1BC,eAAAA,CAAU,IAAM,CACd,GAAIf,CAAAA,CAAS,CACX,IAAMC,CAAAA,CAASL,EAAezD,CAAAA,CAAMqC,CAAE,CAAA,CAChCwC,CAAAA,CAAoB,IAAA,CAAK,SAAA,CAAUf,CAAM,CAAA,CAC3CU,IAAeK,CAAAA,EAAqBH,CAAAA,CAAS,OAAA,GAAYG,CAAAA,GACvD,OAAO,OAAA,CAAQP,CAAI,CAAA,CAAE,MAAA,CAAS,IAChCI,CAAAA,CAAS,OAAA,CAAUG,CAAAA,CAAAA,CAErBJ,CAAAA,CAAcI,CAAiB,CAAA,CAC/BlB,CAAAA,CAAqBpC,CAAAA,CAAMG,EAAQW,CAAAA,CAAI8B,CAAAA,CAAcN,CAAAA,CAASC,CAAM,GAExE,CACF,CAAA,CAAG,CAAC9D,CAAAA,CAAMqC,EAAIX,CAAAA,CAAQH,CAAAA,CAAMsC,CAAAA,CAASM,CAAAA,CAAcK,CAAAA,CAAYF,CAAAA,CAAMI,CAAQ,CAAC,EAE9E,IAAMlC,CAAAA,CAAca,CAAAA,EAAO,kBAAA,CAAmB9B,CAAI,CAAA,CAC5CkB,CAAAA,CAAuBD,CAAAA,CAAcA,CAAAA,CAAY,YAAeE,CAAAA,EAAWA,CAAAA,CAC3EoC,CAAAA,CAAkB,EAAC,CACzB,OAAA9E,CAAAA,CAAK,OAAA,CAASW,GAAc,CAC1B,IAAMoE,CAAAA,CAAapE,CAAAA,CAAK0B,CAAE,CAAA,CACpB2C,CAAAA,CAAcV,CAAAA,CAAKS,CAAU,EACnC,GAAIC,CAAAA,CAAa,CACf,IAAMC,CAAAA,CAAa5B,CAAAA,EAAO,WAAA,CAAY2B,CAAAA,CAAatD,CAAM,CAAA,CACzDoD,CAAAA,CAAW,IAAA,CAAKrC,CAAAA,CAAqBwC,CAAK,CAAM,EAClD,CACF,CAAC,EACMH,CACT","file":"index.js","sourcesContent":["// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport { singularize } from 'ember-inflector';\nimport { camelCase, isArray } from 'lodash-es';\nimport type { InterpreterReturnValue } from './types';\n\nexport function RESTInterpreter(data: Record<string, any>, _options: Record<string, any>) {\n const typedPayload: InterpreterReturnValue = {\n data: [],\n meta: undefined\n };\n let meta: Record<string, any> | undefined = undefined;\n\n if (data !== undefined) {\n Object.keys(data).forEach((k) => {\n const content = data[k];\n if (k !== 'meta') {\n const typedData: Record<string, any>[] = [];\n const typeName = camelCase(singularize(k));\n if (isArray(content)) {\n content.forEach((item: any) => {\n typedData.push(item);\n });\n } else {\n typedData.push(content);\n }\n typedPayload.data.push({\n data: typedData,\n type: typeName\n });\n } else {\n meta = data[k];\n }\n });\n }\n typedPayload.meta = meta;\n return typedPayload;\n}\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport type { FieldTransform } from './types';\n\nexport const json: FieldTransform = {\n serialize(data) {\n return data ? JSON.stringify(data) : null;\n },\n deserialize(data) {\n return data ? JSON.parse(data) : null;\n }\n};\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport { pluralize } from 'ember-inflector';\nimport { camelCase, omit } from 'lodash-es';\nimport { createQueries, createStore, type CellSchema, type Queries, type Row, type Store } from 'tinybase';\nimport { RESTInterpreter } from './interpreter';\nimport { json } from './transforms';\nimport type {\n ConventionalSchema,\n ConventionalSchemas,\n FieldTransforms,\n MethodType,\n MicrostoreInterpreter,\n MicroStoreOptions,\n MicroStoreSchema,\n MicroStoreSchemas,\n RawRecord,\n RawRow,\n RecordTransforms\n} from './types';\n\nconst microStoreReservedKeys = ['transform', 'primaryKey'];\nconst defaultFieldTransforms = {\n json\n};\n\nexport class MicroStore {\n schemas: MicroStoreSchemas;\n fieldTransforms: FieldTransforms;\n recordTransforms: RecordTransforms;\n store: Store;\n queries: Queries;\n interpreter: MicrostoreInterpreter;\n private primaryKeys: Record<string, string>;\n\n constructor(options: MicroStoreOptions) {\n this.primaryKeys = {};\n this.schemas = options.schemas ? options.schemas : {};\n // Thats right, we even support our own field and record level transforms!\n this.fieldTransforms = { ...(options.fieldTransforms ? options.fieldTransforms : {}), ...defaultFieldTransforms };\n this.recordTransforms = options.recordTransforms ? options.recordTransforms : {};\n this.interpreter = options.interpreter ? options.interpreter : RESTInterpreter;\n this.store = createStore();\n this.store.setTablesSchema(this.transformSchemas());\n this.queries = createQueries(this.store);\n }\n\n private transformSchemas() {\n // we have to remove custom properties or tinybase\n // will ignore our fields.\n const transformedSchemas: ConventionalSchemas = {};\n Object.keys(this.schemas).forEach((k) => {\n transformedSchemas[k] = <ConventionalSchema>{};\n Object.keys(this.schemas[k]).forEach((k2) => {\n transformedSchemas[k][k2] = omit(this.schemas[k][k2], microStoreReservedKeys) as CellSchema;\n const possiblePK: boolean | undefined = this.schemas[k][k2]['primaryKey'];\n if (possiblePK && this.schemas[k][k2]['type'] === 'string') {\n if (this.primaryKeys[k]) {\n throw Error(`More than one primary key defined for schema ${k}`);\n }\n this.primaryKeys[k] = k2;\n }\n });\n // Default PK to id\n if (!this.getPrimaryKey(k) && this.schemas[k]['id'] && this.schemas[k]['id']['type'] === 'string') {\n this.primaryKeys[k] = 'id';\n }\n if (!this.getPrimaryKey(k)) {\n throw Error(\n `No primary key defined for schema ${k}. This schema must have an 'id' field of \"type\": \"string\", or another field of \"type\": \"string\" with \"primaryKey\": true.`\n );\n }\n });\n return transformedSchemas;\n }\n\n getPrimaryKey(type: string) {\n const possiblePK = this.primaryKeys[type];\n return possiblePK ? possiblePK : undefined;\n }\n\n getSchema(type: string) {\n if (type) {\n if (Object.hasOwn(this.schemas, type)) {\n return this.schemas[type];\n }\n }\n return null;\n }\n\n getRecordTransform(type: string | undefined) {\n if (type) {\n return this.recordTransforms[type] ? this.recordTransforms[type] : undefined;\n }\n return undefined;\n }\n\n getFieldTransform(type: string | undefined) {\n if (type) {\n return this.fieldTransforms[type] ? this.fieldTransforms[type] : undefined;\n }\n return undefined;\n }\n\n serialize(row: RawRow, schema: MicroStoreSchema) {\n const serializedRow: RawRow = {};\n Object.keys(row).forEach((k) => {\n const field = schema[k];\n if (field) {\n const transform = this.getFieldTransform(field.transform);\n serializedRow[k] = transform ? transform.serialize(row[k]) : row[k];\n }\n });\n return serializedRow;\n }\n\n deserialize(row: Row, schema: MicroStoreSchema) {\n const deserializedRow: RawRow = {};\n Object.keys(row).forEach((k) => {\n const field = schema[k];\n if (field) {\n const transform =\n field.transform && this.fieldTransforms[field.transform] ? this.fieldTransforms[field.transform] : undefined;\n deserializedRow[k] = transform ? transform.deserialize(row[k]) : row[k];\n }\n });\n return deserializedRow;\n }\n\n // pushRecord is a sugar method to take a single record and push it into the store using whatever verb was used\n pushRecord(type: string, data: RawRecord, method: MethodType, options: Record<string, any> = {}) {\n return this.pushRecords(type, [data], method, options);\n }\n\n // pushRecords handles an incoming data (\"row\"s) that are in application level format (with dates, blobs, or other complex types that can't be POJO'd)\n pushRecords(type: string, data: RawRecord[], method: MethodType, options: Record<string, any> = {}) {\n const transform = this.getRecordTransform(type);\n const typeKey = camelCase(pluralize(type));\n if (!transform) {\n return this.pushPayload(method, { [typeKey]: data }, options);\n }\n const rawData = data.map((item) => {\n return transform.serialize(item);\n });\n return this.pushPayload(method, { [typeKey]: rawData }, options);\n }\n\n // pushPayload handles incoming data (many \"row\"s) that are in POJO format\n pushPayload(method: MethodType, data: any, options: Record<string, any> = {}) {\n try {\n const batchedPayload = this.interpreter(data, options);\n batchedPayload.data.forEach((payload) => {\n const schema = this.getSchema(payload.type);\n // Only process typed batches that match a defined data schema\n if (schema) {\n const pk = this.getPrimaryKey(payload.type) || 'id';\n if (method === 'DELETE') {\n payload.data.forEach((row) => {\n this.store.delRow(payload.type, row[pk]);\n });\n } else if (method === 'PATCH') {\n payload.data.forEach((row) => {\n // Only try a partial row update if a row already exists\n if (this.store.getRow(payload.type, row[pk])) {\n this.store.setPartialRow(payload.type, row[pk], this.serialize(row, schema));\n } else {\n this.store.setRow(payload.type, row[pk], this.serialize(row, schema));\n }\n });\n } else {\n payload.data.forEach((row) => {\n this.store.setRow(payload.type, row[pk], this.serialize(row, schema));\n });\n }\n }\n });\n return batchedPayload;\n } catch (e) {\n console.warn(e);\n }\n }\n\n peekRecord<T>(type: string, id: string): T | undefined {\n const schema = this.getSchema(type);\n const pk = this.getPrimaryKey(type);\n const transformer = this.getRecordTransform(type);\n const effectiveTransformer = transformer ? transformer.deserialize : (x: any) => x;\n if (schema && pk) {\n const row = this.getStore().getRow(type, id);\n if (row) {\n return effectiveTransformer(this.deserialize(row, schema));\n }\n }\n return undefined;\n }\n\n peekAll<T>(type: string): T[] {\n const schema = this.getSchema(type);\n const pk = this.getPrimaryKey(type);\n const transformer = this.getRecordTransform(type);\n const effectiveTransformer = transformer ? transformer.deserialize : (x: any) => x;\n if (schema && pk) {\n const table = this.getStore().getTable(type);\n return Object.entries(table).map(([_, row]) => {\n const thing = effectiveTransformer(this.deserialize(row, schema));\n return thing as T;\n });\n }\n return [];\n }\n\n unloadRecord<T>(type: string, id: string): T | undefined {\n const record = this.peekRecord<T>(type, id);\n if (record) {\n this.getStore().delRow(type, id);\n }\n return record;\n }\n\n unloadAll(type: string) {\n if (this.getSchema(type)) {\n this.getStore().delTable(type);\n }\n }\n\n reset() {\n Object.entries(this.schemas).forEach(([schemaName, _]) => {\n this.unloadAll(schemaName);\n });\n }\n\n // Need to get the raw tinybase store? use this method\n getStore() {\n return this.store;\n }\n\n // Need to get the raw tinybase query views? use this method\n getQueries() {\n return this.queries;\n }\n}\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { Provider as TinyBaseProvider } from 'tinybase/ui-react';\nimport type { MicroStore } from './microstore';\n\nconst StoreContext = createContext<MicroStore | null>(null);\n\nexport function useMicroStore(): MicroStore | null {\n const store = useContext(StoreContext);\n\n return store;\n}\n\ntype WithExistingStore = { store: MicroStore; children: ReactNode };\n\nexport function MicroStoreProvider(props: WithExistingStore) {\n const store = useMemo(() => props.store, [props]);\n\n // TODO: Add store.relationships, which can be passed to tinybase\n return (\n <StoreContext.Provider value={store}>\n <TinyBaseProvider store={store.getStore()} queries={store.getQueries()}>\n {props.children}\n </TinyBaseProvider>\n </StoreContext.Provider>\n );\n}\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\n// This whole file will get \"genericized\" so that it can wrap any useQuery that we currently use\n// Right now it is hard coded to do object stuff...\n// The same logic can be made more abstract and receive a couple more\n// arguments to then handle ANYTHING. :)\nimport { useEffect, useRef, useState } from 'react';\nimport { type Queries } from 'tinybase';\nimport { useQueries, useResultTable } from 'tinybase/ui-react';\nimport { useMicroStore } from './provider';\nimport type { MicroStoreSchema } from './types';\n\nfunction generateFilter(data: any[] | undefined, pk: string) {\n return data ? data.map((obj) => obj[pk]) : [];\n}\n\nfunction resetQueryDefinition(\n type: string,\n schema: MicroStoreSchema,\n pk: string,\n queryName: string,\n queries: Queries,\n filter: string[]\n) {\n // So what exactly does this do? It returns all the same tinybase rows that are\n // returned by the same React query being referenced by this reactive wrapper!!\n // That way, when the rows from useResultTable change due to some localized store\n // update through the MicroStore engine, we see those changes reflected without\n // additional server traffic!\n queries.setQueryDefinition(queryName, type, ({ select, where }) => {\n Object.keys(schema).forEach((k) => {\n select(k);\n });\n where((getCell) => filter.includes(<string>getCell(pk)));\n });\n}\n\n// Wrap the objects in tinybase rows to make them react at a record level\n// to individual record changes\nexport function useReactive<T>(type: string, data: T[]): T[] {\n const store = useMicroStore();\n const schema = store?.getSchema(type);\n const pk = store?.getPrimaryKey(type);\n if (!schema || !pk) {\n throw new Error(`No MicroStore schema defined for type ${type}`);\n }\n const [uniqueHookID, _] = useState(crypto.randomUUID());\n const queries = useQueries();\n const rows = useResultTable(uniqueHookID, queries);\n const [lastFilter, setLastFilter] = useState('');\n // Air Brake prevents render loops if a user does something very silly like chasing\n // records in a circle\n const airBrake = useRef('');\n\n // Why is this effect important? Because you ONLY want to update the thing\n // that is bubbling out new rows (the query) if the supporting REST request\n // with its own internal IDs has changed. We\n useEffect(() => {\n if (queries) {\n const filter = generateFilter(data, pk);\n const stringifiedFilter = JSON.stringify(filter);\n if (lastFilter !== stringifiedFilter && airBrake.current !== stringifiedFilter) {\n if (Object.entries(rows).length > 0) {\n airBrake.current = stringifiedFilter;\n }\n setLastFilter(stringifiedFilter);\n resetQueryDefinition(type, schema, pk, uniqueHookID, queries, filter);\n }\n }\n }, [data, pk, schema, type, queries, uniqueHookID, lastFilter, rows, airBrake]);\n\n const transformer = store?.getRecordTransform(type);\n const effectiveTransformer = transformer ? transformer.deserialize : (x: any) => x;\n const returnData: T[] = [];\n data.forEach((item: any) => {\n const identifier = item[pk];\n const possibleRow = rows[identifier];\n if (possibleRow) {\n const thing: any = store?.deserialize(possibleRow, schema);\n returnData.push(effectiveTransformer(thing) as T);\n }\n });\n return returnData;\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {singularize,pluralize}from'ember-inflector';import {camelCase,isArray,omit}from'lodash-es';import {createStore,createQueries}from'tinybase';import {createContext,useContext,useMemo,useState,useRef,useEffect}from'react';import {Provider,useQueries,useResultTable}from'tinybase/ui-react';import {jsx}from'react/jsx-runtime';var K=Object.defineProperty;var z=(o,e,r)=>e in o?K(o,e,{enumerable:true,configurable:true,writable:true,value:r}):o[e]=r;var f=(o,e,r)=>z(o,typeof e!="symbol"?e+"":e,r);function l(o,e){let r={data:[],meta:void 0},s;return o!==void 0&&Object.keys(o).forEach(t=>{let i=o[t];if(t!=="meta"){let n=[],c=camelCase(singularize(t));isArray(i)?i.forEach(a=>{n.push(a);}):n.push(i),r.data.push({data:n,type:c});}else s=o[t];}),r.meta=s,r}var p={serialize(o){return o?JSON.stringify(o):null},deserialize(o){return o?JSON.parse(o):null}};var N=["transform","primaryKey"],q={json:p},y=class{constructor(e){f(this,"schemas");f(this,"fieldTransforms");f(this,"recordTransforms");f(this,"store");f(this,"queries");f(this,"interpreter");f(this,"primaryKeys");this.primaryKeys={},this.schemas=e.schemas?e.schemas:{},this.fieldTransforms={...e.fieldTransforms?e.fieldTransforms:{},...q},this.recordTransforms=e.recordTransforms?e.recordTransforms:{},this.interpreter=e.interpreter?e.interpreter:l,this.store=createStore(),this.store.setTablesSchema(this.transformSchemas()),this.queries=createQueries(this.store);}transformSchemas(){let e={};return Object.keys(this.schemas).forEach(r=>{if(e[r]={},Object.keys(this.schemas[r]).forEach(s=>{if(e[r][s]=omit(this.schemas[r][s],N),this.schemas[r][s].primaryKey&&this.schemas[r][s].type==="string"){if(this.primaryKeys[r])throw Error(`More than one primary key defined for schema ${r}`);this.primaryKeys[r]=s;}}),!this.getPrimaryKey(r)&&this.schemas[r].id&&this.schemas[r].id.type==="string"&&(this.primaryKeys[r]="id"),!this.getPrimaryKey(r))throw Error(`No primary key defined for schema ${r}. This schema must have an 'id' field of "type": "string", or another field of "type": "string" with "primaryKey": true.`)}),e}getPrimaryKey(e){let r=this.primaryKeys[e];return r||void 0}getSchema(e){return e&&Object.hasOwn(this.schemas,e)?this.schemas[e]:null}getRecordTransform(e){if(e)return this.recordTransforms[e]?this.recordTransforms[e]:void 0}getFieldTransform(e){if(e)return this.fieldTransforms[e]?this.fieldTransforms[e]:void 0}serialize(e,r){let s={};return Object.keys(e).forEach(t=>{let i=r[t];if(i){let n=this.getFieldTransform(i.transform);s[t]=n?n.serialize(e[t]):e[t];}}),s}deserialize(e,r){let s={};return Object.keys(e).forEach(t=>{let i=r[t];if(i){let n=i.transform&&this.fieldTransforms[i.transform]?this.fieldTransforms[i.transform]:void 0;s[t]=n?n.deserialize(e[t]):e[t];}}),s}pushRecord(e,r,s,t={}){return this.pushRecords(e,[r],s,t)}pushRecords(e,r,s,t={}){let i=this.getRecordTransform(e),n=camelCase(pluralize(e));if(!i)return this.pushPayload(s,{[n]:r},t);let c=r.map(a=>i.serialize(a));return this.pushPayload(s,{[n]:c},t)}pushPayload(e,r,s={}){try{let t=this.interpreter(r,s);return t.data.forEach(i=>{let n=this.getSchema(i.type);if(n){let c=this.getPrimaryKey(i.type)||"id";e==="DELETE"?i.data.forEach(a=>{this.store.delRow(i.type,a[c]);}):e==="PATCH"?i.data.forEach(a=>{this.store.getRow(i.type,a[c])?this.store.setPartialRow(i.type,a[c],this.serialize(a,n)):this.store.setRow(i.type,a[c],this.serialize(a,n));}):i.data.forEach(a=>{this.store.setRow(i.type,a[c],this.serialize(a,n));});}}),t}catch(t){console.warn(t);}}peekRecord(e,r){let s=this.getSchema(e),t=this.getPrimaryKey(e),i=this.getRecordTransform(e),n=i?i.deserialize:c=>c;if(s&&t){let c=this.getStore().getRow(e,r);if(c)return n(this.deserialize(c,s))}}peekAll(e){let r=this.getSchema(e),s=this.getPrimaryKey(e),t=this.getRecordTransform(e),i=t?t.deserialize:n=>n;if(r&&s){let n=this.getStore().getTable(e);return Object.entries(n).map(([c,a])=>i(this.deserialize(a,r)))}return []}unloadRecord(e,r){let s=this.peekRecord(e,r);return s&&this.getStore().delRow(e,r),s}unloadAll(e){this.getSchema(e)&&this.getStore().delTable(e);}reset(){Object.entries(this.schemas).forEach(([e,r])=>{this.unloadAll(e);});}getStore(){return this.store}getQueries(){return this.queries}};var w=createContext(null);function g(){return useContext(w)}function J(o){let e=useMemo(()=>o.store,[o]);return jsx(w.Provider,{value:e,children:jsx(Provider,{store:e.getStore(),queries:e.getQueries(),children:o.children})})}function U(o,e){return o?o.map(r=>r[e]):[]}function G(o,e,r,s,t,i){t.setQueryDefinition(s,o,({select:n,where:c})=>{Object.keys(e).forEach(a=>{n(a);}),c(a=>i.includes(a(r)));});}function X(o,e){let r=g(),s=r?.getSchema(o),t=r?.getPrimaryKey(o);if(!s||!t)throw new Error(`No MicroStore schema defined for type ${o}`);let[i,n]=useState(crypto.randomUUID()),c=useQueries(),a=useResultTable(i,c),[d,E]=useState(""),u=useRef("");useEffect(()=>{if(c){let m=U(e,t),h=JSON.stringify(m);d!==h&&u.current!==h&&(Object.entries(a).length>0&&(u.current=h),E(h),G(o,s,t,i,c,m));}},[e,t,s,o,c,i,d,a,u]);let R=r?.getRecordTransform(o),P=R?R.deserialize:m=>m,S=[];return e.forEach(m=>{let h=m[t],T=a[h];if(T){let v=r?.deserialize(T,s);S.push(P(v));}}),S}export{y as MicroStore,J as MicroStoreProvider,l as RESTInterpreter,p as json,g as useMicroStore,X as useReactive};//# sourceMappingURL=index.mjs.map
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/interpreter.ts","../src/transforms.ts","../src/microstore.ts","../src/provider.tsx","../src/reactive.ts"],"names":["RESTInterpreter","data","_options","typedPayload","meta","k","content","typedData","typeName","camelCase","singularize","isArray","item","json","microStoreReservedKeys","defaultFieldTransforms","MicroStore","options","__publicField","createStore","createQueries","transformedSchemas","k2","omit","type","possiblePK","row","schema","serializedRow","field","transform","deserializedRow","method","typeKey","pluralize","rawData","batchedPayload","payload","pk","e","id","transformer","effectiveTransformer","x","table","_","record","schemaName","StoreContext","createContext","useMicroStore","useContext","MicroStoreProvider","props","store","useMemo","jsx","TinyBaseProvider","generateFilter","obj","resetQueryDefinition","queryName","queries","filter","select","where","getCell","useReactive","uniqueHookID","useState","useQueries","rows","useResultTable","lastFilter","setLastFilter","airBrake","useRef","useEffect","stringifiedFilter","returnData","identifier","possibleRow","thing"],"mappings":"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","file":"index.mjs","sourcesContent":["// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport { singularize } from 'ember-inflector';\nimport { camelCase, isArray } from 'lodash-es';\nimport type { InterpreterReturnValue } from './types';\n\nexport function RESTInterpreter(data: Record<string, any>, _options: Record<string, any>) {\n const typedPayload: InterpreterReturnValue = {\n data: [],\n meta: undefined\n };\n let meta: Record<string, any> | undefined = undefined;\n\n if (data !== undefined) {\n Object.keys(data).forEach((k) => {\n const content = data[k];\n if (k !== 'meta') {\n const typedData: Record<string, any>[] = [];\n const typeName = camelCase(singularize(k));\n if (isArray(content)) {\n content.forEach((item: any) => {\n typedData.push(item);\n });\n } else {\n typedData.push(content);\n }\n typedPayload.data.push({\n data: typedData,\n type: typeName\n });\n } else {\n meta = data[k];\n }\n });\n }\n typedPayload.meta = meta;\n return typedPayload;\n}\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport type { FieldTransform } from './types';\n\nexport const json: FieldTransform = {\n serialize(data) {\n return data ? JSON.stringify(data) : null;\n },\n deserialize(data) {\n return data ? JSON.parse(data) : null;\n }\n};\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport { pluralize } from 'ember-inflector';\nimport { camelCase, omit } from 'lodash-es';\nimport { createQueries, createStore, type CellSchema, type Queries, type Row, type Store } from 'tinybase';\nimport { RESTInterpreter } from './interpreter';\nimport { json } from './transforms';\nimport type {\n ConventionalSchema,\n ConventionalSchemas,\n FieldTransforms,\n MethodType,\n MicrostoreInterpreter,\n MicroStoreOptions,\n MicroStoreSchema,\n MicroStoreSchemas,\n RawRecord,\n RawRow,\n RecordTransforms\n} from './types';\n\nconst microStoreReservedKeys = ['transform', 'primaryKey'];\nconst defaultFieldTransforms = {\n json\n};\n\nexport class MicroStore {\n schemas: MicroStoreSchemas;\n fieldTransforms: FieldTransforms;\n recordTransforms: RecordTransforms;\n store: Store;\n queries: Queries;\n interpreter: MicrostoreInterpreter;\n private primaryKeys: Record<string, string>;\n\n constructor(options: MicroStoreOptions) {\n this.primaryKeys = {};\n this.schemas = options.schemas ? options.schemas : {};\n // Thats right, we even support our own field and record level transforms!\n this.fieldTransforms = { ...(options.fieldTransforms ? options.fieldTransforms : {}), ...defaultFieldTransforms };\n this.recordTransforms = options.recordTransforms ? options.recordTransforms : {};\n this.interpreter = options.interpreter ? options.interpreter : RESTInterpreter;\n this.store = createStore();\n this.store.setTablesSchema(this.transformSchemas());\n this.queries = createQueries(this.store);\n }\n\n private transformSchemas() {\n // we have to remove custom properties or tinybase\n // will ignore our fields.\n const transformedSchemas: ConventionalSchemas = {};\n Object.keys(this.schemas).forEach((k) => {\n transformedSchemas[k] = <ConventionalSchema>{};\n Object.keys(this.schemas[k]).forEach((k2) => {\n transformedSchemas[k][k2] = omit(this.schemas[k][k2], microStoreReservedKeys) as CellSchema;\n const possiblePK: boolean | undefined = this.schemas[k][k2]['primaryKey'];\n if (possiblePK && this.schemas[k][k2]['type'] === 'string') {\n if (this.primaryKeys[k]) {\n throw Error(`More than one primary key defined for schema ${k}`);\n }\n this.primaryKeys[k] = k2;\n }\n });\n // Default PK to id\n if (!this.getPrimaryKey(k) && this.schemas[k]['id'] && this.schemas[k]['id']['type'] === 'string') {\n this.primaryKeys[k] = 'id';\n }\n if (!this.getPrimaryKey(k)) {\n throw Error(\n `No primary key defined for schema ${k}. This schema must have an 'id' field of \"type\": \"string\", or another field of \"type\": \"string\" with \"primaryKey\": true.`\n );\n }\n });\n return transformedSchemas;\n }\n\n getPrimaryKey(type: string) {\n const possiblePK = this.primaryKeys[type];\n return possiblePK ? possiblePK : undefined;\n }\n\n getSchema(type: string) {\n if (type) {\n if (Object.hasOwn(this.schemas, type)) {\n return this.schemas[type];\n }\n }\n return null;\n }\n\n getRecordTransform(type: string | undefined) {\n if (type) {\n return this.recordTransforms[type] ? this.recordTransforms[type] : undefined;\n }\n return undefined;\n }\n\n getFieldTransform(type: string | undefined) {\n if (type) {\n return this.fieldTransforms[type] ? this.fieldTransforms[type] : undefined;\n }\n return undefined;\n }\n\n serialize(row: RawRow, schema: MicroStoreSchema) {\n const serializedRow: RawRow = {};\n Object.keys(row).forEach((k) => {\n const field = schema[k];\n if (field) {\n const transform = this.getFieldTransform(field.transform);\n serializedRow[k] = transform ? transform.serialize(row[k]) : row[k];\n }\n });\n return serializedRow;\n }\n\n deserialize(row: Row, schema: MicroStoreSchema) {\n const deserializedRow: RawRow = {};\n Object.keys(row).forEach((k) => {\n const field = schema[k];\n if (field) {\n const transform =\n field.transform && this.fieldTransforms[field.transform] ? this.fieldTransforms[field.transform] : undefined;\n deserializedRow[k] = transform ? transform.deserialize(row[k]) : row[k];\n }\n });\n return deserializedRow;\n }\n\n // pushRecord is a sugar method to take a single record and push it into the store using whatever verb was used\n pushRecord(type: string, data: RawRecord, method: MethodType, options: Record<string, any> = {}) {\n return this.pushRecords(type, [data], method, options);\n }\n\n // pushRecords handles an incoming data (\"row\"s) that are in application level format (with dates, blobs, or other complex types that can't be POJO'd)\n pushRecords(type: string, data: RawRecord[], method: MethodType, options: Record<string, any> = {}) {\n const transform = this.getRecordTransform(type);\n const typeKey = camelCase(pluralize(type));\n if (!transform) {\n return this.pushPayload(method, { [typeKey]: data }, options);\n }\n const rawData = data.map((item) => {\n return transform.serialize(item);\n });\n return this.pushPayload(method, { [typeKey]: rawData }, options);\n }\n\n // pushPayload handles incoming data (many \"row\"s) that are in POJO format\n pushPayload(method: MethodType, data: any, options: Record<string, any> = {}) {\n try {\n const batchedPayload = this.interpreter(data, options);\n batchedPayload.data.forEach((payload) => {\n const schema = this.getSchema(payload.type);\n // Only process typed batches that match a defined data schema\n if (schema) {\n const pk = this.getPrimaryKey(payload.type) || 'id';\n if (method === 'DELETE') {\n payload.data.forEach((row) => {\n this.store.delRow(payload.type, row[pk]);\n });\n } else if (method === 'PATCH') {\n payload.data.forEach((row) => {\n // Only try a partial row update if a row already exists\n if (this.store.getRow(payload.type, row[pk])) {\n this.store.setPartialRow(payload.type, row[pk], this.serialize(row, schema));\n } else {\n this.store.setRow(payload.type, row[pk], this.serialize(row, schema));\n }\n });\n } else {\n payload.data.forEach((row) => {\n this.store.setRow(payload.type, row[pk], this.serialize(row, schema));\n });\n }\n }\n });\n return batchedPayload;\n } catch (e) {\n console.warn(e);\n }\n }\n\n peekRecord<T>(type: string, id: string): T | undefined {\n const schema = this.getSchema(type);\n const pk = this.getPrimaryKey(type);\n const transformer = this.getRecordTransform(type);\n const effectiveTransformer = transformer ? transformer.deserialize : (x: any) => x;\n if (schema && pk) {\n const row = this.getStore().getRow(type, id);\n if (row) {\n return effectiveTransformer(this.deserialize(row, schema));\n }\n }\n return undefined;\n }\n\n peekAll<T>(type: string): T[] {\n const schema = this.getSchema(type);\n const pk = this.getPrimaryKey(type);\n const transformer = this.getRecordTransform(type);\n const effectiveTransformer = transformer ? transformer.deserialize : (x: any) => x;\n if (schema && pk) {\n const table = this.getStore().getTable(type);\n return Object.entries(table).map(([_, row]) => {\n const thing = effectiveTransformer(this.deserialize(row, schema));\n return thing as T;\n });\n }\n return [];\n }\n\n unloadRecord<T>(type: string, id: string): T | undefined {\n const record = this.peekRecord<T>(type, id);\n if (record) {\n this.getStore().delRow(type, id);\n }\n return record;\n }\n\n unloadAll(type: string) {\n if (this.getSchema(type)) {\n this.getStore().delTable(type);\n }\n }\n\n reset() {\n Object.entries(this.schemas).forEach(([schemaName, _]) => {\n this.unloadAll(schemaName);\n });\n }\n\n // Need to get the raw tinybase store? use this method\n getStore() {\n return this.store;\n }\n\n // Need to get the raw tinybase query views? use this method\n getQueries() {\n return this.queries;\n }\n}\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\nimport { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { Provider as TinyBaseProvider } from 'tinybase/ui-react';\nimport type { MicroStore } from './microstore';\n\nconst StoreContext = createContext<MicroStore | null>(null);\n\nexport function useMicroStore(): MicroStore | null {\n const store = useContext(StoreContext);\n\n return store;\n}\n\ntype WithExistingStore = { store: MicroStore; children: ReactNode };\n\nexport function MicroStoreProvider(props: WithExistingStore) {\n const store = useMemo(() => props.store, [props]);\n\n // TODO: Add store.relationships, which can be passed to tinybase\n return (\n <StoreContext.Provider value={store}>\n <TinyBaseProvider store={store.getStore()} queries={store.getQueries()}>\n {props.children}\n </TinyBaseProvider>\n </StoreContext.Provider>\n );\n}\n","// This software is provided to the United States Government (USG) with SBIR Data Rights as defined at Federal Acquisition Regulation 52.227-14, \"Rights in Data-SBIR Program\" (May 2014) SBIR Rights Notice (Dec 2026) These SBIR data are furnished with SBIR rights under Contract No. H9241522D0001. For a period of 19 years, unless extended in accordance with FAR 27.409(h), after acceptance of all items to be delivered under this contract, the Government will use these data for Government purposes only, and they shall not be disclosed outside the Government (including disclosure for procurement purposes) during such period without permission of the Contractor, except that, subject to the foregoing use and disclosure prohibitions, these data may be disclosed for use by support Contractors. After the protection period, the Government has a paid-up license to use, and to authorize others to use on its behalf, these data for Government purposes, but is relieved of all disclosure prohibitions and assumes no liability for unauthorized use of these data by third parties. This notice shall be affixed to any reproductions of these data, in whole or in part.\n// This whole file will get \"genericized\" so that it can wrap any useQuery that we currently use\n// Right now it is hard coded to do object stuff...\n// The same logic can be made more abstract and receive a couple more\n// arguments to then handle ANYTHING. :)\nimport { useEffect, useRef, useState } from 'react';\nimport { type Queries } from 'tinybase';\nimport { useQueries, useResultTable } from 'tinybase/ui-react';\nimport { useMicroStore } from './provider';\nimport type { MicroStoreSchema } from './types';\n\nfunction generateFilter(data: any[] | undefined, pk: string) {\n return data ? data.map((obj) => obj[pk]) : [];\n}\n\nfunction resetQueryDefinition(\n type: string,\n schema: MicroStoreSchema,\n pk: string,\n queryName: string,\n queries: Queries,\n filter: string[]\n) {\n // So what exactly does this do? It returns all the same tinybase rows that are\n // returned by the same React query being referenced by this reactive wrapper!!\n // That way, when the rows from useResultTable change due to some localized store\n // update through the MicroStore engine, we see those changes reflected without\n // additional server traffic!\n queries.setQueryDefinition(queryName, type, ({ select, where }) => {\n Object.keys(schema).forEach((k) => {\n select(k);\n });\n where((getCell) => filter.includes(<string>getCell(pk)));\n });\n}\n\n// Wrap the objects in tinybase rows to make them react at a record level\n// to individual record changes\nexport function useReactive<T>(type: string, data: T[]): T[] {\n const store = useMicroStore();\n const schema = store?.getSchema(type);\n const pk = store?.getPrimaryKey(type);\n if (!schema || !pk) {\n throw new Error(`No MicroStore schema defined for type ${type}`);\n }\n const [uniqueHookID, _] = useState(crypto.randomUUID());\n const queries = useQueries();\n const rows = useResultTable(uniqueHookID, queries);\n const [lastFilter, setLastFilter] = useState('');\n // Air Brake prevents render loops if a user does something very silly like chasing\n // records in a circle\n const airBrake = useRef('');\n\n // Why is this effect important? Because you ONLY want to update the thing\n // that is bubbling out new rows (the query) if the supporting REST request\n // with its own internal IDs has changed. We\n useEffect(() => {\n if (queries) {\n const filter = generateFilter(data, pk);\n const stringifiedFilter = JSON.stringify(filter);\n if (lastFilter !== stringifiedFilter && airBrake.current !== stringifiedFilter) {\n if (Object.entries(rows).length > 0) {\n airBrake.current = stringifiedFilter;\n }\n setLastFilter(stringifiedFilter);\n resetQueryDefinition(type, schema, pk, uniqueHookID, queries, filter);\n }\n }\n }, [data, pk, schema, type, queries, uniqueHookID, lastFilter, rows, airBrake]);\n\n const transformer = store?.getRecordTransform(type);\n const effectiveTransformer = transformer ? transformer.deserialize : (x: any) => x;\n const returnData: T[] = [];\n data.forEach((item: any) => {\n const identifier = item[pk];\n const possibleRow = rows[identifier];\n if (possibleRow) {\n const thing: any = store?.deserialize(possibleRow, schema);\n returnData.push(effectiveTransformer(thing) as T);\n }\n });\n return returnData;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@black-cape/microstore",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.mjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"build:watch": "tsup --watch",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"type-check": "tsc --noEmit",
|
|
15
|
+
"lint": "tsc --noEmit --pretty",
|
|
16
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
17
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
|
18
|
+
"test:compatibility": "node test-compatibility.js",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"prepare": "npm run build",
|
|
21
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react",
|
|
25
|
+
"typescript",
|
|
26
|
+
"state-management",
|
|
27
|
+
"data-normalization",
|
|
28
|
+
"normalizer",
|
|
29
|
+
"reactive",
|
|
30
|
+
"store",
|
|
31
|
+
"rest",
|
|
32
|
+
"api",
|
|
33
|
+
"crud",
|
|
34
|
+
"tinybase",
|
|
35
|
+
"single-source-of-truth",
|
|
36
|
+
"react-hooks",
|
|
37
|
+
"cache",
|
|
38
|
+
"orm",
|
|
39
|
+
"immutable",
|
|
40
|
+
"real-time",
|
|
41
|
+
"rest-envelope"
|
|
42
|
+
],
|
|
43
|
+
"homepage": "https://github.com/black-cape/microstore",
|
|
44
|
+
"repository": "https://github.com/black-cape/microstore",
|
|
45
|
+
"authors": [
|
|
46
|
+
{
|
|
47
|
+
"name": "Michael Conaway",
|
|
48
|
+
"email": "mdconaway@noreply.github.com"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"organization": {
|
|
52
|
+
"name": "Black Cape",
|
|
53
|
+
"url": "https://github.com/black-cape"
|
|
54
|
+
},
|
|
55
|
+
"license": "ISC",
|
|
56
|
+
"description": "",
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"ember-inflector": "^6.0.0",
|
|
59
|
+
"lodash-es": "^4.17.22",
|
|
60
|
+
"tinybase": ">=5.4.9"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
64
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"react-compiler-runtime": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@types/lodash-es": "^4.17.12",
|
|
73
|
+
"@types/react": "^19.0.0",
|
|
74
|
+
"@types/react-dom": "^19.0.0",
|
|
75
|
+
"prettier": "^3.8.0",
|
|
76
|
+
"prettier-plugin-organize-imports": "^4.3.0",
|
|
77
|
+
"react": "^18.3.1",
|
|
78
|
+
"react-dom": "^18.3.1",
|
|
79
|
+
"tsup": "^8.0.0",
|
|
80
|
+
"typescript": "^5.0.0"
|
|
81
|
+
}
|
|
82
|
+
}
|