akanjs 3.0.0-alpha.11 → 3.0.0-alpha.13

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.
Files changed (69) hide show
  1. package/base/symbols.ts +4 -0
  2. package/constant/index.ts +1 -0
  3. package/constant/mask.ts +60 -0
  4. package/dictionary/dictInfo.ts +12 -1
  5. package/fetch/client/fetchClient.ts +9 -0
  6. package/package.json +1 -1
  7. package/server/akanApp.ts +3 -1
  8. package/service/predefinedAdaptor/index.ts +1 -0
  9. package/service/predefinedAdaptor/insightQuery.ts +183 -0
  10. package/signal/mcp/Msg.ts +5 -33
  11. package/store/action.ts +9 -20
  12. package/store/actionTag.ts +28 -0
  13. package/store/agent/AgentBridge.ts +281 -0
  14. package/store/agent/StoreCatalogue.ts +296 -0
  15. package/store/agent/index.ts +3 -0
  16. package/store/agent/types.ts +50 -0
  17. package/store/databaseStateNames.ts +31 -0
  18. package/store/formSetterNames.ts +21 -0
  19. package/store/index.ts +7 -0
  20. package/store/rootStore.ts +2 -1
  21. package/store/sliceRole.ts +36 -0
  22. package/store/state.ts +2 -12
  23. package/store/store.ts +5 -0
  24. package/store/storeInstance.ts +54 -16
  25. package/store/storeRegistry.ts +10 -0
  26. package/types/base/symbols.d.ts +4 -0
  27. package/types/constant/index.d.ts +1 -0
  28. package/types/constant/mask.d.ts +34 -0
  29. package/types/dictionary/dictInfo.d.ts +9 -2
  30. package/types/fetch/client/fetchClient.d.ts +9 -0
  31. package/types/service/predefinedAdaptor/index.d.ts +1 -0
  32. package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
  33. package/types/signal/mcp/Msg.d.ts +3 -4
  34. package/types/store/actionTag.d.ts +17 -0
  35. package/types/store/agent/AgentBridge.d.ts +70 -0
  36. package/types/store/agent/StoreCatalogue.d.ts +21 -0
  37. package/types/store/agent/index.d.ts +3 -0
  38. package/types/store/agent/types.d.ts +49 -0
  39. package/types/store/agent.d.ts +1 -0
  40. package/types/store/databaseStateNames.d.ts +25 -0
  41. package/types/store/formSetterNames.d.ts +16 -0
  42. package/types/store/index.d.ts +6 -0
  43. package/types/store/rootStore.d.ts +4 -1
  44. package/types/store/sliceRole.d.ts +25 -0
  45. package/types/store/store.d.ts +4 -1
  46. package/types/store/storeInstance.d.ts +16 -0
  47. package/types/store/storeRegistry.d.ts +3 -0
  48. package/types/ui/Agent/Dock.d.ts +16 -0
  49. package/types/ui/Agent/Section.d.ts +10 -0
  50. package/types/ui/Agent/StateKey.d.ts +15 -0
  51. package/types/ui/Agent/Tool.d.ts +15 -0
  52. package/types/ui/Agent/Transcript.d.ts +13 -0
  53. package/types/ui/Agent/index.d.ts +11 -0
  54. package/types/ui/Agent.d.ts +1 -0
  55. package/types/ui/agentAttrs.d.ts +14 -0
  56. package/types/ui/index.d.ts +2 -0
  57. package/ui/Agent/Dock.tsx +61 -0
  58. package/ui/Agent/Section.tsx +24 -0
  59. package/ui/Agent/StateKey.tsx +42 -0
  60. package/ui/Agent/Tool.tsx +66 -0
  61. package/ui/Agent/Transcript.tsx +33 -0
  62. package/ui/Agent/index.ts +7 -0
  63. package/ui/Button.tsx +2 -0
  64. package/ui/Field.tsx +15 -7
  65. package/ui/Input.tsx +7 -0
  66. package/ui/Select.tsx +2 -1
  67. package/ui/Switch.tsx +2 -0
  68. package/ui/agentAttrs.ts +19 -0
  69. package/ui/index.ts +2 -0
@@ -0,0 +1,21 @@
1
+ import { capitalize, lowerlize } from "akanjs/common";
2
+
3
+ /**
4
+ * The keys `makeFormSetter` publishes for one field of one model.
5
+ *
6
+ * Shared with the agent catalogue, which has to answer what `setNameOnUser` takes without parsing the name apart —
7
+ * `set(.+)On(.+)` has more than one reading whenever a field or a model name contains `On`. Computing the same names
8
+ * forward from the same field metadata is unambiguous, and keeps the two from drifting when a name changes here.
9
+ */
10
+ export const formSetterNames = (className: string, key: string) => {
11
+ const classKeyName = capitalize(key);
12
+ return {
13
+ field: lowerlize(key),
14
+ Field: classKeyName,
15
+ setFieldOnModel: `set${classKeyName}On${className}`,
16
+ addFieldOnModel: `add${classKeyName}On${className}`,
17
+ subFieldOnModel: `sub${classKeyName}On${className}`,
18
+ addOrSubFieldOnModel: `addOrSub${classKeyName}On${className}`,
19
+ uploadFieldOnModel: `upload${classKeyName}On${className}`,
20
+ };
21
+ };
package/store/index.ts CHANGED
@@ -1,8 +1,15 @@
1
1
  export * from "./action";
