@abide/abide 0.53.0 → 0.54.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/AGENTS.md +9 -5
- package/CHANGELOG.md +57 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/checkAbide.ts +1 -1
- package/src/lib/server/runtime/amendBroadcaster.ts +53 -0
- package/src/lib/server/runtime/createUiPageRenderer.ts +26 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +1 -0
- package/src/lib/server/runtime/types/RequestStore.ts +9 -0
- package/src/lib/server/sockets/amendFamilyEntry.ts +39 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -0
- package/src/lib/server/sockets/defineSocket.ts +11 -1
- package/src/lib/shared/AMEND_TOPIC_PREFIX.ts +9 -0
- package/src/lib/shared/SOCKET_SEED.ts +13 -0
- package/src/lib/shared/amend.ts +48 -0
- package/src/lib/shared/amendBroadcastSlot.ts +19 -0
- package/src/lib/shared/applyAmendLocally.ts +20 -0
- package/src/lib/shared/attachRpcSelectorMethods.ts +5 -5
- package/src/lib/shared/buildSocketOverChannel.ts +9 -2
- package/src/lib/shared/cache.ts +35 -10
- package/src/lib/shared/cacheReaderSocketSlot.ts +11 -0
- package/src/lib/shared/createCacheStore.ts +8 -0
- package/src/lib/shared/peek.ts +1 -1
- package/src/lib/shared/socketTailsSlot.ts +13 -0
- package/src/lib/shared/types/AmendApply.ts +15 -0
- package/src/lib/shared/types/CacheReaderHook.ts +11 -0
- package/src/lib/shared/types/RemoteFunction.ts +30 -19
- package/src/lib/shared/types/SocketTails.ts +13 -0
- package/src/lib/shared/types/SsrPayload.ts +6 -0
- package/src/lib/test/createTestApp.ts +6 -0
- package/src/lib/ui/amendReaderHook.ts +125 -0
- package/src/lib/ui/compile/cachedShadowProgram.ts +38 -0
- package/src/lib/ui/compile/compileComponent.ts +9 -2
- package/src/lib/ui/compile/desugarSignals.ts +68 -3
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +8 -13
- package/src/lib/ui/compile/lowerCompoundAssignment.ts +49 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +19 -31
- package/src/lib/ui/compile/lowerUpdateExpression.ts +32 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +18 -24
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +9 -14
- package/src/lib/ui/compile/wrapReactionCellSources.ts +10 -6
- package/src/lib/ui/dom/awaitBlock.ts +1 -1
- package/src/lib/ui/dom/tryBlock.ts +1 -1
- package/src/lib/ui/router.ts +8 -0
- package/src/lib/ui/runtime/createDoc.ts +5 -1
- package/src/lib/ui/runtime/restoreWarmSeeds.ts +20 -0
- package/src/lib/ui/runtime/types/Cell.ts +4 -2
- package/src/lib/ui/runtime/types/Doc.ts +3 -1
- package/src/lib/ui/runtime/warmSeedBackup.ts +10 -0
- package/src/lib/ui/socketProxy.ts +17 -1
- package/src/lib/ui/startClient.ts +30 -0
- package/src/lib/ui/types/Scope.ts +1 -1
- package/src/lib/ui/watch.ts +18 -5
- package/src/serverEntry.ts +15 -0
- package/src/lib/shared/patch.ts +0 -41
- package/src/lib/ui/compile/COMPOUND_ASSIGNMENT_OPERATORS.ts +0 -19
package/src/lib/ui/watch.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { effect } from './effect.ts'
|
|
|
8
8
|
import { generationGuard } from './runtime/generationGuard.ts'
|
|
9
9
|
import type { EffectResult } from './runtime/types/EffectResult.ts'
|
|
10
10
|
import type { State } from './runtime/types/State.ts'
|
|
11
|
+
import { untrack } from './runtime/untrack.ts'
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
14
|
The single reaction primitive: `watch(source, handler)` names its trigger and runs
|
|
@@ -25,6 +26,14 @@ into a server bundle; server-side it is an inert no-op. Unlike bare `watch(…)`
|
|
|
25
26
|
the SSR back-end strips — a `.watch(…)` member call survives to the server and relies on
|
|
26
27
|
that inert stub.
|
|
27
28
|
|
|
29
|
+
Tracking contract (ADR-0044): the named source(s) are the sole triggers — the handler
|
|
30
|
+
is a sink, run untracked, so a reactive read in its body never becomes an accidental
|
|
31
|
+
extra trigger. Only the bare `watch(thunk)` form auto-tracks everything it reads (that
|
|
32
|
+
IS its job — it is the compiler binding). This makes all explicit-source forms uniform:
|
|
33
|
+
the subscribable (cache.on, per-frame) and rpc (handler in a .then microtask) branches
|
|
34
|
+
already ran their handlers out of the tracking window; the two synchronous cell branches
|
|
35
|
+
now match by wrapping the handler in `untrack`.
|
|
36
|
+
|
|
28
37
|
Sources (discriminated at runtime, monomorphic per branch):
|
|
29
38
|
watch(thunk) // compiler binding form — auto-tracked, == effect(thunk)
|
|
30
39
|
watch(count, n => …) // a state cell → handler(newValue)
|
|
@@ -93,18 +102,22 @@ export function watch(
|
|
|
93
102
|
) {
|
|
94
103
|
return reactToRpc(source, undefined, handler)
|
|
95
104
|
}
|
|
96
|
-
/* Multiple cells → fire on any change; hand the handler the current values.
|
|
105
|
+
/* Multiple cells → fire on any change; hand the handler the current values. Read every
|
|
106
|
+
cell (the tracked triggers), then run the handler untracked so a reactive read in its
|
|
107
|
+
body is not captured as an extra trigger (ADR-0044). */
|
|
97
108
|
if (Array.isArray(source)) {
|
|
98
109
|
const cells = source as ReadonlyArray<State<unknown>>
|
|
99
110
|
return effect(() => {
|
|
100
111
|
const values = cells.map((cell) => cell.value)
|
|
101
|
-
handler(values)
|
|
112
|
+
untrack(() => handler(values))
|
|
102
113
|
})
|
|
103
114
|
}
|
|
104
|
-
/* A single state cell → handler(newValue) on change.
|
|
115
|
+
/* A single state cell → handler(newValue) on change. The cell read is the tracked
|
|
116
|
+
trigger; the handler is a sink, run untracked (ADR-0044). */
|
|
105
117
|
const cell = source as State<unknown>
|
|
106
118
|
return effect(() => {
|
|
107
|
-
|
|
119
|
+
const value = cell.value
|
|
120
|
+
untrack(() => handler(value))
|
|
108
121
|
})
|
|
109
122
|
}
|
|
110
123
|
|
|
@@ -120,7 +133,7 @@ function isSubscribable(source: unknown): boolean {
|
|
|
120
133
|
/*
|
|
121
134
|
Reacts to an rpc's cached value: an effect that runs the smart read (subscribing
|
|
122
135
|
its key synchronously, so the effect re-runs when the value changes — a refresh,
|
|
123
|
-
|
|
136
|
+
an amend, an invalidate) and pipes the resolved value to the handler. Triggers the
|
|
124
137
|
read, unlike peek: `watch` observes a live query, so it keeps it flowing.
|
|
125
138
|
*/
|
|
126
139
|
function reactToRpc(fn: unknown, args: unknown, handler: (value: unknown) => void): () => void {
|
package/src/serverEntry.ts
CHANGED
|
@@ -26,10 +26,12 @@ import { shell } from './_virtual/shell.ts'
|
|
|
26
26
|
import { sockets } from './_virtual/sockets.ts'
|
|
27
27
|
import { exitWithParent } from './lib/bundle/exitWithParent.ts'
|
|
28
28
|
import { loadEnvFromBinaryDir } from './lib/cli/loadEnvFromBinaryDir.ts'
|
|
29
|
+
import { broadcastAmend } from './lib/server/runtime/amendBroadcaster.ts'
|
|
29
30
|
import { broadcastCacheStaleness } from './lib/server/runtime/cacheStalenessBroadcaster.ts'
|
|
30
31
|
import { createServer } from './lib/server/runtime/createServer.ts'
|
|
31
32
|
import { requestContext } from './lib/server/runtime/requestContext.ts'
|
|
32
33
|
import { resolvePageSnapshot } from './lib/server/runtime/resolvePageSnapshot.ts'
|
|
34
|
+
import { amendBroadcastSlot } from './lib/shared/amendBroadcastSlot.ts'
|
|
33
35
|
import { cacheStalenessSlot } from './lib/shared/cacheStalenessSlot.ts'
|
|
34
36
|
import { cacheStoreSlot } from './lib/shared/cacheStoreSlot.ts'
|
|
35
37
|
import { createCacheStore } from './lib/shared/createCacheStore.ts'
|
|
@@ -40,6 +42,7 @@ import { pendingAsyncCellsSlot } from './lib/shared/pendingAsyncCellsSlot.ts'
|
|
|
40
42
|
import { resolvedCellsSlot } from './lib/shared/resolvedCellsSlot.ts'
|
|
41
43
|
import { runningAsStandaloneBinary } from './lib/shared/runningAsStandaloneBinary.ts'
|
|
42
44
|
import { sharedCacheStoreSlot } from './lib/shared/sharedCacheStoreSlot.ts'
|
|
45
|
+
import { socketTailsSlot } from './lib/shared/socketTailsSlot.ts'
|
|
43
46
|
import { streamedCellsSlot } from './lib/shared/streamedCellsSlot.ts'
|
|
44
47
|
|
|
45
48
|
/*
|
|
@@ -89,6 +92,14 @@ so its server socket code never enters the client reachability graph.
|
|
|
89
92
|
*/
|
|
90
93
|
cacheStalenessSlot.resolver = () => broadcastCacheStaleness
|
|
91
94
|
|
|
95
|
+
/*
|
|
96
|
+
Server-side amend(args, value) broadcasts the keyed value to every authorized reader
|
|
97
|
+
instead of mutating a throwaway request store (ADR-0043). Imported ONLY here so the
|
|
98
|
+
broadcaster's socket code never enters the client reachability graph — the same import
|
|
99
|
+
discipline as the staleness broadcaster above.
|
|
100
|
+
*/
|
|
101
|
+
amendBroadcastSlot.resolver = () => broadcastAmend
|
|
102
|
+
|
|
92
103
|
/* One process-wide fallback list for reads with no request in flight (boot/cron/socket) —
|
|
93
104
|
real requests each carry their own `pendingAsyncCells`, so the SSR barrier drains a
|
|
94
105
|
per-request list and concurrent renders never cross-contaminate. */
|
|
@@ -99,6 +110,10 @@ const sharedResolvedCells = { entries: [] }
|
|
|
99
110
|
resolvedCellsSlot.resolver = () => requestContext.getStore()?.resolvedCells ?? sharedResolvedCells
|
|
100
111
|
const sharedStreamedCells = { entries: [] }
|
|
101
112
|
streamedCellsSlot.resolver = () => requestContext.getStore()?.streamedCells ?? sharedStreamedCells
|
|
113
|
+
/* No shared fallback: `defineSocket.peek` records into this on every retained-frame read, including
|
|
114
|
+
reads outside any request (ws handlers, boot) — a fallback list would accumulate entries no render
|
|
115
|
+
ever drains. Off-request this resolves undefined and the record is skipped (see socketTailsSlot). */
|
|
116
|
+
socketTailsSlot.resolver = () => requestContext.getStore()?.socketTails
|
|
102
117
|
const sharedDocSnapshots = { entries: [] }
|
|
103
118
|
docSnapshotsSlot.resolver = () => requestContext.getStore()?.docSnapshots ?? sharedDocSnapshots
|
|
104
119
|
|
package/src/lib/shared/patch.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { cache } from './cache.ts'
|
|
2
|
-
import type { CacheOptions } from './types/CacheOptions.ts'
|
|
3
|
-
import type { CacheSelector } from './types/CacheSelector.ts'
|
|
4
|
-
import type { RemoteFunction } from './types/RemoteFunction.ts'
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
Mutate the retained value of the matching cached read(s) in place — reactive
|
|
8
|
-
(readers re-render), no network. The optimistic-update / real-time primitive:
|
|
9
|
-
patch a cached list from a socket frame (`on(chat, m => getList.patch(l => [...l,
|
|
10
|
-
m]))`) or apply an optimistic edit before a write lands. The updater receives the
|
|
11
|
-
current decoded value and returns the next.
|
|
12
|
-
|
|
13
|
-
patch(getFoo, args, updater) → that exact call
|
|
14
|
-
patch(getFoo, updater) → every args-variant of that rpc
|
|
15
|
-
patch({ tags }, updater) → every entry sharing a tag
|
|
16
|
-
|
|
17
|
-
Instance sugar `getFoo.patch(args?, updater)` ≡ `patch(getFoo, args, updater)`.
|
|
18
|
-
The updater is always the last argument; a not-yet-read key has nothing to patch.
|
|
19
|
-
*/
|
|
20
|
-
// @documentation cache
|
|
21
|
-
export function patch<Args, Return>(
|
|
22
|
-
fn: RemoteFunction<Args, Return> | ((args?: Args) => Promise<Return>),
|
|
23
|
-
args: Args | undefined,
|
|
24
|
-
updater: (current: Return) => Return,
|
|
25
|
-
): void
|
|
26
|
-
export function patch<Args, Return>(
|
|
27
|
-
fn: RemoteFunction<Args, Return> | ((args?: Args) => Promise<Return>),
|
|
28
|
-
updater: (current: Return) => Return,
|
|
29
|
-
): void
|
|
30
|
-
export function patch<Return>(
|
|
31
|
-
selector: Pick<CacheOptions, 'tags'>,
|
|
32
|
-
updater: (current: Return) => Return,
|
|
33
|
-
): void
|
|
34
|
-
export function patch(selector: unknown, argsOrUpdater: unknown, maybeUpdater?: unknown): void {
|
|
35
|
-
/* Updater is always last: 3 args → (selector, args, updater); 2 args → (selector, updater). */
|
|
36
|
-
const updater = (maybeUpdater !== undefined ? maybeUpdater : argsOrUpdater) as (
|
|
37
|
-
current: unknown,
|
|
38
|
-
) => unknown
|
|
39
|
-
const args = maybeUpdater !== undefined ? argsOrUpdater : undefined
|
|
40
|
-
cache.patch(selector as CacheSelector<unknown, unknown>, args, updater)
|
|
41
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import ts from 'typescript'
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
Maps a compound-assignment operator to its plain binary counterpart, for lowering
|
|
5
|
-
`x += y` into an unconditional read-combine-write. Logical assignments
|
|
6
|
-
(`||=`/`&&=`/`??=`) lower the same way — the patch/cell write always fires,
|
|
7
|
-
consistent with how `+=` lowers. Shared by lowerDocAccess (the `$$model` write path)
|
|
8
|
-
and renameSignalRefs (the `$$writeCell` linked-cell path) so the two lowerings can't
|
|
9
|
-
drift.
|
|
10
|
-
*/
|
|
11
|
-
export const COMPOUND_ASSIGNMENT_OPERATORS = new Map<ts.SyntaxKind, ts.BinaryOperator>([
|
|
12
|
-
[ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.PlusToken],
|
|
13
|
-
[ts.SyntaxKind.MinusEqualsToken, ts.SyntaxKind.MinusToken],
|
|
14
|
-
[ts.SyntaxKind.AsteriskEqualsToken, ts.SyntaxKind.AsteriskToken],
|
|
15
|
-
[ts.SyntaxKind.SlashEqualsToken, ts.SyntaxKind.SlashToken],
|
|
16
|
-
[ts.SyntaxKind.BarBarEqualsToken, ts.SyntaxKind.BarBarToken],
|
|
17
|
-
[ts.SyntaxKind.AmpersandAmpersandEqualsToken, ts.SyntaxKind.AmpersandAmpersandToken],
|
|
18
|
-
[ts.SyntaxKind.QuestionQuestionEqualsToken, ts.SyntaxKind.QuestionQuestionToken],
|
|
19
|
-
])
|