@stratasync/mobx 0.2.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/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @stratasync/mobx
2
+
3
+ MobX reactivity adapter for the Done.
4
+
5
+ ## Overview
6
+
7
+ sync-mobx implements the reactivity adapter interface defined in `@stratasync/core` using MobX observables:
8
+
9
+ - **Observable model instances** — model properties become MobX observables
10
+ - **Computed values** — derived state from observable model properties
11
+ - **Reaction-based updates** — automatic re-rendering when synced data changes
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @stratasync/mobx
17
+ ```
18
+
19
+ Peer dependency: `mobx` ^6.0.0
20
+
21
+ ## Usage
22
+
23
+ Register the MobX adapter when initializing the sync client:
24
+
25
+ ```typescript
26
+ import { createMobxAdapter } from "@stratasync/mobx";
27
+
28
+ const adapter = createMobxAdapter();
29
+
30
+ // Pass to SyncClient configuration
31
+ const client = new SyncClient({
32
+ reactivityAdapter: adapter,
33
+ });
34
+ ```
35
+
36
+ The adapter makes model instances observable, so MobX `observer()` components and `autorun` / `reaction` will automatically track and respond to sync updates.
37
+
38
+ ## How It Works
39
+
40
+ 1. When models are hydrated from sync deltas, the adapter wraps properties with MobX observables
41
+ 2. React components wrapped with `observer()` automatically re-render when observed properties change
42
+ 3. Computed values can derive state from multiple observable model properties
@@ -0,0 +1,9 @@
1
+ import type { ReactivityAdapter } from "@stratasync/core";
2
+ export declare const mobxReactivityAdapter: ReactivityAdapter;
3
+ /**
4
+ * Registers the MobX observable.box factory with sync-core's observability system.
5
+ * Call this to enable MobX reactivity for model properties without using the full adapter.
6
+ */
7
+ export declare const initMobXObservability: () => void;
8
+ export declare const createMobXReactivity: () => ReactivityAdapter;
9
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAOV,iBAAiB,EAClB,MAAM,kBAAkB,CAAC;AA4J1B,eAAO,MAAM,qBAAqB,EAAE,iBA8CnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAExC,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,iBAGvC,CAAC"}
@@ -0,0 +1,157 @@
1
+ import { setBoxFactory } from "@stratasync/core";
2
+ import { computed, makeObservable, observable, reaction, runInAction, } from "mobx";
3
+ class MobXBox {
4
+ value;
5
+ constructor(initialValue) {
6
+ this.value = initialValue;
7
+ makeObservable(this, {
8
+ value: observable,
9
+ });
10
+ }
11
+ get() {
12
+ return this.value;
13
+ }
14
+ set(value) {
15
+ runInAction(() => {
16
+ this.value = value;
17
+ });
18
+ }
19
+ }
20
+ class MobXMap {
21
+ map;
22
+ constructor(entries) {
23
+ this.map = observable.map(entries ? new Map(entries) : undefined);
24
+ }
25
+ get(key) {
26
+ return this.map.get(key);
27
+ }
28
+ set(key, value) {
29
+ runInAction(() => {
30
+ this.map.set(key, value);
31
+ });
32
+ }
33
+ has(key) {
34
+ return this.map.has(key);
35
+ }
36
+ delete(key) {
37
+ return runInAction(() => this.map.delete(key));
38
+ }
39
+ clear() {
40
+ runInAction(() => {
41
+ this.map.clear();
42
+ });
43
+ }
44
+ keys() {
45
+ return this.map.keys();
46
+ }
47
+ values() {
48
+ return this.map.values();
49
+ }
50
+ entries() {
51
+ return this.map.entries();
52
+ }
53
+ get size() {
54
+ return this.map.size;
55
+ }
56
+ // oxlint-disable-next-line prefer-await-to-callbacks -- event listener registration
57
+ forEach(callback) {
58
+ // oxlint-disable-next-line no-array-for-each
59
+ this.map.forEach(callback);
60
+ }
61
+ }
62
+ class MobXArray {
63
+ array;
64
+ constructor(items) {
65
+ this.array = observable.array(items ?? []);
66
+ }
67
+ get(index) {
68
+ return this.array[index];
69
+ }
70
+ toArray() {
71
+ return [...this.array];
72
+ }
73
+ push(...items) {
74
+ return runInAction(() => this.array.push(...items));
75
+ }
76
+ pop() {
77
+ return runInAction(() => this.array.pop());
78
+ }
79
+ remove(predicate) {
80
+ return runInAction(() => {
81
+ const removed = [];
82
+ for (let i = this.array.length - 1; i >= 0; i -= 1) {
83
+ const item = this.array[i];
84
+ if (item !== undefined && predicate(item)) {
85
+ removed.push(item);
86
+ this.array.splice(i, 1);
87
+ }
88
+ }
89
+ return removed;
90
+ });
91
+ }
92
+ replace(items) {
93
+ runInAction(() => {
94
+ this.array.length = 0;
95
+ this.array.push(...items);
96
+ });
97
+ }
98
+ clear() {
99
+ runInAction(() => {
100
+ this.array.length = 0;
101
+ });
102
+ }
103
+ find(predicate) {
104
+ return this.array.find(predicate);
105
+ }
106
+ filter(predicate) {
107
+ return this.array.filter(predicate);
108
+ }
109
+ map(mapper) {
110
+ return this.array.map(mapper);
111
+ }
112
+ get length() {
113
+ return this.array.length;
114
+ }
115
+ [Symbol.iterator]() {
116
+ return this.array[Symbol.iterator]();
117
+ }
118
+ }
119
+ export const mobxReactivityAdapter = {
120
+ batch(fn) {
121
+ runInAction(fn);
122
+ },
123
+ computed(getter, _options) {
124
+ const c = computed(getter);
125
+ return { get: () => c.get() };
126
+ },
127
+ createArray(items, _options) {
128
+ return new MobXArray(items);
129
+ },
130
+ createBox(initialValue, _options) {
131
+ return new MobXBox(initialValue);
132
+ },
133
+ createMap(entries, _options) {
134
+ return new MobXMap(entries);
135
+ },
136
+ makeObservable(target, _options) {
137
+ return observable(target);
138
+ },
139
+ reaction(expression, effect, options) {
140
+ return reaction(expression, effect, options);
141
+ },
142
+ runInAction(fn) {
143
+ return runInAction(fn);
144
+ },
145
+ };
146
+ /**
147
+ * Registers the MobX observable.box factory with sync-core's observability system.
148
+ * Call this to enable MobX reactivity for model properties without using the full adapter.
149
+ */
150
+ export const initMobXObservability = () => {
151
+ setBoxFactory((initialValue) => observable.box(initialValue));
152
+ };
153
+ export const createMobXReactivity = () => {
154
+ initMobXObservability();
155
+ return mobxReactivityAdapter;
156
+ };
157
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,QAAQ,EACR,cAAc,EACd,UAAU,EACV,QAAQ,EACR,WAAW,GACZ,MAAM,MAAM,CAAC;AAEd,MAAM,OAAO;IACX,KAAK,CAAI;IAET,YAAY,YAAe;QACzB,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;QAC1B,cAAc,CAAC,IAAI,EAAE;YACnB,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;IACL,CAAC;IAED,GAAG;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,GAAG,CAAC,KAAQ;QACV,WAAW,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,OAAO;IACM,GAAG,CAAY;IAEhC,YAAY,OAA0B;QACpC,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG,CAAO,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC1E,CAAC;IAED,GAAG,CAAC,GAAM;QACR,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,GAAG,CAAC,GAAM,EAAE,KAAQ;QAClB,WAAW,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,GAAM;QACR,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,CAAC,GAAM;QACX,OAAO,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,KAAK;QACH,WAAW,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;IAC3B,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,oFAAoF;IACpF,OAAO,CAAC,QAAoC;QAC1C,6CAA6C;QAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,SAAS;IACI,KAAK,CAAM;IAE5B,YAAY,KAAW;QACrB,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAI,KAAK,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,GAAG,CAAC,KAAa;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,GAAG,KAAU;QAChB,OAAO,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,GAAG;QACD,OAAO,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,CAAC,SAA+B;QACpC,OAAO,WAAW,CAAC,GAAG,EAAE;YACtB,MAAM,OAAO,GAAQ,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC3B,IAAI,IAAI,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACnB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,KAAU;QAChB,WAAW,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,WAAW,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,SAA+B;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,SAA+B;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,GAAG,CAAI,MAAsB;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,CAAC,MAAM,CAAC,QAAQ,CAAC;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IACvC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED,QAAQ,CAAI,MAAe,EAAE,QAA4B;QACvD,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;IAChC,CAAC;IAED,WAAW,CACT,KAAW,EACX,QAA4B;QAE5B,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,SAAS,CACP,YAAe,EACf,QAA4B;QAE5B,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IACnC,CAAC;IAED,SAAS,CACP,OAA0B,EAC1B,QAA4B;QAE5B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED,cAAc,CAAmB,MAAS,EAAE,QAA4B;QACtE,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED,QAAQ,CACN,UAAmB,EACnB,MAA0B,EAC1B,OAAyB;QAEzB,OAAO,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,WAAW,CAAI,EAAW;QACxB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAS,EAAE;IAC9C,aAAa,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;AAChE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAsB,EAAE;IAC1D,qBAAqB,EAAE,CAAC;IACxB,OAAO,qBAAqB,CAAC;AAC/B,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { Model } from "@stratasync/core";
2
+ /**
3
+ * Creates a MobX computed value that resolves a foreign key reference.
4
+ * The model must have a store attached.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const team = computedReference<Team>(task, 'teamId', 'Team');
9
+ * const currentTeam = team.get();
10
+ * if (currentTeam) {
11
+ * renderTeamName(currentTeam.name);
12
+ * }
13
+ * ```
14
+ */
15
+ export declare const computedReference: <T>(model: Model, foreignKeyField: string, targetModelName: string) => {
16
+ get(): T | null;
17
+ };
18
+ /**
19
+ * Creates a MobX computed value for a reverse relation (one-to-many).
20
+ * Returns models of targetModelName where foreignKey matches model.id.
21
+ *
22
+ * Requires a `getAll` method on the store. If the store does not have `getAll`,
23
+ * the computed always returns an empty array.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const comments = computedCollection<Comment>(task, 'Comment', 'taskId');
28
+ * const commentCount = comments.get().length;
29
+ * updateCommentBadge(commentCount);
30
+ * ```
31
+ */
32
+ export declare const computedCollection: <T>(model: Model, targetModelName: string, foreignKey: string) => {
33
+ get(): T[];
34
+ };
35
+ //# sourceMappingURL=computed-relations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed-relations.d.ts","sourceRoot":"","sources":["../src/computed-relations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,OAAO,KAAK,EACZ,iBAAiB,MAAM,EACvB,iBAAiB,MAAM,KACtB;IAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;CAOhB,CAAC;AAEL;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAClC,OAAO,KAAK,EACZ,iBAAiB,MAAM,EACvB,YAAY,MAAM,KACjB;IAAE,GAAG,IAAI,CAAC,EAAE,CAAA;CAYX,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { computed } from "mobx";
2
+ /**
3
+ * Creates a MobX computed value that resolves a foreign key reference.
4
+ * The model must have a store attached.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const team = computedReference<Team>(task, 'teamId', 'Team');
9
+ * const currentTeam = team.get();
10
+ * if (currentTeam) {
11
+ * renderTeamName(currentTeam.name);
12
+ * }
13
+ * ```
14
+ */
15
+ export const computedReference = (model, foreignKeyField, targetModelName) => computed(() => {
16
+ const id = model.__data[foreignKeyField];
17
+ if (typeof id !== "string" || !model.store) {
18
+ return null;
19
+ }
20
+ return model.store.get(targetModelName, id) ?? null;
21
+ });
22
+ /**
23
+ * Creates a MobX computed value for a reverse relation (one-to-many).
24
+ * Returns models of targetModelName where foreignKey matches model.id.
25
+ *
26
+ * Requires a `getAll` method on the store. If the store does not have `getAll`,
27
+ * the computed always returns an empty array.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const comments = computedCollection<Comment>(task, 'Comment', 'taskId');
32
+ * const commentCount = comments.get().length;
33
+ * updateCommentBadge(commentCount);
34
+ * ```
35
+ */
36
+ export const computedCollection = (model, targetModelName, foreignKey) => computed(() => {
37
+ const store = model.store;
38
+ if (!store?.getAll) {
39
+ return [];
40
+ }
41
+ const all = store.getAll(targetModelName);
42
+ return all.filter((item) => item.__data[foreignKey] === model.id);
43
+ });
44
+ //# sourceMappingURL=computed-relations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computed-relations.js","sourceRoot":"","sources":["../src/computed-relations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,KAAY,EACZ,eAAuB,EACvB,eAAuB,EACF,EAAE,CACvB,QAAQ,CAAC,GAAG,EAAE;IACZ,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACzC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAQ,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,CAAO,IAAI,IAAI,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEL;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,KAAY,EACZ,eAAuB,EACvB,UAAkB,EACF,EAAE,CAClB,QAAQ,CAAC,GAAG,EAAE;IACZ,MAAM,KAAK,GAAG,KAAK,CAAC,KAIP,CAAC;IACd,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,eAAe,CAAkB,CAAC;IAC3D,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Model } from "@stratasync/core";
2
+ export interface DirtyTracker {
3
+ /** Observable: true if any properties have been modified since last save/clear */
4
+ readonly isDirty: boolean;
5
+ /** Observable: set of property names that have been modified */
6
+ readonly modifiedFields: ReadonlySet<string>;
7
+ /** Observable: count of modified fields */
8
+ readonly modifiedCount: number;
9
+ /** Reset tracking */
10
+ clear(): void;
11
+ }
12
+ export declare const getDirtyTracker: (model: Model) => DirtyTracker | undefined;
13
+ export declare const createDirtyTracker: (model: Model) => DirtyTracker;
14
+ //# sourceMappingURL=dirty-tracking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dirty-tracking.d.ts","sourceRoot":"","sources":["../src/dirty-tracking.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAG9C,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,2CAA2C;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,qBAAqB;IACrB,KAAK,IAAI,IAAI,CAAC;CACf;AAKD,eAAO,MAAM,eAAe,GAAI,OAAO,KAAK,KAAG,YAAY,GAAG,SAG/C,CAAC;AAEhB,eAAO,MAAM,kBAAkB,GAAI,OAAO,KAAK,KAAG,YAuDjD,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { computed, observable, runInAction } from "mobx";
2
+ // Symbol to store tracker on model instances
3
+ const DIRTY_TRACKER = Symbol.for("done:dirty-tracker");
4
+ export const getDirtyTracker = (model) => model[DIRTY_TRACKER];
5
+ export const createDirtyTracker = (model) => {
6
+ // Check if already attached
7
+ const existing = getDirtyTracker(model);
8
+ if (existing) {
9
+ return existing;
10
+ }
11
+ // Create an observable set to mirror modified property names
12
+ const fields = observable.set();
13
+ // Wrap markPropertyChanged to track fields.
14
+ // We wrap markPropertyChanged (not propertyChanged) so that _applyUpdate,
15
+ // which suppresses tracking via suppressTracking counter, does not mark dirty.
16
+ const originalMarkPropertyChanged = model.markPropertyChanged.bind(model);
17
+ model.markPropertyChanged = (name, oldValue, newValue) => {
18
+ originalMarkPropertyChanged(name, oldValue, newValue);
19
+ runInAction(() => {
20
+ fields.add(name);
21
+ });
22
+ };
23
+ // Wrap clearChanges to reset tracking
24
+ const originalClearChanges = model.clearChanges.bind(model);
25
+ model.clearChanges = () => {
26
+ originalClearChanges();
27
+ runInAction(() => {
28
+ fields.clear();
29
+ });
30
+ };
31
+ const isDirtyComputed = computed(() => fields.size > 0);
32
+ const modifiedCountComputed = computed(() => fields.size);
33
+ const tracker = {
34
+ clear() {
35
+ model.clearChanges();
36
+ },
37
+ get isDirty() {
38
+ return isDirtyComputed.get();
39
+ },
40
+ get modifiedCount() {
41
+ return modifiedCountComputed.get();
42
+ },
43
+ get modifiedFields() {
44
+ return fields;
45
+ },
46
+ };
47
+ // Attach to model
48
+ model[DIRTY_TRACKER] = tracker;
49
+ return tracker;
50
+ };
51
+ //# sourceMappingURL=dirty-tracking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dirty-tracking.js","sourceRoot":"","sources":["../src/dirty-tracking.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,MAAM,CAAC;AAazD,6CAA6C;AAC7C,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAEvD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAY,EAA4B,EAAE,CACvE,KAA4C,CAAC,aAAa,CAE9C,CAAC;AAEhB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAY,EAAgB,EAAE;IAC/D,4BAA4B;IAC5B,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,6DAA6D;IAC7D,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAU,CAAC;IAExC,4CAA4C;IAC5C,0EAA0E;IAC1E,+EAA+E;IAC/E,MAAM,2BAA2B,GAAG,KAAK,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1E,KAAK,CAAC,mBAAmB,GAAG,CAC1B,IAAY,EACZ,QAAiB,EACjB,QAAiB,EACjB,EAAE;QACF,2BAA2B,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtD,WAAW,CAAC,GAAG,EAAE;YACf,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,sCAAsC;IACtC,MAAM,oBAAoB,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,KAAK,CAAC,YAAY,GAAG,GAAG,EAAE;QACxB,oBAAoB,EAAE,CAAC;QACvB,WAAW,CAAC,GAAG,EAAE;YACf,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,qBAAqB,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE1D,MAAM,OAAO,GAAiB;QAC5B,KAAK;YACH,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,CAAC;QACD,IAAI,OAAO;YACT,OAAO,eAAe,CAAC,GAAG,EAAE,CAAC;QAC/B,CAAC;QACD,IAAI,aAAa;YACf,OAAO,qBAAqB,CAAC,GAAG,EAAE,CAAC;QACrC,CAAC;QACD,IAAI,cAAc;YAChB,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;IAEF,kBAAkB;IACjB,KAA4C,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC;IACvE,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Model, makeObservableProperty, makeReferenceModelProperty, } from "@stratasync/core";
2
+ export { createMobXReactivity, mobxReactivityAdapter } from "./adapter.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EACL,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // biome-ignore lint/performance/noBarrelFile: package entry point
2
+ export { Model, makeObservableProperty, makeReferenceModelProperty, } from "@stratasync/core";
3
+ export { createMobXReactivity, mobxReactivityAdapter } from "./adapter.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,OAAO,EACL,KAAK,EACL,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { Model } from "@stratasync/core";
2
+ /**
3
+ * Returns a plain JavaScript object from a model instance.
4
+ * Copies all __data properties plus the id.
5
+ * Useful when spread operator doesn't copy prototype getters.
6
+ */
7
+ export declare const toPlainObject: <T extends Model>(model: T) => Record<string, unknown>;
8
+ /**
9
+ * Creates a shallow clone of a model's data for comparison or form editing.
10
+ */
11
+ export declare const cloneModelData: <T extends Model>(model: T) => Record<string, unknown>;
12
+ /**
13
+ * Compares two model instances' data and returns changed fields.
14
+ */
15
+ export declare const diffModels: <T extends Model>(a: T, b: T) => Record<string, {
16
+ old: unknown;
17
+ new: unknown;
18
+ }>;
19
+ /**
20
+ * Returns true if the model has any unsaved changes.
21
+ * Reads from the DirtyTracker if one is attached, otherwise falls back to changeSnapshot().
22
+ */
23
+ export declare const isModelDirty: (model: Model) => boolean;
24
+ //# sourceMappingURL=model-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-utils.d.ts","sourceRoot":"","sources":["../src/model-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,KAAK,EAC3C,OAAO,CAAC,KACP,MAAM,CAAC,MAAM,EAAE,OAAO,CAOxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,KAAK,EAC5C,OAAO,CAAC,KACP,MAAM,CAAC,MAAM,EAAE,OAAO,CAAkC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,KAAK,EACxC,GAAG,CAAC,EACJ,GAAG,CAAC,KACH,MAAM,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAW/C,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,KAAK,KAAG,OAS3C,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Returns a plain JavaScript object from a model instance.
3
+ * Copies all __data properties plus the id.
4
+ * Useful when spread operator doesn't copy prototype getters.
5
+ */
6
+ export const toPlainObject = (model) => {
7
+ const result = { id: model.id };
8
+ const data = model.__data;
9
+ for (const key of Object.keys(data)) {
10
+ result[key] = data[key];
11
+ }
12
+ return result;
13
+ };
14
+ /**
15
+ * Creates a shallow clone of a model's data for comparison or form editing.
16
+ */
17
+ export const cloneModelData = (model) => ({ ...toPlainObject(model) });
18
+ /**
19
+ * Compares two model instances' data and returns changed fields.
20
+ */
21
+ export const diffModels = (a, b) => {
22
+ const result = {};
23
+ const aData = a.__data;
24
+ const bData = b.__data;
25
+ const allKeys = new Set([...Object.keys(aData), ...Object.keys(bData)]);
26
+ for (const key of allKeys) {
27
+ if (aData[key] !== bData[key]) {
28
+ result[key] = { new: bData[key], old: aData[key] };
29
+ }
30
+ }
31
+ return result;
32
+ };
33
+ const DIRTY_TRACKER_SYMBOL = Symbol.for("done:dirty-tracker");
34
+ /**
35
+ * Returns true if the model has any unsaved changes.
36
+ * Reads from the DirtyTracker if one is attached, otherwise falls back to changeSnapshot().
37
+ */
38
+ export const isModelDirty = (model) => {
39
+ const tracker = model[DIRTY_TRACKER_SYMBOL];
40
+ if (tracker) {
41
+ return tracker.isDirty;
42
+ }
43
+ const snapshot = model.changeSnapshot();
44
+ return Object.keys(snapshot.changes).length > 0;
45
+ };
46
+ //# sourceMappingURL=model-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-utils.js","sourceRoot":"","sources":["../src/model-utils.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,KAAQ,EACiB,EAAE;IAC3B,MAAM,MAAM,GAA4B,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;IACzD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,KAAQ,EACiB,EAAE,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAE5D;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,CAAI,EACJ,CAAI,EAC4C,EAAE;IAClD,MAAM,MAAM,GAAmD,EAAE,CAAC;IAClE,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACvB,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACvB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxE,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAE9D;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAY,EAAW,EAAE;IACpD,MAAM,OAAO,GAAI,KAA4C,CAC3D,oBAAoB,CACe,CAAC;IACtC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC,OAAO,CAAC;IACzB,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAClD,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@stratasync/mobx",
3
+ "version": "0.2.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "dev": "tsc --watch -p tsconfig.build.json",
22
+ "lint": "oxlint .",
23
+ "lint:fix": "oxlint --fix .",
24
+ "format": "oxfmt --write .",
25
+ "format:check": "oxfmt --check .",
26
+ "check-types": "tsc --noEmit",
27
+ "test": "vitest"
28
+ },
29
+ "dependencies": {
30
+ "@stratasync/core": "*",
31
+ "mobx": "^6.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "lefthook": "^2.1.4",
35
+ "oxfmt": "^0.41.0",
36
+ "oxlint": "^1.56.0",
37
+ "typescript": "^5.9.3",
38
+ "ultracite": "^7.3.2",
39
+ "vitest": "^4.1.0"
40
+ },
41
+ "peerDependencies": {
42
+ "mobx": "^6.0.0"
43
+ }
44
+ }