2
+ export * from "./actionTag";
3
+ export * from "./agent";
2
4
  export * from "./baseSt";
5
+ export * from "./databaseStateNames";
6
+ export * from "./formSetterNames";
3
7
  export * from "./rootStore";
8
+ export * from "./sliceRole";
4
9
  export * from "./state";
5
10
  export * from "./stateBuilder";
6
11
  export * from "./store";
12
+
13
+ export { StoreInstance } from "./storeInstance";
7
14
  export * from "./storeRegistry";
8
15
  export * from "./types";
@@ -1,4 +1,4 @@
1
- import type { ACTION_META, Cls, STATE_DERIVED_META, STATE_INIT_META, STATE_META } from "akanjs/base";
1
+ import type { ACTION_META, ACTION_OWNER_META, Cls, STATE_DERIVED_META, STATE_INIT_META, STATE_META } from "akanjs/base";
2
2
  import type { SerializedSlice } from "akanjs/signal";
3
3
  import type { StateDerivedMeta, StateInitializerMap } from "./stateBuilder";
4
4
  import type { SetGetWritable } from "./types";
@@ -20,6 +20,7 @@ export type RootStoreCls<
20
20
  [STATE_INIT_META]: StateInitializerMap;
21
21
  [STATE_DERIVED_META]: StateDerivedMeta;
22
22
  [ACTION_META]: { [key: string]: (...args: any[]) => any };
23
+ [ACTION_OWNER_META]: { [key: string]: string };
23
24
  slice: { [key: string]: { [key: string]: SerializedSlice } };
24
25
  _slice: SliceInfoObj;
25
26
  }
@@ -0,0 +1,36 @@
1
+ import type { SerializedArg } from "akanjs/signal";
2
+ import type { SliceStateKey } from "./state";
3
+
4
+ /** The generated slice actions, named by what they do rather than by the key any one slice publishes them under. */
5
+ export type SliceActionKey =
6
+ | "initModel"
7
+ | "refreshModel"
8
+ | "selectModel"
9
+ | "setPageOfModel"
10
+ | "addPageOfModel"
11
+ | "setLimitOfModel"
12
+ | "setQueryArgsOfModel"
13
+ | "setSortOfModel";
14
+
15
+ /**
16
+ * What a generated key on `st.do` / `st.use` actually is.
17
+ *
18
+ * A slice publishes its keys with the model and the suffix spliced into the role name — `setPageOfUserInOrg` for the
19
+ * `setPageOfModel` role of `user`'s `inOrg` slice — and the splicing is string replacement over capitalized names.
20
+ * Recording the role while the key is being built is what saves a reader of the finished store from running that
21
+ * replacement backwards, which does not have one answer: on a model named `page`, `setPageOfPage` is both the
22
+ * paging action and a field setter.
23
+ */
24
+ export interface SliceActionRole {
25
+ role: SliceActionKey;
26
+ refName: string;
27
+ sliceName: string;
28
+ /** The slice's own arguments, which the `initModel` and `setQueryArgsOfModel` roles take positionally. */
29
+ args: SerializedArg[];
30
+ }
31
+
32
+ export interface SliceStateRole {
33
+ role: SliceStateKey;
34
+ refName: string;
35
+ sliceName: string;
36
+ }
package/store/state.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  SlceDbSort,
15
15
  SliceCls,
16
16
  } from "akanjs/signal";
17
+ import { databaseStateNames } from "./databaseStateNames";
17
18
  import type { StoreSliceArgs, StoreSliceMap, StoreSliceSuffixCap, Submit } from "./types";
18
19
 
19
20
  export type SliceStateKey =
@@ -151,18 +152,7 @@ export type DefaultState<
151
152
 
