@blac/core 2.0.16 → 2.0.18
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 +7 -1
- package/dist/StateContainerRegistry-4Ljd9EJx.js +1018 -0
- package/dist/StateContainerRegistry-4Ljd9EJx.js.map +1 -0
- package/dist/StateContainerRegistry-B1GYx9Xo.js +975 -0
- package/dist/StateContainerRegistry-B1GYx9Xo.js.map +1 -0
- package/dist/StateContainerRegistry-CEWpc1bj.cjs +1113 -0
- package/dist/StateContainerRegistry-CEWpc1bj.cjs.map +1 -0
- package/dist/StateContainerRegistry-CSNm2Ws5.cjs +1085 -0
- package/dist/StateContainerRegistry-CSNm2Ws5.cjs.map +1 -0
- package/dist/StateContainerRegistry-CvBOhAT2.js +996 -0
- package/dist/StateContainerRegistry-CvBOhAT2.js.map +1 -0
- package/dist/StateContainerRegistry-DJQqLrli.cjs +1046 -0
- package/dist/StateContainerRegistry-DJQqLrli.cjs.map +1 -0
- package/dist/acquire-B1Wuqla6.cjs +46 -0
- package/dist/acquire-B1Wuqla6.cjs.map +1 -0
- package/dist/acquire-BP-ZfUH2.js +35 -0
- package/dist/acquire-BP-ZfUH2.js.map +1 -0
- package/dist/acquire-BZIs6tpN.cjs +46 -0
- package/dist/acquire-BZIs6tpN.cjs.map +1 -0
- package/dist/acquire-CbMz7gWs.cjs +46 -0
- package/dist/acquire-CbMz7gWs.cjs.map +1 -0
- package/dist/acquire-CwB083au.js +35 -0
- package/dist/acquire-CwB083au.js.map +1 -0
- package/dist/acquire-G8aB3GAx.js +35 -0
- package/dist/acquire-G8aB3GAx.js.map +1 -0
- package/dist/config-C7Ktkjh2.cjs +32 -0
- package/dist/config-C7Ktkjh2.cjs.map +1 -0
- package/dist/config-COT4Xkbc.js +16 -0
- package/dist/config-COT4Xkbc.js.map +1 -0
- package/dist/config-Dk1FxfXj.js +16 -0
- package/dist/config-Dk1FxfXj.js.map +1 -0
- package/dist/config-b2z51Y_C.cjs +32 -0
- package/dist/config-b2z51Y_C.cjs.map +1 -0
- package/dist/config-buwpT7l3.js +16 -0
- package/dist/config-buwpT7l3.js.map +1 -0
- package/dist/config-sCPKPYxZ.cjs +32 -0
- package/dist/config-sCPKPYxZ.cjs.map +1 -0
- package/dist/core/Cubit.d.ts +4 -4
- package/dist/core/StateContainer.d.ts +85 -48
- package/dist/core/StateContainerRegistry.d.ts +3 -3
- package/dist/core/meta.d.ts +73 -0
- package/dist/core/symbols.d.ts +10 -17
- package/dist/debug.cjs +3 -3
- package/dist/debug.js +3 -3
- package/dist/ensure-B9oOqpOB.cjs +558 -0
- package/dist/ensure-B9oOqpOB.cjs.map +1 -0
- package/dist/ensure-BfYnvAsI.js +709 -0
- package/dist/ensure-BfYnvAsI.js.map +1 -0
- package/dist/ensure-C1a8IFWy.js +535 -0
- package/dist/ensure-C1a8IFWy.js.map +1 -0
- package/dist/ensure-DEXSmA-N.js +526 -0
- package/dist/ensure-DEXSmA-N.js.map +1 -0
- package/dist/ensure-Dx86f80B.cjs +567 -0
- package/dist/ensure-Dx86f80B.cjs.map +1 -0
- package/dist/ensure-DzqGej43.js +526 -0
- package/dist/ensure-DzqGej43.js.map +1 -0
- package/dist/ensure-_Oh8XPm4.cjs +732 -0
- package/dist/ensure-_Oh8XPm4.cjs.map +1 -0
- package/dist/ensure-fsQMjaQL.cjs +567 -0
- package/dist/ensure-fsQMjaQL.cjs.map +1 -0
- package/dist/index.cjs +10 -9
- package/dist/index.d.cts +6 -9
- package/dist/index.d.ts +6 -9
- package/dist/index.js +7 -7
- package/dist/management-14vsHr4t.cjs +85 -0
- package/dist/management-14vsHr4t.cjs.map +1 -0
- package/dist/management-444tdCBG.js +38 -0
- package/dist/management-444tdCBG.js.map +1 -0
- package/dist/management-BqGBdL01.js +38 -0
- package/dist/management-BqGBdL01.js.map +1 -0
- package/dist/management-BvUAjril.js +38 -0
- package/dist/management-BvUAjril.js.map +1 -0
- package/dist/management-DAV9sN-k.cjs +85 -0
- package/dist/management-DAV9sN-k.cjs.map +1 -0
- package/dist/management-Df7Y2sF1.cjs +85 -0
- package/dist/management-Df7Y2sF1.cjs.map +1 -0
- package/dist/plugin/BlacPlugin.d.ts +11 -12
- package/dist/plugin/PluginManager.d.ts +3 -4
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/testing.cjs +10 -15
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -6
- package/dist/testing.d.ts +4 -6
- package/dist/testing.js +10 -14
- package/dist/testing.js.map +1 -1
- package/dist/watch-B91opmva.cjs +70 -0
- package/dist/watch-B91opmva.cjs.map +1 -0
- package/dist/watch-CfrSSrRY.js +59 -0
- package/dist/watch-CfrSSrRY.js.map +1 -0
- package/dist/watch-Cnh3PtQA.cjs +70 -0
- package/dist/watch-Cnh3PtQA.cjs.map +1 -0
- package/dist/watch-D8hwbFU1.js +59 -0
- package/dist/watch-D8hwbFU1.js.map +1 -0
- package/dist/watch-DPVUwPUY.js +59 -0
- package/dist/watch-DPVUwPUY.js.map +1 -0
- package/dist/watch-DVmvZBMD.cjs +70 -0
- package/dist/watch-DVmvZBMD.cjs.map +1 -0
- package/dist/watch-entry.cjs +1 -1
- package/dist/watch-entry.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import { a as EMIT, c as getClassEquality, i as APPLY_DEPS, m as getBlacConfig, o as INIT_CONFIG, s as REMOVE_DEPS_OWNER } from "./StateContainerRegistry-4Ljd9EJx.js";
|
|
2
|
+
import { t as getRegistry } from "./config-COT4Xkbc.js";
|
|
3
|
+
import { ALL_PATHS, StructuralContainer } from "@dirtytalk/structural";
|
|
4
|
+
//#region src/utils/idGenerator.ts
|
|
5
|
+
/**
|
|
6
|
+
* Generate simple ID with timestamp and random (no counter tracking)
|
|
7
|
+
*
|
|
8
|
+
* Format: `${prefix}:${timestamp}_${random}`
|
|
9
|
+
*
|
|
10
|
+
* @param prefix - Prefix for the ID
|
|
11
|
+
* @returns Branded ID string
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const id = generateSimpleId('CounterBloc');
|
|
16
|
+
* // Returns: "CounterBloc:1698765432100_a3k9d7f2q"
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
function generateSimpleId(prefix, affix) {
|
|
20
|
+
if (affix) return `${prefix}:${affix}`;
|
|
21
|
+
return `${prefix}:${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/core/meta.ts
|
|
25
|
+
/**
|
|
26
|
+
* Non-enumerable brand carried on every `$blac` object produced by
|
|
27
|
+
* {@link createMeta}. Used by the clobber guard in `StateContainer` to detect
|
|
28
|
+
* a subclass class-field shadowing the base's own `$blac` property, and by the
|
|
29
|
+
* meta tests. Intentionally NOT re-exported from the package barrel.
|
|
30
|
+
*/
|
|
31
|
+
const META_BRAND = Symbol("blac.meta");
|
|
32
|
+
const EMPTY_DEPS$1 = /* @__PURE__ */ new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Build the frozen, branded `$blac` meta object for a container.
|
|
35
|
+
*
|
|
36
|
+
* Called from a `StateContainer` field initializer (`$blac = createMeta(this)`).
|
|
37
|
+
* Only the back-reference is captured at this point; every getter/method reads
|
|
38
|
+
* the container's `_`-private fields lazily on access, so field-initializer
|
|
39
|
+
* ordering relative to those private fields is irrelevant.
|
|
40
|
+
*
|
|
41
|
+
* The getters close over `container` (the real instance), never over a
|
|
42
|
+
* `this`-receiver — this is what keeps `$blac` reads safe through
|
|
43
|
+
* `buildTrackedProxy`.
|
|
44
|
+
*/
|
|
45
|
+
function createMeta(container) {
|
|
46
|
+
const c = container;
|
|
47
|
+
const meta = {
|
|
48
|
+
get name() {
|
|
49
|
+
return c._name;
|
|
50
|
+
},
|
|
51
|
+
get id() {
|
|
52
|
+
return c._instanceId;
|
|
53
|
+
},
|
|
54
|
+
get debug() {
|
|
55
|
+
return c._debug;
|
|
56
|
+
},
|
|
57
|
+
get createdAt() {
|
|
58
|
+
return c._createdAt;
|
|
59
|
+
},
|
|
60
|
+
get disposed() {
|
|
61
|
+
return c._disposed;
|
|
62
|
+
},
|
|
63
|
+
get dependencies() {
|
|
64
|
+
return c._dependencies ?? EMPTY_DEPS$1;
|
|
65
|
+
},
|
|
66
|
+
hydration: Object.freeze({
|
|
67
|
+
get status() {
|
|
68
|
+
return c._hydrationStatus;
|
|
69
|
+
},
|
|
70
|
+
get error() {
|
|
71
|
+
return c._hydrationError;
|
|
72
|
+
},
|
|
73
|
+
get isHydrated() {
|
|
74
|
+
return c._hydrationStatus === "hydrated";
|
|
75
|
+
},
|
|
76
|
+
get changedWhileHydrating() {
|
|
77
|
+
return c._changedWhileHydrating;
|
|
78
|
+
},
|
|
79
|
+
begin() {
|
|
80
|
+
c._beginHydration();
|
|
81
|
+
},
|
|
82
|
+
apply(next) {
|
|
83
|
+
return c._applyHydratedState(next);
|
|
84
|
+
},
|
|
85
|
+
finish() {
|
|
86
|
+
c._finishHydration();
|
|
87
|
+
},
|
|
88
|
+
fail(error) {
|
|
89
|
+
c._failHydration(error);
|
|
90
|
+
},
|
|
91
|
+
wait() {
|
|
92
|
+
return c._waitForHydration();
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
};
|
|
96
|
+
Object.defineProperty(meta, META_BRAND, {
|
|
97
|
+
value: true,
|
|
98
|
+
enumerable: false,
|
|
99
|
+
writable: false,
|
|
100
|
+
configurable: false
|
|
101
|
+
});
|
|
102
|
+
return Object.freeze(meta);
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/core/StateContainer.ts
|
|
106
|
+
/**
|
|
107
|
+
* Brand symbol carried (non-enumerable) on every handle returned by
|
|
108
|
+
* `depend()`. Framework adapters (`@blac/react`) use this to detect handles
|
|
109
|
+
* inside a tracked-proxy and swap in a session-bound wrapper.
|
|
110
|
+
*
|
|
111
|
+
* @internal — exported for `@blac/react`; not part of the public API.
|
|
112
|
+
*/
|
|
113
|
+
const DEP_BRAND = Symbol("blac.depHandle");
|
|
114
|
+
const EMPTY_DEPS = /* @__PURE__ */ new Map();
|
|
115
|
+
const warnedMembers = /* @__PURE__ */ new WeakMap();
|
|
116
|
+
function warnDeprecated(ctor, member, replacement) {
|
|
117
|
+
if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "production") return;
|
|
118
|
+
let seen = warnedMembers.get(ctor);
|
|
119
|
+
if (!seen) {
|
|
120
|
+
seen = /* @__PURE__ */ new Set();
|
|
121
|
+
warnedMembers.set(ctor, seen);
|
|
122
|
+
}
|
|
123
|
+
if (seen.has(member)) return;
|
|
124
|
+
seen.add(member);
|
|
125
|
+
console.warn(`[blac] \`StateContainer#${member}\` is deprecated; use \`${replacement}\` instead. (${ctor.name}; this warning fires once per class.)`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Shallow per-key `Object.is` comparison of two plain records. Keys are
|
|
129
|
+
* considered: a key present in one but not the other (regardless of value)
|
|
130
|
+
* makes the records unequal. Used to detect real deps changes.
|
|
131
|
+
*/
|
|
132
|
+
function shallowEqualRecord(a, b) {
|
|
133
|
+
if (a === b) return true;
|
|
134
|
+
const aKeys = Object.keys(a);
|
|
135
|
+
if (aKeys.length !== Object.keys(b).length) return false;
|
|
136
|
+
for (const key of aKeys) {
|
|
137
|
+
if (!(key in b)) return false;
|
|
138
|
+
if (!Object.is(a[key], b[key])) return false;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* BlaC's lifecycle/identity/dependency layer on top of `StructuralContainer`.
|
|
144
|
+
*
|
|
145
|
+
* StructuralContainer provides:
|
|
146
|
+
* - `state` getter
|
|
147
|
+
* - `emit` / `patch` / `update` (path-tracked, microtask-flushed)
|
|
148
|
+
* - `channel` (the underlying `DirtyChannel`)
|
|
149
|
+
* - `registerConsumerPaths` / `unregisterConsumer` (for fine-grained consumers)
|
|
150
|
+
* - per-class `PathInterner`
|
|
151
|
+
*
|
|
152
|
+
* StateContainer layers on:
|
|
153
|
+
* - identity (`name`, `instanceId`, `createdAt`, `debug`)
|
|
154
|
+
* - lifecycle (`dispose`, `isDisposed`, `onSystemEvent`)
|
|
155
|
+
* - hydration (`beginHydration` / `applyHydratedState` / `finishHydration` / `failHydration` / `waitForHydration`)
|
|
156
|
+
* - cross-bloc deps (`depend()`, `dependencies` getter)
|
|
157
|
+
* - per-consumer deps slices (`APPLY_DEPS` / `REMOVE_DEPS_OWNER` / `onDepsChanged`)
|
|
158
|
+
* - registry integration (config-driven equality, emit-rate circuit breaker)
|
|
159
|
+
*
|
|
160
|
+
* Subscribers can attach via:
|
|
161
|
+
* - `subscribe(listener)` — legacy state listener (back-compat); fires on
|
|
162
|
+
* every flush with the latest state.
|
|
163
|
+
* - `onSystemEvent('stateChanged' | 'dispose' | 'hydrationChanged', cb)` —
|
|
164
|
+
* coarse lifecycle events.
|
|
165
|
+
* - `this.channel.subscribe(interest, cb)` — path-scoped (new code).
|
|
166
|
+
*/
|
|
167
|
+
var StateContainer = class extends StructuralContainer {
|
|
168
|
+
static {
|
|
169
|
+
this.__excludeFromDevTools = false;
|
|
170
|
+
}
|
|
171
|
+
get deps() {
|
|
172
|
+
return this._deps;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* @internal Apply one owner's (consumer's) deps slice. Shallow-merges the
|
|
176
|
+
* slice, reconciles keys the owner dropped since its last apply (other
|
|
177
|
+
* owners' keys untouched), dev-warns on cross-owner key collisions, then
|
|
178
|
+
* recomputes the merged view and fires onDepsChanged if it changed.
|
|
179
|
+
*
|
|
180
|
+
* Idempotent: re-applying an identical slice for the same owner is a no-op.
|
|
181
|
+
*/
|
|
182
|
+
[APPLY_DEPS](ownerId, slice) {
|
|
183
|
+
if (this._disposed) return;
|
|
184
|
+
const owners = this._depsByOwner ??= /* @__PURE__ */ new Map();
|
|
185
|
+
const prevSlice = owners.get(ownerId);
|
|
186
|
+
if (prevSlice && shallowEqualRecord(prevSlice, slice)) return;
|
|
187
|
+
if (process.env.NODE_ENV !== "production") for (const key of Object.keys(slice)) for (const [otherOwner, otherSlice] of owners) {
|
|
188
|
+
if (otherOwner === ownerId) continue;
|
|
189
|
+
if (key in otherSlice && otherSlice[key] !== slice[key]) console.warn(`[${this._name}] multiple owners writing dep \`${String(key)}\`; last write wins`);
|
|
190
|
+
}
|
|
191
|
+
owners.set(ownerId, { ...slice });
|
|
192
|
+
this.reconcileDeps();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* @internal Withdraw an owner's entire deps slice (consumer unmounted).
|
|
196
|
+
*/
|
|
197
|
+
[REMOVE_DEPS_OWNER](ownerId) {
|
|
198
|
+
if (this._disposed) return;
|
|
199
|
+
if (!this._depsByOwner?.delete(ownerId)) return;
|
|
200
|
+
this.reconcileDeps();
|
|
201
|
+
}
|
|
202
|
+
reconcileDeps() {
|
|
203
|
+
const prev = this._deps;
|
|
204
|
+
const next = {};
|
|
205
|
+
if (this._depsByOwner) for (const slice of this._depsByOwner.values()) Object.assign(next, slice);
|
|
206
|
+
for (const key of Object.keys(prev)) if (!(key in next)) next[key] = void 0;
|
|
207
|
+
if (shallowEqualRecord(prev, next)) return;
|
|
208
|
+
this._deps = next;
|
|
209
|
+
this.onDepsChanged(next, prev);
|
|
210
|
+
this._registry.emit("depsChanged", this, prev, next);
|
|
211
|
+
}
|
|
212
|
+
onDepsChanged(_next, _prev) {}
|
|
213
|
+
/**
|
|
214
|
+
* @deprecated Use `$blac.name`. Setter kept until M5 (`[INIT_CONFIG]` and
|
|
215
|
+
* legacy `initConfig` historically wrote `name`).
|
|
216
|
+
*/
|
|
217
|
+
get name() {
|
|
218
|
+
warnDeprecated(this.constructor, "name", "$blac.name");
|
|
219
|
+
return this._name;
|
|
220
|
+
}
|
|
221
|
+
set name(value) {
|
|
222
|
+
warnDeprecated(this.constructor, "name", "$blac.name");
|
|
223
|
+
this._name = value;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* @deprecated Use `$blac.debug`. Setter kept until M5.
|
|
227
|
+
*/
|
|
228
|
+
get debug() {
|
|
229
|
+
warnDeprecated(this.constructor, "debug", "$blac.debug");
|
|
230
|
+
return this._debug;
|
|
231
|
+
}
|
|
232
|
+
set debug(value) {
|
|
233
|
+
warnDeprecated(this.constructor, "debug", "$blac.debug");
|
|
234
|
+
this._debug = value;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* @deprecated Use `$blac.id`. Setter kept until M5.
|
|
238
|
+
*/
|
|
239
|
+
get instanceId() {
|
|
240
|
+
warnDeprecated(this.constructor, "instanceId", "$blac.id");
|
|
241
|
+
return this._instanceId;
|
|
242
|
+
}
|
|
243
|
+
set instanceId(value) {
|
|
244
|
+
warnDeprecated(this.constructor, "instanceId", "$blac.id");
|
|
245
|
+
this._instanceId = value;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* @deprecated Use `$blac.createdAt`.
|
|
249
|
+
*/
|
|
250
|
+
get createdAt() {
|
|
251
|
+
warnDeprecated(this.constructor, "createdAt", "$blac.createdAt");
|
|
252
|
+
return this._createdAt;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* @deprecated Use `$blac.dependencies`.
|
|
256
|
+
*/
|
|
257
|
+
get dependencies() {
|
|
258
|
+
warnDeprecated(this.constructor, "dependencies", "$blac.dependencies");
|
|
259
|
+
return this._dependencies ?? EMPTY_DEPS;
|
|
260
|
+
}
|
|
261
|
+
get args() {
|
|
262
|
+
return this._config.args;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Declare a cross-bloc dependency. Returns a branded handle with two
|
|
266
|
+
* accessors — the dep instance is resolved against the registry on each call,
|
|
267
|
+
* which keeps the surface immune to dep-instance churn:
|
|
268
|
+
*
|
|
269
|
+
* - `handle.track(options?)` — reactive read. Returns `[state, instance]`.
|
|
270
|
+
* Inside a getter reached through the React proxy this subscribes the
|
|
271
|
+
* reading component to the dep's changes (base impl: live, no subscription).
|
|
272
|
+
* - `handle.untracked(options?)` — returns the live instance with no tracking,
|
|
273
|
+
* for imperative method calls and one-off reads.
|
|
274
|
+
*
|
|
275
|
+
* `defaultArgs` resolves the dep instance when an accessor is called without
|
|
276
|
+
* its own `args`; per-call `options.args` overrides it and can derive from
|
|
277
|
+
* current state. This does NOT auto-resubscribe outside the React proxy;
|
|
278
|
+
* non-React consumers needing updates should subscribe explicitly.
|
|
279
|
+
*/
|
|
280
|
+
depend(Type, defaultArgs) {
|
|
281
|
+
if (!this._dependencies) this._dependencies = /* @__PURE__ */ new Map();
|
|
282
|
+
this._dependencies.set(Type, this._registry.resolveKey(Type, void 0, defaultArgs));
|
|
283
|
+
const resolve = (args) => {
|
|
284
|
+
const effectiveArgs = args ?? defaultArgs;
|
|
285
|
+
const key = this._registry.resolveKey(Type, void 0, effectiveArgs);
|
|
286
|
+
return this._registry.ensure(Type, key, effectiveArgs);
|
|
287
|
+
};
|
|
288
|
+
const handle = {
|
|
289
|
+
track: (options) => {
|
|
290
|
+
const instance = resolve(options?.args);
|
|
291
|
+
return [instance.state, instance];
|
|
292
|
+
},
|
|
293
|
+
untracked: (options) => resolve(options?.args)
|
|
294
|
+
};
|
|
295
|
+
Object.defineProperty(handle, DEP_BRAND, {
|
|
296
|
+
value: {
|
|
297
|
+
Type,
|
|
298
|
+
defaultArgs
|
|
299
|
+
},
|
|
300
|
+
enumerable: false,
|
|
301
|
+
writable: false,
|
|
302
|
+
configurable: false
|
|
303
|
+
});
|
|
304
|
+
return handle;
|
|
305
|
+
}
|
|
306
|
+
constructor(initialState, options) {
|
|
307
|
+
super(initialState, options);
|
|
308
|
+
this._depsByOwner = null;
|
|
309
|
+
this._deps = {};
|
|
310
|
+
this._disposed = false;
|
|
311
|
+
this._hydrationStatus = "idle";
|
|
312
|
+
this._changedWhileHydrating = false;
|
|
313
|
+
this._hydrationPromise = null;
|
|
314
|
+
this._hydrationPromiseSettled = false;
|
|
315
|
+
this._config = {};
|
|
316
|
+
this._initCalled = false;
|
|
317
|
+
this._emitWindowStart = 0;
|
|
318
|
+
this._emitCount = 0;
|
|
319
|
+
this._emitRateWarned = false;
|
|
320
|
+
this._listeners = /* @__PURE__ */ new Set();
|
|
321
|
+
this._systemEventHandlers = /* @__PURE__ */ new Map();
|
|
322
|
+
this._dependencies = null;
|
|
323
|
+
this._pendingChange = null;
|
|
324
|
+
this._bridgeUnsub = null;
|
|
325
|
+
this._registry = getRegistry();
|
|
326
|
+
this._equalityFn = getBlacConfig().equality;
|
|
327
|
+
this._name = this.constructor.name;
|
|
328
|
+
this._debug = false;
|
|
329
|
+
this._instanceId = generateSimpleId(this.constructor.name, "main");
|
|
330
|
+
this._createdAt = Date.now();
|
|
331
|
+
this.$blac = createMeta(this);
|
|
332
|
+
this.onSystemEvent = (event, handler) => {
|
|
333
|
+
let handlers = this._systemEventHandlers.get(event);
|
|
334
|
+
if (!handlers) {
|
|
335
|
+
handlers = /* @__PURE__ */ new Set();
|
|
336
|
+
this._systemEventHandlers.set(event, handlers);
|
|
337
|
+
}
|
|
338
|
+
handlers.add(handler);
|
|
339
|
+
return () => {
|
|
340
|
+
handlers?.delete(handler);
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
this._bridgeUnsub = this.channel.subscribe(() => ALL_PATHS, () => this._drainPending());
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Called once after construction with the args passed at acquire time, before the first
|
|
347
|
+
* state snapshot is read by any consumer. Override to seed args-derived state (via
|
|
348
|
+
* this.emit(...)) or kick off loads.
|
|
349
|
+
*/
|
|
350
|
+
init(_args) {}
|
|
351
|
+
/**
|
|
352
|
+
* @internal Framework-only configuration entry point (registry + testing
|
|
353
|
+
* helpers). Writes the `_`-private identity fields directly (NOT through the
|
|
354
|
+
* deprecated setters, so it never trips a deprecation warning), resolves
|
|
355
|
+
* per-class equality, emits the registry `created` event, and runs `init()`
|
|
356
|
+
* once. See {@link INIT_CONFIG}.
|
|
357
|
+
*/
|
|
358
|
+
[INIT_CONFIG](config) {
|
|
359
|
+
this._config = { ...config };
|
|
360
|
+
this._name = this._config.name || this.constructor.name;
|
|
361
|
+
this._debug = this._config.debug ?? false;
|
|
362
|
+
this._instanceId = generateSimpleId(this.constructor.name, this._config.instanceId);
|
|
363
|
+
const perClass = getClassEquality(this.constructor);
|
|
364
|
+
this._equalityFn = perClass ?? getBlacConfig().equality;
|
|
365
|
+
this._registry.emit("created", this);
|
|
366
|
+
if (!this._initCalled) {
|
|
367
|
+
this._initCalled = true;
|
|
368
|
+
this.init(this._config.args);
|
|
369
|
+
}
|
|
370
|
+
if (process.env.NODE_ENV !== "production") {
|
|
371
|
+
if (!this.$blac?.[META_BRAND]) console.warn(`[blac] ${this.constructor.name} appears to declare its own \`$blac\` member, which shadows the reserved meta namespace. \`$blac\` is reserved by StateContainer — remove the subclass field.`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* @deprecated Use `[INIT_CONFIG]` (the symbol from `@blac/core`). Kept as a
|
|
376
|
+
* delegate until M5.
|
|
377
|
+
*/
|
|
378
|
+
initConfig(config) {
|
|
379
|
+
warnDeprecated(this.constructor, "initConfig", "[INIT_CONFIG]");
|
|
380
|
+
this[INIT_CONFIG](config);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* @deprecated Use `$blac.disposed`.
|
|
384
|
+
*/
|
|
385
|
+
get isDisposed() {
|
|
386
|
+
warnDeprecated(this.constructor, "isDisposed", "$blac.disposed");
|
|
387
|
+
return this._disposed;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* @deprecated Use `$blac.hydration.status`.
|
|
391
|
+
*/
|
|
392
|
+
get hydrationStatus() {
|
|
393
|
+
warnDeprecated(this.constructor, "hydrationStatus", "$blac.hydration.status");
|
|
394
|
+
return this._hydrationStatus;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* @deprecated Use `$blac.hydration.error`.
|
|
398
|
+
*/
|
|
399
|
+
get hydrationError() {
|
|
400
|
+
warnDeprecated(this.constructor, "hydrationError", "$blac.hydration.error");
|
|
401
|
+
return this._hydrationError;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* @deprecated Use `$blac.hydration.isHydrated`.
|
|
405
|
+
*/
|
|
406
|
+
get isHydrated() {
|
|
407
|
+
warnDeprecated(this.constructor, "isHydrated", "$blac.hydration.isHydrated");
|
|
408
|
+
return this._hydrationStatus === "hydrated";
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* @deprecated Use `$blac.hydration.changedWhileHydrating`.
|
|
412
|
+
*/
|
|
413
|
+
get changedWhileHydrating() {
|
|
414
|
+
warnDeprecated(this.constructor, "changedWhileHydrating", "$blac.hydration.changedWhileHydrating");
|
|
415
|
+
return this._changedWhileHydrating;
|
|
416
|
+
}
|
|
417
|
+
subscribe(listener) {
|
|
418
|
+
if (this._disposed) throw new Error(`Cannot subscribe to disposed container ${this._name}`);
|
|
419
|
+
this._listeners.add(listener);
|
|
420
|
+
return () => {
|
|
421
|
+
this._listeners.delete(listener);
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
dispose() {
|
|
425
|
+
if (this._disposed) return;
|
|
426
|
+
if (this._debug) console.log(`[${this._name}] Disposing...`);
|
|
427
|
+
if (this._depsByOwner) {
|
|
428
|
+
this._depsByOwner.clear();
|
|
429
|
+
this.reconcileDeps();
|
|
430
|
+
}
|
|
431
|
+
this._disposed = true;
|
|
432
|
+
if (this._hydrationStatus === "hydrating") this._failHydration(/* @__PURE__ */ new Error(`Hydration cancelled because ${this._name} was disposed`));
|
|
433
|
+
this.emitSystemEvent("dispose", void 0);
|
|
434
|
+
this._bridgeUnsub?.();
|
|
435
|
+
this._bridgeUnsub = null;
|
|
436
|
+
this._listeners.clear();
|
|
437
|
+
this._systemEventHandlers.clear();
|
|
438
|
+
this._pendingChange = null;
|
|
439
|
+
this._registry.emit("disposed", this);
|
|
440
|
+
if (this._debug) console.log(`[${this._name}] Disposed successfully`);
|
|
441
|
+
}
|
|
442
|
+
emit(next) {
|
|
443
|
+
this.applyState(next, "default");
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Override of `StructuralContainer.patch` that routes through the
|
|
447
|
+
* StateContainer concerns: disposed guard, dev-only emit-rate check,
|
|
448
|
+
* `_changedWhileHydrating` flag, pending-change capture (so legacy
|
|
449
|
+
* listeners and `stateChanged` system events see the merged prev/next),
|
|
450
|
+
* and the registry-level `stateChanged` notification. We still call
|
|
451
|
+
* `super.patch` so path-marking semantics (the whole point of patch) are
|
|
452
|
+
* preserved.
|
|
453
|
+
*/
|
|
454
|
+
patch(partial) {
|
|
455
|
+
if (this._disposed) throw new Error(`Cannot emit state from disposed container ${this._name}`);
|
|
456
|
+
const partialKeys = Object.keys(partial);
|
|
457
|
+
if (partialKeys.length === 0) return;
|
|
458
|
+
const prev = this.state;
|
|
459
|
+
let allEqual = true;
|
|
460
|
+
for (const key of partialKeys) if (!Object.is(prev[key], partial[key])) {
|
|
461
|
+
allEqual = false;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (allEqual) return;
|
|
465
|
+
if (process.env.NODE_ENV !== "production") this._checkEmitRate();
|
|
466
|
+
super.patch(partial);
|
|
467
|
+
const next = this.state;
|
|
468
|
+
if (Object.is(prev, next)) return;
|
|
469
|
+
if (this._hydrationStatus === "hydrating") this._changedWhileHydrating = true;
|
|
470
|
+
if (this._pendingChange) this._pendingChange.next = next;
|
|
471
|
+
else this._pendingChange = {
|
|
472
|
+
prev,
|
|
473
|
+
next
|
|
474
|
+
};
|
|
475
|
+
if (this._registry.hasStateChangedListeners) this._registry.notifyStateChanged(this, prev, next);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* @internal @deprecated Symbol-keyed alias for `emit`. Kept only so legacy
|
|
479
|
+
* in-package tests that index with `[EMIT]` typecheck/run unchanged. C5
|
|
480
|
+
* removes this along with the `EMIT` symbol.
|
|
481
|
+
*/
|
|
482
|
+
[EMIT](next) {
|
|
483
|
+
this.applyState(next, "default");
|
|
484
|
+
}
|
|
485
|
+
applyState(next, source) {
|
|
486
|
+
if (this._disposed) throw new Error(`Cannot emit state from disposed container ${this._name}`);
|
|
487
|
+
const prev = this.state;
|
|
488
|
+
if (prev === next) return;
|
|
489
|
+
if (this._equalityFn(prev, next)) return;
|
|
490
|
+
if (process.env.NODE_ENV !== "production") this._checkEmitRate();
|
|
491
|
+
if (this._hydrationStatus === "hydrating" && source !== "hydration") this._changedWhileHydrating = true;
|
|
492
|
+
if (this._pendingChange) this._pendingChange.next = next;
|
|
493
|
+
else this._pendingChange = {
|
|
494
|
+
prev,
|
|
495
|
+
next
|
|
496
|
+
};
|
|
497
|
+
super.emit(next);
|
|
498
|
+
if (this._registry.hasStateChangedListeners) this._registry.notifyStateChanged(this, prev, next);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Called by the channel-bridge subscriber on each flush. Drains the
|
|
502
|
+
* pending change (if any) into legacy listeners and the 'stateChanged'
|
|
503
|
+
* system event. No pending change == flush from a no-op patch == skip.
|
|
504
|
+
*/
|
|
505
|
+
_drainPending() {
|
|
506
|
+
const pending = this._pendingChange;
|
|
507
|
+
if (!pending) return;
|
|
508
|
+
this._pendingChange = null;
|
|
509
|
+
const handlers = this._systemEventHandlers.get("stateChanged");
|
|
510
|
+
if (handlers && handlers.size > 0) {
|
|
511
|
+
const payload = {
|
|
512
|
+
state: pending.next,
|
|
513
|
+
previousState: pending.prev
|
|
514
|
+
};
|
|
515
|
+
let count = 0;
|
|
516
|
+
const size = handlers.size;
|
|
517
|
+
for (const handler of handlers) {
|
|
518
|
+
if (++count > size) break;
|
|
519
|
+
try {
|
|
520
|
+
handler(payload);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
console.error(`[${this._name}] Error in system event handler:`, error);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (this._listeners.size > 0) {
|
|
527
|
+
const current = this.state;
|
|
528
|
+
let count = 0;
|
|
529
|
+
const size = this._listeners.size;
|
|
530
|
+
for (const listener of this._listeners) {
|
|
531
|
+
if (++count > size) break;
|
|
532
|
+
try {
|
|
533
|
+
listener(current);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error(`[${this._name}] Error in listener:`, error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
_beginHydration() {
|
|
541
|
+
if (this._disposed) throw new Error(`Cannot begin hydration for disposed container ${this._name}`);
|
|
542
|
+
this._changedWhileHydrating = false;
|
|
543
|
+
this._hydrationError = void 0;
|
|
544
|
+
this._hydrationPromise = null;
|
|
545
|
+
this._resolveHydrationPromise = void 0;
|
|
546
|
+
this._rejectHydrationPromise = void 0;
|
|
547
|
+
this._hydrationPromiseSettled = false;
|
|
548
|
+
this.ensureHydrationPromise();
|
|
549
|
+
this.setHydrationStatus("hydrating");
|
|
550
|
+
}
|
|
551
|
+
_applyHydratedState(next) {
|
|
552
|
+
if (this._disposed) return false;
|
|
553
|
+
if (this._hydrationStatus !== "hydrating" || this._changedWhileHydrating) return false;
|
|
554
|
+
this.applyState(next, "hydration");
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
_finishHydration() {
|
|
558
|
+
if (this._hydrationStatus !== "hydrating") {
|
|
559
|
+
if (this._hydrationStatus === "hydrated") return;
|
|
560
|
+
if (this._hydrationStatus === "error") this.ensureHydrationPromise();
|
|
561
|
+
}
|
|
562
|
+
this.setHydrationStatus("hydrated");
|
|
563
|
+
this.resolveHydration();
|
|
564
|
+
}
|
|
565
|
+
_failHydration(error) {
|
|
566
|
+
const err = error instanceof Error ? error : /* @__PURE__ */ new Error(`Hydration failed: ${String(error)}`);
|
|
567
|
+
this._hydrationError = err;
|
|
568
|
+
this.setHydrationStatus("error", err);
|
|
569
|
+
this.rejectHydration(err);
|
|
570
|
+
}
|
|
571
|
+
_waitForHydration() {
|
|
572
|
+
if (this._hydrationStatus === "idle" || this._hydrationStatus === "hydrated") return Promise.resolve();
|
|
573
|
+
if (this._hydrationStatus === "error") return Promise.reject(this._hydrationError ?? /* @__PURE__ */ new Error(`Hydration failed for container ${this._name}`));
|
|
574
|
+
return this.ensureHydrationPromise();
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* @deprecated Use `$blac.hydration.begin()`.
|
|
578
|
+
*/
|
|
579
|
+
beginHydration() {
|
|
580
|
+
warnDeprecated(this.constructor, "beginHydration", "$blac.hydration.begin()");
|
|
581
|
+
this._beginHydration();
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* @deprecated Use `$blac.hydration.apply()`.
|
|
585
|
+
*/
|
|
586
|
+
applyHydratedState(next) {
|
|
587
|
+
warnDeprecated(this.constructor, "applyHydratedState", "$blac.hydration.apply()");
|
|
588
|
+
return this._applyHydratedState(next);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* @deprecated Use `$blac.hydration.finish()`.
|
|
592
|
+
*/
|
|
593
|
+
finishHydration() {
|
|
594
|
+
warnDeprecated(this.constructor, "finishHydration", "$blac.hydration.finish()");
|
|
595
|
+
this._finishHydration();
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* @deprecated Use `$blac.hydration.fail()`.
|
|
599
|
+
*/
|
|
600
|
+
failHydration(error) {
|
|
601
|
+
warnDeprecated(this.constructor, "failHydration", "$blac.hydration.fail()");
|
|
602
|
+
this._failHydration(error);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* @deprecated Use `$blac.hydration.wait()`.
|
|
606
|
+
*/
|
|
607
|
+
waitForHydration() {
|
|
608
|
+
warnDeprecated(this.constructor, "waitForHydration", "$blac.hydration.wait()");
|
|
609
|
+
return this._waitForHydration();
|
|
610
|
+
}
|
|
611
|
+
setHydrationStatus(status, error) {
|
|
612
|
+
const previousStatus = this._hydrationStatus;
|
|
613
|
+
this._hydrationStatus = status;
|
|
614
|
+
this._hydrationError = error;
|
|
615
|
+
this.emitSystemEvent("hydrationChanged", {
|
|
616
|
+
status,
|
|
617
|
+
previousStatus,
|
|
618
|
+
error,
|
|
619
|
+
changedWhileHydrating: this._changedWhileHydrating
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
ensureHydrationPromise() {
|
|
623
|
+
if (!this._hydrationPromise || this._hydrationPromiseSettled) {
|
|
624
|
+
this._hydrationPromiseSettled = false;
|
|
625
|
+
this._hydrationPromise = new Promise((resolve, reject) => {
|
|
626
|
+
this._resolveHydrationPromise = () => {
|
|
627
|
+
if (this._hydrationPromiseSettled) return;
|
|
628
|
+
this._hydrationPromiseSettled = true;
|
|
629
|
+
resolve();
|
|
630
|
+
};
|
|
631
|
+
this._rejectHydrationPromise = (error) => {
|
|
632
|
+
if (this._hydrationPromiseSettled) return;
|
|
633
|
+
this._hydrationPromiseSettled = true;
|
|
634
|
+
reject(error);
|
|
635
|
+
};
|
|
636
|
+
});
|
|
637
|
+
this._hydrationPromise.catch(() => {});
|
|
638
|
+
}
|
|
639
|
+
return this._hydrationPromise;
|
|
640
|
+
}
|
|
641
|
+
resolveHydration() {
|
|
642
|
+
this._resolveHydrationPromise?.();
|
|
643
|
+
}
|
|
644
|
+
rejectHydration(error) {
|
|
645
|
+
this.ensureHydrationPromise();
|
|
646
|
+
this._rejectHydrationPromise?.(error);
|
|
647
|
+
}
|
|
648
|
+
emitSystemEvent(event, payload) {
|
|
649
|
+
const handlers = this._systemEventHandlers.get(event);
|
|
650
|
+
if (!handlers) return;
|
|
651
|
+
for (const handler of handlers) try {
|
|
652
|
+
handler(payload);
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.error(`[${this._name}] Error in system event handler:`, error);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Dev-only soft circuit breaker. Counts real state changes in a rolling 1s
|
|
659
|
+
* window and warns once if the rate exceeds `maxEmitsPerSecond` — the
|
|
660
|
+
* signature of a runaway loop (RAF/animation or emit-on-every-commit) pushing
|
|
661
|
+
* high-frequency data through state, which freezes subscribers/plugins.
|
|
662
|
+
*/
|
|
663
|
+
_checkEmitRate() {
|
|
664
|
+
const limit = getBlacConfig().maxEmitsPerSecond;
|
|
665
|
+
if (!(limit > 0) || !Number.isFinite(limit) || this._emitRateWarned) return;
|
|
666
|
+
const now = Date.now();
|
|
667
|
+
if (now - this._emitWindowStart >= 1e3) {
|
|
668
|
+
this._emitWindowStart = now;
|
|
669
|
+
this._emitCount = 1;
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
this._emitCount++;
|
|
673
|
+
if (this._emitCount > limit) {
|
|
674
|
+
this._emitRateWarned = true;
|
|
675
|
+
console.warn(`[${this._name}] emitted more than ${limit} state changes in under a second. This is usually a runaway loop pushing high-frequency data through state (e.g. \`emit\`/\`patch\` inside a requestAnimationFrame loop, or an effect that emits on every render). It can freeze the app by saturating subscribers and plugins (logging/devtools). Keep high-frequency work imperative and emit only coarse/throttled state, or raise \`configureBlac({ maxEmitsPerSecond })\`. (This warning fires once.)`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/core/Cubit.ts
|
|
681
|
+
/**
|
|
682
|
+
* `Cubit<S>` is a `StateContainer<S>` with `emit` / `patch` exposed as
|
|
683
|
+
* public mutation surface. Today it adds nothing structurally beyond
|
|
684
|
+
* `StateContainer` — both are inherited from the underlying
|
|
685
|
+
* `StructuralContainer<S>`. Kept as a real class (not a type alias) because
|
|
686
|
+
* downstream code does `instance instanceof Cubit` checks (see A2 audit).
|
|
687
|
+
*
|
|
688
|
+
* The class body is intentionally empty: a no-op `emit` override would
|
|
689
|
+
* still go through `applyState`, and `patch` is inherited from
|
|
690
|
+
* `StructuralContainer` (path-diffed, microtask-flushed). If a caller
|
|
691
|
+
* needs the old "skip if no real change" patch semantics, they can wrap
|
|
692
|
+
* `patch` themselves or call `emit` after a manual equality check.
|
|
693
|
+
*/
|
|
694
|
+
var Cubit = class extends StateContainer {};
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/registry/ensure.ts
|
|
697
|
+
/**
|
|
698
|
+
* Ensure an instance exists without taking ownership (no ref added). Instance
|
|
699
|
+
* identity is derived purely from `args`, matching `acquire`.
|
|
700
|
+
*/
|
|
701
|
+
function ensure(BlocClass, opts) {
|
|
702
|
+
const registry = getRegistry();
|
|
703
|
+
const key = registry.resolveKey(BlocClass, void 0, opts?.args);
|
|
704
|
+
return registry.ensure(BlocClass, key, opts?.args);
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
export { StateContainer as i, Cubit as n, DEP_BRAND as r, ensure as t };
|
|
708
|
+
|
|
709
|
+
//# sourceMappingURL=ensure-BfYnvAsI.js.map
|