@danxbot/ui 3.6.1 → 3.8.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/dist/index.js +13330 -11807
- package/dist/index.js.map +1 -1
- package/dist/types/components/Menu.d.ts +33 -0
- package/dist/types/components/kanban/KanbanBoard.d.ts +47 -1
- package/dist/types/components/kanban/recipe.d.ts +24 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/lib/dnd/geometry.d.ts +54 -6
- package/dist/types/lib/dnd/index.d.ts +1 -1
- package/dist/types/lib/reads/hooks.d.ts +49 -0
- package/dist/types/lib/reads/index.d.ts +4 -0
- package/dist/types/lib/reads/refetch.d.ts +52 -0
- package/dist/types/lib/reads/slots.d.ts +52 -0
- package/dist/types/lib/reads/types.d.ts +139 -0
- package/dist/types/lib/realtime/hooks.d.ts +38 -0
- package/dist/types/lib/realtime/index.d.ts +4 -0
- package/dist/types/lib/realtime/provider.d.ts +38 -0
- package/dist/types/lib/realtime/registry.d.ts +172 -0
- package/dist/types/lib/realtime/types.d.ts +202 -0
- package/dist/types/lib/records/define-record-type.d.ts +29 -0
- package/dist/types/lib/records/hooks.d.ts +46 -0
- package/dist/types/lib/records/index.d.ts +5 -0
- package/dist/types/lib/records/store.d.ts +117 -0
- package/dist/types/lib/records/types.d.ts +188 -0
- package/dist/types/lib/records/wire.d.ts +96 -0
- package/dist/types/lib/use-fill-height.d.ts +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A payload as it arrives off the wire — from a fetch response, from a
|
|
3
|
+
* mutation's returned item, or from a broadcast.
|
|
4
|
+
*
|
|
5
|
+
* DELIBERATELY LOOSE. The store requires exactly two things of it (`__type`
|
|
6
|
+
* and `id`) and interprets five more (`__timestamp`, `__deleted_at`,
|
|
7
|
+
* `deleted_at`, `__stale`, `__model`). Everything else is data it carries
|
|
8
|
+
* without understanding — which is the whole of this module's claim to being
|
|
9
|
+
* domain-agnostic.
|
|
10
|
+
*/
|
|
11
|
+
export interface RecordPayload {
|
|
12
|
+
readonly [field: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* How a payload arrived. NOT telemetry — each value changes real behaviour:
|
|
16
|
+
*
|
|
17
|
+
* - `"fetch"` may SATISFY a field-set tag and CLEARS staleness on every field
|
|
18
|
+
* it carries. A broadcast can never do either, because a broadcast is
|
|
19
|
+
* partial by whitelist and can never complete a record.
|
|
20
|
+
* - `"broadcast"` is the only source that MARKS a field stale, using the
|
|
21
|
+
* `staleFields` its type declared. Saying so out loud prevents the obvious
|
|
22
|
+
* wrong reading, in which a fetch that omits a stale-declared field re-marks
|
|
23
|
+
* it and the refetch loop never settles.
|
|
24
|
+
*
|
|
25
|
+
* A mutation's returned item is a `"fetch"`: it is a full server answer to a
|
|
26
|
+
* request this client made, so it may satisfy a tag.
|
|
27
|
+
*/
|
|
28
|
+
export type WriteSource = "fetch" | "broadcast";
|
|
29
|
+
/** Options every write carries. */
|
|
30
|
+
export interface WriteOptions {
|
|
31
|
+
readonly source: WriteSource;
|
|
32
|
+
/**
|
|
33
|
+
* The tenant the REQUEST was issued under — stamped by the caller at request
|
|
34
|
+
* time, never read from module state inside `write`.
|
|
35
|
+
*
|
|
36
|
+
* Reading it inside `write` would read the tenant as it is NOW, which is
|
|
37
|
+
* precisely the bug this parameter exists to prevent: a team-A response that
|
|
38
|
+
* resolves after a switch to team B would write team A's records into a
|
|
39
|
+
* store now being read as team B's. A broadcast is stamped with the tenant
|
|
40
|
+
* whose channel delivered it, which the subscription already knows. So both
|
|
41
|
+
* sources carry it and neither infers it.
|
|
42
|
+
*/
|
|
43
|
+
readonly tenant?: string | number;
|
|
44
|
+
/**
|
|
45
|
+
* An OPAQUE field-set name — `"card"`, `"detail"`, whatever the caller's own
|
|
46
|
+
* api layer already calls the selector it asked for. The store never
|
|
47
|
+
* interprets it; it records which tags a key has satisfied and compares them
|
|
48
|
+
* for equality, so the library still knows nothing about any type's fields.
|
|
49
|
+
*
|
|
50
|
+
* Only a `"fetch"` may satisfy a tag. It is what makes `useRecord(…,
|
|
51
|
+
* {needs:"card"})` report `unknown` for a record seeded only by a broadcast,
|
|
52
|
+
* rather than handing a projection function a record with no `files` and no
|
|
53
|
+
* `workflow_runs` — which renders a confident "0 documents" card for a
|
|
54
|
+
* 434-page demand.
|
|
55
|
+
*/
|
|
56
|
+
readonly tag?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* What a write did. Every value is observable so a refusal leaves a trace: the
|
|
60
|
+
* prior art in this codebase records nothing at all on rejection, which is why
|
|
61
|
+
* its out-of-order guard was inert for years without anyone noticing.
|
|
62
|
+
*
|
|
63
|
+
* - `applied` — accepted, and something a reader can observe changed.
|
|
64
|
+
* - `unchanged` — accepted, and nothing changed. NOBODY IS NOTIFIED. A
|
|
65
|
+
* redelivered duplicate must not invalidate every hydration memo that
|
|
66
|
+
* touched the key and re-render every subscriber.
|
|
67
|
+
* - `dropped-older` — strictly older than the record's own stored timestamp.
|
|
68
|
+
* - `dropped-tenant` — stamped for a tenant this store is no longer serving.
|
|
69
|
+
* - `rejected-shape` — not a record at all: no `__type`, no `id`, or a
|
|
70
|
+
* `{__stale:true}` ping, which carries neither.
|
|
71
|
+
*/
|
|
72
|
+
export type WriteVerdict = "applied" | "unchanged" | "dropped-older" | "dropped-tenant" | "rejected-shape";
|
|
73
|
+
/** The result of one `write`. */
|
|
74
|
+
export interface WriteResult {
|
|
75
|
+
readonly verdict: WriteVerdict;
|
|
76
|
+
/**
|
|
77
|
+
* Every key this write touched, INCLUDING nested ones — a demand payload
|
|
78
|
+
* carrying 434 files returns 435 keys.
|
|
79
|
+
*
|
|
80
|
+
* The caller that issued a refetch to clear a staleness signal needs this to
|
|
81
|
+
* decide whether the write it just made actually satisfied what it was
|
|
82
|
+
* issued for. Empty for every dropped or rejected verdict.
|
|
83
|
+
*/
|
|
84
|
+
readonly keys: readonly string[];
|
|
85
|
+
/** Why, for the verdicts that are refusals. Present only when it helps. */
|
|
86
|
+
readonly reason?: string;
|
|
87
|
+
}
|
|
88
|
+
/** The result of one `writeAll`. */
|
|
89
|
+
export interface WriteAllResult {
|
|
90
|
+
/** One entry per payload, in the order they were given. */
|
|
91
|
+
readonly results: readonly WriteResult[];
|
|
92
|
+
/** The union of every payload's touched keys, de-duplicated. */
|
|
93
|
+
readonly keys: readonly string[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* What a reader is told about one key.
|
|
97
|
+
*
|
|
98
|
+
* FOUR ABSENCES, NOT ONE. Never-fetched, deleted and broadcast-partial are
|
|
99
|
+
* different answers and a screen renders each of them differently. The fourth —
|
|
100
|
+
* a 404 — is deliberately NOT a member: a refusal carries a message and a
|
|
101
|
+
* remedy that the store has no way to build, so it belongs to whatever layer
|
|
102
|
+
* performed the request.
|
|
103
|
+
*
|
|
104
|
+
* `stale` is part of the state rather than a side channel because a write that
|
|
105
|
+
* changes no value but marks a field stale must still notify — and notifying
|
|
106
|
+
* for something no reader can observe is worse than not notifying at all.
|
|
107
|
+
*/
|
|
108
|
+
export type RecordState<T> = {
|
|
109
|
+
readonly status: "unknown";
|
|
110
|
+
} | {
|
|
111
|
+
readonly status: "present";
|
|
112
|
+
readonly record: T;
|
|
113
|
+
/** Bumps on every accepted write that changed something observable. */
|
|
114
|
+
readonly version: number;
|
|
115
|
+
/**
|
|
116
|
+
* Fields the last broadcast could not carry. Sorted, so two structurally
|
|
117
|
+
* equal stale sets compare equal.
|
|
118
|
+
*
|
|
119
|
+
* A held stale value can only be stale-LOW for a monotonic high-water
|
|
120
|
+
* mark, never stale-high, so holding the last number on screen while the
|
|
121
|
+
* refetch lands is honest. Dimming it is available, not mandatory.
|
|
122
|
+
*/
|
|
123
|
+
readonly stale: readonly string[];
|
|
124
|
+
} | {
|
|
125
|
+
readonly status: "deleted";
|
|
126
|
+
readonly at: string;
|
|
127
|
+
};
|
|
128
|
+
/** What `useRecord`/`useRecords` require of their caller. */
|
|
129
|
+
export interface ReadOptions {
|
|
130
|
+
/**
|
|
131
|
+
* REQUIRED, not optional. The tag a `"fetch"` must have satisfied before
|
|
132
|
+
* this key reports `present`.
|
|
133
|
+
*
|
|
134
|
+
* The tag mechanism existed once as an optional parameter and nothing forced
|
|
135
|
+
* a caller to use it, which reopened the exact hole it was introduced to
|
|
136
|
+
* close. One word in the signature is the whole fix.
|
|
137
|
+
*/
|
|
138
|
+
readonly needs: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* A registered record type: the runtime string and the compile-time type, so
|
|
142
|
+
* the two can never drift.
|
|
143
|
+
*
|
|
144
|
+
* `_record` is a phantom — it carries `T` through inference and holds no value
|
|
145
|
+
* at runtime.
|
|
146
|
+
*/
|
|
147
|
+
export interface RecordTypeToken<T> {
|
|
148
|
+
/** The NORMALIZED model name. Never carries a `Resource` suffix. */
|
|
149
|
+
readonly type: string;
|
|
150
|
+
/** @see RecordTypeOptions.staleFields */
|
|
151
|
+
readonly staleFields: readonly string[] | "*";
|
|
152
|
+
/** Phantom. Never read. */
|
|
153
|
+
readonly _record?: T;
|
|
154
|
+
}
|
|
155
|
+
/** What a type may declare at registration. */
|
|
156
|
+
export interface RecordTypeOptions {
|
|
157
|
+
/**
|
|
158
|
+
* The fields this type's BROADCAST cannot carry — because they are computed,
|
|
159
|
+
* or deliberately withheld from the whitelist. Everything else in a
|
|
160
|
+
* broadcast merges normally.
|
|
161
|
+
*
|
|
162
|
+
* Staleness is PER FIELD and never a whole-type on/off switch. A per-type
|
|
163
|
+
* binary discards the twelve real fields a broadcast does carry in order to
|
|
164
|
+
* signal the two it does not.
|
|
165
|
+
*
|
|
166
|
+
* `"*"` means every data field — the case where a type's broadcast carries
|
|
167
|
+
* identity and nothing else, and where the field set is not knowable at
|
|
168
|
+
* registration because it is a bag of dynamic attributes. It resolves at
|
|
169
|
+
* write time against the keys the record actually holds; see
|
|
170
|
+
* `resolveStaleFields` in `store.ts` for what that buys and what it costs.
|
|
171
|
+
*/
|
|
172
|
+
readonly staleFields?: readonly string[] | "*";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* One root affected by a staleness mark, and the fields under it that are now
|
|
176
|
+
* suspect.
|
|
177
|
+
*
|
|
178
|
+
* `key` is a ROOT, not the key that went stale. For a queue, the key that goes
|
|
179
|
+
* stale is a run while the list holds demands; naming the raw key would match
|
|
180
|
+
* no list slot and nothing would ever refetch. Roots are resolved through the
|
|
181
|
+
* hydration memo's deps map, which is safe in a way a maintained reverse index
|
|
182
|
+
* is not: it is rebuilt by the same walk that reads it, so there is no separate
|
|
183
|
+
* maintenance step to get wrong.
|
|
184
|
+
*/
|
|
185
|
+
export interface StalenessEntry {
|
|
186
|
+
readonly key: string;
|
|
187
|
+
readonly fields: readonly string[];
|
|
188
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { RecordPayload } from "./types";
|
|
2
|
+
/** A relation, once normalized: a pointer to another record's key. */
|
|
3
|
+
export interface RecordRef {
|
|
4
|
+
readonly __ref: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function isRecordRef(value: unknown): value is RecordRef;
|
|
7
|
+
/**
|
|
8
|
+
* Strip a trailing `Resource` so a delete and the record it deletes land on
|
|
9
|
+
* the same key.
|
|
10
|
+
*
|
|
11
|
+
* CREATED AND DELETED PAYLOADS DISAGREE ABOUT `__type`, verified in the
|
|
12
|
+
* backend twice: created/updated take it from a resource class name that no
|
|
13
|
+
* resource overrides, so it keeps the suffix (`"WorkflowRunResource"`), while
|
|
14
|
+
* deletes take it from a helper that strips the suffix (`"WorkflowRun"`).
|
|
15
|
+
*
|
|
16
|
+
* A store keyed on the raw string files a delete under a different key than
|
|
17
|
+
* the record it is deleting, so the tombstone never lands and the row stays on
|
|
18
|
+
* screen forever. One line, and without it a whole class of update silently
|
|
19
|
+
* does nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeTypeName(raw: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* `Type:id`, with the id COERCED TO A STRING.
|
|
24
|
+
*
|
|
25
|
+
* Ids are not uniformly numeric — a file's id is a UUID while a run's is an
|
|
26
|
+
* integer — and a Map keyed on the raw value would file `7` and `"7"`
|
|
27
|
+
* separately. There is deliberately NO `id || name` fallback: that is a quirk
|
|
28
|
+
* of one prior implementation, not part of any contract, and a record keyed on
|
|
29
|
+
* a name silently merges two different rows the day two of them share one.
|
|
30
|
+
*/
|
|
31
|
+
export declare function recordKey(type: string, id: string | number): string;
|
|
32
|
+
/** The identity a payload carries, or `undefined` if it is not a record. */
|
|
33
|
+
export interface PayloadIdentity {
|
|
34
|
+
readonly key: string;
|
|
35
|
+
readonly type: string;
|
|
36
|
+
readonly id: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function payloadIdentity(payload: RecordPayload): PayloadIdentity | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* A `{__stale:true, id, __model}` ping: an invalidation the backend sends
|
|
41
|
+
* under the normal event name with NO `__type` at all.
|
|
42
|
+
*
|
|
43
|
+
* Recognised by name and dropped, because keying it would need a type it does
|
|
44
|
+
* not have — and guessing one from `__model` (a PHP class name) is exactly the
|
|
45
|
+
* sort of inference that produces a store full of records nothing reads.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isStalePing(payload: RecordPayload): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The tombstone marker, or `undefined`.
|
|
50
|
+
*
|
|
51
|
+
* BOTH SPELLINGS, deliberately. A `deleted` event carries `deleted_at`; a
|
|
52
|
+
* soft-delete arriving as an `updated` event carries `__deleted_at`. The prior
|
|
53
|
+
* art in this codebase checks only the second, so its delete handling has never
|
|
54
|
+
* run once — the payload says `deleted_at` and the store asks for
|
|
55
|
+
* `__deleted_at`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function tombstoneAt(payload: RecordPayload): string | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* True when the payload says, in so many words, that the record is NOT
|
|
60
|
+
* deleted — which is what un-deletes a tombstoned record.
|
|
61
|
+
*
|
|
62
|
+
* Needed because absent keeps the previous value: without this, a record that
|
|
63
|
+
* was tombstoned could never come back, even when the server says it has.
|
|
64
|
+
*/
|
|
65
|
+
export declare function explicitlyUndeleted(payload: RecordPayload): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Normalize `__timestamp` to milliseconds.
|
|
68
|
+
*
|
|
69
|
+
* IT ARRIVES IN TWO UNITS, 1000x APART, and this is verified rather than
|
|
70
|
+
* theoretical: the backend reads the model's `updated_at` in MILLISECONDS when
|
|
71
|
+
* the model has that column, and falls back to a SECONDS-resolution wall clock
|
|
72
|
+
* when it does not. At least one real model has no `updated_at` column at all.
|
|
73
|
+
*
|
|
74
|
+
* A comparison across the two units is not merely wrong, it is CONSISTENTLY
|
|
75
|
+
* wrong in one direction: a seconds value is ~1000x smaller, so it always
|
|
76
|
+
* loses to a milliseconds value and such a record would never update again.
|
|
77
|
+
*
|
|
78
|
+
* An implausible magnitude — below either window — is a contract violation,
|
|
79
|
+
* and it is surfaced rather than coerced. It returns `undefined`, which means
|
|
80
|
+
* "cannot adjudicate": the write is still ACCEPTED (a bogus tiny number must
|
|
81
|
+
* not be allowed to make a record permanently un-writable), it just cannot
|
|
82
|
+
* refuse anything.
|
|
83
|
+
*/
|
|
84
|
+
export declare function normalizeTimestamp(raw: unknown): number | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Keys the store INTERPRETS rather than stores.
|
|
87
|
+
*
|
|
88
|
+
* Everything `__`-prefixed is envelope by this wire format's own convention,
|
|
89
|
+
* and two of them would actively break the store if kept as data:
|
|
90
|
+
* `__broadcasted_at` moves on every delivery, so storing it would make an
|
|
91
|
+
* identical redelivered payload look like a change and re-render the world —
|
|
92
|
+
* which is the exact cost the notify-only-if-changed rule exists to avoid.
|
|
93
|
+
*
|
|
94
|
+
* `deleted_at` is here because it is the tombstone marker, not a field.
|
|
95
|
+
*/
|
|
96
|
+
export declare function isEnvelopeKey(key: string): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type RefCallback } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Size an element to the space between its own top and the bottom of the box
|
|
4
|
+
* that scrolls it, and keep it sized as the page changes around it.
|
|
5
|
+
*
|
|
6
|
+
* Returns a ref callback for the element. The height is written as inline style
|
|
7
|
+
* rather than returned as state on purpose — a returned number would re-render
|
|
8
|
+
* the subtree on every resize frame, and the value is only ever consumed by one
|
|
9
|
+
* property on one element.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const fillRef = useFillHeight(fill);
|
|
13
|
+
* <div ref={fillRef} className="max-h-full …" />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* WHAT IT WATCHES, and why that list rather than a global observer. The number
|
|
17
|
+
* changes when a box ABOVE the element changes size: the scrollport itself, an
|
|
18
|
+
* ancestor between the two, or a sibling laid out before it. All three are
|
|
19
|
+
* observed. The element itself is deliberately NOT observed — it is the thing
|
|
20
|
+
* being written, and observing it is the loop.
|
|
21
|
+
*
|
|
22
|
+
* A sibling ADDED after mount is the known gap: the observer set is built once
|
|
23
|
+
* per attach. In practice the content above a filling element is a page header,
|
|
24
|
+
* not a live list, and a `key`ed remount covers the rest.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useFillHeight(enabled?: boolean): RefCallback<HTMLElement>;
|