jq79 0.5.6 → 0.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jq79",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Mini reactive component library: single-file components, Svelte-style setup scripts, fine-grained proxy reactivity. Single-file build, zero dependencies.",
5
5
  "keywords": [
6
6
  "reactive",
package/src/jq79.ts CHANGED
@@ -128,34 +128,23 @@ let pendingScripts = 0
128
128
  let flushScheduled = false
129
129
 
130
130
  // everything else an expression can throw - overwhelmingly a member access on
131
- // an undefined value, `{{ game.is.loaded }}` over a game with no `is`. There is
132
- // no pure question to ask about it the way there is for a missing name: `in`
133
- // answers "declared anywhere?" without evaluating, and nothing answers "would
134
- // this still throw?" without running the expression again, which is exactly
135
- // what mustn't happen (`@click="count++ + missing"` increments twice).
131
+ // an undefined value, `{{ game.is.loaded }}` over a game with no `is`. Unlike a
132
+ // missing name it needs no deferral and gets none: the engine already caught a
133
+ // real exception and wrote the message, so there is nothing left to decide and
134
+ // it is reported where it throws, as an error rather than a warning.
136
135
  //
137
- // What is pure is watching the evaluations that were going to happen anyway. An
138
- // expression whose value is merely late is re-evaluated when the value arrives -
139
- // the reads that did land registered as dependencies, so the write wakes the
140
- // effect - and it succeeds then. So transient means "succeeds later", and the
141
- // re-check is to clear the entry on the next successful evaluation and report
142
- // only what is still queued once the page has gone quiet.
136
+ // That means a value still on its way is reported too - `{{ user.name }}` over
137
+ // a user that a fetch will fill renders empty and says so, once. It is a
138
+ // deliberate trade against the silence it replaces, which hid a render that was
139
+ // actively wrong (a thrown `:disabled` is falsy, so the button rendered
140
+ // *enabled*). The fix is the one the message names: `user?.name`, or `:if`.
143
141
  //
144
- // Quiet is a timer rather than the microtask the other queue flushes on,
145
- // because a value from fetch() arrives many tasks later and clearing on success
146
- // exists to let it. A second covers that and still lands the warning inside the
147
- // page load. It is a guess, and the cost is a slower fetch warning about correct
148
- // code with nothing to take it back - accepted because the silence it replaces
149
- // hid a render that was actively wrong
150
- const FAILED_EXPR_DELAY = 1000
151
-
152
- // keyed by expression text alone, not `message|expr`: the expression is what the
153
- // author edits, and one broken path throwing two messages is still one bug. The
154
- // entries hold no scope, only strings, so this queue cannot retain a live
155
- // component the way pendingReports can
156
- const failedExprs = new Map<string, string>()
142
+ // Deduped by expression text alone. A `:each` over 1000 rows throws 1000 times
143
+ // per render and the Set is what keeps that to one line; keying finer - by
144
+ // message too, as the missing-name queue does - would let one broken path
145
+ // report once per distinct message. There is no queue behind it because there
146
+ // is nothing to re-check, and so nothing that could retain a live scope
157
147
  const reportedFailedExprs = new Set<string>()
158
- let failedFlushTimer: ReturnType<typeof setTimeout> | undefined
159
148
 
160
149
  const flushExprReports = () => {
161
150
  flushScheduled = false
@@ -179,25 +168,6 @@ const scheduleExprReportFlush = () => {
179
168
  queueMicrotask(flushExprReports)
180
169
  }
181
170
 
182
- const flushFailedExprs = () => {
183
- failedFlushTimer = undefined
184
- if (pendingScripts > 0) return // a script started meanwhile; its release re-schedules
185
- failedExprs.forEach((message, expr) => {
186
- reportedFailedExprs.add(expr)
187
- console.warn(
188
- `jq79: ${message} - evaluating "${expr}". It rendered as nothing and was ` +
189
- `still failing a moment later, so the value is probably never coming: check ` +
190
- `the path. If it does arrive asynchronously, guard it - "a?.b", or :if on the element.`
191
- )
192
- })
193
- failedExprs.clear()
194
- }
195
-
196
- const scheduleFailedExprFlush = () => {
197
- if (failedFlushTimer !== undefined || pendingScripts > 0 || !failedExprs.size) return
198
- failedFlushTimer = setTimeout(flushFailedExprs, FAILED_EXPR_DELAY)
199
- }
200
-
201
171
  // scripts run before the template renders, so the counter is already up when
202
172
  // the first evaluation fails. Both script modes settle through a promise;
203
173
  // the factory's has to cover the merge, not just the module body
@@ -206,16 +176,22 @@ const trackScript = (settled: Promise<unknown>) => {
206
176
  const release = () => {
207
177
  pendingScripts--
208
178
  scheduleExprReportFlush()
209
- scheduleFailedExprFlush()
210
179
  }
211
180
  settled.then(release, release)
212
181
  }
213
182
 
183
+ // console.error, not warn: a name that resolves nowhere is a warning because
184
+ // the runtime can only say the name is absent, while this one caught a real
185
+ // exception - it has a message the engine wrote, and the expression rendered as
186
+ // nothing instead of doing what it says
214
187
  const reportFailedExpr = (expr: string, error: unknown) => {
215
- if (reportedFailedExprs.has(expr) || failedExprs.has(expr)) return
216
- if (failedExprs.size >= MAX_PENDING_REPORTS) return
217
- failedExprs.set(expr, (error as Error)?.message || String(error))
218
- scheduleFailedExprFlush()
188
+ if (reportedFailedExprs.has(expr)) return
189
+ reportedFailedExprs.add(expr)
190
+ const message = (error as Error)?.message || String(error)
191
+ console.error(
192
+ `jq79: ${message} - evaluating "${expr}". The expression rendered as nothing. ` +
193
+ `If the value arrives later, guard it - "a?.b", or :if on the element.`
194
+ )
219
195
  }
220
196
 
221
197
  const reportExprError = (expr: string, scope: Record<string, any>, error: unknown) => {
@@ -241,12 +217,7 @@ const runExpr = (expr: string, scope: Record<string, any>, extras?: Record<strin
241
217
 
242
218
  const evalExpr = (expr: string, scope: Record<string, any>, extras?: Record<string, any>): any => {
243
219
  try {
244
- const value = runExpr(expr, scope, extras)
245
- // the re-check for the failed-expression queue: this run is the proof that
246
- // the earlier throw was a value arriving late. Guarded on size so the
247
- // normal page - where nothing has ever thrown - pays one comparison
248
- if (failedExprs.size) failedExprs.delete(expr)
249
- return value
220
+ return runExpr(expr, scope, extras)
250
221
  } catch (error) {
251
222
  reportExprError(expr, scope, error)
252
223
  return undefined
@@ -2339,7 +2310,6 @@ export class Component79 {
2339
2310
  reportedExprErrors.clear()
2340
2311
  pendingReports.clear()
2341
2312
  reportedFailedExprs.clear()
2342
- failedExprs.clear()
2343
2313
  const marker = this.startMarker
2344
2314
  const rendered = !!(marker && this.content)
2345
2315