152
153
  export const createDatabaseState = (refName: string) => {
153
154
  const cnst = ConstantRegistry.getDatabase(refName);
154
- const [fieldName, className] = [refName, capitalize(refName)];
155
- const names = {
156
- model: fieldName,
157
- Model: className,
158
- modelLoading: `${fieldName}Loading`,
159
- modelForm: `${fieldName}Form`,
160
- modelFormLoading: `${fieldName}FormLoading`,
161
- modelSubmit: `${fieldName}Submit`,
162
- modelViewAt: `${fieldName}ViewAt`,
163
- modelModal: `${fieldName}Modal`,
164
- modelOperation: `${fieldName}Operation`,
165
- };
155
+ const names = databaseStateNames(refName);
166
156
  const baseState = {
167
157
  [names.model]: null,
168
158
  [names.modelLoading]: true,
package/store/store.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ACTION_META,
3
+ ACTION_OWNER_META,
3
4
  type Cls,
4
5
  type MergeAllActionTypes,
5
6
  type MergeAllKeyOfObjects,
@@ -120,6 +121,7 @@ export type StoreCls<
120
121
  [STATE_INIT_META]: StateInitializerMap;
121
122
  [STATE_DERIVED_META]: StateDerivedMeta;
122
123
  [ACTION_META]: { [key: string]: (...args: any[]) => any };
124
+ [ACTION_OWNER_META]: { [key: string]: string };
123
125
  slice: { [key: string]: SerializedSlice };
124
126
  _slice: SlceCls[typeof SLICE_META];
125
127
  }
@@ -187,6 +189,7 @@ export function store<Sig extends ClientSignal<any, any, any> | string, State>(
187
189
  static [STATE_INIT_META] = {};
188
190
  static [STATE_DERIVED_META] = createEmptyDerivedMeta();
189
191
  static [ACTION_META] = {};
192
+ static [ACTION_OWNER_META] = {};
190
193
  static slice = {};
191
194
  } as StoreCls;
192
195
  const writableStateRaw = stateFactory(createWritableStateBuilder());
@@ -204,6 +207,7 @@ export function store<Sig extends ClientSignal<any, any, any> | string, State>(
204
207
  writable.meta,
205
208
  );
206
209
  Object.assign(storeCls[ACTION_META], ...libStores.map((libStore) => libStore[ACTION_META]));
210
+ Object.assign(storeCls[ACTION_OWNER_META], ...libStores.map((libStore) => libStore[ACTION_OWNER_META] ?? {}));
207
211
  applyMixins(storeCls, libStores);
208
212
  if (signal) {
209
213
  const signalState = {
@@ -218,6 +222,7 @@ export function store<Sig extends ClientSignal<any, any, any> | string, State>(
218
222
  };
219
223
  Object.assign(storeCls.prototype, actions);
220
224
  Object.assign(storeCls[ACTION_META], actions);
225
+ Object.assign(storeCls[ACTION_OWNER_META], Object.fromEntries(Object.keys(actions).map((key) => [key, refName])));
221
226
  Object.assign(storeCls.slice, signal.serializedSignal.slice ?? {});
222
227
  }
223
228
  if (derivedStateFactory) {
@@ -1,10 +1,13 @@
1
- import { ACTION_META, STATE_DERIVED_META, STATE_INIT_META } from "akanjs/base";
1
+ import { ACTION_META, ACTION_OWNER_META, STATE_DERIVED_META, STATE_INIT_META } from "akanjs/base";
2
2
  import { Translator } from "akanjs/client";
3
3
  import { capitalize, Logger, parseAkanI18nEnv } from "akanjs/common";
4
+ import type { SerializedArg } from "akanjs/signal";
4
5
  import { enableMapSet, produce } from "immer";
5
6
  import type { RefObject } from "react";
7
+ import { actionTagOf, tagAction } from "./actionTag";
6
8
  import { useEffect, useRef, useSyncExternalStore } from "./hooks";
7
9
  import type { RootStoreCls } from "./rootStore";
10
+ import type { SliceActionKey, SliceActionRole, SliceStateRole } from "./sliceRole";
8
11
  import type { SliceStateKey } from "./state";
9
12
  import { evaluateInitializers, type SearchParamsState, type StateDerivedMeta } from "./stateBuilder";
10
13
 
@@ -14,16 +17,6 @@ type StoreStateRecord = Record<string, unknown>;
14
17
  type StoreAction = (...args: unknown[]) => unknown;
15
18
  type TranslationParam = Record<string, string | number>;
16
19
 
17
- type SliceActionKey =
18
- | "initModel"
19
- | "refreshModel"
20
- | "selectModel"
21
- | "setPageOfModel"
22
- | "addPageOfModel"
23
- | "setLimitOfModel"
24
- | "setQueryArgsOfModel"
25
- | "setSortOfModel";
26
-
27
20
  const isRecord = (value: unknown): value is Record<string, unknown> =>
28
21
  Boolean(value && typeof value === "object" && !Array.isArray(value));
29
22
 
@@ -172,6 +165,41 @@ export class StoreInstance {
172
165
  do: { [key: string]: StoreAction } = {};
173
166
  slice: { [key: string]: unknown } = {};
174
167
 
168
+ readonly #sliceActionRoles = new Map<string, SliceActionRole>();
169
+ readonly #sliceStateRoles = new Map<string, SliceStateRole>();
170
+ readonly #actionArity = new Map<string, number>();
171
+ readonly #actionOwners = new Map<string, string>();
172
+
173
+ /** Which module declared each action. The dictionary node its words are written in is named after it. */
174
+ get actionOwners(): ReadonlyMap<string, string> {
175
+ return this.#actionOwners;
176
+ }
177
+
178
+ /**
179
+ * How many arguments each action declares.
180
+ *
181
+ * Recorded because `do[key]` is a rest-argument wrapper around the real method, so its own `length` is zero for
182
+ * everything — the arity is gone by the time anyone holding the instance could ask. A rest parameter on the method
183
+ * itself still reads as zero; nothing can recover that.
184
+ */
185
+ get actionArity(): ReadonlyMap<string, number> {
186
+ return this.#actionArity;
187
+ }
188
+
189
+ /** What each generated slice key is, for a reader that has only the finished store. See `SliceActionRole`. */
190
+ get sliceActionRoles(): ReadonlyMap<string, SliceActionRole> {
191
+ return this.#sliceActionRoles;
192
+ }
193
+
194
+ get sliceStateRoles(): ReadonlyMap<string, SliceStateRole> {
195
+ return this.#sliceStateRoles;
196
+ }
197
+
198
+ /** Keys the store materializes from a computation, the URL, or storage. `set` throws on them. */
199
+ get derivedKeys(): ReadonlySet<string> {
200
+ return this.#derivedMeta.derivedKeys;
201
+ }
202
+
175
203
  constructor(store?: RootStoreCls) {
176
204
  if (store) this.addStore(store);
177
205
  }
@@ -189,6 +217,7 @@ export class StoreInstance {
189
217
  hasNewStateKey = true;
190
218
  }
191
219
  if (hasNewStateKey) this.#state = nextState;
220
+ for (const [key, refName] of Object.entries(store[ACTION_OWNER_META] ?? {})) this.#actionOwners.set(key, refName);
192
221
  this.#mergeActions(store[ACTION_META]);
193
222
  this.#extendAccessors(derivedState, store[ACTION_META]);
194
223
  this.#buildSlices(store);
@@ -199,6 +228,7 @@ export class StoreInstance {
199
228
  #mergeActions(actions: { [key: string]: StoreAction }) {
200
229
  for (const [k, method] of Object.entries(actions)) {
201
230
  this.#ctx[k] = (...args: unknown[]) => method.call(this.#ctx, ...args);
231
+ this.#actionArity.set(k, method.length);
202
232
  }
203
233
  }
204
234
 
@@ -208,11 +238,11 @@ export class StoreInstance {
208
238
  this.use[k] = () => this.sel((s) => s[k]);
209
239
  if (this.#derivedMeta.derivedKeys.has(k)) continue;
210
240
  const setKey = `set${capitalize(k)}`;
211
- this.do[setKey] = (value: unknown) => this.set({ [k]: value });
241
+ this.do[setKey] = tagAction((value: unknown) => this.set({ [k]: value }), { action: setKey, state: k });
212
242
  }
213
243
  }
214
244
  for (const k of Object.keys(actions)) {
215
- this.do[k] = async (...args: unknown[]) => {
245
+ const dispatch = async (...args: unknown[]) => {
216
246
  Logger.verbose(`${k} action loading...`);
217
247
  const start = Date.now();
218
248
  try {
@@ -225,6 +255,8 @@ export class StoreInstance {
225
255
  throw error;
226
256
  }
227
257
  };
258
+
259
+ this.do[k] = tagAction(dispatch, actionTagOf(actions[k]) ?? { action: k });
228
260
  }
229
261
  }
230
262
 
@@ -254,7 +286,7 @@ export class StoreInstance {
254
286
  });
255
287
  }
256
288
 
257
- #buildSlice(refName: string, sliceName: string, serializedSlice: { args?: any[] }) {
289
+ #buildSlice(refName: string, sliceName: string, serializedSlice: { args?: SerializedArg[] }) {
258
290
  const [fieldName, className] = [refName, capitalize(refName)];
259
291
  const names: { [key in SliceStateKey | SliceActionKey | "model" | "Model"]: string } = {
260
292
  model: fieldName,
@@ -324,14 +356,20 @@ export class StoreInstance {
324
356
  argLength: serializedSlice.args?.length ?? 0,
325
357
  };
326
358
 
359
+ const args = serializedSlice.args ?? [];
327
360
  for (const key of Object.keys(namesOfSliceAction) as SliceActionKey[]) {
328
361
  const rootActionKey = namesOfSliceAction[key];
329
- if (this.do[rootActionKey]) targetSlice.do[names[key]] = this.do[rootActionKey];
362
+ if (!this.do[rootActionKey]) continue;
363
+ targetSlice.do[names[key]] = this.do[rootActionKey];
364
+ this.#sliceActionRoles.set(rootActionKey, { role: key, refName, sliceName, args });
330
365
  }
331
366
 
332
367
  for (const key of Object.keys(namesOfSliceState) as SliceStateKey[]) {
333
368
  const rootStateKey = namesOfSliceState[key];
334
- if (this.use[rootStateKey]) targetSlice.use[names[key]] = this.use[rootStateKey];
369
+ if (this.use[rootStateKey]) {
370
+ targetSlice.use[names[key]] = this.use[rootStateKey];
371
+ this.#sliceStateRoles.set(rootStateKey, { role: key, refName, sliceName });
372
+ }
335
373
  const setRootKey = `set${capitalize(rootStateKey)}`;
336
374
  const setLocalKey = `set${capitalize(names[key])}`;
337
375
  if (this.do[setRootKey]) targetSlice.do[setLocalKey] = this.do[setRootKey];
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ACTION_META,
3
+ ACTION_OWNER_META,
3
4
  type MergeAllKeyOfObjects,
4
5
  type MergeAllKeyOfTypes,
5
6
  type MergeAllTypes,
@@ -37,15 +38,23 @@ function getStoreRegistryState(): StoreRegistryState {
37
38
 
38
39
  export class StoreRegistry {
39
40
  static #state = getStoreRegistryState();
41
+ /** The one store every `st.use` / `st.do` in the process goes through. What an agent bridge drives. */
42
+ static get instance(): StoreInstance {
43
+ return StoreRegistry.#state.instance;
44
+ }
40
45
  static register<StrCls extends StoreCls>(store: StrCls): StrCls {
41
46
  const parentStore = Object.getPrototypeOf(store) as StoreCls | null;
42
47
  const actions = { ...(parentStore?.[ACTION_META] ?? {}) };
48
+
49
+ const owners = { ...(parentStore?.[ACTION_OWNER_META] ?? {}) };
43
50
  Object.entries(Object.getOwnPropertyDescriptors(store.prototype)).forEach(([key, descriptor]) => {
44
51
  if (key === "constructor") return;
45
52
  if (!descriptor.value || typeof descriptor.value !== "function") return;
46
53
  actions[key] = descriptor.value;
54
+ owners[key] = store.refName;
47
55
  });
48
56
  store[ACTION_META] = actions;
57
+ store[ACTION_OWNER_META] = owners;
49
58
  StoreRegistry.#state.store.set(store.refName, store);
50
59
  return store;
51
60
  }
@@ -69,6 +78,7 @@ export class StoreRegistry {
69
78
  static [STATE_INIT_META] = Object.assign({}, ...stores.map((store) => store[STATE_INIT_META]));
70
79
  static [STATE_DERIVED_META] = mergeDerivedMeta(...stores.map((store) => store[STATE_DERIVED_META]));
71
80
  static [ACTION_META] = Object.assign({}, ...stores.map((store) => store[ACTION_META]));
81
+ static [ACTION_OWNER_META] = Object.assign({}, ...stores.map((store) => store[ACTION_OWNER_META] ?? {}));
72
82
  static slice: { [key: string]: { [key: string]: SerializedSlice } } = {};
73
83
  }
74
84
  stores.forEach((store) => {
@@ -13,6 +13,10 @@ export declare const STATE_META: unique symbol;
13
13
  export declare const STATE_INIT_META: unique symbol;
14
14
  export declare const STATE_DERIVED_META: unique symbol;
15
15
  export declare const ACTION_META: unique symbol;
16
+ /** Which module declared each action, which is the dictionary node its words are written in. */
17
+ export declare const ACTION_OWNER_META: unique symbol;
18
+ /** What a dispatcher does, carried on the function so a component handed one can annotate the DOM with it. */
19
+ export declare const ACTION_TAG: unique symbol;
16
20
  export declare const SERVER_VALUE: unique symbol;
17
21
  export declare const CLIENT_VALUE: unique symbol;
18
22
  export declare const DEFAULT_VALUE: unique symbol;
@@ -5,6 +5,7 @@ export * from "./deserialize.d.ts";
5
5
  export * from "./fieldInfo.d.ts";
6
6
  export * from "./getDefault.d.ts";
7
7
  export * from "./immerify.d.ts";
8
+ export * from "./mask.d.ts";
8
9
  export * from "./purify.d.ts";
9
10
  export * from "./serialize.d.ts";
10
11
  export * from "./textFieldPathSet.d.ts";
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A model as masking reads it — the constructor, for the field metadata it carries at runtime.
3
+ *
4
+ * Structural rather than `ConstantModelRef` so that anything holding the class can name it, and read through
5
+ * `FIELD_META` the way `resolveReturn` reads it.
6
+ */
7
+ export interface MaskModel {
8
+ name: string;
9
+ }
10
+ /** The part of a field's metadata masking turns on. Mirrors what `resolveReturn` branches over. */
11
+ interface MaskField {
12
+ fieldType?: string;
13
+ isClass?: boolean;
14
+ modelRef?: MaskModel;
15
+ }
16
+ export declare const maskFieldsOf: (model: MaskModel) => Record<string, MaskField> | null;
17
+ /** The `hidden` and `secret` field names of `model` that `value` still carries populated. */
18
+ export declare const leakingFieldsOf: (model: MaskModel, value: Record<string, unknown>) => string[];
19
+ /**
20
+ * Strips what a model marks `hidden` or `secret`, by the model the caller names rather than by the one the value
21
+ * happens to still carry.
22
+ *
23
+ * That distinction is the whole point. A check that reads the class off the value can only mask what arrives as an
24
+ * instance, so a `{ ...doc }` spread, a `toJSON()`, an `immerify()`, or a round-trip through `JSON.stringify` reaches
25
+ * its destination with the metadata already gone and nothing can be done about it. A named model is metadata the
26
+ * value cannot lose, so a hydrated document and a plain object copied out of one mask identically.
27
+ *
28
+ * This is the field half of `resolveReturn` and deliberately not the whole of it. That one also loads every relation
29
+ * it walks past, which is right for a query's return value and wrong here, where the value is already in hand.
30
+ *
31
+ * Returns `unknown` rather than the argument's type, because what comes back is missing fields that type promises.
32
+ */
33
+ export declare const mask: (model: MaskModel, value: unknown) => unknown;
34
+ export {};
@@ -180,8 +180,15 @@ export declare class ModelDictInfo<Languages extends [string, ...string[]] = [st
180
180
  [K in Enum["value"]]: FieldTranslation<Languages>;
181
181
  };
182
182
  }
183
- type AnyModelDictInfo = ModelDictInfo<any, any, any, any, any, any, any, any, any, any, any>;
184
- type MergeTwoModelDicts<ModelDict1, ModelDict2> = ModelDict1 extends ModelDictInfo<infer Languages1, infer ModelKey1, infer InsightKey1, infer QueryKey1, infer SortKey1, infer EnumKey1, infer BaseSignalKey1, infer SliceKey1, infer EndpointKey1, infer ErrorKey1, infer EtcKey1> ? ModelDict2 extends ModelDictInfo<infer _Languages2, infer ModelKey2, infer InsightKey2, infer QueryKey2, infer SortKey2, infer EnumKey2, infer BaseSignalKey2, infer SliceKey2, infer EndpointKey2, infer ErrorKey2, infer EtcKey2> ? ModelDictInfo<Languages1, ModelKey1 | ModelKey2, InsightKey1 | InsightKey2, QueryKey1 | QueryKey2, SortKey1 | SortKey2, EnumKey1 | EnumKey2, BaseSignalKey1 | BaseSignalKey2, SliceKey1 | SliceKey2, EndpointKey1 | EndpointKey2, ErrorKey1 | ErrorKey2, EtcKey1 | EtcKey2> : ModelDict1 : never;
183
+ /**
184
+ * Every parameter of `ModelDictInfo` is listed here positionally, so a parameter added to the class has to be
185
+ * added to all three lists below in the same slot. Omitting one does not fail to compile — inference silently
186
+ * shifts, so the last parameter falls off the end and becomes its default `never`: adding `StoreKey` before
187
+ * `ErrorKey` once cost an extending app the whole of the lib's `EtcKey` (`.translate()`) union, which reads at
188
+ * the call site as `l("<model>.<key>")` no longer existing.
189
+ */
190
+ type AnyModelDictInfo = ModelDictInfo<any, any, any, any, any, any, any, any, any, any, any, any>;
191
+ type MergeTwoModelDicts<ModelDict1, ModelDict2> = ModelDict1 extends ModelDictInfo<infer Languages1, infer ModelKey1, infer InsightKey1, infer QueryKey1, infer SortKey1, infer EnumKey1, infer BaseSignalKey1, infer SliceKey1, infer EndpointKey1, infer StoreKey1, infer ErrorKey1, infer EtcKey1> ? ModelDict2 extends ModelDictInfo<infer _Languages2, infer ModelKey2, infer InsightKey2, infer QueryKey2, infer SortKey2, infer EnumKey2, infer BaseSignalKey2, infer SliceKey2, infer EndpointKey2, infer StoreKey2, infer ErrorKey2, infer EtcKey2> ? ModelDictInfo<Languages1, ModelKey1 | ModelKey2, InsightKey1 | InsightKey2, QueryKey1 | QueryKey2, SortKey1 | SortKey2, EnumKey1 | EnumKey2, BaseSignalKey1 | BaseSignalKey2, SliceKey1 | SliceKey2, EndpointKey1 | EndpointKey2, StoreKey1 | StoreKey2, ErrorKey1 | ErrorKey2, EtcKey1 | EtcKey2> : ModelDict1 : never;
185
192
  type MergeModelDicts<ModelDicts extends AnyModelDictInfo[]> = ModelDicts extends [
186
193
  infer First extends AnyModelDictInfo,
187
194
  ...infer Rest extends AnyModelDictInfo[]
@@ -34,6 +34,15 @@ export declare class FetchClient {
34
34
  constructor(origin: string, handler?: Record<string, FetchHandler>, serializedSignal?: {
35
35
  [key: string]: SerializedSignal;
36
36
  }, ErrorCls?: ErrorConstructor | undefined);
37
+ /**
38
+ * Every signal any client in this process has applied, which is the whole callable surface of the app.
39
+ *
40
+ * A copy, because this is the registry each client merges its own signals into and a reader that mutated it
41
+ * would change what the next client applies. Read by the agent catalogue, which needs the argument schemas.
42
+ */
43
+ static get sharedSerializedSignal(): {
44
+ [key: string]: SerializedSignal;
45
+ };
37
46
  static resetSharedRegistry(): void;
38
47
  static resetSharedClient(): void;
39
48
  setErrorConstructor(ErrorCls?: ErrorConstructor): void;
@@ -1,6 +1,7 @@
1
1
  export * from "./cache.adaptor";
2
2
  export * from "./compress.adaptor";
3
3
  export * from "./database.adaptor";
4
+ export * from "./insightQuery.d.ts";
4
5
  export * from "./logging.adaptor";
5
6
  export * from "./queue.adaptor";
6
7
  export * from "./role.adaptor";
@@ -0,0 +1,50 @@
1
+ import type { AkanSqlClient } from "./database.adaptor";
2
+ export interface InsightQueryOptions {
3
+ /** Rows to return at most. Clamped to `InsightQuery.maxRows`, which no caller can raise. */
4
+ limit?: number;
5
+ /** How long to wait for the driver, in ms. See the note on `#raced` for which dialects this can actually stop. */
6
+ timeoutMs?: number;
7
+ }
8
+ export interface InsightQueryResult {
9
+ columns: string[];
10
+ rows: Record<string, unknown>[];
11
+ /** The ceiling cut the answer short, so the caller knows not to read it as complete. */
12
+ truncated: boolean;
13
+ }
14
+ /**
15
+ * One read-only SQL statement, for an agent or an operator asking a question the domain endpoints cannot express.
16
+ *
17
+ * This is the layer-bypassing read, and every safeguard the framework has is bypassed with it — guards, soft delete,
18
+ * cascade, `_postRemove`, and the `hidden`/`secret` masking every other response path performs. So it is read-only
19
+ * by construction rather than by convention, and it is deliberately *not* wired to an endpoint here: the framework
20
+ * owns no guard strong enough to sit in front of it. An app that wants it writes the endpoint with its own
21
+ * `SuperAdmin`, the same way guards ship with the library that owns the model.
22
+ *
23
+ * Three things enforce read-only, and only the third is ours:
24
+ *
25
+ * 1. The statement is wrapped as a derived table — `SELECT * FROM (<sql>) AS "akanInsight" LIMIT ?`. Nothing but a
26
+ * query is legal in that position, in either dialect, so a write is a syntax error from the engine rather than a
27
+ * pattern this code had to recognise. A second statement smuggled behind `;` is a syntax error for the same
28
+ * reason, and the row ceiling rides along on the same wrapper.
29
+ * 2. A rejection before execution, so the caller reads why rather than a syntax error. It runs on the statement with
30
+ * comments and string literals removed, because that is what makes `-- ` and `'…'` unable to hide anything.
31
+ * 3. **`_doc` never crosses the boundary.** Every non-base field lives in that one JSON column, which is where the
32
+ * plan's "re-apply schema-based masking" runs into the fact that an arbitrary SELECT has no model to mask by. So
33
+ * the enforceable rule is the column itself: unnameable in the statement, dropped from the rows, and any cell
34
+ * that still arrives holding a JSON object or array is refused. An insight is made of scalars; a value that is
35
+ * not one is either a document or indistinguishable from it.
36
+ *
37
+ * What that costs is real and worth saying: this answers "how many, since when, grouped how" over base columns and
38
+ * the search mirror, and it cannot read a domain field. Field-level reads go through the domain tools, which mask.
39
+ */
40
+ export declare class InsightQuery {
41
+ #private;
42
+ /** Not an option. A caller asking for more gets this, because the point is that no caller sets the ceiling. */
43
+ static readonly maxRows = 1000;
44
+ constructor(client: AkanSqlClient);
45
+ run(sql: string, { limit, timeoutMs }?: InsightQueryOptions): Promise<{
46
+ columns: string[];
47
+ rows: Record<string, unknown>[];
48
+ truncated: boolean;
49
+ }>;
50
+ }
@@ -1,3 +1,4 @@
1
+ import { type MaskModel } from "akanjs/constant";
1
2
  import type { JsonSchema } from "../schema.d.ts";
2
3
  export type PromptRole = "user" | "assistant";
3
4
  /**
@@ -60,11 +61,9 @@ export interface PromptFileSource {
60
61
  }
61
62
  /**
62
63
  * A model class, named by the caller so an attachment can be masked by what it *is* rather than by what it still
63
- * carries at runtime. Structural, and read through `FIELD_META` the way `resolveReturn` reads it.
64
+ * carries at runtime. The same model any other audience masks by — see `mask` in `akanjs/constant`.
64
65
  */
65
- export interface PromptModel {
66
- name: string;
67
- }
66
+ export type PromptModel = MaskModel;
68
67
  /**
69
68
  * Builds the messages a `prompt()` endpoint returns.
70
69
  *
@@ -0,0 +1,17 @@
1
+ export interface ActionTag {
2
+ /** The `st.do` key this function is. */
3
+ action: string;
4
+ /** The state path it writes, when it writes exactly one — `userForm.name` for a field setter. */
5
+ state?: string;
6
+ }
7
+ /**
8
+ * Marks a dispatcher with what it does, so a component handed one by reference can say so in the DOM.
9
+ *
10
+ * `onChange={st.do.setNameOnUser}` is the house form for every model field, which means the component already holds
11
+ * everything an annotation needs — it just has no way to read it off a function. This is that way, and it is why
12
+ * `data-akan-*` costs an app no code at all: nobody writes the attribute, the setter carries its own name.
13
+ *
14
+ * Non-enumerable, so it survives neither `{...fn}` nor `JSON.stringify` and shows up in no spread.
15
+ */
16
+ export declare const tagAction: <T extends (...args: never[]) => unknown>(fn: T, tag: ActionTag) => T;
17
+ export declare const actionTagOf: (value: unknown) => ActionTag | undefined;
@@ -0,0 +1,70 @@
1
+ import type { AgentRefusal, AgentUndescribed, JsonSchema, SerializedSignal } from "akanjs/signal";
2
+ import type { StoreInstance } from "../storeInstance.d.ts";
3
+ import type { SerializedStoreState, StoreActionEffect } from "./types.d.ts";
4
+ export interface AgentTool {
5
+ name: string;
6
+ title?: string;
7
+ description?: string;
8
+ /** One flat named object, the shape MCP publishes. The bridge maps it onto the action's positional parameters. */
9
+ inputSchema: JsonSchema;
10
+ effect: StoreActionEffect;
11
+ }
12
+ /** One call the agent made, in the order it made them. */
13
+ export interface AgentCall {
14
+ name: string;
15
+ args: Record<string, unknown>;
16
+ at: Date;
17
+ error?: string;
18
+ }
19
+ export interface AgentBridgeOptions {
20
+ /** Resolves a dictionary key to its text. Defaults to the seeded `Translator` in the active locale. */
21
+ resolveDescription?: (key: string) => string | undefined;
22
+ }
23
+ /**
24
+ * What an in-page agent may do to the app the user is looking at.
25
+ *
26
+ * Every call goes through `st.do`, which is the same single dispatch point a click goes through — so the agent
27
+ * cannot reach past what the UI already lets this user do, the app re-renders from the write, and the user watches
28
+ * the result rather than being told about it. That is why the exposure default here is the opposite of the MCP
29
+ * catalogue's: an external agent's `tools/list` is an attack surface built out of names the operator never chose to
30
+ * publish, while this one is the user's own session, under their own credential, with them watching.
31
+ *
32
+ * It is deliberately not an agent. There is no model, no provider, and no key here — an app wires whichever it uses
33
+ * to `tools`, `call`, and `read`. The framework's half is the catalogue, the argument checking, the masking, and the
34
+ * transcript; the conversation is the app's.
35
+ */
36
+ export declare class AgentBridge {
37
+ #private;
38
+ readonly tools: AgentTool[];
39
+ readonly refusals: AgentRefusal[];
40
+ /** Published entries with no words an author wrote. What a source scanner cannot see, per `AgentCatalogue`. */
41
+ readonly undescribed: AgentUndescribed[];
42
+ /**
43
+ * The bridge for the app running in this process: the one store every `st.do` goes through, and every signal any
44
+ * client has applied. An app needs no arguments to reach its own agent surface.
45
+ */
46
+ static of(options?: AgentBridgeOptions): AgentBridge;
47
+ constructor(instance: StoreInstance, serializedSignal: Record<string, SerializedSignal>, options?: AgentBridgeOptions);
48
+ get state(): {
49
+ [key: string]: SerializedStoreState;
50
+ };
51
+ get transcript(): readonly AgentCall[];
52
+ subscribe(listener: () => void): () => void;
53
+ /**
54
+ * The value behind a state key, stripped of what the model marks `hidden` or `secret`.
55
+ *
56
+ * Masking is not optional here even though the data mostly came from the server already masked: `<model>Form`
57
+ * holds what the *user* typed, credentials included, and an in-page agent ships what it reads to a remote model.
58
+ * The mask is by the declared model rather than by the value's class, because `immerify` copies a form into a
59
+ * plain object and the class is gone by the time anyone can ask.
60
+ */
61
+ read(key: string): unknown;
62
+ /**
63
+ * Dispatches through `st.do`, so what happens is what happens when the user clicks.
64
+ *
65
+ * Arguments arrive named and are mapped onto the action's parameters in declared order. An omitted optional one
66
+ * becomes `null`, which is what the slice query builders already expect; an omitted required one is refused,
67
+ * because the alternative is a call that writes `undefined` into state and reports success.
68
+ */
69
+ call(name: string, args?: Record<string, unknown>): Promise<void>;
70
+ }
@@ -0,0 +1,21 @@
1
+ import type { AgentRefusal, SerializedSignal } from "akanjs/signal";
2
+ import type { StoreInstance } from "../storeInstance.d.ts";
3
+ import type { SerializedStore } from "./types.d.ts";
4
+ /**
5
+ * What one built store offers an agent, derived from the store the browser is already running.
6
+ *
7
+ * The store is the audience-neutral half of the client surface the way a signal registry is the server's: a key on
8
+ * `st.do` is the same call the user's own click makes, so an agent that drives it cannot reach past what the UI
9
+ * already permits. That is why the default here is the opposite of the MCP catalogue's — every key is published
10
+ * unless something about it cannot be described, and each of those is recorded as a refusal rather than dropped.
11
+ *
12
+ * Nothing is re-declared. An action's arguments come from the endpoint it is named after, from the field metadata
13
+ * the form setter was generated from, or from the role the store recorded while building the slice; a key that
14
+ * matches none of those three is published only when it takes no arguments at all.
15
+ */
16
+ export declare class StoreCatalogue {
17
+ #private;
18
+ readonly store: SerializedStore;
19
+ readonly refusals: AgentRefusal[];
20
+ constructor(instance: StoreInstance, serializedSignal: Record<string, SerializedSignal>);
21
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./AgentBridge.d.ts";
2
+ export * from "./StoreCatalogue.d.ts";
3
+ export * from "./types.d.ts";