• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hybridsjs / hybrids / 19760852732

28 Nov 2025 10:18AM UTC coverage: 42.528% (-57.4%) from 99.915%
19760852732

Pull #296

github

web-flow
Merge 191660095 into 92ed729fc
Pull Request #296: fix(store): improve `store.record()` support & error messages while defining the model

819 of 1842 branches covered (44.46%)

9 of 9 new or added lines in 1 file covered. (100.0%)

1345 existing lines in 27 files now uncovered.

996 of 2342 relevant lines covered (42.53%)

76.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.5
/src/cache.js
1
import * as emitter from "./emitter.js";
2

3
const entries = new WeakMap();
2✔
4
const stack = new Set();
2✔
5

6
function dispatch(entry, resolved = false) {
799✔
7
  const contexts = [];
1,209✔
8
  let index = 0;
1,209✔
9

10
  entry.resolved = resolved;
1,209✔
11

12
  while (entry) {
1,209✔
13
    if (entry.contexts) {
1,399✔
14
      for (const context of entry.contexts) {
197✔
15
        if (!stack.has(context) && !contexts.includes(context)) {
191✔
16
          context.resolved = false;
190✔
17
          contexts.push(context);
190✔
18
        }
19
      }
20
    }
21

22
    if (entry.observe) {
1,399✔
23
      emitter.add(entry.observe);
1✔
24
    }
25

26
    entry = contexts[index++];
1,399✔
27
  }
28
}
29

30
export function getEntry(target, key) {
31
  let map = entries.get(target);
2,850✔
32
  if (!map) {
2,850✔
33
    map = new Map();
730✔
34
    entries.set(target, map);
730✔
35
  }
36

37
  let entry = map.get(key);
2,848✔
38
  if (!entry) {
2,848✔
39
    entry = {
1,122✔
40
      key,
41
      target,
42
      value: undefined,
43
      assertValue: undefined,
44
      lastValue: undefined,
45
      resolved: false,
46
      contexts: undefined,
47
      deps: undefined,
48
      observe: undefined,
49
    };
50
    map.set(key, entry);
1,122✔
51
  }
52

53
  return entry;
2,848✔
54
}
55

56
export function getEntries(target) {
57
  const targetMap = entries.get(target);
17✔
58
  if (targetMap) return [...targetMap.values()];
17✔
59
  return [];
1✔
60
}
61

62
let context = null;
2✔
63
export function getCurrentValue() {
64
  return context?.value;
144✔
65
}
66

67
export function get(target, key, fn) {
68
  const entry = getEntry(target, key);
1,044✔
69

70
  if (context) {
1,042✔
71
    if (!entry.contexts) entry.contexts = new Set();
328✔
72
    if (!context.deps) context.deps = new Set();
328✔
73

74
    entry.contexts.add(context);
328✔
75
    context.deps.add(entry);
328✔
76
  }
77

78
  if (entry.resolved) return entry.value;
1,042✔
79

80
  if (entry.deps) {
760✔
81
    for (const depEntry of entry.deps) {
47✔
82
      depEntry.contexts.delete(entry);
63✔
83
    }
84
    entry.deps.clear();
47✔
85
  }
86

87
  const lastContext = context;
760✔
88

89
  try {
760✔
90
    if (stack.has(entry)) {
760!
UNCOV
91
      throw Error(`Circular get invocation is forbidden: '${key}'`);
×
92
    }
93

94
    context = entry;
760✔
95
    stack.add(entry);
760✔
96

97
    entry.value = fn(target, entry.assertValue);
760✔
98
    entry.resolved = true;
759✔
99

100
    context = lastContext;
759✔
101

102
    stack.delete(entry);
759✔
103
  } catch (e) {
104
    context = lastContext;
1✔
105
    stack.delete(entry);
1✔
106

107
    if (context) {
1!
UNCOV
108
      context.deps.delete(entry);
×
UNCOV
109
      entry.contexts.delete(context);
×
110
    }
111

112
    throw e;
1✔
113
  }
114

115
  return entry.value;
759✔
116
}
117

118
export function assert(target, key, value, force) {
119
  if (context && context.target === target && !force) {
294!
UNCOV
120
    throw Error(
×
121
      `Try to update the '${key}' property while getting the '${context.key}' property`,
122
    );
123
  }
124

125
  const entry = getEntry(target, key);
294✔
126

127
  entry.value = undefined;
294✔
128
  entry.assertValue = value;
294✔
129

130
  dispatch(entry);
294✔
131
}
132

133
export function sync(target, key, fn, value) {
134
  const entry = getEntry(target, key);
410✔
135
  const nextValue = fn(target, value, entry.value);
410✔
136

137
  if (nextValue !== entry.value) {
410!
138
    entry.value = nextValue;
410✔
139
    entry.assertValue = undefined;
410✔
140

141
    dispatch(entry, true);
410✔
142

143
    // mark as resolved to avoid double fn call in get
144
    entry.resolved = true;
410✔
145
  }
146
}
147

148
export function observe(target, key, fn, callback) {
149
  const entry = getEntry(target, key);
2✔
150

151
  entry.observe = () => {
2✔
152
    const value = get(target, key, fn);
3✔
153

154
    if (value !== entry.lastValue) {
3!
155
      callback(target, value, entry.lastValue);
3✔
156
      entry.lastValue = value;
3✔
157
    }
158
  };
159

160
  try {
2✔
161
    entry.observe();
2✔
162
  } catch (e) {
UNCOV
163
    console.error(e);
×
164
  }
165

166
  return () => {
2✔
167
    entry.observe = undefined;
1✔
168
    entry.lastValue = undefined;
1✔
169
  };
170
}
171

172
const gc = new Set();
2✔
173
function deleteEntry(entry) {
174
  if (!gc.size) {
72✔
175
    setTimeout(() => {
40✔
176
      for (const e of gc) {
40✔
177
        if (!e.contexts || e.contexts.size === 0) {
72✔
178
          const targetMap = entries.get(e.target);
70✔
179
          targetMap.delete(e.key);
70✔
180
        }
181
      }
182

183
      gc.clear();
40✔
184
    });
185
  }
186

187
  gc.add(entry);
72✔
188
}
189

190
function invalidateEntry(entry, options) {
191
  dispatch(entry);
505✔
192

193
  if (options.clearValue) {
505✔
194
    entry.value = undefined;
363✔
195
    entry.assertValue = undefined;
363✔
196
    entry.lastValue = undefined;
363✔
197
  }
198

199
  if (options.deleteEntry) {
505✔
200
    if (entry.deps) {
72✔
201
      for (const depEntry of entry.deps) {
36✔
202
        depEntry.contexts.delete(entry);
70✔
203
      }
204
      entry.deps = undefined;
36✔
205
    }
206

207
    if (entry.contexts) {
72✔
208
      for (const context of entry.contexts) {
61✔
209
        context.deps.delete(entry);
61✔
210
      }
211
      entry.contexts = undefined;
61✔
212
    }
213

214
    deleteEntry(entry);
72✔
215
  }
216
}
217

218
export function invalidate(target, key, options = {}) {
×
219
  const entry = getEntry(target, key);
363✔
220
  invalidateEntry(entry, options);
363✔
221
}
222

223
export function invalidateAll(target, options = {}) {
43✔
224
  const targetMap = entries.get(target);
58✔
225
  if (targetMap) {
58✔
226
    for (const entry of targetMap.values()) {
52✔
227
      invalidateEntry(entry, options);
142✔
228
    }
229
  }
230
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc