@trypattern/sdk 0.1.25 → 0.1.26

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/app.d.ts CHANGED
@@ -10,6 +10,7 @@ import type { Clock } from './clock.ts';
10
10
  import type { Gate } from './gate.ts';
11
11
  import { ActionsStore } from './actions.ts';
12
12
  import type { Action, ActionsBuilder } from './actions.ts';
13
+ import { Device } from './builtins/Device.ts';
13
14
  import { Toasts } from './builtins/Toasts.ts';
14
15
  import { PageStore } from './page.ts';
15
16
  import type { BuiltInModels } from './registered.ts';
@@ -83,6 +84,7 @@ export type StoresOf<Group extends Tables, Custom, Item extends Action = Action>
83
84
  app: AppStore;
84
85
  actions: ActionsStore<Item>;
85
86
  toasts: Toasts;
87
+ device: Device;
86
88
  page: PageStore;
87
89
  } & TableStores<Group> & PublicStores<Group> & BlockStores<Group> & Custom;
88
90
  type BlockTablesMissing<Name extends string> = StaticTypeError<`The ${Name} block keeps its rows in Pattern's own tables: add them to tables in schema.ts (for bookings, ...bookingTables from '@trypattern/sdk/schema'), then run app migration add_${Name}`>;
package/dist/app.js CHANGED
@@ -10,6 +10,7 @@ import { AppSettings } from "./builtins/AppSettings.js";
10
10
  import { BUILT_IN_GROUPS, builtInActions } from "./builtins/actions.js";
11
11
  import { Palette } from "./builtins/Palette.js";
12
12
  import { SignIn } from "./builtins/SignIn.js";
13
+ import { Device } from "./builtins/Device.js";
13
14
  import { Toasts } from "./builtins/Toasts.js";
14
15
  import { PageStore } from "./page.js";
15
16
  import { assertNotBuilding, snapshotsOf } from "./store.js";
@@ -126,15 +127,16 @@ export function defineApp(definition) {
126
127
  new block.Store(cache.persisted(`stores.${block.name}`), api.services)
127
128
  ]));
128
129
  const toasts = new Toasts(cache.persisted('stores.toasts'), {});
130
+ const device = new Device(cache.persisted('stores.device'), {});
129
131
  const clash = clashOf([
130
- ['session', 'app', 'actions', 'toasts', 'page'],
132
+ ['session', 'app', 'actions', 'toasts', 'device', 'page'],
131
133
  Object.keys(data.tables),
132
134
  Object.keys(data.publicViews),
133
135
  Object.keys(blockStores),
134
136
  Object.keys(custom)
135
137
  ]);
136
138
  if (clash !== undefined) {
137
- 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, each block and each store in src/stores/index.ts need their own name.`);
139
+ throw new Error(`Two stores are called "${clash}". The built-in session, app, actions, toasts, device and page stores, each table, each table's public view, each block and each store in src/stores/index.ts need their own name.`);
138
140
  }
