@feasibleone/blong 1.14.0 → 1.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.16.0](https://github.com/feasibleone/blong/compare/blong-v1.15.0...blong-v1.16.0) (2026-04-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * blong-ui ([9ca1281](https://github.com/feasibleone/blong/commit/9ca1281c0178c69dfc722bc4a5978f649bc1fa0d))
9
+
10
+ ## [1.15.0](https://github.com/feasibleone/blong/compare/blong-v1.14.0...blong-v1.15.0) (2026-04-01)
11
+
12
+
13
+ ### Features
14
+
15
+ * implement ConfigRuntime for hot configuration reload ([#115](https://github.com/feasibleone/blong/issues/115)) ([61bab7c](https://github.com/feasibleone/blong/commit/61bab7ce8587bf83d72fe80138aaef68943f21c4))
16
+ * unified test and handlers ([#117](https://github.com/feasibleone/blong/issues/117)) ([bf0ed96](https://github.com/feasibleone/blong/commit/bf0ed96c5df3d949fa225dd8a30fc25698a7855a))
17
+
3
18
  ## [1.14.0](https://github.com/feasibleone/blong/compare/blong-v1.13.0...blong-v1.14.0) (2026-03-29)
4
19
 
5
20
 
package/dist/types.d.ts CHANGED
@@ -17625,6 +17625,22 @@ export type Config<T, C> = {
17625
17625
  logLevel: Parameters<ILog["logger"]>[0];
17626
17626
  namespace: string | string[];
17627
17627
  imports: string | RegExp | (string | RegExp)[];
17628
+ /**
17629
+ * Strip this many dot-separated namespace segments from the incoming method
17630
+ * name before looking up and calling a local handler. Useful when the
17631
+ * adapter handles a namespace prefix (e.g. `backend`) that must be removed
17632
+ * before the real handler name is used.
17633
+ *
17634
+ * Only applied when the method name does NOT contain a `/` separator
17635
+ * (the `/` form is auto-stripped by `methodPath` already).
17636
+ */
17637
+ stripNamespace?: number;
17638
+ /**
17639
+ * Prepend this namespace segment (dot-separated) to the outgoing method
17640
+ * name when dispatching. This is the dot-notation counterpart of the
17641
+ * existing `destination` field (which uses `/` as separator).
17642
+ */
17643
+ appendNamespace?: string;
17628
17644
  } & T;
17629
17645
  export type RemoteMethod = (...params: unknown[]) => Promise<unknown>;
17630
17646
  export interface IRemote {
@@ -17640,6 +17656,7 @@ export interface IRpcServer {
17640
17656
  unregister: (methods: string[], namespace: string, reply: boolean) => void;
17641
17657
  start: () => Promise<IRpcServer>;
17642
17658
  stop: () => Promise<IRpcServer>;
17659
+ setAttachCheckpoint?: (fn: ((meta: IMeta) => void) | undefined) => void;
17643
17660
  }
17644
17661
  export interface ILocal {
17645
17662
  register: (methods: object, namespace: string, reply: boolean, pkg: {
@@ -17683,6 +17700,7 @@ export interface IRegistry {
17683
17700
  methods: Map<string, Handlers>;
17684
17701
  modules: Map<string | symbol, IRegistry[]>;
17685
17702
  createPort: (id: string) => Promise<ReturnType<IAdapterFactory>>;
17703
+ getPort: (id: string) => ReturnType<IAdapterFactory> | undefined;
17686
17704
  replaceHandlers: (id: string, handlers: object) => Promise<void>;
17687
17705
  loadApi: (id: string, def: {
17688
17706
  namespace: Record<string, string | string[]>;
@@ -17725,6 +17743,7 @@ export interface IApi {
17725
17743
  utLog: {
17726
17744
  createLog: ILog["logger"];
17727
17745
  };
17746
+ attachCheckpoint?: (meta: IMeta) => void;
17728
17747
  handlers?: (api: {
17729
17748
  utError: IError;
17730
17749
  remote: IRemote;
@@ -17801,6 +17820,21 @@ export interface IAdapter<T, C> {
17801
17820
  waiting: unknown;
17802
17821
  buffer: unknown;
17803
17822
  }) => void;
17823
+ /**
17824
+ * Optional lifecycle hook called when configuration changes.
17825
+ * When present, the framework calls this instead of a full stop+start cycle.
17826
+ * The adapter should inspect `diff` and only recreate the resources that
17827
+ * actually changed (e.g. destroy and rebuild the DB connection pool when
17828
+ * the `knex` sub-key is in the diff, but leave everything else intact).
17829
+ *
17830
+ * @param diff Flat map of dotted config paths to `{prev, next}` pairs
17831
+ * @param next The full new effective config snapshot (via proxy)
17832
+ * @param prev The full previous effective config snapshot
17833
+ */
17834
+ configChanged?: (diff: Map<string, {
17835
+ prev: unknown;
17836
+ next: unknown;
17837
+ }>, next: object, prev: object) => Promise<void>;
17804
17838
  }
17805
17839
  export interface IAdapterFactory<T = Record<string, unknown>, C = Record<string, unknown>> {
17806
17840
  config?: Config<T, C> | false;
@@ -17878,6 +17912,13 @@ export interface IMeta {
17878
17912
  };
17879
17913
  gateway?: object;
17880
17914
  validation?: unknown;
17915
+ name?: string;
17916
+ checkpoint?: CheckpointFn;
17917
+ checkpoints?: Array<{
17918
+ name: string;
17919
+ data?: unknown;
17920
+ timestamp: number;
17921
+ }>;
17881
17922
  }
17882
17923
  export type HRTime = [
17883
17924
  number,
@@ -18001,18 +18042,22 @@ export type ThenableProxy = Promise<unknown> & {
18001
18042
  export type ChainStep = ((assert: typeof Assert, context: {
18002
18043
  $meta: IMeta;
18003
18044
  } & Record<string, Promise<unknown[]> & ThenableProxy>) => Promise<object>) | object;
18045
+ export type CheckpointFn = (this: IMeta, name: string, data?: unknown) => void;
18004
18046
  export interface ILib {
18005
18047
  type: typeof Type;
18006
18048
  error: <T>(errors: T) => Record<keyof T, (params?: unknown, $meta?: IMeta) => ITypedError>;
18007
18049
  rename: <T extends object>(object: T, name: string) => T & {
18008
18050
  name: string;
18009
18051
  };
18052
+ /** @deprecated The framework now auto-names step arrays from handler names. */
18010
18053
  group: (name: string) => (handlers: ChainStep[]) => ChainStep[] & {
18011
18054
  name: string;
18012
18055
  };
18056
+ assert: typeof Assert | undefined;
18013
18057
  ulid: () => string;
18014
18058
  uuid4: () => string;
18015
18059
  uuid7: () => string;
18060
+ setProperty: (obj: Record<string, unknown>, path: string, value: unknown) => void;
18016
18061
  merge<T, S1>(target: T, source: S1): T & S1;
18017
18062
  merge<T, S1, S2>(target: T, source1: S1, source2: S2): T & S1 & S2;
18018
18063
  merge<T, S1, S2, S3>(target: T, source1: S1, source2: S2, source3: S3): T & S1 & S2 & S3;
@@ -18109,6 +18154,65 @@ export declare abstract class Internal {
18109
18154
  start(...params: unknown[]): Promise<unknown>;
18110
18155
  }
18111
18156
  export declare const handler: <T = Record<string, unknown>, C = AdapterContext>(definition: Definition<T, C>) => Definition<T, C>;
18157
+ /**
18158
+ * Browser-side equivalent of `handler`. Use this to define a component handler
18159
+ * in a realm's `component/` layer. Functionally identical to `handler` — the
18160
+ * distinction is semantic and makes intent clear in code review.
18161
+ *
18162
+ * The inner function should return a map whose keys are dot-notation method names
18163
+ * (e.g. `'coral.browse'`) and values are async functions that return component
18164
+ * metadata `{title, permission, icon, component: async () => ReactComponent}`.
18165
+ *
18166
+ * @example
18167
+ * ```ts
18168
+ * export default componentHandler(blong => function coralBrowse() {
18169
+ * return {
18170
+ * 'coral.browse': async () => ({
18171
+ * title: 'Browse Corals',
18172
+ * permission: 'marine.coral.browse',
18173
+ * component: async () => (await import('./CoralBrowse.js')).default,
18174
+ * }),
18175
+ * };
18176
+ * });
18177
+ * ```
18178
+ */
18179
+ export declare const componentHandler: <T = Record<string, unknown>, C = AdapterContext>(definition: Definition<T, C>) => Definition<T, C>;
18180
+ /** Action definition for use with `defineActions`. */
18181
+ export interface IActionDef {
18182
+ title?: string;
18183
+ permission?: string;
18184
+ icon?: string;
18185
+ /** Lazy-load a page component (page action). */
18186
+ component?: () => Promise<unknown>;
18187
+ /** Bus method name to invoke (query or mutation action). */
18188
+ method?: string;
18189
+ /** When true the action mutates data and should invalidate related caches. */
18190
+ mutates?: boolean;
18191
+ /** Action names whose query caches should be invalidated on success. */
18192
+ invalidates?: string[];
18193
+ /** Static params merged into every invocation. */
18194
+ params?: Record<string, unknown> | ((params: Record<string, unknown>) => Record<string, unknown>);
18195
+ }
18196
+ /**
18197
+ * Register action metadata for a realm's browser layer.
18198
+ *
18199
+ * Returns a handler-compatible function that the blong framework loads from
18200
+ * the realm's `actions/` or `action/` folder. Each entry in `actions` is
18201
+ * wrapped in a no-argument function so the framework can call it as a method.
18202
+ *
18203
+ * @example
18204
+ * ```ts
18205
+ * // marine/actions/index.ts
18206
+ * export default defineActions({
18207
+ * 'marine.coral.browse': {
18208
+ * title: 'Browse Corals',
18209
+ * permission: 'marine.coral.browse',
18210
+ * component: () => import('./components/CoralBrowse.js'),
18211
+ * },
18212
+ * });
18213
+ * ```
18214
+ */
18215
+ export declare const defineActions: (actions: Record<string, IActionDef>) => ((_blong: unknown) => Record<string, () => IActionDef>);
18112
18216
  export declare const library: <T = Record<string, unknown>>(definition: Lib<T>) => Lib<T>;
18113
18217
  export declare const validation: (validation: ValidationDefinition) => ValidationDefinition;
18114
18218
  export declare const api: (api: ApiDefinition) => ApiDefinition;
@@ -18125,6 +18229,8 @@ export type Kinds = "lib" | "validation" | "api" | "solution" | "server" | "brow
18125
18229
  export declare const kind: (what: {}) => Kinds | undefined;
18126
18230
  declare const _default: {
18127
18231
  handler: <T = Record<string, unknown>, C = AdapterContext>(definition: Definition<T, C>) => Definition<T, C>;
18232
+ componentHandler: <T = Record<string, unknown>, C = AdapterContext>(definition: Definition<T, C>) => Definition<T, C>;
18233
+ defineActions: (actions: Record<string, IActionDef>) => ((_blong: unknown) => Record<string, () => IActionDef>);
18128
18234
  library: <T = Record<string, unknown>>(definition: Lib<T>) => Lib<T>;
18129
18235
  validation: (validation: ValidationDefinition) => ValidationDefinition;
18130
18236
  api: (api: ApiDefinition) => ApiDefinition;
package/dist/types.js CHANGED
@@ -21,6 +21,49 @@ export class Internal {
21
21
  }
22
22
  }
23
23
  export const handler = (definition) => Object.defineProperty(definition, Kind, { value: 'handler' });
24
+ /**
25
+ * Browser-side equivalent of `handler`. Use this to define a component handler
26
+ * in a realm's `component/` layer. Functionally identical to `handler` — the
27
+ * distinction is semantic and makes intent clear in code review.
28
+ *
29
+ * The inner function should return a map whose keys are dot-notation method names
30
+ * (e.g. `'coral.browse'`) and values are async functions that return component
31
+ * metadata `{title, permission, icon, component: async () => ReactComponent}`.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * export default componentHandler(blong => function coralBrowse() {
36
+ * return {
37
+ * 'coral.browse': async () => ({
38
+ * title: 'Browse Corals',
39
+ * permission: 'marine.coral.browse',
40
+ * component: async () => (await import('./CoralBrowse.js')).default,
41
+ * }),
42
+ * };
43
+ * });
44
+ * ```
45
+ */
46
+ export const componentHandler = (definition) => Object.defineProperty(definition, Kind, { value: 'handler' });
47
+ /**
48
+ * Register action metadata for a realm's browser layer.
49
+ *
50
+ * Returns a handler-compatible function that the blong framework loads from
51
+ * the realm's `actions/` or `action/` folder. Each entry in `actions` is
52
+ * wrapped in a no-argument function so the framework can call it as a method.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // marine/actions/index.ts
57
+ * export default defineActions({
58
+ * 'marine.coral.browse': {
59
+ * title: 'Browse Corals',
60
+ * permission: 'marine.coral.browse',
61
+ * component: () => import('./components/CoralBrowse.js'),
62
+ * },
63
+ * });
64
+ * ```
65
+ */
66
+ export const defineActions = (actions) => Object.defineProperty((_blong) => Object.fromEntries(Object.entries(actions).map(([key, value]) => [key, () => value])), Kind, { value: 'handler' });
24
67
  export const library = (definition) => Object.defineProperty(definition, Kind, { value: 'lib' });
25
68
  export const validation = (validation) => Object.defineProperty(validation, Kind, { value: 'validation' });
26
69
  export const api = (api) => Object.defineProperty(api, Kind, { value: 'api' });
@@ -41,6 +84,8 @@ export const orchestrator = (definition) => Object.defineProperty(definition, Ki
41
84
  export const kind = (what) => what[Kind];
42
85
  export default {
43
86
  handler,
87
+ componentHandler,
88
+ defineActions,
44
89
  library,
45
90
  validation,
46
91
  api,
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAYA,OAAO,EACH,IAAI,GAWP,MAAM,SAAS,CAAC;AAMjB,OAAO,KAAK,MAAM,mBAAmB,CAAC;AAulBtC,MAAM,IAAI,GAAW,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAG9C,MAAM,OAAgB,QAAQ;IAC1B,IAAI,CAAQ;IACF,GAAG,CAA8B;IAC3C,YAAmB,GAAiB;QAChC,IAAI,CAAC,IAAI,GAAG,GAAG,EAAE,GAAG,CAAC;IACzB,CAAC;IACS,KAAK,GAAkB,CAAC,GAAG,IAA+B,EAAE,EAAE;QACpE,MAAM,MAAM,GAAG,KAAK,CAAqB,GAAG,IAAI,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI;YAC5B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAC,CAAC,CAAC;QAChF,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;IACK,KAAK,CAAC,IAAI;QACb,OAAO,IAAI,CAAC;IAChB,CAAC;IACM,KAAK,CAAC,KAAK,CAAC,GAAG,MAAiB;QACnC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,CACnB,UAA4B,EACZ,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;AACnF,MAAM,CAAC,MAAM,OAAO,GAAG,CAA8B,UAAkB,EAAU,EAAE,CAC/E,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,KAAK,EAAC,CAAC,CAAC;AAC5D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,UAAgC,EAAwB,EAAE,CACjF,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,YAAY,EAAC,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAkB,EAAiB,EAAE,CACrD,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,KAAK,EAAC,CAAC,CAAC;AAErD,MAAM,CAAC,MAAM,kBAAkB,GAEH,QAAQ,CAAC,EAAE,CACnC,UAAU,CAAC,GAAG,EAAE,CACZ,MAAM,CAAC,WAAW,CACd,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;IAC9C,IAAI;IACJ,MAAM,CAAC,cAAc,CACjB,GAAG,EAAE,CAAC,CAAC;QACH,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC9C,WAAW,EAAE,aAAa,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;KAC1E,CAAC,EACF,MAAM,EACN,EAAC,KAAK,EAAE,IAAI,EAAC,CAChB;CACJ,CAAC,CACL,CACJ,CAAC;AAEN,MAAM,CAAC,MAAM,KAAK,GAAG,CAAoB,UAA8B,EAAsB,EAAE,CAC3F,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,UAAU,EAAC,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAoB,UAA8B,EAAsB,EAAE,CAC5F,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,QAAQ,EAAC,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,OAAO,GAAG,CAAoB,UAA8B,EAAsB,EAAE,CAC7F,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;AAChE,MAAM,CAAC,MAAM,KAAK,GAAG,CACjB,UAA4C,EACZ,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,OAAO,EAAC,CAAC,CAAC;AACjG,MAAM,CAAC,MAAM,OAAO,GAAG,CACnB,UAAiC,EACZ,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,SAAS,EAAC,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,YAAY,GAAG,CACxB,UAAiC,EACZ,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,cAAc,EAAC,CAAC,CAAC;AAW7F,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,IAAiC,EAAqB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEzF,eAAe;IACX,OAAO;IACP,OAAO;IACP,UAAU;IACV,GAAG;IACH,KAAK;IACL,MAAM;IACN,OAAO;IACP,OAAO;IACP,YAAY;IACZ,IAAI;CACP,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feasibleone/blong",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "API and DRY focused RAD framework https://feasibleone.github.io/blong-docs",
5
5
  "keywords": [
6
6
  "blong",
package/types.ts CHANGED
@@ -113,6 +113,22 @@ export type Config<T, C> = {
113
113
  logLevel: Parameters<ILog['logger']>[0];
114
114
  namespace: string | string[];
115
115
  imports: string | RegExp | (string | RegExp)[];
116
+ /**
117
+ * Strip this many dot-separated namespace segments from the incoming method
118
+ * name before looking up and calling a local handler. Useful when the
119
+ * adapter handles a namespace prefix (e.g. `backend`) that must be removed
120
+ * before the real handler name is used.
121
+ *
122
+ * Only applied when the method name does NOT contain a `/` separator
123
+ * (the `/` form is auto-stripped by `methodPath` already).
124
+ */
125
+ stripNamespace?: number;
126
+ /**
127
+ * Prepend this namespace segment (dot-separated) to the outgoing method
128
+ * name when dispatching. This is the dot-notation counterpart of the
129
+ * existing `destination` field (which uses `/` as separator).
130
+ */
131
+ appendNamespace?: string;
116
132
  } & T;
117
133
 
118
134
  export type RemoteMethod = (...params: unknown[]) => Promise<unknown>;
@@ -128,6 +144,7 @@ export interface IRpcServer {
128
144
  unregister: (methods: string[], namespace: string, reply: boolean) => void;
129
145
  start: () => Promise<IRpcServer>;
130
146
  stop: () => Promise<IRpcServer>;
147
+ setAttachCheckpoint?: (fn: ((meta: IMeta) => void) | undefined) => void;
131
148
  }
132
149
 
133
150
  export interface ILocal {
@@ -172,6 +189,7 @@ export interface IRegistry {
172
189
  methods: Map<string, Handlers>;
173
190
  modules: Map<string | symbol, IRegistry[]>;
174
191
  createPort: (id: string) => Promise<ReturnType<IAdapterFactory>>;
192
+ getPort: (id: string) => ReturnType<IAdapterFactory> | undefined;
175
193
  replaceHandlers: (id: string, handlers: object) => Promise<void>;
176
194
  loadApi: (
177
195
  id: string,
@@ -220,6 +238,7 @@ export interface IApi {
220
238
  utLog: {
221
239
  createLog: ILog['logger'];
222
240
  };
241
+ attachCheckpoint?: (meta: IMeta) => void;
223
242
  handlers?: (api: {utError: IError; remote: IRemote; type: typeof Type}) => {
224
243
  extends?:
225
244
  | string
@@ -303,6 +322,22 @@ export interface IAdapter<T, C> {
303
322
  what: unknown,
304
323
  context: {requests: unknown; waiting: unknown; buffer: unknown},
305
324
  ) => void;
325
+ /**
326
+ * Optional lifecycle hook called when configuration changes.
327
+ * When present, the framework calls this instead of a full stop+start cycle.
328
+ * The adapter should inspect `diff` and only recreate the resources that
329
+ * actually changed (e.g. destroy and rebuild the DB connection pool when
330
+ * the `knex` sub-key is in the diff, but leave everything else intact).
331
+ *
332
+ * @param diff Flat map of dotted config paths to `{prev, next}` pairs
333
+ * @param next The full new effective config snapshot (via proxy)
334
+ * @param prev The full previous effective config snapshot
335
+ */
336
+ configChanged?: (
337
+ diff: Map<string, {prev: unknown; next: unknown}>,
338
+ next: object,
339
+ prev: object,
340
+ ) => Promise<void>;
306
341
  }
307
342
 
308
343
  export interface IAdapterFactory<T = Record<string, unknown>, C = Record<string, unknown>> {
@@ -382,6 +417,9 @@ export interface IMeta {
382
417
  };
383
418
  gateway?: object;
384
419
  validation?: unknown;
420
+ name?: string;
421
+ checkpoint?: CheckpointFn;
422
+ checkpoints?: Array<{name: string; data?: unknown; timestamp: number}>;
385
423
  }
386
424
 
387
425
  export type HRTime = [number, number];
@@ -520,14 +558,19 @@ export type ChainStep =
520
558
  ) => Promise<object>)
521
559
  | object;
522
560
 
561
+ export type CheckpointFn = (this: IMeta, name: string, data?: unknown) => void;
562
+
523
563
  export interface ILib {
524
564
  type: typeof Type;
525
565
  error: <T>(errors: T) => Record<keyof T, (params?: unknown, $meta?: IMeta) => ITypedError>;
526
566
  rename: <T extends object>(object: T, name: string) => T & {name: string};
567
+ /** @deprecated The framework now auto-names step arrays from handler names. */
527
568
  group: (name: string) => (handlers: ChainStep[]) => ChainStep[] & {name: string};
569
+ assert: typeof Assert | undefined;
528
570
  ulid: () => string;
529
571
  uuid4: () => string;
530
572
  uuid7: () => string;
573
+ setProperty: (obj: Record<string, unknown>, path: string, value: unknown) => void;
531
574
  merge<T, S1>(target: T, source: S1): T & S1;
532
575
  merge<T, S1, S2>(target: T, source1: S1, source2: S2): T & S1 & S2;
533
576
  merge<T, S1, S2, S3>(target: T, source1: S1, source2: S2, source3: S3): T & S1 & S2 & S3;
@@ -653,6 +696,81 @@ export abstract class Internal {
653
696
  export const handler = <T = Record<string, unknown>, C = AdapterContext>(
654
697
  definition: Definition<T, C>,
655
698
  ): Definition<T, C> => Object.defineProperty(definition, Kind, {value: 'handler'});
699
+
700
+ /**
701
+ * Browser-side equivalent of `handler`. Use this to define a component handler
702
+ * in a realm's `component/` layer. Functionally identical to `handler` — the
703
+ * distinction is semantic and makes intent clear in code review.
704
+ *
705
+ * The inner function should return a map whose keys are dot-notation method names
706
+ * (e.g. `'coral.browse'`) and values are async functions that return component
707
+ * metadata `{title, permission, icon, component: async () => ReactComponent}`.
708
+ *
709
+ * @example
710
+ * ```ts
711
+ * export default componentHandler(blong => function coralBrowse() {
712
+ * return {
713
+ * 'coral.browse': async () => ({
714
+ * title: 'Browse Corals',
715
+ * permission: 'marine.coral.browse',
716
+ * component: async () => (await import('./CoralBrowse.js')).default,
717
+ * }),
718
+ * };
719
+ * });
720
+ * ```
721
+ */
722
+ export const componentHandler = <T = Record<string, unknown>, C = AdapterContext>(
723
+ definition: Definition<T, C>,
724
+ ): Definition<T, C> => Object.defineProperty(definition, Kind, {value: 'handler'});
725
+
726
+ /** Action definition for use with `defineActions`. */
727
+ export interface IActionDef {
728
+ title?: string;
729
+ permission?: string;
730
+ icon?: string;
731
+ /** Lazy-load a page component (page action). */
732
+ component?: () => Promise<unknown>;
733
+ /** Bus method name to invoke (query or mutation action). */
734
+ method?: string;
735
+ /** When true the action mutates data and should invalidate related caches. */
736
+ mutates?: boolean;
737
+ /** Action names whose query caches should be invalidated on success. */
738
+ invalidates?: string[];
739
+ /** Static params merged into every invocation. */
740
+ params?: Record<string, unknown> | ((params: Record<string, unknown>) => Record<string, unknown>);
741
+ }
742
+
743
+ /**
744
+ * Register action metadata for a realm's browser layer.
745
+ *
746
+ * Returns a handler-compatible function that the blong framework loads from
747
+ * the realm's `actions/` or `action/` folder. Each entry in `actions` is
748
+ * wrapped in a no-argument function so the framework can call it as a method.
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * // marine/actions/index.ts
753
+ * export default defineActions({
754
+ * 'marine.coral.browse': {
755
+ * title: 'Browse Corals',
756
+ * permission: 'marine.coral.browse',
757
+ * component: () => import('./components/CoralBrowse.js'),
758
+ * },
759
+ * });
760
+ * ```
761
+ */
762
+ export const defineActions = (
763
+ actions: Record<string, IActionDef>,
764
+ ): ((_blong: unknown) => Record<string, () => IActionDef>) =>
765
+ Object.defineProperty(
766
+ (_blong: unknown) =>
767
+ Object.fromEntries(
768
+ Object.entries(actions).map(([key, value]) => [key, () => value]),
769
+ ),
770
+ Kind,
771
+ {value: 'handler'},
772
+ );
773
+
656
774
  export const library = <T = Record<string, unknown>>(definition: Lib<T>): Lib<T> =>
657
775
  Object.defineProperty(definition, Kind, {value: 'lib'});
658
776
  export const validation = (validation: ValidationDefinition): ValidationDefinition =>
@@ -709,6 +827,8 @@ export const kind = (what: {[Kind]: Kinds | undefined}): Kinds | undefined => wh
709
827
 
710
828
  export default {
711
829
  handler,
830
+ componentHandler,
831
+ defineActions,
712
832
  library,
713
833
  validation,
714
834
  api,