@diphyx/harlemify 6.1.1 → 6.3.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
@@ -23,9 +23,11 @@ If you are an AI coding agent working on a project that uses `@diphyx/harlemify`
23
23
  ## Install
24
24
 
25
25
  ```bash
26
- npm install @diphyx/harlemify
26
+ npm install @diphyx/harlemify zod
27
27
  ```
28
28
 
29
+ > `zod` is a peer dependency — install it in your project.
30
+
29
31
  ```typescript
30
32
  // nuxt.config.ts
31
33
  export default defineNuxtConfig({
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.1.1",
7
+ "version": "6.3.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "unknown"
@@ -1,5 +1,5 @@
1
- import type { ConsolaInstance } from "consola";
1
+ import type { Logger } from "../types/base.js";
2
2
  import type { ModelDefinitions } from "../types/model.js";
3
3
  import type { ViewDefinitions } from "../types/view.js";
4
4
  import { type RuntimeActionConfig, type ActionFactory } from "../types/action.js";
5
- export declare function createActionFactory<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>>(config?: RuntimeActionConfig, logger?: ConsolaInstance): ActionFactory<MD, VD>;
5
+ export declare function createActionFactory<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>>(config?: RuntimeActionConfig, logger?: Logger): ActionFactory<MD, VD>;
@@ -1,3 +1,3 @@
1
- import type { ConsolaInstance } from "consola";
1
+ import type { Logger } from "../types/base.js";
2
2
  import { type RuntimeModelConfig, type ModelFactory } from "../types/model.js";
3
- export declare function createModelFactory(config?: RuntimeModelConfig, logger?: ConsolaInstance): ModelFactory;
3
+ export declare function createModelFactory(config?: RuntimeModelConfig, logger?: Logger): ModelFactory;
@@ -1,4 +1,4 @@
1
- import type { ConsolaInstance } from "consola";
1
+ import type { Logger } from "../types/base.js";
2
2
  import type { ModelDefinitions } from "../types/model.js";
3
3
  import type { RuntimeViewConfig, ViewFactory } from "../types/view.js";
4
- export declare function createViewFactory<MD extends ModelDefinitions>(config?: RuntimeViewConfig, logger?: ConsolaInstance): ViewFactory<MD>;
4
+ export declare function createViewFactory<MD extends ModelDefinitions>(config?: RuntimeViewConfig, logger?: Logger): ViewFactory<MD>;
@@ -1,5 +1,4 @@
1
1
  import { effectScope } from "vue";
2
- import { createConsola } from "consola";
3
2
  import { createStore as createStoreSource } from "@harlem/core";
4
3
  import { runtimeConfig } from "../config.js";
5
4
  import { createModelFactory } from "./layers/model.js";
@@ -7,14 +6,10 @@ import { createViewFactory } from "./layers/view.js";
7
6
  import { createActionFactory } from "./layers/action.js";
8
7
  import { createStoreState, createStoreModel, createStoreView, createStoreAction } from "./utils/store.js";
9
8
  import { createStoreCompose } from "./utils/compose.js";
9
+ import { createLogger } from "./utils/logger.js";
10
10
  export function createStore(config) {
11
11
  function init() {
12
- const logger = createConsola({
13
- level: runtimeConfig.logger,
14
- defaults: {
15
- tag: `harlemify:${config.name}`
16
- }
17
- });
12
+ const logger = createLogger(`harlemify:${config.name}`, runtimeConfig.logger);
18
13
  logger.info("Creating store");
19
14
  const modelFactory = createModelFactory(runtimeConfig.model, logger);
20
15
  const viewFactory = createViewFactory(runtimeConfig.view, logger);
@@ -46,16 +46,26 @@ export interface ActionApiRequest<MD extends ModelDefinitions, VD extends ViewDe
46
46
  concurrent?: ActionConcurrent;
47
47
  }
48
48
  export type ActionApiRequestShortcut<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> = Omit<ActionApiRequest<MD, VD>, "method">;
49
- export interface ActionApiCommit<MD extends ModelDefinitions, K extends keyof MD = keyof MD> {
49
+ export interface ActionApiCommitContext<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> {
50
+ request: Readonly<{
51
+ url: string;
52
+ method: ActionApiMethod;
53
+ headers: Readonly<Record<string, string>>;
54
+ query: Readonly<Record<string, unknown>>;
55
+ body: unknown;
56
+ }>;
57
+ view: DeepReadonly<StoreView<MD, VD>>;
58
+ }
59
+ export interface ActionApiCommit<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>, K extends keyof MD = keyof MD> {
50
60
  model: K;
51
61
  mode: ModelOneMode | ModelManyMode;
52
- value?: (data: unknown) => unknown;
62
+ transform?: (data: unknown, context: ActionApiCommitContext<MD, VD>) => unknown;
53
63
  options?: ModelOneCommitOptions | ModelManyCommitOptions;
54
64
  }
55
- export type ActionApiCommitReturn<MD extends ModelDefinitions, C extends readonly ActionApiCommit<MD>[]> = C extends readonly [] ? unknown : {
65
+ export type ActionApiCommitReturn<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>, C extends readonly ActionApiCommit<MD, VD>[]> = C extends readonly [] ? unknown : {
56
66
  [E in C[number] as E["model"] & string]: E["model"] extends keyof MD ? ModelDefinitionInfer<MD, E["model"]> : never;
57
67
  };
58
- export interface ActionApiDefinition<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>, C extends readonly ActionApiCommit<MD>[] = readonly ActionApiCommit<MD>[]> extends BaseDefinition {
68
+ export interface ActionApiDefinition<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>, C extends readonly ActionApiCommit<MD, VD>[] = readonly ActionApiCommit<MD, VD>[]> extends BaseDefinition {
59
69
  request: ActionApiRequest<MD, VD>;
60
70
  commits: C;
61
71
  }
@@ -74,25 +84,25 @@ export interface ActionHandlerDefinition<MD extends ModelDefinitions, VD extends
74
84
  }
75
85
  export type ActionDefinition<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> = ActionApiDefinition<MD, VD, any> | ActionHandlerDefinition<MD, VD, any, any>;
76
86
  export type ActionDefinitions<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> = Record<string, ActionDefinition<MD, VD>>;
77
- type ActionApiCommitTuple<MD extends ModelDefinitions> = readonly [
78
- ActionApiCommit<MD, keyof MD>,
79
- ...ActionApiCommit<MD, keyof MD>[]
87
+ type ActionApiCommitTuple<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> = readonly [
88
+ ActionApiCommit<MD, VD, keyof MD>,
89
+ ...ActionApiCommit<MD, VD, keyof MD>[]
80
90
  ];
81
91
  export interface ActionApiFactory<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> {
82
92
  (request: ActionApiRequest<MD, VD>): ActionApiDefinition<MD, VD, []>;
83
- <const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequest<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
93
+ <const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequest<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
84
94
  get(request: ActionApiRequestShortcut<MD, VD>): ActionApiDefinition<MD, VD, []>;
85
- get<const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
95
+ get<const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
86
96
  head(request: ActionApiRequestShortcut<MD, VD>): ActionApiDefinition<MD, VD, []>;
87
- head<const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
97
+ head<const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
88
98
  post(request: ActionApiRequestShortcut<MD, VD>): ActionApiDefinition<MD, VD, []>;
89
- post<const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
99
+ post<const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
90
100
  put(request: ActionApiRequestShortcut<MD, VD>): ActionApiDefinition<MD, VD, []>;
91
- put<const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
101
+ put<const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
92
102
  patch(request: ActionApiRequestShortcut<MD, VD>): ActionApiDefinition<MD, VD, []>;
93
- patch<const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
103
+ patch<const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
94
104
  delete(request: ActionApiRequestShortcut<MD, VD>): ActionApiDefinition<MD, VD, []>;
95
- delete<const C extends ActionApiCommitTuple<MD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
105
+ delete<const C extends ActionApiCommitTuple<MD, VD>>(request: ActionApiRequestShortcut<MD, VD>, ...commits: C): ActionApiDefinition<MD, VD, C>;
96
106
  }
97
107
  export interface ActionHandlerFactory<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>> {
98
108
  <P = unknown, R = void>(callback: ActionHandlerCallback<MD, VD, P, R>, options?: ActionHandlerOptions<P>): ActionHandlerDefinition<MD, VD, P, R>;
@@ -160,6 +170,6 @@ export interface ActionHandlerCall<P = unknown, T = void> extends ActionCallBase
160
170
  }
161
171
  export type ActionCall<T = void> = ActionApiCall<T> | ActionHandlerCall<any, T>;
162
172
  export type StoreAction<MD extends ModelDefinitions, VD extends ViewDefinitions<MD>, AD extends ActionDefinitions<MD, VD>> = {
163
- [K in keyof AD]: AD[K] extends ActionApiDefinition<MD, VD, infer C> ? C extends readonly ActionApiCommit<MD>[] ? ActionApiCall<ActionApiCommitReturn<MD, C>> : ActionApiCall : AD[K] extends ActionHandlerDefinition<MD, VD, infer P, infer R> ? ActionHandlerCall<P, R> : never;
173
+ [K in keyof AD]: AD[K] extends ActionApiDefinition<MD, VD, infer C> ? C extends readonly ActionApiCommit<MD, VD>[] ? ActionApiCall<ActionApiCommitReturn<MD, VD, C>> : ActionApiCall : AD[K] extends ActionHandlerDefinition<MD, VD, infer P, infer R> ? ActionHandlerCall<P, R> : never;
164
174
  };
165
175
  export {};
@@ -1,5 +1,10 @@
1
- import type { ConsolaInstance } from "consola";
1
+ export interface Logger {
2
+ info(message: string, ...args: unknown[]): void;
3
+ debug(message: string, ...args: unknown[]): void;
4
+ warn(message: string, ...args: unknown[]): void;
5
+ error(message: string, ...args: unknown[]): void;
6
+ }
2
7
  export interface BaseDefinition {
3
8
  key: string;
4
- logger?: ConsolaInstance;
9
+ logger?: Logger;
5
10
  }
@@ -31,15 +31,19 @@ export type ModelDefaultIdentifier<S extends Shape> = "id" extends keyof S ? "id
31
31
  export type AtLeastOne<S extends Shape> = {
32
32
  [K in keyof S]: Pick<S, K>;
33
33
  }[keyof S];
34
- export interface ModelDefinitionOptions {
35
- pre?: () => void;
36
- post?: () => void;
34
+ export type ModelHookContext<T> = {
35
+ mode: ModelOneMode | ModelManyMode;
36
+ state: T;
37
+ };
38
+ export interface ModelDefinitionOptions<T = unknown> {
39
+ pre?: (context: ModelHookContext<T>) => void;
40
+ post?: (context: ModelHookContext<T>) => void;
37
41
  }
38
42
  export interface ModelOneDefinition<S extends Shape> extends BaseDefinition {
39
43
  shape: ShapeType<S>;
40
44
  type: ModelType.ONE;
41
45
  default: () => S;
42
- options?: ModelDefinitionOptions;
46
+ options?: ModelDefinitionOptions<S>;
43
47
  }
44
48
  export interface ModelManyDefinition<S extends Shape, I extends keyof S = ModelDefaultIdentifier<S>, T extends ModelManyKind = ModelManyKind.LIST> extends BaseDefinition {
45
49
  shape: ShapeType<S>;
@@ -47,7 +51,7 @@ export interface ModelManyDefinition<S extends Shape, I extends keyof S = ModelD
47
51
  kind: T;
48
52
  identifier: [T] extends [ModelManyKind.LIST] ? I : never;
49
53
  default: () => [T] extends [ModelManyKind.LIST] ? S[] : Record<string, S[]>;
50
- options?: ModelDefinitionOptions;
54
+ options?: ModelDefinitionOptions<[T] extends [ModelManyKind.LIST] ? S[] : Record<string, S[]>>;
51
55
  }
52
56
  export type ModelDefinition<S extends Shape> = ModelOneDefinition<S> | ModelManyDefinition<S, any, any>;
53
57
  export type ModelDefinitions = Record<string, ModelDefinition<any>>;
@@ -59,10 +63,10 @@ export type ModelDefinitionsInfer<MD extends ModelDefinitions> = {
59
63
  [K in keyof MD]: ModelDefinitionInfer<MD, K>;
60
64
  };
61
65
  export interface ModelFactory {
62
- one<S extends Shape>(shape: ShapeType<S>, options?: ModelDefinitionOptions & {
66
+ one<S extends Shape>(shape: ShapeType<S>, options?: ModelDefinitionOptions<S> & {
63
67
  default?: () => S;
64
68
  }): ModelOneDefinition<S>;
65
- many<S extends Shape, I extends keyof S = ModelDefaultIdentifier<S>, T extends ModelManyKind = ModelManyKind.LIST>(shape: ShapeType<S>, options?: ModelDefinitionOptions & {
69
+ many<S extends Shape, I extends keyof S = ModelDefaultIdentifier<S>, T extends ModelManyKind = ModelManyKind.LIST>(shape: ShapeType<S>, options?: ModelDefinitionOptions<[T] extends [ModelManyKind.LIST] ? S[] : Record<string, S[]>> & {
66
70
  kind?: T;
67
71
  identifier?: [T] extends [ModelManyKind.LIST] ? I : never;
68
72
  default?: () => [T] extends [ModelManyKind.LIST] ? S[] : Record<string, S[]>;
@@ -1,4 +1,3 @@
1
- import { defu } from "defu";
2
1
  import { ref, computed, readonly, toValue, nextTick } from "vue";
3
2
  import {
4
3
  ActionApiMethod,
@@ -6,7 +5,7 @@ import {
6
5
  ActionStatus,
7
6
  ActionConcurrent
8
7
  } from "../types/action.js";
9
- import { trimStart, trimEnd, isEmptyRecord, isPlainObject } from "./base.js";
8
+ import { trimStart, trimEnd, isEmptyRecord, isPlainObject, merge } from "./base.js";
10
9
  import {
11
10
  ActionApiError,
12
11
  ActionHandlerError,
@@ -38,12 +37,12 @@ function resolveApiUrl(definition, view, options) {
38
37
  function resolveApiHeaders(definition, view, options) {
39
38
  const initial = resolveValue(definition.request.headers, view, {});
40
39
  const custom = options?.headers ?? {};
41
- return defu(custom, initial);
40
+ return merge(custom, initial);
42
41
  }
43
42
  function resolveApiQuery(definition, view, options) {
44
43
  const initial = resolveValue(definition.request.query, view, {});
45
44
  const custom = options?.query ?? {};
46
- return defu(custom, initial);
45
+ return merge(custom, initial);
47
46
  }
48
47
  function resolveApiBody(definition, view, target, options) {
49
48
  if (definition.request.method === ActionApiMethod.GET || definition.request.method === ActionApiMethod.HEAD) {
@@ -51,7 +50,7 @@ function resolveApiBody(definition, view, target, options) {
51
50
  }
52
51
  const initial = resolveValue(definition.request.body, view, {});
53
52
  const custom = options?.body ?? {};
54
- const body = defu(custom, initial);
53
+ const body = merge(custom, initial);
55
54
  if (!isPlainObject(body)) {
56
55
  return body;
57
56
  }
@@ -97,9 +96,9 @@ function resolveCommitMode(commit, options) {
97
96
  }
98
97
  return commit.mode;
99
98
  }
100
- function resolveCommitValue(commit, data) {
101
- if (typeof commit.value === "function") {
102
- return commit.value(data);
99
+ function resolveCommitTransform(commit, data, context) {
100
+ if (typeof commit.transform === "function") {
101
+ return commit.transform(data, context);
103
102
  }
104
103
  return data;
105
104
  }
@@ -177,7 +176,7 @@ async function executeHandler(definition, handler) {
177
176
  throw handlerError;
178
177
  }
179
178
  }
180
- function executeCommit(definition, model, data, options) {
179
+ function executeCommit(definition, model, data, context, options) {
181
180
  const commits = definition.commits;
182
181
  if (!commits || commits.length === 0) {
183
182
  return data;
@@ -192,7 +191,7 @@ function executeCommit(definition, model, data, options) {
192
191
  });
193
192
  }
194
193
  const mode = resolveCommitMode(commit, options);
195
- let value = resolveCommitValue(commit, data);
194
+ let value = resolveCommitTransform(commit, data, context);
196
195
  if (!isEmptyRecord(target.aliases())) {
197
196
  value = resolveAliasInbound(value, target.aliases());
198
197
  }
@@ -296,7 +295,11 @@ export function createAction(definition, model, view) {
296
295
  },
297
296
  options
298
297
  );
299
- data = executeCommit(definition, model, response, options);
298
+ const context = {
299
+ request: { url, method, headers, query, body },
300
+ view
301
+ };
302
+ data = executeCommit(definition, model, response, context, options);
300
303
  } else if (isHandlerDefinition(definition)) {
301
304
  const payload = resolveHandlerPayload(definition, options);
302
305
  data = await executeHandler(definition, {
@@ -1,4 +1,5 @@
1
1
  import type { BaseDefinition } from "../types/base.js";
2
+ export declare function snapshot<T>(value: T): T;
2
3
  export declare function wrapBaseDefinition<T extends Omit<BaseDefinition, "key">>(definition: T): T & BaseDefinition;
3
4
  export declare function trimStart(value: string, char: string): string;
4
5
  export declare function trimEnd(value: string, char: string): string;
@@ -6,5 +7,6 @@ export declare function ensureArray<T>(value: T | T[]): T[];
6
7
  export declare function isObject(value: unknown): value is object;
7
8
  export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
8
9
  export declare function isEmptyRecord(record: Record<string, unknown> | undefined): record is undefined;
10
+ export declare function merge<T>(priority: unknown, base: T): T;
9
11
  export declare function debounce<T extends (...args: any[]) => any>(callback: T, delay: number): T;
10
12
  export declare function throttle<T extends (...args: any[]) => any>(callback: T, delay: number): T;
@@ -1,3 +1,7 @@
1
+ import { objectClone, typeIsObject } from "@harlem/utilities";
2
+ export function snapshot(value) {
3
+ return objectClone(value);
4
+ }
1
5
  export function wrapBaseDefinition(definition) {
2
6
  let key = "";
3
7
  return Object.defineProperties(definition, {
@@ -23,16 +27,10 @@ export function ensureArray(value) {
23
27
  return Array.isArray(value) ? value : [value];
24
28
  }
25
29
  export function isObject(value) {
26
- return value != null && typeof value === "object";
30
+ return typeIsObject(value);
27
31
  }
28
32
  export function isPlainObject(value) {
29
- if (!isObject(value)) {
30
- return false;
31
- }
32
- if (Array.isArray(value)) {
33
- return false;
34
- }
35
- return true;
33
+ return isObject(value);
36
34
  }
37
35
  export function isEmptyRecord(record) {
38
36
  if (!record) {
@@ -43,6 +41,20 @@ export function isEmptyRecord(record) {
43
41
  }
44
42
  return false;
45
43
  }
44
+ export function merge(priority, base) {
45
+ if (!isPlainObject(priority) || !isPlainObject(base)) {
46
+ return priority;
47
+ }
48
+ const output = { ...base };
49
+ for (const key of Object.keys(priority)) {
50
+ const value = priority[key];
51
+ if (value === null || value === void 0) {
52
+ continue;
53
+ }
54
+ output[key] = merge(value, output[key]);
55
+ }
56
+ return output;
57
+ }
46
58
  export function debounce(callback, delay) {
47
59
  let timer = null;
48
60
  return (...args) => {
@@ -1,3 +1,3 @@
1
- import type { ConsolaInstance } from "consola";
1
+ import type { Logger } from "../types/base.js";
2
2
  import type { ComposeDefinitions, StoreCompose } from "../types/compose.js";
3
- export declare function createStoreCompose<CD extends ComposeDefinitions>(composeConfig: ((...args: any[]) => CD) | undefined, context: Record<string, unknown>, logger?: ConsolaInstance): StoreCompose<CD>;
3
+ export declare function createStoreCompose<CD extends ComposeDefinitions>(composeConfig: ((...args: any[]) => CD) | undefined, context: Record<string, unknown>, logger?: Logger): StoreCompose<CD>;
@@ -0,0 +1,2 @@
1
+ import type { Logger } from "../types/base.js";
2
+ export declare function createLogger(tag: string, level?: number): Logger;
@@ -0,0 +1,22 @@
1
+ const LEVELS = {
2
+ error: 0,
3
+ warn: 1,
4
+ info: 3,
5
+ debug: 4
6
+ };
7
+ export function createLogger(tag, level = -999) {
8
+ function report(method, sink) {
9
+ return (message, ...args) => {
10
+ if (LEVELS[method] > level) {
11
+ return;
12
+ }
13
+ sink(`[${tag}]`, message, ...args);
14
+ };
15
+ }
16
+ return {
17
+ info: report("info", console.info),
18
+ debug: report("debug", console.debug),
19
+ warn: report("warn", console.warn),
20
+ error: report("error", console.error)
21
+ };
22
+ }
@@ -1,5 +1,4 @@
1
- import { defu } from "defu";
2
- import { ensureArray } from "./base.js";
1
+ import { ensureArray, snapshot, merge } from "./base.js";
3
2
  import { resolveShapeAliases } from "./shape.js";
4
3
  import {
5
4
  ModelType,
@@ -8,12 +7,17 @@ import {
8
7
  ModelManyMode,
9
8
  ModelSilent
10
9
  } from "../types/model.js";
11
- function callHook(definition, hook, silent) {
10
+ function callHook(definition, source, hook, mode, silent) {
12
11
  if (silent === true || silent === hook) {
13
12
  return;
14
13
  }
14
+ const handler = definition.options?.[hook];
15
+ if (!handler) {
16
+ return;
17
+ }
15
18
  try {
16
- definition.options?.[hook]?.();
19
+ const state = snapshot(source.state[definition.key]);
20
+ handler({ mode, state });
17
21
  } catch (error) {
18
22
  definition.logger?.error(`Model ${hook} hook error`, {
19
23
  model: definition.key,
@@ -21,14 +25,14 @@ function callHook(definition, hook, silent) {
21
25
  });
22
26
  }
23
27
  }
24
- function wrapOperation(definition, mutation, operation, silent) {
28
+ function wrapOperation(definition, source, mode, operation, silent) {
25
29
  definition.logger?.debug("Model mutation", {
26
30
  model: definition.key,
27
- mutation
31
+ mutation: mode
28
32
  });
29
- callHook(definition, ModelSilent.PRE, silent);
33
+ callHook(definition, source, ModelSilent.PRE, mode, silent);
30
34
  operation();
31
- callHook(definition, ModelSilent.POST, silent);
35
+ callHook(definition, source, ModelSilent.POST, mode, silent);
32
36
  }
33
37
  function createOneCommit(definition, source) {
34
38
  const setOperation = source.mutation(`${definition.key}:set`, (state, { payload }) => {
@@ -39,7 +43,7 @@ function createOneCommit(definition, source) {
39
43
  });
40
44
  const patchOperation = source.mutation(`${definition.key}:patch`, (state, { payload, options }) => {
41
45
  if (options?.deep) {
42
- state[definition.key] = defu(payload, state[definition.key]);
46
+ state[definition.key] = merge(payload, state[definition.key]);
43
47
  return;
44
48
  }
45
49
  state[definition.key] = {
@@ -49,13 +53,19 @@ function createOneCommit(definition, source) {
49
53
  });
50
54
  return {
51
55
  set(payload, options) {
52
- wrapOperation(definition, "set", () => setOperation({ payload }), options?.silent);
56
+ wrapOperation(definition, source, ModelOneMode.SET, () => setOperation({ payload }), options?.silent);
53
57
  },
54
58
  reset(options) {
55
- wrapOperation(definition, "reset", () => resetOperation(), options?.silent);
59
+ wrapOperation(definition, source, ModelOneMode.RESET, () => resetOperation(), options?.silent);
56
60
  },
57
61
  patch(payload, options) {
58
- wrapOperation(definition, "patch", () => patchOperation({ payload, options }), options?.silent);
62
+ wrapOperation(
63
+ definition,
64
+ source,
65
+ ModelOneMode.PATCH,
66
+ () => patchOperation({ payload, options }),
67
+ options?.silent
68
+ );
59
69
  }
60
70
  };
61
71
  }
@@ -77,7 +87,7 @@ function createManyListCommit(definition, source) {
77
87
  return item;
78
88
  }
79
89
  if (options?.deep) {
80
- return defu(found, item);
90
+ return merge(found, item);
81
91
  }
82
92
  return {
83
93
  ...item,
@@ -120,19 +130,37 @@ function createManyListCommit(definition, source) {
120
130
  });
121
131
  return {
122
132
  set(payload, options) {
123
- wrapOperation(definition, "set", () => setOperation({ payload }), options?.silent);
133
+ wrapOperation(definition, source, ModelManyMode.SET, () => setOperation({ payload }), options?.silent);
124
134
  },
125
135
  reset(options) {
126
- wrapOperation(definition, "reset", () => resetOperation(), options?.silent);
136
+ wrapOperation(definition, source, ModelManyMode.RESET, () => resetOperation(), options?.silent);
127
137
  },
128
138
  patch(payload, options) {
129
- wrapOperation(definition, "patch", () => patchOperation({ payload, options }), options?.silent);
139
+ wrapOperation(
140
+ definition,
141
+ source,
142
+ ModelManyMode.PATCH,
143
+ () => patchOperation({ payload, options }),
144
+ options?.silent
145
+ );
130
146
  },
131
147
  remove(payload, options) {
132
- wrapOperation(definition, "remove", () => removeOperation({ payload }), options?.silent);
148
+ wrapOperation(
149
+ definition,
150
+ source,
151
+ ModelManyMode.REMOVE,
152
+ () => removeOperation({ payload }),
153
+ options?.silent
154
+ );
133
155
  },
134
156
  add(payload, options) {
135
- wrapOperation(definition, "add", () => addOperation({ payload, options }), options?.silent);
157
+ wrapOperation(
158
+ definition,
159
+ source,
160
+ ModelManyMode.ADD,
161
+ () => addOperation({ payload, options }),
162
+ options?.silent
163
+ );
136
164
  }
137
165
  };
138
166
  }
@@ -145,7 +173,7 @@ function createManyRecordCommit(definition, source) {
145
173
  });
146
174
  const patchOperation = source.mutation(`${definition.key}:patch`, (state, { payload, options }) => {
147
175
  if (options?.deep) {
148
- state[definition.key] = defu(payload, state[definition.key]);
176
+ state[definition.key] = merge(payload, state[definition.key]);
149
177
  return;
150
178
  }
151
179
  state[definition.key] = {
@@ -170,19 +198,31 @@ function createManyRecordCommit(definition, source) {
170
198
  });
171
199
  return {
172
200
  set(payload, options) {
173
- wrapOperation(definition, "set", () => setOperation({ payload }), options?.silent);
201
+ wrapOperation(definition, source, ModelManyMode.SET, () => setOperation({ payload }), options?.silent);
174
202
  },
175
203
  reset(options) {
176
- wrapOperation(definition, "reset", () => resetOperation(), options?.silent);
204
+ wrapOperation(definition, source, ModelManyMode.RESET, () => resetOperation(), options?.silent);
177
205
  },
178
206
  patch(payload, options) {
179
- wrapOperation(definition, "patch", () => patchOperation({ payload, options }), options?.silent);
207
+ wrapOperation(
208
+ definition,
209
+ source,
210
+ ModelManyMode.PATCH,
211
+ () => patchOperation({ payload, options }),
212
+ options?.silent
213
+ );
180
214
  },
181
215
  remove(payload, options) {
182
- wrapOperation(definition, "remove", () => removeOperation({ payload }), options?.silent);
216
+ wrapOperation(
217
+ definition,
218
+ source,
219
+ ModelManyMode.REMOVE,
220
+ () => removeOperation({ payload }),
221
+ options?.silent
222
+ );
183
223
  },
184
224
  add(payload, options) {
185
- wrapOperation(definition, "add", () => addOperation({ payload }), options?.silent);
225
+ wrapOperation(definition, source, ModelManyMode.ADD, () => addOperation({ payload }), options?.silent);
186
226
  }
187
227
  };
188
228
  }
@@ -1,6 +1,5 @@
1
- import { defu } from "defu";
2
1
  import { z } from "zod";
3
- import { isPlainObject, isEmptyRecord } from "./base.js";
2
+ import { isPlainObject, isEmptyRecord, merge } from "./base.js";
4
3
  function resolveShapeFields(shape) {
5
4
  if (!("shape" in shape) || typeof shape.shape !== "object" || !shape.shape) {
6
5
  return void 0;
@@ -183,7 +182,7 @@ export function decorateShape(object) {
183
182
  defaults(overrides) {
184
183
  const zero = resolveZeroValues(object);
185
184
  if (overrides) {
186
- return defu(overrides, zero);
185
+ return merge(overrides, zero);
187
186
  }
188
187
  return zero;
189
188
  }
@@ -1,10 +1,11 @@
1
+ import { snapshot } from "./base.js";
1
2
  import { ViewClone } from "../types/view.js";
2
3
  function resolveClonedValue(definition, value) {
3
4
  if (!definition.options?.clone) {
4
5
  return value;
5
6
  }
6
7
  if (definition.options.clone === ViewClone.DEEP) {
7
- return JSON.parse(JSON.stringify(value));
8
+ return snapshot(value);
8
9
  }
9
10
  if (Array.isArray(value)) {
10
11
  return [...value];
@@ -7,8 +7,8 @@ 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, ActionHandlerCall, ActionCallOptions, ActionCallBaseOptions, ActionApiCallOptions, ActionHandlerCallOptions, ActionCallTransformerOptions, ActionCallBindOptions, ActionCallCommitOptions, ActionHandlerOptions, ActionResolvedApi, } from "./core/types/action.js";
11
- export { ActionApiError, ActionHandlerError, ActionCommitError, ActionConcurrentError, isError, toError } from "./core/utils/error.js";
10
+ export type { ActionCall, ActionApiCall, ActionApiCommitContext, ActionHandlerCall, ActionCallOptions, ActionCallBaseOptions, ActionApiCallOptions, ActionHandlerCallOptions, ActionCallTransformerOptions, ActionCallBindOptions, ActionCallCommitOptions, ActionHandlerOptions, ActionResolvedApi, } from "./core/types/action.js";
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";
14
14
  export type { UseStoreCompose } from "./composables/compose.js";
@@ -3,7 +3,14 @@ export { shape } from "./core/layers/shape.js";
3
3
  export { ModelType, ModelManyKind, ModelOneMode, ModelManyMode, ModelSilent } from "./core/types/model.js";
4
4
  export { ViewClone } from "./core/types/view.js";
5
5
  export { ActionStatus, ActionConcurrent, ActionType, ActionApiMethod } from "./core/types/action.js";
6
- export { ActionApiError, ActionHandlerError, ActionCommitError, ActionConcurrentError, isError, toError } from "./core/utils/error.js";
6
+ export {
7
+ ActionApiError,
8
+ ActionHandlerError,
9
+ ActionCommitError,
10
+ ActionConcurrentError,
11
+ isError,
12
+ toError
13
+ } from "./core/utils/error.js";
7
14
  export { useStoreCompose } from "./composables/compose.js";
8
15
  export { useIsolatedActionStatus, useIsolatedActionError, useStoreAction } from "./composables/action.js";
9
16
  export { useStoreModel } from "./composables/model.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diphyx/harlemify",
3
- "version": "6.1.1",
3
+ "version": "6.3.0",
4
4
  "description": "API state management for Nuxt powered by Harlem",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -45,29 +45,33 @@
45
45
  "prepare": "pnpm nuxt prepare && pnpm nuxt prepare playground",
46
46
  "dev": "pnpm nuxt dev playground",
47
47
  "build": "nuxt-module-build build",
48
- "lint": "eslint . --fix",
48
+ "lint": "eslint ./ --fix",
49
+ "format": "prettier ./ --write",
49
50
  "test": "vitest run",
50
51
  "test:e2e": "playwright test"
51
52
  },
53
+ "peerDependencies": {
54
+ "zod": "^4.4.3"
55
+ },
52
56
  "dependencies": {
53
57
  "@harlem/core": "^3.1.8",
54
- "@nuxt/kit": "^3.14.0 || ^4.0.0",
55
- "consola": "^3.4.2",
56
- "defu": "^6.1.4",
57
- "zod": "^4.3.6"
58
+ "@harlem/utilities": "^3.1.8",
59
+ "@nuxt/kit": "^4.4.6"
58
60
  },
59
61
  "devDependencies": {
60
62
  "@nuxt/eslint-config": "^0.6.2",
61
63
  "@nuxt/module-builder": "^0.8.4",
62
- "@nuxt/schema": "^4.3.1",
64
+ "@nuxt/schema": "^4.4.6",
63
65
  "@nuxt/test-utils": "^3.23.0",
64
- "@playwright/test": "^1.58.2",
65
- "@types/node": "^22.19.11",
66
+ "@playwright/test": "^1.60.0",
67
+ "@types/node": "^22.19.19",
66
68
  "@vitest/coverage-v8": "^2.1.9",
67
- "eslint": "^9.39.2",
68
- "nuxt": "^4.3.1",
69
+ "eslint": "^9.39.4",
70
+ "nuxt": "^4.4.6",
71
+ "prettier": "^3.8.3",
69
72
  "typescript": "^5.9.3",
70
73
  "vitest": "^2.1.9",
71
- "vue": "^3.5.28"
74
+ "vue": "^3.5.35",
75
+ "zod": "^4.4.3"
72
76
  }
73
77
  }