akanjs 3.0.0-alpha.89 → 3.0.0-alpha.90

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": "akanjs",
3
- "version": "3.0.0-alpha.89",
3
+ "version": "3.0.0-alpha.90",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -10,6 +10,7 @@ import {
10
10
  type PrimitiveScalar,
11
11
  } from "akanjs/base";
12
12
  import { ConstantRegistry } from "akanjs/constant";
13
+ import { databaseStateNames } from "./databaseStateNames";
13
14
 
14
15
  export type StateInitializer = () => unknown;
15
16
  export type StateInitializerMap = Record<string, StateInitializer>;
@@ -155,6 +156,25 @@ export interface DerivedStateBuilder<WritableState> {
155
156
  };
156
157
  }
157
158
 
159
+ const draftMetaByRefName = new Map<string, DraftMeta>();
160
+
161
+ /**
162
+ * The one `DraftMeta` for a model, memoized.
163
+ *
164
+ * Every field of it is derived from the refName, so two stores for the same model describe the same thing. They
165
+ * have to describe it with the *same object*, though: an app store that extends a lib store for one model carries
166
+ * the lib's copy and registers its own, and both reach `mergeDerivedMeta` — where an entry is a conflict only when
167
+ * two declarations disagree, which is decided by identity.
168
+ */
169
+ export const draftMetaOf = (refName: string): DraftMeta => {
170
+ const cached = draftMetaByRefName.get(refName);
171
+ if (cached) return cached;
172
+ const names = databaseStateNames(refName);
173
+ const meta: DraftMeta = { refName, formKey: names.modelForm, draftKey: names.modelDraft };
174
+ draftMetaByRefName.set(refName, meta);
175
+ return meta;
176
+ };
177
+
158
178
  export const createEmptyDerivedMeta = (): StateDerivedMeta => ({
159
179
  drafts: {},
160
180
  persistSession: {},
@@ -171,17 +191,26 @@ export const mergeDerivedMeta = (...metas: (StateDerivedMeta | undefined)[]): St
171
191
  mergeMetaRecord(merged.persistSession, meta.persistSession);
172
192
  mergeMetaRecord(merged.search, meta.search);
173
193
  mergeMetaRecord(merged.computed, meta.computed);
174
- for (const key of meta.derivedKeys) {
175
- if (merged.derivedKeys.has(key)) throw new Error(`Duplicate derived state key: ${key}`);
176
- merged.derivedKeys.add(key);
177
- }
178
194
  }
195
+
196
+ for (const key of [...Object.keys(merged.search), ...Object.keys(merged.computed)]) merged.derivedKeys.add(key);
179
197
  return merged;
180
198
  };
181
199
 
200
+ /**
201
+ * Copies `source` over `target`, rejecting only a genuine conflict.
202
+ *
203
+ * The same key arriving twice is normal: a store lists another as a lib store, so it already carries that store's
204
+ * metadata when both are registered and `StoreRegistry.merge` sees each of them. What arrives then is the very
205
+ * object the first declaration produced, because merging copies the reference. Two *different* objects under one
206
+ * key are two declarations claiming the same state, which is the mistake this is here to catch.
207
+ */
182
208
  const mergeMetaRecord = <T>(target: Record<string, T>, source: Record<string, T>) => {
183
209
  for (const [key, value] of Object.entries(source)) {
184
- if (key in target) throw new Error(`Duplicate state metadata key: ${key}`);
210
+ if (key in target) {
211
+ if (Object.is(target[key], value)) continue;
212
+ throw new Error(`Duplicate state metadata key: ${key}`);
213
+ }
185
214
  target[key] = value;
186
215
  }
187
216
  };
package/store/store.ts CHANGED
@@ -26,7 +26,6 @@ import type {
26
26
  } from "akanjs/signal";
27
27
  import { type DefaultAction, makeActions, makeFormSetter } from "./action";
28
28
  import type { ActionOwner } from "./actionTag";
29
- import { databaseStateNames } from "./databaseStateNames";
30
29
  import { createDatabaseState, createSliceState, type DefaultState } from "./state";
31
30
  import {
32
31
  createDerivedStateBuilder,
@@ -35,6 +34,7 @@ import {
35
34
  createWritableStateBuilder,
36
35
  type DerivedStateBuilder,
37
36
  type DerivedStateOf,
37
+ draftMetaOf,
38
38
  mergeDerivedMeta,
39
39
  resolveDerivedState,
40
40
  resolveWritableState,
@@ -218,12 +218,8 @@ export function store<Sig extends ClientSignal<any, any, any> | string, State>(
218
218
  };
219
219
  Object.assign(storeCls[STATE_META], signalState);
220
220
  Object.assign(storeCls[STATE_INIT_META], createStateInitializerMap(signalState));
221
- const draftNames = databaseStateNames(refName);
222
- storeCls[STATE_DERIVED_META].drafts[draftNames.modelForm] = {
223
- refName,
224
- formKey: draftNames.modelForm,
225
- draftKey: draftNames.modelDraft,
226
- };
221
+ const draftMeta = draftMetaOf(refName);
222
+ storeCls[STATE_DERIVED_META].drafts[draftMeta.formKey] = draftMeta;
227
223
  const actions = {
228
224
  ...makeFormSetter(refName, signal.fetch),
229
225
  ...makeActions(refName, signal.serializedSignal.slice ?? {}, signal.fetch),
@@ -132,6 +132,15 @@ export interface DerivedStateBuilder<WritableState> {
132
132
  options: ComputedOptions<Result>;
133
133
  };
134
134
  }
135
+ /**
136
+ * The one `DraftMeta` for a model, memoized.
137
+ *
138
+ * Every field of it is derived from the refName, so two stores for the same model describe the same thing. They
139
+ * have to describe it with the *same object*, though: an app store that extends a lib store for one model carries
140
+ * the lib's copy and registers its own, and both reach `mergeDerivedMeta` — where an entry is a conflict only when
141
+ * two declarations disagree, which is decided by identity.
142
+ */
143
+ export declare const draftMetaOf: (refName: string) => DraftMeta;
135
144
  export declare const createEmptyDerivedMeta: () => StateDerivedMeta;
136
145
  export declare const mergeDerivedMeta: (...metas: (StateDerivedMeta | undefined)[]) => StateDerivedMeta;
137
146
  export declare const createWritableStateBuilder: () => WritableStateBuilder;