@indietabletop/appkit 7.0.0-rc.4 → 7.0.0-rc.6
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/lib/AppConfig/AppConfig.tsx +1 -1
- package/lib/RichText/style.css.ts +0 -1
- package/lib/client.ts +44 -30
- package/lib/store/index.tsx +16 -9
- package/lib/store/utils.ts +12 -2
- package/package.json +1 -1
|
@@ -58,7 +58,7 @@ export type AppConfig = {
|
|
|
58
58
|
*/
|
|
59
59
|
fmt: AppkitFormatters;
|
|
60
60
|
|
|
61
|
-
database: ModernIDB<any, any> & DatabaseAppMachineMethods
|
|
61
|
+
database: ModernIDB<any, any> & DatabaseAppMachineMethods<any>;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
const [AppConfigContext, useAppConfig] = createStrictContext<AppConfig>();
|
package/lib/client.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
partial,
|
|
15
15
|
string,
|
|
16
16
|
Struct,
|
|
17
|
-
unknown,
|
|
18
17
|
type Infer,
|
|
19
18
|
} from "superstruct";
|
|
20
19
|
import { Failure, Success } from "./async-op.ts";
|
|
@@ -42,9 +41,8 @@ type ClientEventMap = {
|
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
type ClientEventArgs<T extends ClientEventType> =
|
|
45
|
-
ClientEventMap[T] extends undefined
|
|
46
|
-
|
|
47
|
-
: [type: T, detail: ClientEventMap[T]];
|
|
44
|
+
ClientEventMap[T] extends undefined ? [type: T]
|
|
45
|
+
: [type: T, detail: ClientEventMap[T]];
|
|
48
46
|
|
|
49
47
|
export class ClientEvent<T extends keyof ClientEventMap> extends CustomEvent<
|
|
50
48
|
ClientEventMap[T]
|
|
@@ -70,8 +68,8 @@ function toParams(init: Record<string, Primitives | Array<Primitives>>) {
|
|
|
70
68
|
const params = new URLSearchParams();
|
|
71
69
|
|
|
72
70
|
const entries = Object.entries(init).flatMap(([key, value]) => {
|
|
73
|
-
return Array.isArray(value)
|
|
74
|
-
|
|
71
|
+
return Array.isArray(value) ?
|
|
72
|
+
value.map((v) => [key, v] as const)
|
|
75
73
|
: [[key, value] as const];
|
|
76
74
|
});
|
|
77
75
|
|
|
@@ -82,13 +80,22 @@ function toParams(init: Record<string, Primitives | Array<Primitives>>) {
|
|
|
82
80
|
return params;
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
export class IndieTabletopClient
|
|
83
|
+
export class IndieTabletopClient<
|
|
84
|
+
SnapshotPayload = any,
|
|
85
|
+
GameDataPayload = any,
|
|
86
|
+
RulesetPayload = any,
|
|
87
|
+
> {
|
|
86
88
|
origin: string;
|
|
87
89
|
private refreshTokenPromise?: Promise<
|
|
88
90
|
Success<{ sessionInfo: SessionInfo }> | Failure<FailurePayload>
|
|
89
91
|
>;
|
|
90
92
|
private maxLogLevel: number;
|
|
91
93
|
private eventTarget: EventTarget;
|
|
94
|
+
private validation: {
|
|
95
|
+
snapshot: Struct<SnapshotPayload, any>;
|
|
96
|
+
gameData: Struct<GameDataPayload, any>;
|
|
97
|
+
ruleset: Struct<RulesetPayload, any>;
|
|
98
|
+
};
|
|
92
99
|
|
|
93
100
|
constructor(props: {
|
|
94
101
|
apiOrigin: string;
|
|
@@ -119,10 +126,17 @@ export class IndieTabletopClient {
|
|
|
119
126
|
* @default 'info'
|
|
120
127
|
*/
|
|
121
128
|
logLevel?: LogLevel;
|
|
129
|
+
|
|
130
|
+
validation: {
|
|
131
|
+
snapshot: Struct<SnapshotPayload, any>;
|
|
132
|
+
gameData: Struct<GameDataPayload, any>;
|
|
133
|
+
ruleset: Struct<RulesetPayload, any>;
|
|
134
|
+
};
|
|
122
135
|
}) {
|
|
123
136
|
this.eventTarget = new EventTarget();
|
|
124
137
|
this.origin = props.apiOrigin;
|
|
125
138
|
this.maxLogLevel = props.logLevel ? logLevelToInt[props.logLevel] : 1;
|
|
139
|
+
this.validation = props.validation;
|
|
126
140
|
|
|
127
141
|
// If handlers were passed to the constructor, we set them up here. No need
|
|
128
142
|
// to clean them up, as if the instance is destroyed, the listeners will
|
|
@@ -466,12 +480,11 @@ export class IndieTabletopClient {
|
|
|
466
480
|
return req;
|
|
467
481
|
}
|
|
468
482
|
|
|
469
|
-
async getSnapshot
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
return await this.fetch(`/v1/snapshots/${gameCode}/${snapshotId}`, struct);
|
|
483
|
+
async getSnapshot(gameCode: string, snapshotId: string) {
|
|
484
|
+
return await this.fetch(
|
|
485
|
+
`/v1/snapshots/${gameCode}/${snapshotId}`,
|
|
486
|
+
this.validation.snapshot,
|
|
487
|
+
);
|
|
475
488
|
}
|
|
476
489
|
|
|
477
490
|
async createSnapshot(gameCode: string, payload: object) {
|
|
@@ -503,7 +516,10 @@ export class IndieTabletopClient {
|
|
|
503
516
|
}
|
|
504
517
|
|
|
505
518
|
getRuleset(game: string, version: string) {
|
|
506
|
-
return this.fetch(
|
|
519
|
+
return this.fetch(
|
|
520
|
+
`/v1/rulesets/${game}/${version}`,
|
|
521
|
+
this.validation.ruleset,
|
|
522
|
+
);
|
|
507
523
|
}
|
|
508
524
|
|
|
509
525
|
/**
|
|
@@ -578,7 +594,9 @@ export class IndieTabletopClient {
|
|
|
578
594
|
|
|
579
595
|
async pullUserData(props: {
|
|
580
596
|
sinceTs: number | null;
|
|
581
|
-
include:
|
|
597
|
+
include:
|
|
598
|
+
| (string & keyof GameDataPayload)
|
|
599
|
+
| (string & keyof GameDataPayload)[];
|
|
582
600
|
expectCurrentUserId: string;
|
|
583
601
|
}) {
|
|
584
602
|
const params = toParams({
|
|
@@ -590,30 +608,26 @@ export class IndieTabletopClient {
|
|
|
590
608
|
|
|
591
609
|
return await this.fetch(
|
|
592
610
|
`/v1/me/game-data?${params}`,
|
|
593
|
-
|
|
611
|
+
this.validation.gameData,
|
|
594
612
|
);
|
|
595
613
|
}
|
|
596
614
|
|
|
597
615
|
async pushUserData(props: {
|
|
598
616
|
currentSyncTs: number;
|
|
599
617
|
pullSinceTs: number | null;
|
|
600
|
-
data:
|
|
618
|
+
data: GameDataPayload;
|
|
601
619
|
expectCurrentUserId: string | null | undefined;
|
|
602
620
|
}) {
|
|
603
|
-
return await this.fetch(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
pullSinceTs: props.pullSinceTs ?? 0,
|
|
612
|
-
pullOmitDeleted: !props.pullSinceTs,
|
|
613
|
-
expectCurrentUserId: props.expectCurrentUserId,
|
|
614
|
-
},
|
|
621
|
+
return await this.fetch(`/v1/me/game-data`, this.validation.gameData, {
|
|
622
|
+
method: "PATCH",
|
|
623
|
+
json: {
|
|
624
|
+
data: props.data,
|
|
625
|
+
syncedTs: props.currentSyncTs,
|
|
626
|
+
pullSinceTs: props.pullSinceTs ?? 0,
|
|
627
|
+
pullOmitDeleted: !props.pullSinceTs,
|
|
628
|
+
expectCurrentUserId: props.expectCurrentUserId,
|
|
615
629
|
},
|
|
616
|
-
);
|
|
630
|
+
});
|
|
617
631
|
}
|
|
618
632
|
|
|
619
633
|
async getUnlocks<T extends FeatureUnlock["gameCode"]>(gameCode: T) {
|
package/lib/store/index.tsx
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { UserGameData } from "@indietabletop/types";
|
|
2
1
|
import { useActorRef, useSelector } from "@xstate/react";
|
|
3
2
|
import { useMemo, type ReactNode } from "react";
|
|
4
3
|
import { Actor, fromCallback, fromPromise } from "xstate";
|
|
5
4
|
import { useAppConfig } from "../AppConfig/AppConfig.tsx";
|
|
6
5
|
import { Failure, Success } from "../async-op.ts";
|
|
7
|
-
import type {
|
|
6
|
+
import type { IndieTabletopClient } from "../client.ts";
|
|
8
7
|
import { createStrictContext } from "../createStrictContext.ts";
|
|
9
8
|
import type { ModernIDB } from "../ModernIDB/ModernIDB.ts";
|
|
10
9
|
import type { ModernIDBIndexes, ModernIDBSchema } from "../ModernIDB/types.ts";
|
|
@@ -16,7 +15,7 @@ import {
|
|
|
16
15
|
type PushChangesInput,
|
|
17
16
|
} from "./store.ts";
|
|
18
17
|
import type { MachineEvent, PullResult, PushResult } from "./types.ts";
|
|
19
|
-
import { toSyncedItems } from "./utils.ts";
|
|
18
|
+
import { toSyncedItems, type UserGameDataShape } from "./utils.ts";
|
|
20
19
|
|
|
21
20
|
export type AppActions = ReturnType<typeof useCreateActions>;
|
|
22
21
|
|
|
@@ -104,13 +103,13 @@ export function useAppActions() {
|
|
|
104
103
|
return useAppMachineContext().actions;
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
export type DatabaseAppMachineMethods = {
|
|
108
|
-
upsertGameData(data:
|
|
106
|
+
export type DatabaseAppMachineMethods<GameDataPayload> = {
|
|
107
|
+
upsertGameData(data: GameDataPayload): Promise<Success<string[]>>;
|
|
109
108
|
|
|
110
109
|
getUpdatedGameDataSince(props: {
|
|
111
110
|
sinceTs: number | null;
|
|
112
111
|
exclude: Set<string>;
|
|
113
|
-
}): Promise<Success<
|
|
112
|
+
}): Promise<Success<GameDataPayload>>;
|
|
114
113
|
|
|
115
114
|
clearAll(): Promise<Success<string> | Failure<string>>;
|
|
116
115
|
};
|
|
@@ -118,9 +117,14 @@ export type DatabaseAppMachineMethods = {
|
|
|
118
117
|
export function createMachine<
|
|
119
118
|
Schema extends ModernIDBSchema,
|
|
120
119
|
Indexes extends ModernIDBIndexes<Schema>,
|
|
120
|
+
SnapshotPayload,
|
|
121
|
+
GameDataPayload extends UserGameDataShape,
|
|
122
|
+
RulesetPayload,
|
|
121
123
|
>(options: {
|
|
122
|
-
database: ModernIDB<Schema, Indexes> &
|
|
123
|
-
|
|
124
|
+
database: ModernIDB<Schema, Indexes> &
|
|
125
|
+
DatabaseAppMachineMethods<GameDataPayload>;
|
|
126
|
+
|
|
127
|
+
client: IndieTabletopClient<SnapshotPayload, GameDataPayload, RulesetPayload>;
|
|
124
128
|
|
|
125
129
|
/**
|
|
126
130
|
* Which games should the machine pull game data for?
|
|
@@ -130,7 +134,10 @@ export function createMachine<
|
|
|
130
134
|
* @remarks Honestly this is a bit hacky and we should have a better way to
|
|
131
135
|
* disable sync entirely for apps that don't need it (e.g. the Creators App).
|
|
132
136
|
*/
|
|
133
|
-
pullGameDataFor:
|
|
137
|
+
pullGameDataFor:
|
|
138
|
+
| (string & keyof GameDataPayload)
|
|
139
|
+
| (string & keyof GameDataPayload)[]
|
|
140
|
+
| null;
|
|
134
141
|
}) {
|
|
135
142
|
const { client, database } = options;
|
|
136
143
|
|
package/lib/store/utils.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import type { UserGameData } from "@indietabletop/types";
|
|
2
1
|
import { Failure } from "../async-op.js";
|
|
3
2
|
import type { FailurePayload } from "../types.ts";
|
|
4
3
|
import type { SyncedItem } from "./types.ts";
|
|
5
4
|
|
|
5
|
+
export type UserGameDataShape = Record<
|
|
6
|
+
string,
|
|
7
|
+
Record<
|
|
8
|
+
string,
|
|
9
|
+
(
|
|
10
|
+
| { id: string; name: string; updatedTs: number }
|
|
11
|
+
| { id: string; name: string; updatedTs: number; deleted: boolean }
|
|
12
|
+
)[]
|
|
13
|
+
>
|
|
14
|
+
>;
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* Flattens the UserGameData structure to a flat SyncedItem[].
|
|
8
18
|
*/
|
|
9
|
-
export function toSyncedItems(data:
|
|
19
|
+
export function toSyncedItems(data: UserGameDataShape) {
|
|
10
20
|
return Object.values(data).flatMap((groups) => {
|
|
11
21
|
return Object.values(groups).flatMap((items) => {
|
|
12
22
|
return items.map((item): SyncedItem => {
|