139
141
  const actions = new ActionsStore(this.#page, [...(definition.actions ?? []), ...builtInActions], [
140
142
  ...(definition.actionGroups ?? []),
@@ -145,6 +147,7 @@ export function defineApp(definition) {
145
147
  app,
146
148
  actions,
147
149
  toasts,
150
+ device,
148
151
  page: this.#page,
149
152
  ...data.tables,
150
153
  ...data.publicViews,
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { Persisted } from '../state.ts';
3
+ declare const Device_base: import("../store.ts").StoreClass<{
4
+ today: z.ZodDefault<z.ZodString>;
5
+ isCompact: z.ZodDefault<z.ZodBoolean>;
6
+ }, [], Record<never, never>>;
7
+ export declare class Device extends Device_base {
8
+ #private;
9
+ constructor(persisted: Persisted, services: Record<never, never>);
10
+ protected initialState(): {
11
+ today: string;
12
+ isCompact: boolean;
13
+ };
14
+ }
15
+ export {};
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import { defineStore } from "../store.js";
3
+ const Store = defineStore();
4
+ const COMPACT = '(max-width: 767px)';
5
+ function dayOf(at) {
6
+ const month = String(at.getMonth() + 1).padStart(2, '0');
7
+ const day = String(at.getDate()).padStart(2, '0');
8
+ return `${at.getFullYear()}-${month}-${day}`;
9
+ }
10
+ function msUntilTomorrow(at) {
11
+ const tomorrow = new Date(at);
12
+ tomorrow.setHours(24, 0, 1, 0);
13
+ return tomorrow.getTime() - at.getTime();
14
+ }
15
+ function compactQuery() {
16
+ return typeof window === 'undefined' || !('matchMedia' in window)
17
+ ? undefined
18
+ : window.matchMedia(COMPACT);
19
+ }
20
+ export class Device extends Store({
21
+ state: {
22
+ today: z.string().default(''),
23
+ isCompact: z.boolean().default(false)
24
+ }
25
+ }) {
26
+ #midnight;
27
+ constructor(persisted, services) {
28
+ super(persisted, services);
29
+ const query = compactQuery();
30
+ if (query === undefined)
31
+ return;
32
+ query.addEventListener('change', () => this.set({ isCompact: query.matches }));
33
+ const refresh = () => this.#refreshToday();
34
+ window.addEventListener('focus', refresh);
35
+ document.addEventListener('visibilitychange', refresh);
36
+ this.#waitForMidnight();
37
+ }
38
+ initialState() {
39
+ return {
40
+ today: dayOf(new Date()),
41
+ isCompact: compactQuery()?.matches ?? false
42
+ };
43
+ }
44
+ #refreshToday() {
45
+ this.set({ today: dayOf(new Date()) });
46
+ this.#waitForMidnight();
47
+ }
48
+ #waitForMidnight() {
49
+ clearTimeout(this.#midnight);
50
+ this.#midnight = setTimeout(() => this.#refreshToday(), msUntilTomorrow(new Date()));
51
+ }
52
+ }
@@ -2,6 +2,7 @@ import type { AppSnapshotShape, ModelServicesOf, NavigateCommand, PressKeyComman
2
2
  import { z } from 'zod';
3
3
  import type { Action, DefineActions } from './actions.ts';
4
4
  import type { ServiceClient, Services } from './services.ts';
5
+ import type { StoreFactory } from './store.ts';
5
6
  import type { Instances, MessagesOf, ModelClassLike, ModelSnapshots } from './model.ts';
6
7
  import type { BUILT_IN_GROUPS } from './builtins/actions.ts';
7
8
  import type { Account } from './builtins/Account.ts';
@@ -48,7 +49,7 @@ export type ActionContext = {
48
49
  };
49
50
  export type ModelServices = ModelServicesOf<AppPath>;
50
51
  export declare const Model: <S extends import("./state.ts").StateSpec = Record<never, never>, const P extends readonly (keyof S & string)[] = []>(config?: import("./model.ts").ModelConfig<S, P>) => import("./model.ts").ModelClass<AppStores, ModelServices, S, P>;
51
- export declare const Store: <S extends import("./state.ts").StateSpec = Record<never, never>, const P extends readonly (keyof S & string)[] = []>(config: import("./store.ts").StoreConfig<S, P>) => import("./store.ts").StoreClass<S, P, ServiceClient<Empty>>;
52
+ export declare const Store: StoreFactory<AppServices>;
52
53
  export declare const defineActions: DefineActions<ActionContext, RegisteredAction, AppCommand>;
53
54
  export declare const appPath: z.ZodCustom<AppPath, AppPath>;
54
55
  type ActionInput = Pick<AppAction, 'id' | 'label' | 'group' | 'icon'> & Partial<Pick<AppAction, 'keys' | 'keywords' | 'isDanger' | 'isSearchOnly' | 'isHidden'>> & {
@@ -378,14 +378,6 @@ function misplacedSourceFiles() {
378
378
  return ownedFiles(SRC_DIRECTORY).filter((path) => !isWhereItBelongs(relative(SRC_DIRECTORY, path)) || !isInItsFolder(path));
379
379
  }
380
380
  const SCHEMA_FILE = join(ROOT, 'schema.ts');
381
- function rowTypeName(table) {
382
- const one = table.endsWith('ies')
383
- ? `${table.slice(0, -3)}y`
384
- : table.endsWith('s')
385
- ? table.slice(0, -1)
386
- : table;
387
- return `${one.charAt(0).toUpperCase()}${one.slice(1)}`;
388
- }
389
381
  function isExported(node) {
390
382
  return (ts.canHaveModifiers(node) &&
391
383
  (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
@@ -403,8 +395,6 @@ function schemaFindings() {
403
395
  return [];
404
396
  const text = readFileSync(SCHEMA_FILE, 'utf8');
405
397
  const source = ts.createSourceFile(SCHEMA_FILE, text, ts.ScriptTarget.Latest, true);
406
- const tables = [];
407
- const rowTypes = new Set();
408
398
  let exportsTables = false;
409
399
  let exportsAdmins = false;
410
400
  const visit = (node) => {
@@ -422,29 +412,6 @@ function schemaFindings() {
422
412
  definesTables(declaration.initializer, source))) {
423
413
  exportsTables = true;
424
414
  }
425
- if (ts.isCallExpression(node) &&
426
- node.expression.getText(source) === 'defineTables') {
427
- const [definitions] = node.arguments;
428
- if (definitions !== undefined &&
429
- ts.isObjectLiteralExpression(definitions)) {
430
- for (const property of definitions.properties) {
431
- const name = property.name;
432
- if (name !== undefined &&
433
- (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
434
- tables.push({ key: name.text, offset: property.getStart(source) });
435
- }
436
- }
437
- }
438
- }
439
- if (ts.isTypeAliasDeclaration(node) &&
440
- isExported(node) &&
441
- ts.isTypeReferenceNode(node.type) &&
442
- node.type.typeName.getText(source) === 'Row') {
443
- const [argument] = node.type.typeArguments ?? [];
444
- if (argument !== undefined && ts.isTypeQueryNode(argument)) {
445
- rowTypes.add(argument.exprName.getText(source));
446
- }
447
- }
448
415
  ts.forEachChild(node, visit);
449
416
  };
450
417
  visit(source);
@@ -456,18 +423,9 @@ function schemaFindings() {
456
423
  }
457
424
  ];
458
425
  }
459
- const missingAdmins = exportsAdmins
426
+ return exportsAdmins
460
427
  ? []
461
428
  : [{ offset: 0, fix: 'export const admins: string[] = []' }];
462
- return [
463
- ...missingAdmins,
464
- ...tables
465
- .filter(({ key }) => !rowTypes.has(`tables.${key}`))
466
- .map(({ key, offset }) => ({
467
- offset,
468
- fix: `export type ${rowTypeName(key)} = Row<typeof tables.${key}>`
469
- }))
470
- ];
471
429
  }
472
430
  const ACCESS_FLOWS_FILE = join(ROOT, 'flows', 'Access.flow.ts');
473
431
  function toasterPlacements() {
@@ -22,7 +22,7 @@ export declare const DEV_ONLY_SIGN_IN_MESSAGE = "The server has exactly one dev-
22
22
  export declare const STATE_VIA_HOOKS_MESSAGE = "A screen reads only its model: const issueList = useModel('issueList'), then issueList.issues, issueList.isLoading and issueList.createIssue(). Reading .state, .models, .stores or getSnapshot() directly does not subscribe, so the screen would show stale data. If the screen needs something from a store, add a getter for it to the model.";
23
23
  export declare const CLASSES_NOT_FACTORIES_MESSAGE = "A function that returns an object of methods is a class in disguise: write it as a class, with private fields for its state and methods for its behaviour. One shape for everything that has state keeps the codebase predictable. Plain functions are for pure helpers that compute a value (sortProjects, newClientSideId).";
24
24
  export declare const ACCESS_FLOWS_MESSAGE = "flows/Access.flow.ts runs Pattern's generated access flows, which prove every table's access rules against the seed on each app test. Keep it: `import { accessFlows } from '@trypattern/sdk/flows'` and `accessFlows({ App })`.";
25
- export declare const SCHEMA_TABLES_MESSAGE = "schema.ts defines every saved table in one place and says who may do what: `export const tables = defineTables({ issues: { columns: { title: z.string() }, access: { read: 'signedIn', create: 'signedIn', update: 'signedIn', delete: 'signedIn' }, writes: 'optimistic' } })`. It exports a row type for each table, named for one row (`export type Issue = Row<typeof tables.issues>`), and the emails of the people who run the app, `export const admins: string[] = []`, even when there are none yet, so the question is answered on purpose. Add the line shown.";
25
+ export declare const SCHEMA_TABLES_MESSAGE = "schema.ts defines every saved table in one place and says who may do what: `export const tables = defineTables({ issues: { columns: { title: z.string() }, access: { read: 'signedIn', create: 'signedIn', update: 'signedIn', delete: 'signedIn' }, writes: 'optimistic' } })`. When another file needs a table's row type, schema.ts exports it, named for one row (`export type Issue = Row<typeof tables.issues>`). It also exports the emails of the people who run the app, `export const admins: string[] = []`, even when there are none yet, so the question is answered on purpose. Add the line shown.";
26
26
  export declare const NO_UNUSED_EXPORTS_MESSAGE = "Export only what another file imports. Nothing imports this, so remove `export` (and the code itself if this file does not use it either). Unused exports hide dead code and make a file look like it offers more than it does. Pattern reads `tables` from schema.ts and `seed` from seed.ts, and vite.config.ts and server/worker.ts default-export their config, so those stay.";
27
27
  export declare const NO_RE_EXPORTS_MESSAGE = "Do not pass imports through: a file exports only what it defines. Import the thing from where it lives instead (command from @trypattern/sdk, a component from its own file), so every import names the real source. server/worker.ts is the one exception: Cloudflare runs the Workflow and Durable Object classes its Worker exports, so it may export them from where they live.";
28
28
  export declare const EXPLICIT_ACCESS_MESSAGE = "Mark every class member public or private (or protected, to override a base class member), so a reader sees at a glance what other code may call: `public setSort = command(...)`, `public override onPageOpen()`, `private loadIssues()`. Use the word private, never #name, so there is one way to say it. Constructors need neither.";
@@ -30,7 +30,7 @@ export declare const IMPORT_FROM_ROOT_MESSAGE = "Import project files by their p
30
30
  export declare const MODEL_MEMBERS_MESSAGE = "A model shows the outside world three kinds of public member, so a reader can tell them apart at a glance: getters are its answers (public get hasNext()), command() fields are what it can do (public openNext = command(() => ...)), and onPageOpen() runs when its page opens. Everything else is private, and a private helper that works something out is a method (private neighbours()), never a getter, because Pattern publishes every getter to the screen and the snapshot. A model has no `actions`: menu entries and shortcuts live in src/actions/.";
31
31
  export declare const MODEL_BY_NAME_MESSAGE = "Keep the model whole and read it by name: const issueList = useModel('issueList'), then issueList.isLoading and onClick={issueList.createIssue}. Destructuring it hides where each value comes from and leaves loose names a reader has to trace back.";
32
32
  export declare const USE_MODEL_WHERE_MESSAGE = "useModel is for screens (src/screens/) and layouts (src/layouts/). Other components take what they show as props and the commands they call as callbacks, so the screen decides what they read: <IssueListRow issue={issue} onDelete={issueList.deleteIssue} />.";
33
- export declare const PURE_GETTERS_MESSAGE = "A getter works an answer out from this.state and this.stores.<name>.state and nothing else. It never changes anything (this.set, a store method, this.app.navigate), never waits, and never reads the clock, randomness or a private field, because Pattern caches each getter until the state it read changes, and flows compare its value. Put the change in a command.";
33
+ export declare const PURE_GETTERS_MESSAGE = "A getter works an answer out from this.state and this.stores.<name>.state and nothing else. It never changes anything (this.set, a store method, this.app.navigate), never waits, and never reads the clock, randomness or a private field, because Pattern caches each getter until the state it read changes, and flows compare its value. Put the change in a command. For the date today read this.stores.device.state.today (YYYY-MM-DD on this device, kept current past midnight), and for a phone-width screen this.stores.device.state.isCompact.";
34
34
  export declare const ACTIONS_WHERE_MESSAGE = "Menu entries and shortcuts are actions, and actions live only in src/actions/: one file per thing they act on, `export const issueActions = defineActions(({ page, models }) => [action({ id: 'issues.next', label: 'Next issue', keys: ['j'], send: [{ type: 'issueDetail.openNext' }] })])`, listed in src/actions/index.ts. If a person can see it in a menu or press a key for it, it is an action; what it does is a command on a model, and an action only sends commands. Models never build actions.";
35
35
  export declare const PURE_ACTIONS_MESSAGE = "An actions file works its entries out from `page` and `models` (each model's state and answers) and nothing else, because Pattern reruns it only when something it read changes, and flows compare the result. It never waits, never uses `this`, never reads the clock or randomness, and imports only types from src/models/, src/stores/ and src/components/. If an entry needs something a model does not answer yet, add a getter to that model.";
36
36
  export declare const USE_PRIMITIVES_MESSAGE = "Write <Button>, <IconButton>, <TextField>, <Select>, <Switch> or <Dialog> from @trypattern/sdk/react instead of this element. The primitives carry the focus, keyboard, disabled, pending and error behaviour and the design system's look, so a screen only says what the control does. If no primitive fits, the gap is in the SDK; do not build around it here.";
@@ -22,7 +22,7 @@ export const DEV_ONLY_SIGN_IN_MESSAGE = 'The server has exactly one dev-only swi
22
22
  export const STATE_VIA_HOOKS_MESSAGE = "A screen reads only its model: const issueList = useModel('issueList'), then issueList.issues, issueList.isLoading and issueList.createIssue(). Reading .state, .models, .stores or getSnapshot() directly does not subscribe, so the screen would show stale data. If the screen needs something from a store, add a getter for it to the model.";
23
23
  export const CLASSES_NOT_FACTORIES_MESSAGE = 'A function that returns an object of methods is a class in disguise: write it as a class, with private fields for its state and methods for its behaviour. One shape for everything that has state keeps the codebase predictable. Plain functions are for pure helpers that compute a value (sortProjects, newClientSideId).';
24
24
  export const ACCESS_FLOWS_MESSAGE = "flows/Access.flow.ts runs Pattern's generated access flows, which prove every table's access rules against the seed on each app test. Keep it: `import { accessFlows } from '@trypattern/sdk/flows'` and `accessFlows({ App })`.";
25
- export const SCHEMA_TABLES_MESSAGE = "schema.ts defines every saved table in one place and says who may do what: `export const tables = defineTables({ issues: { columns: { title: z.string() }, access: { read: 'signedIn', create: 'signedIn', update: 'signedIn', delete: 'signedIn' }, writes: 'optimistic' } })`. It exports a row type for each table, named for one row (`export type Issue = Row<typeof tables.issues>`), and the emails of the people who run the app, `export const admins: string[] = []`, even when there are none yet, so the question is answered on purpose. Add the line shown.";
25
+ export const SCHEMA_TABLES_MESSAGE = "schema.ts defines every saved table in one place and says who may do what: `export const tables = defineTables({ issues: { columns: { title: z.string() }, access: { read: 'signedIn', create: 'signedIn', update: 'signedIn', delete: 'signedIn' }, writes: 'optimistic' } })`. When another file needs a table's row type, schema.ts exports it, named for one row (`export type Issue = Row<typeof tables.issues>`). It also exports the emails of the people who run the app, `export const admins: string[] = []`, even when there are none yet, so the question is answered on purpose. Add the line shown.";
26
26
  export const NO_UNUSED_EXPORTS_MESSAGE = 'Export only what another file imports. Nothing imports this, so remove `export` (and the code itself if this file does not use it either). Unused exports hide dead code and make a file look like it offers more than it does. Pattern reads `tables` from schema.ts and `seed` from seed.ts, and vite.config.ts and server/worker.ts default-export their config, so those stay.';
27
27
  export const NO_RE_EXPORTS_MESSAGE = 'Do not pass imports through: a file exports only what it defines. Import the thing from where it lives instead (command from @trypattern/sdk, a component from its own file), so every import names the real source. server/worker.ts is the one exception: Cloudflare runs the Workflow and Durable Object classes its Worker exports, so it may export them from where they live.';
28
28
  export const EXPLICIT_ACCESS_MESSAGE = 'Mark every class member public or private (or protected, to override a base class member), so a reader sees at a glance what other code may call: `public setSort = command(...)`, `public override onPageOpen()`, `private loadIssues()`. Use the word private, never #name, so there is one way to say it. Constructors need neither.';
@@ -30,7 +30,7 @@ export const IMPORT_FROM_ROOT_MESSAGE = 'Import project files by their path from
30
30
  export const MODEL_MEMBERS_MESSAGE = 'A model shows the outside world three kinds of public member, so a reader can tell them apart at a glance: getters are its answers (public get hasNext()), command() fields are what it can do (public openNext = command(() => ...)), and onPageOpen() runs when its page opens. Everything else is private, and a private helper that works something out is a method (private neighbours()), never a getter, because Pattern publishes every getter to the screen and the snapshot. A model has no `actions`: menu entries and shortcuts live in src/actions/.';
31
31
  export const MODEL_BY_NAME_MESSAGE = "Keep the model whole and read it by name: const issueList = useModel('issueList'), then issueList.isLoading and onClick={issueList.createIssue}. Destructuring it hides where each value comes from and leaves loose names a reader has to trace back.";
32
32
  export const USE_MODEL_WHERE_MESSAGE = 'useModel is for screens (src/screens/) and layouts (src/layouts/). Other components take what they show as props and the commands they call as callbacks, so the screen decides what they read: <IssueListRow issue={issue} onDelete={issueList.deleteIssue} />.';
33
- export const PURE_GETTERS_MESSAGE = 'A getter works an answer out from this.state and this.stores.<name>.state and nothing else. It never changes anything (this.set, a store method, this.app.navigate), never waits, and never reads the clock, randomness or a private field, because Pattern caches each getter until the state it read changes, and flows compare its value. Put the change in a command.';
33
+ export const PURE_GETTERS_MESSAGE = 'A getter works an answer out from this.state and this.stores.<name>.state and nothing else. It never changes anything (this.set, a store method, this.app.navigate), never waits, and never reads the clock, randomness or a private field, because Pattern caches each getter until the state it read changes, and flows compare its value. Put the change in a command. For the date today read this.stores.device.state.today (YYYY-MM-DD on this device, kept current past midnight), and for a phone-width screen this.stores.device.state.isCompact.';
34
34
  export const ACTIONS_WHERE_MESSAGE = "Menu entries and shortcuts are actions, and actions live only in src/actions/: one file per thing they act on, `export const issueActions = defineActions(({ page, models }) => [action({ id: 'issues.next', label: 'Next issue', keys: ['j'], send: [{ type: 'issueDetail.openNext' }] })])`, listed in src/actions/index.ts. If a person can see it in a menu or press a key for it, it is an action; what it does is a command on a model, and an action only sends commands. Models never build actions.";
35
35
  export const PURE_ACTIONS_MESSAGE = "An actions file works its entries out from `page` and `models` (each model's state and answers) and nothing else, because Pattern reruns it only when something it read changes, and flows compare the result. It never waits, never uses `this`, never reads the clock or randomness, and imports only types from src/models/, src/stores/ and src/components/. If an entry needs something a model does not answer yet, add a getter to that model.";
36
36
  export const USE_PRIMITIVES_MESSAGE = "Write <Button>, <IconButton>, <TextField>, <Select>, <Switch> or <Dialog> from @trypattern/sdk/react instead of this element. The primitives carry the focus, keyboard, disabled, pending and error behaviour and the design system's look, so a screen only says what the control does. If no primitive fits, the gap is in the SDK; do not build around it here.";
package/dist/store.d.ts CHANGED
@@ -34,7 +34,8 @@ export declare abstract class DeclaredStore<State extends object, Client = Recor
34
34
  export type StoreClass<S extends StateSpec, P extends readonly (keyof S & string)[], Client = Record<never, never>> = (abstract new (persisted: Persisted, services: Client) => DeclaredStore<StateOf<S>, Client>) & {
35
35
  readonly config: StoreConfig<S, P>;
36
36
  };
37
- export declare function defineStore<Client>(): <S extends StateSpec = Record<never, never>, const P extends readonly (keyof S & string)[] = []>(config: StoreConfig<S, P>) => StoreClass<S, P, Client>;
37
+ export type StoreFactory<Client> = <S extends StateSpec = Record<never, never>, const P extends readonly (keyof S & string)[] = []>(config: StoreConfig<S, P>) => StoreClass<S, P, Client>;
38
+ export declare function defineStore<Client>(): StoreFactory<Client>;
38
39
  export type Snapshots<Group> = {
39
40
  [Key in keyof Group]: Group[Key] extends {
40
41
  readonly state: infer State;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trypattern/sdk",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",