@systemfsoftware/xstate-store 0.0.0-bootstrap.0 → 5.0.0-beta.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/dist/undo.mjs ADDED
@@ -0,0 +1,222 @@
1
+ import { r as createEnqueueObject, t as appendInternalEventTypes } from "./store-BMZdYhoE.mjs";
2
+ // src/undo.ts
3
+ function undoRedoFromLogic(logic, options) {
4
+ const historyLimit = options?.strategy === "snapshot" ? options.historyLimit ?? Infinity : Infinity;
5
+ if (options?.strategy === "snapshot") {
6
+ const restore = (snapshot, historicalSnapshot, direction, past, future) => {
7
+ if (!options.restore) return [{
8
+ ...snapshot,
9
+ ...historicalSnapshot,
10
+ past,
11
+ future
12
+ }, []];
13
+ const effects = [];
14
+ const triggeredEvents = [];
15
+ const enqueue = createEnqueueObject(effects, (event) => {
16
+ triggeredEvents.push(event);
17
+ });
18
+ let triggeredSnapshot = {
19
+ ...snapshot,
20
+ ...historicalSnapshot,
21
+ context: options.restore({
22
+ current: snapshot.context,
23
+ next: historicalSnapshot.context,
24
+ direction
25
+ }, enqueue)
26
+ };
27
+ for (const triggeredEvent of triggeredEvents) {
28
+ const [nextTriggeredSnapshot, triggeredEffects] = logic.transition(triggeredSnapshot, triggeredEvent);
29
+ triggeredSnapshot = nextTriggeredSnapshot;
30
+ effects.push(...triggeredEffects);
31
+ }
32
+ return [{
33
+ ...triggeredSnapshot,
34
+ past,
35
+ future
36
+ }, effects];
37
+ };
38
+ return {
39
+ ...logic,
40
+ eventTypes: appendInternalEventTypes(logic.eventTypes, ["undo", "redo"], "undoRedo"),
41
+ getInitialSnapshot: () => ({
42
+ ...logic.getInitialSnapshot(),
43
+ past: [],
44
+ future: []
45
+ }),
46
+ transition: (snapshot, event) => {
47
+ if (event.type === "undo") {
48
+ const past = snapshot.past.slice();
49
+ const future = snapshot.future.slice();
50
+ if (!past.length) return [snapshot, []];
51
+ const currentSnapshot = {
52
+ status: snapshot.status,
53
+ context: snapshot.context,
54
+ output: snapshot.output,
55
+ error: snapshot.error
56
+ };
57
+ const lastTransactionId = past[past.length - 1].transactionId;
58
+ let newSnapshot;
59
+ if (lastTransactionId === void 0) {
60
+ newSnapshot = past.pop().snapshot;
61
+ future.unshift({
62
+ snapshot: currentSnapshot,
63
+ transactionId: lastTransactionId
64
+ });
65
+ } else {
66
+ const transactionSnapshots = [];
67
+ while (past.length > 0 && past[past.length - 1].transactionId === lastTransactionId) transactionSnapshots.unshift(past.pop());
68
+ newSnapshot = transactionSnapshots[0].snapshot;
69
+ future.unshift({
70
+ snapshot: currentSnapshot,
71
+ transactionId: lastTransactionId
72
+ });
73
+ }
74
+ return restore(snapshot, newSnapshot, "undo", past, future);
75
+ }
76
+ if (event.type === "redo") {
77
+ const past = snapshot.past.slice();
78
+ const future = snapshot.future.slice();
79
+ if (!future.length) return [snapshot, []];
80
+ const firstTransactionId = future[0].transactionId;
81
+ const currentSnapshot = {
82
+ status: snapshot.status,
83
+ context: snapshot.context,
84
+ output: snapshot.output,
85
+ error: snapshot.error
86
+ };
87
+ let newSnapshot;
88
+ if (firstTransactionId === void 0) newSnapshot = future.shift().snapshot;
89
+ else while (future.length > 0 && future[0].transactionId === firstTransactionId) newSnapshot = future.shift().snapshot;
90
+ past.push({
91
+ snapshot: currentSnapshot,
92
+ transactionId: firstTransactionId
93
+ });
94
+ const excessCount = past.length - historyLimit;
95
+ if (excessCount > 0) past.splice(0, excessCount);
96
+ return restore(snapshot, newSnapshot, "redo", past, future);
97
+ }
98
+ const [state, effects] = logic.transition(snapshot, event);
99
+ if (options?.skipEvent?.(event, snapshot)) return [{
100
+ ...state,
101
+ past: snapshot.past,
102
+ future: snapshot.future
103
+ }, effects];
104
+ const currentSnapshot = {
105
+ status: snapshot.status,
106
+ context: snapshot.context,
107
+ output: snapshot.output,
108
+ error: snapshot.error
109
+ };
110
+ const lastPastSnapshot = snapshot.past[snapshot.past.length - 1]?.snapshot;
111
+ if (lastPastSnapshot && options?.compare?.(lastPastSnapshot, currentSnapshot)) return [{
112
+ ...state,
113
+ past: snapshot.past,
114
+ future: []
115
+ }, effects];
116
+ const past = snapshot.past.slice();
117
+ past.push({
118
+ snapshot: currentSnapshot,
119
+ transactionId: options?.getTransactionId?.(event, snapshot)
120
+ });
121
+ const excessCount = past.length - historyLimit;
122
+ if (excessCount > 0) past.splice(0, excessCount);
123
+ return [{
124
+ ...state,
125
+ past,
126
+ future: []
127
+ }, effects];
128
+ }
129
+ };
130
+ }
131
+ return {
132
+ ...logic,
133
+ eventTypes: appendInternalEventTypes(logic.eventTypes, ["undo", "redo"], "undoRedo"),
134
+ getInitialSnapshot: () => ({
135
+ ...logic.getInitialSnapshot(),
136
+ events: [],
137
+ undoStack: []
138
+ }),
139
+ transition: (snapshot, event) => {
140
+ if (event.type === "undo") {
141
+ const events = snapshot.events.slice();
142
+ const undoStack = snapshot.undoStack.slice();
143
+ if (!events.length) return [snapshot, []];
144
+ const lastTransactionId = events[events.length - 1].transactionId;
145
+ if (lastTransactionId === void 0) {
146
+ const ev = events.pop();
147
+ undoStack.push(ev);
148
+ } else while (true) {
149
+ const ev = events.pop();
150
+ undoStack.push(ev);
151
+ if (!events.length || events[events.length - 1].transactionId !== lastTransactionId) break;
152
+ }
153
+ let state = {
154
+ ...logic.getInitialSnapshot(),
155
+ events,
156
+ undoStack
157
+ };
158
+ for (const { event: ev } of events) {
159
+ const [newState] = logic.transition(state, ev);
160
+ state = {
161
+ ...newState,
162
+ events,
163
+ undoStack
164
+ };
165
+ }
166
+ return [state, []];
167
+ }
168
+ if (event.type === "redo") {
169
+ const events = snapshot.events.slice();
170
+ const undoStack = snapshot.undoStack.slice();
171
+ if (!undoStack.length) return [{
172
+ ...snapshot,
173
+ events,
174
+ undoStack
175
+ }, []];
176
+ const lastTransactionId = undoStack[undoStack.length - 1].transactionId;
177
+ let state = {
178
+ ...snapshot,
179
+ events,
180
+ undoStack
181
+ };
182
+ const allEffects = [];
183
+ if (lastTransactionId === void 0) {
184
+ const undoEvent = undoStack.pop();
185
+ events.push(undoEvent);
186
+ const [newState, effects] = logic.transition(state, undoEvent.event);
187
+ state = {
188
+ ...newState,
189
+ events,
190
+ undoStack
191
+ };
192
+ allEffects.push(...effects);
193
+ } else while (undoStack.length > 0 && undoStack[undoStack.length - 1].transactionId === lastTransactionId) {
194
+ const undoEvent = undoStack.pop();
195
+ events.push(undoEvent);
196
+ const [newState, effects] = logic.transition(state, undoEvent.event);
197
+ state = {
198
+ ...newState,
199
+ events,
200
+ undoStack
201
+ };
202
+ allEffects.push(...effects);
203
+ }
204
+ return [state, allEffects];
205
+ }
206
+ const [state, effects] = logic.transition(snapshot, event);
207
+ const events = options?.skipEvent?.(event, snapshot) ? snapshot.events : snapshot.events.concat({
208
+ event,
209
+ transactionId: options?.getTransactionId?.(event, snapshot)
210
+ });
211
+ return [{
212
+ ...state,
213
+ events,
214
+ undoStack: []
215
+ }, effects];
216
+ }
217
+ };
218
+ }
219
+ function undoRedo(options) {
220
+ return (logic) => undoRedoFromLogic(logic, options);
221
+ }
222
+ export { undoRedo };
@@ -0,0 +1,35 @@
1
+ import { B as StoreContext, U as StoreExtension, _ as EventObject, v as EventPayloadMap } from "./types-0n0RpfhK.js";
2
+ declare const storeValidationErrorSymbol: unique symbol;
3
+ type ValidationReason = 'invalidContext' | 'invalidEvent' | 'invalidEmitted' | 'unknownEvent' | 'unknownEmitted' | 'asyncValidationUnsupported';
4
+ interface StoreValidationErrorOptions {
5
+ reason: ValidationReason;
6
+ eventType?: string;
7
+ context?: unknown;
8
+ payload?: unknown;
9
+ issues?: unknown;
10
+ }
11
+ /** Error thrown by `validateSchemas()` when schema validation fails. */
12
+ export declare class StoreValidationError extends Error {
13
+ readonly [storeValidationErrorSymbol] = true;
14
+ readonly reason: ValidationReason;
15
+ readonly eventType?: string | undefined;
16
+ readonly context?: unknown;
17
+ readonly payload?: unknown;
18
+ readonly issues?: unknown;
19
+ constructor(options: StoreValidationErrorOptions);
20
+ }
21
+ interface ValidateSchemasOptions {
22
+ context?: boolean;
23
+ events?: boolean;
24
+ emitted?: boolean;
25
+ unknownEvents?: 'throw' | 'ignore';
26
+ unknownEmitted?: 'throw' | 'ignore';
27
+ }
28
+ /**
29
+ * Validates schema-declared events, emitted events, and context at runtime.
30
+ *
31
+ * Validation is opt-in; stores with schemas only use those schemas for typing
32
+ * and metadata unless this extension is applied.
33
+ */
34
+ export declare function validateSchemas<TContext extends StoreContext, TEventPayloadMap extends EventPayloadMap, TEmitted extends EventObject>(options?: ValidateSchemasOptions): StoreExtension<TContext, TEventPayloadMap, {}, TEmitted>;
35
+ export {};
@@ -0,0 +1,138 @@
1
+ import { n as storeValidationErrorSymbol } from "./validationError-Ndy-qk0W.mjs";
2
+ // src/validate.ts
3
+ /** Error thrown by `validateSchemas()` when schema validation fails. */
4
+ var StoreValidationError = class extends Error {
5
+ [storeValidationErrorSymbol] = true;
6
+ reason;
7
+ eventType;
8
+ context;
9
+ payload;
10
+ issues;
11
+ constructor(options) {
12
+ super(getStoreValidationErrorMessage(options));
13
+ this.name = "StoreValidationError";
14
+ this.reason = options.reason;
15
+ this.eventType = options.eventType;
16
+ this.context = options.context;
17
+ this.payload = options.payload;
18
+ this.issues = options.issues;
19
+ }
20
+ };
21
+ const isDevelopment = globalThis.process?.env?.NODE_ENV !== "production";
22
+ function getStoreValidationErrorMessage(options) {
23
+ switch (options.reason) {
24
+ case "invalidContext": return "Invalid context";
25
+ case "invalidEvent": return `Invalid event "${options.eventType}"`;
26
+ case "invalidEmitted": return `Invalid emitted event "${options.eventType}"`;
27
+ case "unknownEvent": return `Unknown event "${options.eventType}"`;
28
+ case "unknownEmitted": return `Unknown emitted event "${options.eventType}"`;
29
+ case "asyncValidationUnsupported": return `Async schema validation is unsupported${options.eventType ? ` for event "${options.eventType}"` : ""}`;
30
+ }
31
+ }
32
+ function isPromiseLike(value) {
33
+ return !!value && typeof value.then === "function";
34
+ }
35
+ function getPayload(event) {
36
+ const { type: _, ...payload } = event;
37
+ return payload;
38
+ }
39
+ function hasAnySchemas(schemas) {
40
+ return !!(schemas?.context || schemas?.events || schemas?.emitted);
41
+ }
42
+ function validateSchema(schema, value, errorOptions) {
43
+ const result = schema["~standard"].validate(value);
44
+ if (isPromiseLike(result)) throw new StoreValidationError({
45
+ ...errorOptions,
46
+ reason: "asyncValidationUnsupported"
47
+ });
48
+ if (result.issues) throw new StoreValidationError({
49
+ ...errorOptions,
50
+ issues: result.issues
51
+ });
52
+ }
53
+ function validateEvent(event, schemas, eventTypes, unknownEvents) {
54
+ const eventSchemas = schemas.events;
55
+ if (!eventSchemas) return;
56
+ const payload = getPayload(event);
57
+ const schema = eventSchemas[event.type];
58
+ if (!schema) {
59
+ if (eventTypes?.includes(event.type)) return;
60
+ if (unknownEvents === "throw") throw new StoreValidationError({
61
+ reason: "unknownEvent",
62
+ eventType: event.type,
63
+ payload
64
+ });
65
+ return;
66
+ }
67
+ validateSchema(schema, payload, {
68
+ reason: "invalidEvent",
69
+ eventType: event.type,
70
+ payload
71
+ });
72
+ }
73
+ function validateContext(context, schemas) {
74
+ const schema = schemas.context;
75
+ if (!schema) return;
76
+ validateSchema(schema, context, {
77
+ reason: "invalidContext",
78
+ context
79
+ });
80
+ }
81
+ function validateEmitted(effect, schemas, unknownEmitted) {
82
+ const emittedSchemas = schemas.emitted;
83
+ if (!emittedSchemas) return;
84
+ const payload = getPayload(effect);
85
+ const schema = emittedSchemas[effect.type];
86
+ if (!schema) {
87
+ if (unknownEmitted === "throw") throw new StoreValidationError({
88
+ reason: "unknownEmitted",
89
+ eventType: effect.type,
90
+ payload
91
+ });
92
+ return;
93
+ }
94
+ validateSchema(schema, payload, {
95
+ reason: "invalidEmitted",
96
+ eventType: effect.type,
97
+ payload
98
+ });
99
+ }
100
+ /**
101
+ * Validates schema-declared events, emitted events, and context at runtime.
102
+ *
103
+ * Validation is opt-in; stores with schemas only use those schemas for typing
104
+ * and metadata unless this extension is applied.
105
+ */
106
+ function validateSchemas(options = {}) {
107
+ return (logic) => {
108
+ const schemas = logic.schemas;
109
+ if (!hasAnySchemas(schemas)) {
110
+ if (isDevelopment) (logic.warn ?? console.warn)("The \"validateSchemas\" store extension was used, but the store has no schemas to validate.");
111
+ return logic;
112
+ }
113
+ const validateContextEnabled = options.context ?? true;
114
+ const validateEventsEnabled = options.events ?? true;
115
+ const validateEmittedEnabled = options.emitted ?? true;
116
+ const unknownEvents = options.unknownEvents ?? "throw";
117
+ const unknownEmitted = options.unknownEmitted ?? "throw";
118
+ return {
119
+ ...logic,
120
+ getInitialSnapshot() {
121
+ const snapshot = logic.getInitialSnapshot();
122
+ if (validateContextEnabled) validateContext(snapshot.context, schemas);
123
+ return snapshot;
124
+ },
125
+ transition(snapshot, event) {
126
+ if (validateEventsEnabled) validateEvent(event, schemas, logic.eventTypes, unknownEvents);
127
+ const result = logic.transition(snapshot, event);
128
+ const [nextSnapshot, effects] = result;
129
+ if (validateContextEnabled) validateContext(nextSnapshot.context, schemas);
130
+ if (validateEmittedEnabled) {
131
+ for (const effect of effects) if (typeof effect !== "function") validateEmitted(effect, schemas, unknownEmitted);
132
+ }
133
+ return result;
134
+ }
135
+ };
136
+ };
137
+ }
138
+ export { StoreValidationError, validateSchemas };
@@ -0,0 +1,6 @@
1
+ // src/validationError.ts
2
+ const storeValidationErrorSymbol = Symbol.for("xstate.store.validationError");
3
+ function isStoreValidationError(error) {
4
+ return !!error && typeof error === "object" && storeValidationErrorSymbol in error;
5
+ }
6
+ export { storeValidationErrorSymbol as n, isStoreValidationError as t };
package/package.json CHANGED
@@ -1,10 +1,83 @@
1
1
  {
2
2
  "name": "@systemfsoftware/xstate-store",
3
- "version": "0.0.0-bootstrap.0",
4
- "description": "Reserved for trusted publishing from github.com/systemfsoftware/xstate.",
3
+ "license": "MIT",
4
+ "version": "5.0.0-beta.6",
5
+ "author": "Ryan Lee <drdgvhbh@gmail.com>",
5
6
  "repository": {
6
7
  "type": "git",
7
- "url": "git+https://github.com/systemfsoftware/xstate.git"
8
+ "url": "git+https://github.com/systemfsoftware/xstate.git",
9
+ "directory": "packages/xstate-store"
8
10
  },
9
- "license": "Apache-2.0"
10
- }
11
+ "homepage": "https://github.com/systemfsoftware/xstate/tree/main/packages/xstate-store#readme",
12
+ "bugs": "https://github.com/systemfsoftware/xstate/issues",
13
+ "description": "Event-driven stores with selectors, undo, persistence and validation — forked under systemfsoftware from statelyai/xstate (@xstate/store at 2146ae26).",
14
+ "type": "module",
15
+ "sideEffects": false,
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "peerDependencies": {
20
+ "@systemfsoftware/xstate": "^6.0.0-beta.65"
21
+ },
22
+ "peerDependenciesMeta": {
23
+ "@systemfsoftware/xstate": {
24
+ "optional": true
25
+ }
26
+ },
27
+ "devDependencies": {
28
+ "@effect/tsgo": "0.64.1",
29
+ "@microsoft/api-extractor": "7.59.1",
30
+ "@statelyai/inspect": "0.7.2",
31
+ "@systemfsoftware/arethetypeswrong-cli": "file:../../.deps/arethetypeswrong-cli.tgz",
32
+ "@systemfsoftware/tsconfig": "file:../../.deps/tsconfig.tgz",
33
+ "@systemfsoftware/vitest": "file:../../.deps/vitest.tgz",
34
+ "@systemfsoftware/xstate": "^6.0.0-beta.65",
35
+ "@types/node": "24.19.1",
36
+ "effect": "4.1.2",
37
+ "happy-dom": "20.14.5",
38
+ "immer": "10.2.0",
39
+ "react": "19.3.0",
40
+ "rimraf": "6.1.3",
41
+ "tsdown": "0.23.0",
42
+ "typescript": "7.0.2",
43
+ "vitest": "5.0.3",
44
+ "zod": "4.4.3"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "provenance": true
49
+ },
50
+ "exports": {
51
+ ".": {
52
+ "types": "./dist/index.d.ts",
53
+ "default": "./dist/index.mjs"
54
+ },
55
+ "./persist": {
56
+ "types": "./dist/persist.d.ts",
57
+ "default": "./dist/persist.mjs"
58
+ },
59
+ "./reset": {
60
+ "types": "./dist/reset.d.ts",
61
+ "default": "./dist/reset.mjs"
62
+ },
63
+ "./undo": {
64
+ "types": "./dist/undo.d.ts",
65
+ "default": "./dist/undo.mjs"
66
+ },
67
+ "./validate": {
68
+ "types": "./dist/validate.d.ts",
69
+ "default": "./dist/validate.mjs"
70
+ },
71
+ "./package.json": "./package.json"
72
+ },
73
+ "scripts": {
74
+ "clean": "rimraf dist",
75
+ "build": "tsdown -l warn && pnpm api:check",
76
+ "typecheck": "tsc -b",
77
+ "test": "vitest run",
78
+ "lint:tsgo": "effect-tsgo diagnostics --project tsconfig.app.json --format ${TSGO_FORMAT:-text} && effect-tsgo diagnostics --project tsconfig.test.json --format ${TSGO_FORMAT:-text}",
79
+ "attw": "attw --pack .",
80
+ "api:check": "api-extractor run && api-extractor run --config api-extractor.persist.json && api-extractor run --config api-extractor.reset.json && api-extractor run --config api-extractor.undo.json && api-extractor run --config api-extractor.validate.json",
81
+ "api:update": "api-extractor run --local && api-extractor run --local --config api-extractor.persist.json && api-extractor run --local --config api-extractor.reset.json && api-extractor run --local --config api-extractor.undo.json && api-extractor run --local --config api-extractor.validate.json"
82
+ }
83
+ }