@gasboost/replica 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +464 -0
- package/dist/Replica.d.ts +16 -0
- package/dist/Replica.js +36 -0
- package/dist/ReplicaTable.d.ts +9 -0
- package/dist/ReplicaTable.js +22 -0
- package/dist/ReplicaTableDefinition.d.ts +9 -0
- package/dist/ReplicaTableDefinition.js +2 -0
- package/dist/createReplica.d.ts +6 -0
- package/dist/createReplica.js +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gasboost
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# @gasboost/replica
|
|
2
|
+
|
|
3
|
+
Dexie と Zod を利用した、ブラウザ向けの型安全な IndexedDB レプリカです。
|
|
4
|
+
|
|
5
|
+
`@gasboost/replica` は、Zod Schema で定義された Record を IndexedDB 上に保存し、ローカルレプリカとして扱うためのパッケージです。
|
|
6
|
+
|
|
7
|
+
Query 処理は `@gasboost/query` と連携して行うため、Filter、Sort、Pagination、JOIN、Nested JOIN のロジックを Replica 側で重複実装しません。
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Remote Data
|
|
11
|
+
↓
|
|
12
|
+
Full Sync
|
|
13
|
+
↓
|
|
14
|
+
@gasboost/replica
|
|
15
|
+
↓
|
|
16
|
+
IndexedDB / Dexie
|
|
17
|
+
↑
|
|
18
|
+
Loader
|
|
19
|
+
↑
|
|
20
|
+
@gasboost/query
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Dexie を利用した IndexedDB ストレージ
|
|
28
|
+
- Zod Schema からの型推論
|
|
29
|
+
- 型安全な Table 名
|
|
30
|
+
- 型安全な Primary Key
|
|
31
|
+
- `put`
|
|
32
|
+
- `bulkPut`
|
|
33
|
+
- `delete`
|
|
34
|
+
- `toArray`
|
|
35
|
+
- Full Sync
|
|
36
|
+
- Dexie Transaction による同期
|
|
37
|
+
- `@gasboost/query` との統合
|
|
38
|
+
- JOIN
|
|
39
|
+
- Nested JOIN
|
|
40
|
+
- Google Apps Script ランタイム非依存
|
|
41
|
+
- SheetORM ランタイム非依存
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
# Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm add @gasboost/replica @gasboost/query dexie zod
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
npm の場合:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install @gasboost/replica @gasboost/query dexie zod
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
# Table Definition
|
|
60
|
+
|
|
61
|
+
Replica では以下を使って Table を定義します。
|
|
62
|
+
|
|
63
|
+
- Table 名
|
|
64
|
+
- Zod Schema
|
|
65
|
+
- Primary Key
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { z } from "zod";
|
|
69
|
+
|
|
70
|
+
const UserSchema = z.object({
|
|
71
|
+
id: z.string(),
|
|
72
|
+
name: z.string(),
|
|
73
|
+
active: z.boolean(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const ReservationSchema = z.object({
|
|
77
|
+
id: z.string(),
|
|
78
|
+
userId: z.string(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const tables = [
|
|
82
|
+
{
|
|
83
|
+
name: "users",
|
|
84
|
+
schema: UserSchema,
|
|
85
|
+
primaryKey: "id",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "reservations",
|
|
89
|
+
schema: ReservationSchema,
|
|
90
|
+
primaryKey: "id",
|
|
91
|
+
},
|
|
92
|
+
] as const;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`primaryKey` には、対応する Zod Schema に存在する Column のみ指定できます。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
# Replica の作成
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { createReplica } from "@gasboost/replica";
|
|
103
|
+
|
|
104
|
+
const replica = createReplica({
|
|
105
|
+
name: "example-app",
|
|
106
|
+
tables,
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`name` は IndexedDB の Database 名として利用されます。
|
|
111
|
+
|
|
112
|
+
各 Table は、定義された `primaryKey` を使って Dexie に登録されます。
|
|
113
|
+
|
|
114
|
+
例えば、
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
{
|
|
118
|
+
name: "users",
|
|
119
|
+
primaryKey: "id",
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
は概念的に次の Dexie Schema として登録されます。
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
users: "id"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
# Table Access
|
|
132
|
+
|
|
133
|
+
`table()` で Replica 内の Table を参照します。
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const users = replica.table("users");
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Table 名は Table Definition から型推論されます。
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
replica.table("users"); // OK
|
|
143
|
+
replica.table("reservations"); // OK
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
定義されていない Table 名は TypeScript 上で拒否されます。
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
# Put
|
|
151
|
+
|
|
152
|
+
Record を追加または更新します。
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
await replica.table("users").put({
|
|
156
|
+
id: "u1",
|
|
157
|
+
name: "Alice",
|
|
158
|
+
active: true,
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Record 型は、選択した Table の Zod Schema から推論されます。
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
# Bulk Put
|
|
167
|
+
|
|
168
|
+
複数 Record をまとめて追加または更新します。
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
await replica.table("users").bulkPut([
|
|
172
|
+
{
|
|
173
|
+
id: "u1",
|
|
174
|
+
name: "Alice",
|
|
175
|
+
active: true,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "u2",
|
|
179
|
+
name: "Bob",
|
|
180
|
+
active: false,
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# Delete
|
|
188
|
+
|
|
189
|
+
Primary Key を指定して Record を削除します。
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
await replica.table("users").delete("u1");
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Primary Key の型は、Table Definition の `primaryKey` Column から推論されます。
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
# To Array
|
|
200
|
+
|
|
201
|
+
Table 内の Record をすべて取得します。
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
const users = await replica.table("users").toArray();
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
戻り値の型は、対象 Table の Zod Schema から推論されます。
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# Full Sync
|
|
212
|
+
|
|
213
|
+
リモート側から取得した完全な Table データを、ローカル Replica へ同期できます。
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
await replica.sync("users", remoteUsers);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
同期時には、
|
|
220
|
+
|
|
221
|
+
- remote に存在する Record を local に反映
|
|
222
|
+
- remote に存在しない Record を local から削除
|
|
223
|
+
- 同期処理全体を Dexie Transaction 内で実行
|
|
224
|
+
|
|
225
|
+
します。
|
|
226
|
+
|
|
227
|
+
例えば、local に以下の Record があるとします。
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
await replica.table("users").bulkPut([
|
|
231
|
+
{
|
|
232
|
+
id: "u1",
|
|
233
|
+
name: "Old Alice",
|
|
234
|
+
active: true,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "u2",
|
|
238
|
+
name: "Deleted User",
|
|
239
|
+
active: false,
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
remote の完全データが以下だった場合、
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
await replica.sync("users", [
|
|
248
|
+
{
|
|
249
|
+
id: "u1",
|
|
250
|
+
name: "Alice",
|
|
251
|
+
active: true,
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
同期後の local は次の状態になります。
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
[
|
|
260
|
+
{
|
|
261
|
+
id: "u1",
|
|
262
|
+
name: "Alice",
|
|
263
|
+
active: true,
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
remote に存在しない `u2` は local から削除されます。
|
|
269
|
+
|
|
270
|
+
空配列を同期すると Table は空になります。
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
await replica.sync("users", []);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
# Query Integration
|
|
279
|
+
|
|
280
|
+
`@gasboost/replica` は `@gasboost/query` と直接連携できます。
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { Query } from "@gasboost/query";
|
|
284
|
+
|
|
285
|
+
const query = new Query<typeof tables, "users">({
|
|
286
|
+
tableName: "users",
|
|
287
|
+
})
|
|
288
|
+
.and("active", "=", [true])
|
|
289
|
+
.orderBy("name", "asc");
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Replica に対して Query を実行します。
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
const users = await replica.find(query);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Replica 自身は Filter や Sort のロジックを持ちません。
|
|
299
|
+
|
|
300
|
+
内部では IndexedDB から Record を取得し、`Query.resolve()` に渡します。
|
|
301
|
+
|
|
302
|
+
```text
|
|
303
|
+
replica.find(query)
|
|
304
|
+
↓
|
|
305
|
+
query.resolve(loader)
|
|
306
|
+
↓
|
|
307
|
+
replica.table(name).toArray()
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
これにより、異なる Storage Adapter 間で Query semantics を共有できます。
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
# JOIN
|
|
315
|
+
|
|
316
|
+
JOIN の評価は `@gasboost/query` が担当します。
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const query = new Query<typeof tables, "users">({
|
|
320
|
+
tableName: "users",
|
|
321
|
+
}).join("id", "reservations", "userId");
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
const users = await replica.find(query);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
結果:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
[
|
|
332
|
+
{
|
|
333
|
+
id: "u1",
|
|
334
|
+
name: "Alice",
|
|
335
|
+
active: true,
|
|
336
|
+
reservations: [
|
|
337
|
+
{
|
|
338
|
+
id: "r1",
|
|
339
|
+
userId: "u1",
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Replica 側に JOIN Engine は持ちません。
|
|
347
|
+
|
|
348
|
+
Replica は `@gasboost/query` に Record を提供するだけです。
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
# Nested JOIN
|
|
353
|
+
|
|
354
|
+
Nested JOIN も `@gasboost/query` によって解決されます。
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
const reservations = new Query<typeof tables, "reservations">({
|
|
358
|
+
tableName: "reservations",
|
|
359
|
+
}).join("staffId", "staffs", "id");
|
|
360
|
+
|
|
361
|
+
const users = new Query<typeof tables, "users">({
|
|
362
|
+
tableName: "users",
|
|
363
|
+
}).join("id", "reservations", "userId", reservations);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const result = await replica.find(users);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Nested JOIN は `Query.resolve()` によって bottom-up に解決されます。
|
|
371
|
+
|
|
372
|
+
```text
|
|
373
|
+
staffs
|
|
374
|
+
↓
|
|
375
|
+
reservations
|
|
376
|
+
↓
|
|
377
|
+
users
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
# Architecture
|
|
383
|
+
|
|
384
|
+
`@gasboost/replica` は Storage の責務だけを持ちます。
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
@gasboost/replica
|
|
388
|
+
- IndexedDB 初期化
|
|
389
|
+
- Dexie Table
|
|
390
|
+
- put
|
|
391
|
+
- bulkPut
|
|
392
|
+
- delete
|
|
393
|
+
- toArray
|
|
394
|
+
- Full Sync
|
|
395
|
+
- Query Loader
|
|
396
|
+
|
|
397
|
+
@gasboost/query
|
|
398
|
+
- Filter
|
|
399
|
+
- Sort
|
|
400
|
+
- Offset
|
|
401
|
+
- Limit
|
|
402
|
+
- JOIN
|
|
403
|
+
- Nested JOIN
|
|
404
|
+
- Query Resolution
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
この責務分離により、ローカル Storage とリモート Storage の間で Query 処理を重複実装せずに済みます。
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
# Shared Schemas
|
|
412
|
+
|
|
413
|
+
Server と Browser で同じ Table Definition を共有できます。
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
export const tables = [
|
|
417
|
+
{
|
|
418
|
+
name: "users",
|
|
419
|
+
schema: UserSchema,
|
|
420
|
+
primaryKey: "id",
|
|
421
|
+
},
|
|
422
|
+
] as const;
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```text
|
|
426
|
+
Server / GAS
|
|
427
|
+
↓
|
|
428
|
+
shared tables
|
|
429
|
+
↑
|
|
430
|
+
Browser / Replica
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Shared Schema を置く Module には、以下のような Runtime 固有依存を含めないことを推奨します。
|
|
434
|
+
|
|
435
|
+
- `SpreadsheetApp`
|
|
436
|
+
- `CacheService`
|
|
437
|
+
- GAS Handler
|
|
438
|
+
- Browser 固有の Application Code
|
|
439
|
+
|
|
440
|
+
これにより、Server Runtime が Frontend Bundle に混入することを防ぎます。
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
# 対象外
|
|
445
|
+
|
|
446
|
+
`@gasboost/replica` core では以下を扱いません。
|
|
447
|
+
|
|
448
|
+
- Firebase Realtime Database 同期
|
|
449
|
+
- 端末間リアルタイム同期
|
|
450
|
+
- Firebase Authentication
|
|
451
|
+
- Row Level Security
|
|
452
|
+
- Offline Mutation Queue
|
|
453
|
+
- Conflict Resolution
|
|
454
|
+
- Incremental Sync
|
|
455
|
+
- Secondary Index を利用した Query 最適化
|
|
456
|
+
- React Hooks
|
|
457
|
+
|
|
458
|
+
これらは Replica core の上に別レイヤーとして追加できます。
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
# License
|
|
463
|
+
|
|
464
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Query } from "@gasboost/query";
|
|
2
|
+
import { ReplicaTable } from "./ReplicaTable";
|
|
3
|
+
import { type ReplicaRecord, type ReplicaTableByName, type ReplicaTableDefinition } from "./ReplicaTableDefinition";
|
|
4
|
+
type PrimaryKey<T extends readonly ReplicaTableDefinition[], N extends T[number]["name"]> = ReplicaRecord<T, N>[ReplicaTableByName<T, N>["primaryKey"] & keyof ReplicaRecord<T, N>];
|
|
5
|
+
export declare class Replica<T extends readonly ReplicaTableDefinition[]> {
|
|
6
|
+
private readonly db;
|
|
7
|
+
private readonly tables;
|
|
8
|
+
constructor({ name, tables }: {
|
|
9
|
+
name: string;
|
|
10
|
+
tables: T;
|
|
11
|
+
});
|
|
12
|
+
table<N extends T[number]["name"]>(name: N): ReplicaTable<ReplicaRecord<T, N>, PrimaryKey<T, N>>;
|
|
13
|
+
sync<N extends T[number]["name"]>(name: N, records: ReplicaRecord<T, N>[]): Promise<void>;
|
|
14
|
+
find<N extends T[number]["name"]>(query: Query<T, N>): Promise<Record<string, unknown>[]>;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
package/dist/Replica.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Replica = void 0;
|
|
7
|
+
const dexie_1 = __importDefault(require("dexie"));
|
|
8
|
+
const ReplicaTable_1 = require("./ReplicaTable");
|
|
9
|
+
class Replica {
|
|
10
|
+
db;
|
|
11
|
+
tables;
|
|
12
|
+
constructor({ name, tables }) {
|
|
13
|
+
this.tables = tables;
|
|
14
|
+
this.db = new dexie_1.default(name);
|
|
15
|
+
const schema = Object.fromEntries(tables.map((table) => [table.name, table.primaryKey]));
|
|
16
|
+
this.db.version(1).stores(schema);
|
|
17
|
+
}
|
|
18
|
+
table(name) {
|
|
19
|
+
return new ReplicaTable_1.ReplicaTable(this.db.table(name));
|
|
20
|
+
}
|
|
21
|
+
async sync(name, records) {
|
|
22
|
+
const table = this.db.table(name);
|
|
23
|
+
await this.db.transaction("rw", table, async () => {
|
|
24
|
+
await table.clear();
|
|
25
|
+
if (records.length > 0) {
|
|
26
|
+
await table.bulkPut(records);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async find(query) {
|
|
31
|
+
return query.resolve(async (name) => {
|
|
32
|
+
return this.db.table(name).toArray();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.Replica = Replica;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Table } from "dexie";
|
|
2
|
+
export declare class ReplicaTable<RecordType, KeyType> {
|
|
3
|
+
private readonly table;
|
|
4
|
+
constructor(table: Table<RecordType, KeyType>);
|
|
5
|
+
put(record: RecordType): Promise<void>;
|
|
6
|
+
bulkPut(records: RecordType[]): Promise<void>;
|
|
7
|
+
delete(key: KeyType): Promise<void>;
|
|
8
|
+
toArray(): Promise<RecordType[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReplicaTable = void 0;
|
|
4
|
+
class ReplicaTable {
|
|
5
|
+
table;
|
|
6
|
+
constructor(table) {
|
|
7
|
+
this.table = table;
|
|
8
|
+
}
|
|
9
|
+
async put(record) {
|
|
10
|
+
await this.table.put(record);
|
|
11
|
+
}
|
|
12
|
+
async bulkPut(records) {
|
|
13
|
+
await this.table.bulkPut(records);
|
|
14
|
+
}
|
|
15
|
+
async delete(key) {
|
|
16
|
+
await this.table.delete(key);
|
|
17
|
+
}
|
|
18
|
+
async toArray() {
|
|
19
|
+
return this.table.toArray();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.ReplicaTable = ReplicaTable;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TableDefinition } from "@gasboost/query";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
export type ReplicaTableDefinition<N extends string = string, S extends z.ZodObject<any> = z.ZodObject<any>, PK extends Extract<keyof z.infer<S>, string> = Extract<keyof z.infer<S>, string>> = TableDefinition<N, S> & {
|
|
4
|
+
readonly primaryKey: PK;
|
|
5
|
+
};
|
|
6
|
+
export type ReplicaTableByName<T extends readonly ReplicaTableDefinition[], N extends T[number]["name"]> = Extract<T[number], {
|
|
7
|
+
name: N;
|
|
8
|
+
}>;
|
|
9
|
+
export type ReplicaRecord<T extends readonly ReplicaTableDefinition[], N extends T[number]["name"]> = z.infer<ReplicaTableByName<T, N>["schema"]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createReplica = createReplica;
|
|
4
|
+
const Replica_1 = require("./Replica");
|
|
5
|
+
function createReplica({ name, tables, }) {
|
|
6
|
+
return new Replica_1.Replica({
|
|
7
|
+
name,
|
|
8
|
+
tables,
|
|
9
|
+
});
|
|
10
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./createReplica";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./createReplica"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gasboost/replica",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe IndexedDB replica powered by Dexie and Zod",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/gasboost/db.git",
|
|
8
|
+
"directory": "packages/replica"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "tiger-oshima",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"indexeddb",
|
|
14
|
+
"dexie",
|
|
15
|
+
"replica",
|
|
16
|
+
"offline",
|
|
17
|
+
"zod",
|
|
18
|
+
"typescript",
|
|
19
|
+
"type-safe"
|
|
20
|
+
],
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"require": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"dexie": "^4.4.6",
|
|
37
|
+
"zod": "^4.5.4",
|
|
38
|
+
"@gasboost/query": "0.1.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"fake-indexeddb": "^6.2.5"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
50
|
+
"pack:check": "pnpm pack"
|
|
51
|
+
}
|
|
52
|
+
}
|