@diphyx/harlemify 6.3.1 → 6.4.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/README.md CHANGED
@@ -99,8 +99,6 @@ await execute();
99
99
  | Vue | `^3.5.0` |
100
100
  | Zod | `^4.0.0` |
101
101
 
102
- > **Note:** Early Nuxt 4 versions (e.g., 4.1.x) may have issues resolving the `#build` alias for module templates. If you encounter build errors related to `#build/harlemify.config`, upgrade to the latest Nuxt 4 release.
103
-
104
102
  ## Documentation
105
103
 
106
104
  [https://diphyx.github.io/harlemify/](https://diphyx.github.io/harlemify/)
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.14.0 || ^4.0.0"
6
6
  },
7
- "version": "6.3.1",
7
+ "version": "6.4.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "unknown"
@@ -13,3 +13,6 @@ export type UseStoreView<T> = {
13
13
  export declare function useStoreView<V extends Record<string, ViewCall>, K extends keyof V & string, T = V[K] extends ComputedRef<infer R> ? R : unknown>(store: {
14
14
  view: V;
15
15
  }, key: K): UseStoreView<T>;
16
+ export declare function useStoreView<V extends Record<string, ViewCall>, K extends keyof V & string, R, T = V[K] extends ComputedRef<infer U> ? U : unknown>(store: {
17
+ view: V;
18
+ }, key: K, resolver: (value: T) => R): UseStoreView<R>;
@@ -1,11 +1,11 @@
1
- import { watch } from "vue";
1
+ import { computed, watch } from "vue";
2
2
  import { debounce, throttle } from "../core/utils/base.js";
3
- export function useStoreView(store, key) {
3
+ export function useStoreView(store, key, resolver) {
4
4
  if (!store.view[key]) {
5
5
  throw new Error(`View "${key}" not found in store`);
6
6
  }
7
7
  const source = store.view[key];
8
- const data = source;
8
+ const data = resolver ? computed(() => resolver(source.value)) : source;
9
9
  function resolveCallback(callback, callbackOptions) {
10
10
  if (callbackOptions?.debounce) {
11
11
  return debounce(callback, callbackOptions.debounce);
@@ -18,7 +18,7 @@ export function useStoreView(store, key) {
18
18
  function track(handler, trackOptions) {
19
19
  const callback = resolveCallback(handler, trackOptions);
20
20
  const stop = watch(
21
- source,
21
+ data,
22
22
  (value) => {
23
23
  callback(value);
24
24
  },
@@ -44,6 +44,7 @@ export interface ActionApiRequest<MD extends ModelDefinitions, VD extends ViewDe
44
44
  body?: ActionApiRequestValue<MD, VD, unknown>;
45
45
  timeout?: ActionApiRequestValue<MD, VD, number>;
46
46
  concurrent?: ActionConcurrent;
47
+ hooks?: ActionHooks;
47
48
  }
48
49
  export type ActionApiRequestShortcut<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> = Omit<ActionApiRequest<MD, VD>, "method">;
49
50
  export interface ActionApiCommitContext<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> {
@@ -129,12 +130,32 @@ export interface ActionResolvedApi {
129
130
  timeout?: number;
130
131
  signal: AbortSignal;
131
132
  }
133
+ export interface ActionHookRequest extends ActionResolvedApi {
134
+ error?: Error;
135
+ }
136
+ export interface ActionHookResponse {
137
+ status: number;
138
+ headers: Record<string, string>;
139
+ data: unknown;
140
+ }
141
+ export interface ActionHookPreContext {
142
+ request: Readonly<ActionResolvedApi>;
143
+ }
144
+ export interface ActionHookPostContext {
145
+ request: Readonly<ActionHookRequest>;
146
+ response?: Readonly<ActionHookResponse>;
147
+ }
148
+ export interface ActionHooks {
149
+ pre?: (context: ActionHookPreContext) => void | Promise<void>;
150
+ post?: (context: ActionHookPostContext) => void | Promise<void>;
151
+ }
132
152
  export interface ActionCallTransformerOptions {
133
153
  request?: (api: ActionResolvedApi) => ActionResolvedApi;
134
154
  response?: (data: unknown) => unknown;
135
155
  }
136
156
  export interface ActionCallCommitOptions {
137
157
  mode?: ModelOneMode | ModelManyMode | Record<string, ModelOneMode | ModelManyMode>;
158
+ options?: ModelOneCommitOptions | ModelManyCommitOptions | Record<string, ModelOneCommitOptions | ModelManyCommitOptions>;
138
159
  }
139
160
  export interface ActionApiCallOptions extends ActionCallBaseOptions {
140
161
  params?: Record<string, string | number>;
@@ -96,6 +96,19 @@ function resolveCommitMode(commit, options) {
96
96
  }
97
97
  return commit.mode;
98
98
  }
99
+ function resolveCommitOptions(commit, options) {
100
+ const override = options?.commit?.options;
101
+ if (!override) {
102
+ return commit.options;
103
+ }
104
+ const values = Object.values(override);
105
+ const perModel = values.length > 0 && values.every((value) => isPlainObject(value));
106
+ const resolved = perModel ? override[commit.model] : override;
107
+ if (!resolved) {
108
+ return commit.options;
109
+ }
110
+ return merge(resolved, commit.options);
111
+ }
99
112
  function resolveCommitTransform(commit, data, context) {
100
113
  if (typeof commit.transform === "function") {
101
114
  return commit.transform(data, context);
@@ -120,6 +133,25 @@ function resolveConcurrent(definition, options) {
120
133
  }
121
134
  return ActionConcurrent.BLOCK;
122
135
  }
136
+ async function executeHook(definition, hooks, key, context) {
137
+ const hook = hooks?.[key];
138
+ if (!hook) {
139
+ return;
140
+ }
141
+ definition.logger?.debug("Action API hook", {
142
+ action: definition.key,
143
+ hook: key
144
+ });
145
+ try {
146
+ await hook(context);
147
+ } catch (error) {
148
+ definition.logger?.error("Action API hook error", {
149
+ action: definition.key,
150
+ hook: key,
151
+ error
152
+ });
153
+ }
154
+ }
123
155
  async function executeApi(definition, api, options) {
124
156
  try {
125
157
  definition.logger?.debug("Action API request", {
@@ -137,7 +169,31 @@ async function executeApi(definition, api, options) {
137
169
  body: api.body,
138
170
  timeout: api.timeout,
139
171
  signal: api.signal,
140
- responseType: "json"
172
+ responseType: "json",
173
+ async onRequest() {
174
+ await executeHook(definition, definition.request.hooks, "pre", {
175
+ request: api
176
+ });
177
+ },
178
+ async onResponse({ response: response2 }) {
179
+ await executeHook(definition, definition.request.hooks, "post", {
180
+ request: api,
181
+ response: {
182
+ status: response2.status,
183
+ headers: Object.fromEntries(response2.headers),
184
+ data: response2._data
185
+ }
186
+ });
187
+ },
188
+ async onRequestError({ error }) {
189
+ await executeHook(definition, definition.request.hooks, "post", {
190
+ request: {
191
+ ...api,
192
+ error
193
+ }
194
+ });
195
+ throw error;
196
+ }
141
197
  });
142
198
  definition.logger?.debug("Action API response received", {
143
199
  action: definition.key,
@@ -191,11 +247,12 @@ function executeCommit(definition, model, data, context, options) {
191
247
  });
192
248
  }
193
249
  const mode = resolveCommitMode(commit, options);
250
+ const commitOptions = resolveCommitOptions(commit, options);
194
251
  let value = resolveCommitTransform(commit, data, context);
195
252
  if (!isEmptyRecord(target.aliases())) {
196
253
  value = resolveAliasInbound(value, target.aliases());
197
254
  }
198
- plan.push({ commit, target, mode, value });
255
+ plan.push({ commit, target, mode, options: commitOptions, value });
199
256
  }
200
257
  } catch (error) {
201
258
  const commitError = toError(error, ActionCommitError);
@@ -213,7 +270,7 @@ function executeCommit(definition, model, data, context, options) {
213
270
  mode: entry.mode
214
271
  });
215
272
  try {
216
- entry.target.commit(entry.mode, entry.value, entry.commit.options);
273
+ entry.target.commit(entry.mode, entry.value, entry.options);
217
274
  } catch (error) {
218
275
  const commitError = toError(error, ActionCommitError);
219
276
  definition.logger?.error("Action commit error", {
@@ -7,7 +7,7 @@ export type { ModelOneCommitOptions, ModelManyCommitOptions } from "./core/types
7
7
  export { ViewClone } from "./core/types/view.js";
8
8
  export type { ViewDefinitionOptions } from "./core/types/view.js";
9
9
  export { ActionStatus, ActionConcurrent, ActionType, ActionApiMethod } from "./core/types/action.js";
10
- export type { ActionCall, ActionApiCall, ActionApiCommitContext, ActionHandlerCall, ActionCallOptions, ActionCallBaseOptions, ActionApiCallOptions, ActionHandlerCallOptions, ActionCallTransformerOptions, ActionCallBindOptions, ActionCallCommitOptions, ActionHandlerOptions, ActionResolvedApi, } from "./core/types/action.js";
10
+ export type { ActionCall, ActionApiCall, ActionApiCommitContext, ActionHandlerCall, ActionCallOptions, ActionCallBaseOptions, ActionApiCallOptions, ActionHandlerCallOptions, ActionCallTransformerOptions, ActionCallBindOptions, ActionCallCommitOptions, ActionHandlerOptions, ActionResolvedApi, ActionHooks, ActionHookPreContext, ActionHookPostContext, ActionHookRequest, ActionHookResponse, } from "./core/types/action.js";
11
11
  export { ActionApiError, ActionHandlerError, ActionCommitError, ActionConcurrentError, isError, toError, } from "./core/utils/error.js";
12
12
  export type { ComposeCallback, ComposeCall, ComposeDefinitions, ComposeContext, StoreCompose, } from "./core/types/compose.js";
13
13
  export { useStoreCompose } from "./composables/compose.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diphyx/harlemify",
3
- "version": "6.3.1",
3
+ "version": "6.4.0",
4
4
  "description": "API state management for Nuxt powered by Harlem",
5
5
  "keywords": [
6
6
  "nuxt",