@trypattern/sdk 0.1.0 → 0.1.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/dist/api.d.ts +17 -7
- package/dist/api.js +34 -8
- package/dist/app.d.ts +12 -3
- package/dist/app.js +49 -7
- package/dist/appInfo.d.ts +35 -0
- package/dist/appInfo.js +67 -0
- package/dist/appTitle.d.ts +6 -0
- package/dist/appTitle.js +3 -0
- package/dist/builtins/AppSettings.d.ts +12 -0
- package/dist/builtins/AppSettings.js +31 -0
- package/dist/builtins/Toasts.d.ts +1 -1
- package/dist/builtins/Toasts.js +2 -1
- package/dist/cli/app.js +91 -14
- package/dist/cli/publishing.d.ts +21 -0
- package/dist/cli/publishing.js +493 -0
- package/dist/clock.d.ts +6 -0
- package/dist/clock.js +31 -0
- package/dist/components/AppFrame.js +3 -3
- package/dist/components/Button.d.ts +2 -1
- package/dist/components/Button.js +6 -3
- package/dist/components/CopyButton.d.ts +11 -0
- package/dist/components/CopyButton.js +16 -0
- package/dist/components/Divider.d.ts +2 -1
- package/dist/components/Divider.js +19 -4
- package/dist/components/IconButton.js +2 -2
- package/dist/components/Link.d.ts +8 -3
- package/dist/components/Link.js +11 -2
- package/dist/components/ListRow.js +2 -2
- package/dist/components/Menu.js +2 -2
- package/dist/components/RadioGroup.d.ts +18 -0
- package/dist/components/RadioGroup.js +85 -0
- package/dist/components/SegmentedControl.js +2 -2
- package/dist/components/Sidebar.js +2 -3
- package/dist/components/SignInScreen.js +3 -3
- package/dist/components/Switch.js +2 -1
- package/dist/components/controlLook.d.ts +10 -0
- package/dist/components/controlLook.js +14 -1
- package/dist/components/icons.d.ts +4 -3
- package/dist/components/icons.js +9 -3
- package/dist/components/interaction.d.ts +1 -1
- package/dist/flows/accessFlows.js +19 -3
- package/dist/flows/browser.d.ts +3 -1
- package/dist/flows/browser.js +6 -3
- package/dist/flows/flowBrowser.d.ts +3 -1
- package/dist/flows/flowBrowser.js +4 -2
- package/dist/flows/flows.d.ts +6 -0
- package/dist/flows/flows.js +32 -5
- package/dist/hooks/appContext.d.ts +2 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/publishWire.d.ts +271 -0
- package/dist/publishWire.js +168 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +2 -0
- package/dist/registered.d.ts +6 -0
- package/dist/registered.js +2 -0
- package/dist/rules/checkAllFiles.js +10 -2
- package/dist/rules/lintPlugin.js +2 -0
- package/dist/rules/messages.d.ts +2 -2
- package/dist/rules/messages.js +2 -2
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +1 -0
- package/dist/server/auth.js +4 -1
- package/dist/server/createServer.d.ts +48 -3
- package/dist/server/createServer.js +25 -5
- package/dist/server/env.d.ts +2 -2
- package/dist/server/index.d.ts +6 -1
- package/dist/server/index.js +5 -1
- package/dist/server/services.d.ts +9 -0
- package/dist/server/services.js +47 -0
- package/dist/server/session.d.ts +1 -0
- package/dist/server/session.js +11 -0
- package/dist/server/settings.d.ts +49 -0
- package/dist/server/settings.js +25 -0
- package/dist/server/signInWire.d.ts +1 -0
- package/dist/server/signInWire.js +2 -1
- package/dist/serviceClients.d.ts +5 -0
- package/dist/serviceClients.js +70 -0
- package/dist/services.d.ts +53 -0
- package/dist/services.js +30 -0
- package/dist/start.js +30 -6
- package/dist/store.d.ts +5 -3
- package/dist/store.js +13 -3
- package/dist/table.d.ts +1 -1
- package/dist/vite/localWorker.d.ts +1 -1
- package/dist/vite/localWorker.js +5 -4
- package/dist/vite/projectFile.d.ts +9 -0
- package/dist/vite/projectFile.js +17 -0
- package/dist/vite/vite.d.ts +2 -1
- package/dist/vite/vite.js +10 -3
- package/package.json +5 -1
- package/presets/register.d.ts +2 -0
package/dist/api.d.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import type { SessionApi, SessionCall } from './auth/sessionApi.ts';
|
|
2
|
+
import type { AppInfoApi, AppInfoCall } from './appInfo.ts';
|
|
2
3
|
import type { Gate } from './gate.ts';
|
|
4
|
+
import type { Services } from './services.ts';
|
|
3
5
|
import type { Seed, Tables } from './table.ts';
|
|
4
6
|
import type { TableCall, TablesApi } from './tables.ts';
|
|
5
7
|
export type AppApi<Group extends Tables> = {
|
|
6
8
|
tables: TablesApi<Group>;
|
|
7
9
|
session: SessionApi;
|
|
10
|
+
app: AppInfoApi;
|
|
11
|
+
services: object;
|
|
8
12
|
};
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
export type ServicesSetup = {
|
|
14
|
+
services?: Services;
|
|
15
|
+
memory?: object;
|
|
16
|
+
world?: unknown;
|
|
17
|
+
};
|
|
18
|
+
export type ApiCall<Group extends Tables> = TableCall<Group> | `session.${SessionCall}` | `app.${AppInfoCall}`;
|
|
19
|
+
export declare function apiCalls<Group extends Tables>(tables: Group, services?: Services): readonly ApiCall<Group>[];
|
|
20
|
+
export declare function httpApi<Group extends Tables>(tables: Group, gate: Gate, setup?: ServicesSetup): AppApi<Group>;
|
|
21
|
+
export declare function memoryApi<Group extends Tables>(tables: Group, seed: Seed<Group>, { gate, signedInAs, admins, title, ...setup }: {
|
|
13
22
|
gate: Gate;
|
|
14
23
|
signedInAs: string | null;
|
|
15
24
|
admins: readonly string[];
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
title: string;
|
|
26
|
+
} & ServicesSetup): AppApi<Group>;
|
|
27
|
+
export declare function browserApi<Group extends Tables>(tables: Group, seed: Seed<Group>, gate: Gate, { inMemory, admins, ...setup }: {
|
|
18
28
|
inMemory: boolean;
|
|
19
29
|
admins: readonly string[];
|
|
20
|
-
}): AppApi<Group>;
|
|
30
|
+
} & ServicesSetup): AppApi<Group>;
|
package/dist/api.js
CHANGED
|
@@ -1,34 +1,60 @@
|
|
|
1
1
|
import { adminEmails, viewerByEmail, viewerOf } from "./access.js";
|
|
2
2
|
import { HttpSessionApi, MemorySessionApi, sessionCalls } from "./auth/sessionApi.js";
|
|
3
|
+
import { HttpAppInfoApi, MemoryAppInfoApi, appInfoCalls } from "./appInfo.js";
|
|
4
|
+
import { httpServices, memoryServices } from "./serviceClients.js";
|
|
5
|
+
import { serviceCallNames } from "./services.js";
|
|
3
6
|
import { httpTables, memoryTables, tableCalls } from "./tables.js";
|
|
4
|
-
export function apiCalls(tables) {
|
|
7
|
+
export function apiCalls(tables, services = {}) {
|
|
5
8
|
return [
|
|
9
|
+
...serviceCallNames(services),
|
|
6
10
|
...tableCalls(tables),
|
|
7
|
-
...sessionCalls.map((call) => `session.${call}`)
|
|
11
|
+
...sessionCalls.map((call) => `session.${call}`),
|
|
12
|
+
...appInfoCalls.map((call) => `app.${call}`)
|
|
8
13
|
];
|
|
9
14
|
}
|
|
10
|
-
|
|
15
|
+
function memoryClient(setup, options) {
|
|
16
|
+
if (setup.services === undefined)
|
|
17
|
+
return {};
|
|
18
|
+
if (setup.memory === undefined) {
|
|
19
|
+
throw new Error('This app declares services but no memory fake: pass defineApp({ services, memory }) so flows and ?memory can run without a server.');
|
|
20
|
+
}
|
|
21
|
+
return memoryServices(setup.services, setup.memory, setup.world ?? {}, options);
|
|
22
|
+
}
|
|
23
|
+
export function httpApi(tables, gate, setup = {}) {
|
|
11
24
|
const session = new HttpSessionApi(gate);
|
|
12
25
|
return {
|
|
13
26
|
tables: httpTables(tables, gate, () => session.reportSignedOut()),
|
|
14
|
-
session
|
|
27
|
+
session,
|
|
28
|
+
app: new HttpAppInfoApi(gate),
|
|
29
|
+
services: setup.services === undefined
|
|
30
|
+
? {}
|
|
31
|
+
: httpServices(setup.services, gate, () => session.reportSignedOut())
|
|
15
32
|
};
|
|
16
33
|
}
|
|
17
|
-
export function memoryApi(tables, seed, { gate, signedInAs, admins }) {
|
|
34
|
+
export function memoryApi(tables, seed, { gate, signedInAs, admins, title, ...setup }) {
|
|
18
35
|
const options = { latencyMs: 0, gate };
|
|
19
36
|
const session = new MemorySessionApi(signedInAs, admins, options);
|
|
20
37
|
return {
|
|
21
38
|
tables: memoryTables(tables, seed, () => viewerOf(session.rememberedUser()), options),
|
|
22
|
-
session
|
|
39
|
+
session,
|
|
40
|
+
app: new MemoryAppInfoApi(title, options),
|
|
41
|
+
services: memoryClient(setup, options)
|
|
23
42
|
};
|
|
24
43
|
}
|
|
25
|
-
export function browserApi(tables, seed, gate, { inMemory, admins }) {
|
|
44
|
+
export function browserApi(tables, seed, gate, { inMemory, admins, ...setup }) {
|
|
26
45
|
const session = new HttpSessionApi(gate);
|
|
27
46
|
const emails = adminEmails(admins);
|
|
47
|
+
const options = { latencyMs: 0, gate };
|
|
28
48
|
return {
|
|
29
49
|
tables: inMemory
|
|
30
50
|
? memoryTables(tables, seed, () => viewerByEmail(session.rememberedUser(), emails), { latencyMs: 0, gate })
|
|
31
51
|
: httpTables(tables, gate, () => session.reportSignedOut()),
|
|
32
|
-
session
|
|
52
|
+
session,
|
|
53
|
+
app: new HttpAppInfoApi(gate),
|
|
54
|
+
services: inMemory
|
|
55
|
+
? memoryClient(setup, options)
|
|
56
|
+
: setup.services === undefined
|
|
57
|
+
? {}
|
|
58
|
+
: httpServices(setup.services, gate, () => session.reportSignedOut())
|
|
33
59
|
};
|
|
34
60
|
}
|
package/dist/app.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { DataRouter } from 'react-router';
|
|
2
2
|
import type { AppApi } from './api.ts';
|
|
3
|
+
import { AppStore } from './appInfo.ts';
|
|
3
4
|
import { SessionStore } from './auth/sessionStore.ts';
|
|
4
5
|
import type { Cache } from './cache.ts';
|
|
6
|
+
import type { Clock } from './clock.ts';
|
|
5
7
|
import type { Gate } from './gate.ts';
|
|
6
8
|
import { ActionsStore } from './actions.ts';
|
|
7
9
|
import type { Action, ActionsBuilder } from './actions.ts';
|
|
@@ -12,6 +14,7 @@ import type { Snapshots } from './store.ts';
|
|
|
12
14
|
import type { Json, Persisted } from './state.ts';
|
|
13
15
|
import type { DerivedOf, Instances, MessagesOf, ModelClassLike, ModelSnapshots } from './model.ts';
|
|
14
16
|
import type { PageOf, PathOf, RouteDefinition } from './routes.ts';
|
|
17
|
+
import type { Services } from './services.ts';
|
|
15
18
|
import type { Tables } from './table.ts';
|
|
16
19
|
import { PublicTableStore, TableStore } from './tables.ts';
|
|
17
20
|
type Observed = {
|
|
@@ -21,6 +24,7 @@ type Observed = {
|
|
|
21
24
|
export type { DataRouter };
|
|
22
25
|
export type ModelServicesOf<Path extends string> = {
|
|
23
26
|
navigate: (to: Path) => Promise<void>;
|
|
27
|
+
every: (ms: number, run: () => void | Promise<void>) => () => void;
|
|
24
28
|
};
|
|
25
29
|
type StaticTypeError<Text extends string> = Text;
|
|
26
30
|
type NonJsonGetters<Instance> = {
|
|
@@ -39,6 +43,7 @@ export type AppDependencies<Api> = {
|
|
|
39
43
|
api: Api;
|
|
40
44
|
cache: Cache;
|
|
41
45
|
gate: Gate;
|
|
46
|
+
clock?: Clock;
|
|
42
47
|
};
|
|
43
48
|
export type RunningApp<Command, Snapshot extends {
|
|
44
49
|
readonly path: string;
|
|
@@ -50,8 +55,9 @@ export type RunningApp<Command, Snapshot extends {
|
|
|
50
55
|
};
|
|
51
56
|
getSnapshot(): Snapshot;
|
|
52
57
|
whenIdle(): Promise<void>;
|
|
58
|
+
tick(): void;
|
|
53
59
|
};
|
|
54
|
-
export type StoreClasses = Record<string, new (persisted: Persisted) => Observed>;
|
|
60
|
+
export type StoreClasses = Record<string, new (persisted: Persisted, services: never) => Observed>;
|
|
55
61
|
export type StoreInstances<Classes extends StoreClasses> = {
|
|
56
62
|
[Key in keyof Classes]: InstanceType<Classes[Key]>;
|
|
57
63
|
};
|
|
@@ -65,12 +71,12 @@ export type PublicStores<Group extends Tables> = {
|
|
|
65
71
|
};
|
|
66
72
|
export type StoresOf<Group extends Tables, Custom, Item extends Action = Action> = {
|
|
67
73
|
session: SessionStore;
|
|
74
|
+
app: AppStore;
|
|
68
75
|
actions: ActionsStore<Item>;
|
|
69
76
|
toasts: Toasts;
|
|
70
77
|
page: PageStore;
|
|
71
78
|
} & TableStores<Group> & PublicStores<Group> & Custom;
|
|
72
79
|
export type AppDefinition<Group extends Tables, Custom extends StoreClasses, Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[]> = {
|
|
73
|
-
name: string;
|
|
74
80
|
tables: Group;
|
|
75
81
|
stores?: Custom;
|
|
76
82
|
models: {
|
|
@@ -79,12 +85,16 @@ export type AppDefinition<Group extends Tables, Custom extends StoreClasses, Cla
|
|
|
79
85
|
routes: Routes;
|
|
80
86
|
actions?: readonly ActionsBuilder<never, Action>[];
|
|
81
87
|
actionGroups?: readonly string[];
|
|
88
|
+
services?: Services;
|
|
89
|
+
memory?: object;
|
|
82
90
|
};
|
|
83
91
|
export type AppClass<Group extends Tables, Instance> = {
|
|
84
92
|
new (router: DataRouter, deps: AppDependencies<AppApi<Group>>): Instance;
|
|
85
93
|
readonly definition: {
|
|
86
94
|
readonly tables: Group;
|
|
87
95
|
readonly routes: readonly RouteDefinition[];
|
|
96
|
+
readonly services?: Services;
|
|
97
|
+
readonly memory?: object;
|
|
88
98
|
};
|
|
89
99
|
};
|
|
90
100
|
export type AppSnapshotShape<Models, Stores, Page> = {
|
|
@@ -96,7 +106,6 @@ export type AppSnapshotShape<Models, Stores, Page> = {
|
|
|
96
106
|
type AppCommandOf<Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[]> = MessagesOf<Instances<BuiltInModels>> | MessagesOf<Instances<Classes>> | NavigateCommand<PathOf<Routes>> | PressKeyCommand;
|
|
97
107
|
export type DefinedApp<Group extends Tables, Classes extends Record<string, ModelClassLike>, Routes extends readonly RouteDefinition[], Custom extends StoreClasses> = {
|
|
98
108
|
new (router: DataRouter, dependencies: AppDependencies<AppApi<Group>>): RunningApp<AppCommandOf<Classes, Routes>, AppSnapshotShape<Instances<BuiltInModels & Classes>, StoresOf<Group, StoreInstances<Custom>>, PageOf<Routes>>> & {
|
|
99
|
-
readonly name: string;
|
|
100
109
|
readonly models: Instances<BuiltInModels & Classes>;
|
|
101
110
|
readonly stores: StoresOf<Group, StoreInstances<Custom>>;
|
|
102
111
|
pressKey(key: string): Promise<void> | undefined;
|
package/dist/app.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { viewerOf } from "./access.js";
|
|
3
|
+
import { AppStore } from "./appInfo.js";
|
|
3
4
|
import { SessionStore } from "./auth/sessionStore.js";
|
|
5
|
+
import { realClock } from "./clock.js";
|
|
4
6
|
import { ActionsStore } from "./actions.js";
|
|
5
7
|
import { Account } from "./builtins/Account.js";
|
|
8
|
+
import { AppSettings } from "./builtins/AppSettings.js";
|
|
6
9
|
import { BUILT_IN_GROUPS, builtInActions } from "./builtins/actions.js";
|
|
7
10
|
import { Palette } from "./builtins/Palette.js";
|
|
8
11
|
import { SignIn } from "./builtins/SignIn.js";
|
|
@@ -43,7 +46,12 @@ function clashOf(groups) {
|
|
|
43
46
|
}
|
|
44
47
|
return undefined;
|
|
45
48
|
}
|
|
46
|
-
const builtInModels = {
|
|
49
|
+
const builtInModels = {
|
|
50
|
+
account: Account,
|
|
51
|
+
appSettings: AppSettings,
|
|
52
|
+
palette: Palette,
|
|
53
|
+
signIn: SignIn
|
|
54
|
+
};
|
|
47
55
|
const appPathInput = z.custom((value) => typeof value === 'string' && isRoutedPath(value), { error: 'Not a page in src/routes.tsx' });
|
|
48
56
|
export function defineApp(definition) {
|
|
49
57
|
const pages = new Set(pageIds(definition.routes));
|
|
@@ -58,7 +66,6 @@ export function defineApp(definition) {
|
|
|
58
66
|
}
|
|
59
67
|
return class App {
|
|
60
68
|
static definition = definition;
|
|
61
|
-
name = definition.name;
|
|
62
69
|
router;
|
|
63
70
|
models;
|
|
64
71
|
stores;
|
|
@@ -69,28 +76,35 @@ export function defineApp(definition) {
|
|
|
69
76
|
#session;
|
|
70
77
|
#data;
|
|
71
78
|
#navigations = new Set();
|
|
79
|
+
#repeats = new Set();
|
|
80
|
+
#clock;
|
|
72
81
|
#failures = [];
|
|
73
82
|
#path;
|
|
74
83
|
#user;
|
|
75
|
-
constructor(router, { api, cache, gate }) {
|
|
84
|
+
constructor(router, { api, cache, gate, clock = realClock() }) {
|
|
76
85
|
this.router = router;
|
|
77
86
|
this.#cache = cache;
|
|
78
87
|
this.#gate = gate;
|
|
88
|
+
this.#clock = clock;
|
|
79
89
|
const session = new SessionStore({ api: api.session });
|
|
90
|
+
const app = new AppStore({
|
|
91
|
+
api: api.app,
|
|
92
|
+
remembered: cache.scope('app.title', z.string())
|
|
93
|
+
});
|
|
80
94
|
const data = dataStores(definition.tables, api.tables, cache, () => viewerOf(session.state.user));
|
|
81
95
|
const custom = Object.fromEntries(Object.entries(definition.stores ?? {}).map(([key, Store]) => [
|
|
82
96
|
key,
|
|
83
|
-
new Store(cache.persisted(`stores.${key}`))
|
|
97
|
+
new Store(cache.persisted(`stores.${key}`), api.services)
|
|
84
98
|
]));
|
|
85
|
-
const toasts = new Toasts(cache.persisted('stores.toasts'));
|
|
99
|
+
const toasts = new Toasts(cache.persisted('stores.toasts'), {});
|
|
86
100
|
const clash = clashOf([
|
|
87
|
-
['session', 'actions', 'toasts', 'page'],
|
|
101
|
+
['session', 'app', 'actions', 'toasts', 'page'],
|
|
88
102
|
Object.keys(data.tables),
|
|
89
103
|
Object.keys(data.publicViews),
|
|
90
104
|
Object.keys(custom)
|
|
91
105
|
]);
|
|
92
106
|
if (clash !== undefined) {
|
|
93
|
-
throw new Error(`Two stores are called "${clash}". The built-in session, actions, toasts and page stores, each table, each table's public view and each store in src/stores/index.ts need their own name.`);
|
|
107
|
+
throw new Error(`Two stores are called "${clash}". The built-in session, app, actions, toasts and page stores, each table, each table's public view and each store in src/stores/index.ts need their own name.`);
|
|
94
108
|
}
|
|
95
109
|
const actions = new ActionsStore(this.#page, [...(definition.actions ?? []), ...builtInActions], [
|
|
96
110
|
...(definition.actionGroups ?? []),
|
|
@@ -98,6 +112,7 @@ export function defineApp(definition) {
|
|
|
98
112
|
], (message) => this.#runAction(message));
|
|
99
113
|
this.stores = {
|
|
100
114
|
session,
|
|
115
|
+
app,
|
|
101
116
|
actions,
|
|
102
117
|
toasts,
|
|
103
118
|
page: this.#page,
|
|
@@ -111,6 +126,10 @@ export function defineApp(definition) {
|
|
|
111
126
|
navigate: (to) => {
|
|
112
127
|
assertNotBuilding('navigate');
|
|
113
128
|
return this.#navigate(to);
|
|
129
|
+
},
|
|
130
|
+
every: (ms, run) => {
|
|
131
|
+
assertNotBuilding('repeat');
|
|
132
|
+
return this.#every(ms, run);
|
|
114
133
|
}
|
|
115
134
|
};
|
|
116
135
|
this.models = createModels({ ...builtInModels, ...definition.models }, this.stores, services, (model) => cache.persisted(model));
|
|
@@ -128,6 +147,7 @@ export function defineApp(definition) {
|
|
|
128
147
|
router.subscribe(() => this.#routeChanged());
|
|
129
148
|
session.subscribe(() => this.#sessionChanged());
|
|
130
149
|
void session.confirm();
|
|
150
|
+
void app.load();
|
|
131
151
|
for (const view of Object.values(data.publicViews))
|
|
132
152
|
void view.load();
|
|
133
153
|
this.#loadReadableTables();
|
|
@@ -149,6 +169,9 @@ export function defineApp(definition) {
|
|
|
149
169
|
stores: snapshotsOf(this.stores)
|
|
150
170
|
};
|
|
151
171
|
}
|
|
172
|
+
tick() {
|
|
173
|
+
this.#clock.tick();
|
|
174
|
+
}
|
|
152
175
|
async whenIdle() {
|
|
153
176
|
do {
|
|
154
177
|
await Promise.allSettled(this.#navigations);
|
|
@@ -184,11 +207,29 @@ export function defineApp(definition) {
|
|
|
184
207
|
this.#navigations.add(navigation);
|
|
185
208
|
return navigation;
|
|
186
209
|
}
|
|
210
|
+
#every(ms, run) {
|
|
211
|
+
const stop = this.#clock.every(ms, () => {
|
|
212
|
+
void Promise.resolve(run()).catch((failure) => {
|
|
213
|
+
this.#failures.push(failure);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
const remove = () => {
|
|
217
|
+
stop();
|
|
218
|
+
this.#repeats.delete(remove);
|
|
219
|
+
};
|
|
220
|
+
this.#repeats.add(remove);
|
|
221
|
+
return remove;
|
|
222
|
+
}
|
|
223
|
+
#stopRepeats() {
|
|
224
|
+
for (const stop of [...this.#repeats])
|
|
225
|
+
stop();
|
|
226
|
+
}
|
|
187
227
|
#routeChanged() {
|
|
188
228
|
const path = this.router.state.location.pathname;
|
|
189
229
|
if (path === this.#path)
|
|
190
230
|
return;
|
|
191
231
|
this.#path = path;
|
|
232
|
+
this.#stopRepeats();
|
|
192
233
|
this.#page.moved(this.#currentPage(), this.#currentParams());
|
|
193
234
|
this.#openCurrentModel();
|
|
194
235
|
}
|
|
@@ -197,6 +238,7 @@ export function defineApp(definition) {
|
|
|
197
238
|
if (user === this.#user)
|
|
198
239
|
return;
|
|
199
240
|
this.#user = user;
|
|
241
|
+
this.#stopRepeats();
|
|
200
242
|
this.#cache.clear();
|
|
201
243
|
for (const store of Object.values(this.#data.tables))
|
|
202
244
|
store.forget();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AppInfo } from './appTitle.ts';
|
|
2
|
+
import type { Cached } from './cache.ts';
|
|
3
|
+
import type { Gate } from './gate.ts';
|
|
4
|
+
import type { MemoryApiOptions } from './memoryApi.ts';
|
|
5
|
+
import { StoreBase } from './store.ts';
|
|
6
|
+
export type AppInfoApi = {
|
|
7
|
+
read: () => Promise<AppInfo>;
|
|
8
|
+
rename: (title: string) => Promise<AppInfo>;
|
|
9
|
+
};
|
|
10
|
+
export type AppInfoCall = 'read' | 'rename';
|
|
11
|
+
export declare const appInfoCalls: readonly AppInfoCall[];
|
|
12
|
+
export declare class HttpAppInfoApi implements AppInfoApi {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(gate: Gate);
|
|
15
|
+
read(): Promise<AppInfo>;
|
|
16
|
+
rename(title: string): Promise<AppInfo>;
|
|
17
|
+
}
|
|
18
|
+
export declare class MemoryAppInfoApi implements AppInfoApi {
|
|
19
|
+
#private;
|
|
20
|
+
constructor(title: string, options: MemoryApiOptions);
|
|
21
|
+
read(): Promise<AppInfo>;
|
|
22
|
+
rename(title: string): Promise<AppInfo>;
|
|
23
|
+
}
|
|
24
|
+
export type AppState = {
|
|
25
|
+
title: string;
|
|
26
|
+
};
|
|
27
|
+
export declare class AppStore extends StoreBase<AppState, {
|
|
28
|
+
api: AppInfoApi;
|
|
29
|
+
remembered: Cached<string>;
|
|
30
|
+
}> {
|
|
31
|
+
#private;
|
|
32
|
+
protected initialState(): AppState;
|
|
33
|
+
load(): Promise<void>;
|
|
34
|
+
rename(title: string): Promise<void>;
|
|
35
|
+
}
|
package/dist/appInfo.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { appInfo } from "./appTitle.js";
|
|
2
|
+
import { MemoryServer } from "./memoryApi.js";
|
|
3
|
+
import { StoreBase } from "./store.js";
|
|
4
|
+
export const appInfoCalls = ['read', 'rename'];
|
|
5
|
+
export class HttpAppInfoApi {
|
|
6
|
+
#gate;
|
|
7
|
+
constructor(gate) {
|
|
8
|
+
this.#gate = gate;
|
|
9
|
+
}
|
|
10
|
+
read() {
|
|
11
|
+
return this.#gate.pass('app.read', async () => {
|
|
12
|
+
const response = await fetch('/api/app');
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
throw new Error('app.read failed');
|
|
15
|
+
return appInfo.parse(await response.json());
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
rename(title) {
|
|
19
|
+
return this.#gate.pass('app.rename', async () => {
|
|
20
|
+
const response = await fetch('/api/app', {
|
|
21
|
+
method: 'PUT',
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({ title })
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
throw new Error('app.rename failed');
|
|
27
|
+
return appInfo.parse(await response.json());
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class MemoryAppInfoApi {
|
|
32
|
+
#server;
|
|
33
|
+
#title;
|
|
34
|
+
constructor(title, options) {
|
|
35
|
+
this.#server = new MemoryServer('app', options);
|
|
36
|
+
this.#title = title;
|
|
37
|
+
}
|
|
38
|
+
read() {
|
|
39
|
+
return this.#server.reply('read', () => ({ title: this.#title }));
|
|
40
|
+
}
|
|
41
|
+
rename(title) {
|
|
42
|
+
return this.#server.reply('rename', () => {
|
|
43
|
+
this.#title = title;
|
|
44
|
+
return { title };
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class AppStore extends StoreBase {
|
|
49
|
+
initialState() {
|
|
50
|
+
return { title: this.deps.remembered.read() ?? '' };
|
|
51
|
+
}
|
|
52
|
+
async load() {
|
|
53
|
+
try {
|
|
54
|
+
this.#keep(await this.deps.api.read());
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async rename(title) {
|
|
61
|
+
this.#keep(await this.deps.api.rename(title));
|
|
62
|
+
}
|
|
63
|
+
#keep({ title }) {
|
|
64
|
+
this.set({ title });
|
|
65
|
+
this.deps.remembered.write(title);
|
|
66
|
+
}
|
|
67
|
+
}
|
package/dist/appTitle.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const AppSettings_base: import("../model.ts").ModelClass<import("../registered.ts").AppStores, import("../registered.ts").ModelServices, {
|
|
3
|
+
isRenaming: z.ZodDefault<z.ZodBoolean>;
|
|
4
|
+
}, []>;
|
|
5
|
+
export declare class AppSettings extends AppSettings_base {
|
|
6
|
+
get title(): string;
|
|
7
|
+
get canRename(): boolean;
|
|
8
|
+
rename: import("../model.ts").Command<{
|
|
9
|
+
title: z.ZodString;
|
|
10
|
+
}, Promise<void>>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { appTitle } from "../appTitle.js";
|
|
3
|
+
import { command } from "../model.js";
|
|
4
|
+
import { Model } from "../registered.js";
|
|
5
|
+
export class AppSettings extends Model({
|
|
6
|
+
state: { isRenaming: z.boolean().default(false) }
|
|
7
|
+
}) {
|
|
8
|
+
get title() {
|
|
9
|
+
return this.stores.app.state.title;
|
|
10
|
+
}
|
|
11
|
+
get canRename() {
|
|
12
|
+
return this.stores.session.state.user?.isAdmin === true;
|
|
13
|
+
}
|
|
14
|
+
rename = command({ input: { title: appTitle } }, async ({ title }) => {
|
|
15
|
+
this.set({ isRenaming: true });
|
|
16
|
+
try {
|
|
17
|
+
await this.stores.app.rename(title);
|
|
18
|
+
this.stores.toasts.show({ tone: 'success', title: 'Name saved' });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
this.stores.toasts.show({
|
|
22
|
+
tone: 'error',
|
|
23
|
+
title: 'Couldn’t save the name',
|
|
24
|
+
description: 'Check your connection and try again.'
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
this.set({ isRenaming: false });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -24,7 +24,7 @@ declare const Toasts_base: import("../store.ts").StoreClass<{
|
|
|
24
24
|
title: z.ZodString;
|
|
25
25
|
description: z.ZodNullable<z.ZodString>;
|
|
26
26
|
}, z.core.$strip>>>;
|
|
27
|
-
}, []
|
|
27
|
+
}, [], Record<never, never>>;
|
|
28
28
|
export declare class Toasts extends Toasts_base {
|
|
29
29
|
show({ description, ...shown }: ToastInput): void;
|
|
30
30
|
dismiss(id: string): void;
|
package/dist/builtins/Toasts.js
CHANGED