@diphyx/harlemify 2.0.0 → 3.0.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
@@ -2,25 +2,22 @@
2
2
 
3
3
  > Schema-driven state management for Nuxt powered by [Harlem](https://harlemjs.com/)
4
4
 
5
- ![Harlemify](https://raw.githubusercontent.com/diphyx/harlemify/main/docs/_media/icon.svg)
6
-
7
- Define your data schema once with Zod, and Harlemify handles the rest: type-safe API calls, reactive state, request monitoring, and automatic memory management. Your schema becomes the single source of truth for types, validation, and API payloads.
8
-
9
5
  ## Features
10
6
 
11
7
  - **Schema-Driven** - Zod schema defines types, validation, and API payloads
12
- - **Custom Adapters** - Pluggable HTTP adapters for custom request handling
13
- - **Reactive Memory** - Unit and collection caching with Vue reactivity
14
- - **Request Monitoring** - Track pending, success, and failed states
15
- - **SSR Support** - Server-side rendering via Harlem SSR plugin
8
+ - **Free-form Actions** - Define any action with custom naming
9
+ - **Chainable Builders** - Fluent `Endpoint` and `Memory` APIs
10
+ - **Function-based Monitor** - Track status with `pending()`, `success()`, `failed()`, `idle()`
11
+ - **Custom Adapters** - Override HTTP at module, store, endpoint, or call-time
12
+ - **SSR Support** - Server-side rendering with automatic hydration
16
13
 
17
- ## Installation
14
+ ## Install
18
15
 
19
16
  ```bash
20
17
  npm install @diphyx/harlemify
21
18
  ```
22
19
 
23
- ## Quick Start
20
+ ## Setup
24
21
 
25
22
  ```typescript
26
23
  // nuxt.config.ts
@@ -30,130 +27,59 @@ export default defineNuxtConfig({
30
27
  api: {
31
28
  adapter: {
32
29
  baseURL: "https://api.example.com",
33
- timeout: 10000,
34
30
  },
35
31
  },
36
32
  },
37
33
  });
38
34
  ```
39
35
 
36
+ ## Usage
37
+
40
38
  ```typescript
41
39
  // stores/user.ts
42
40
  import { z } from "zod";
43
- import { createStore, Endpoint, EndpointMethod } from "@diphyx/harlemify";
44
41
 
45
- const UserSchema = z.object({
46
- id: z.number().meta({ indicator: true }),
47
- name: z.string().meta({
48
- methods: [EndpointMethod.POST, EndpointMethod.PATCH],
49
- }),
50
- });
42
+ enum UserAction {
43
+ LIST = "list",
44
+ CREATE = "create",
45
+ }
51
46
 
52
- export type User = z.infer<typeof UserSchema>;
53
-
54
- export const userStore = createStore("user", UserSchema, {
55
- [Endpoint.GET_UNITS]: { method: EndpointMethod.GET, url: "/users" },
56
- [Endpoint.POST_UNITS]: { method: EndpointMethod.POST, url: "/users" },
57
- [Endpoint.PATCH_UNITS]: { method: EndpointMethod.PATCH, url: (p) => `/users/${p.id}` },
58
- [Endpoint.DELETE_UNITS]: { method: EndpointMethod.DELETE, url: (p) => `/users/${p.id}` },
47
+ const userSchema = z.object({
48
+ id: z.number().meta({ indicator: true }),
49
+ name: z.string().meta({ actions: [UserAction.CREATE] }),
50
+ email: z.string().meta({ actions: [UserAction.CREATE] }),
59
51
  });
60
- ```
61
-
62
- ## Custom Adapters
63
52
 
64
- Harlemify supports custom HTTP adapters for advanced use cases like streaming responses, file uploads with progress, or custom authentication.
65
-
66
- ### Adapter Hierarchy
67
-
68
- Adapters are resolved in the following order (highest to lowest priority):
69
-
70
- 1. **Endpoint adapter** - Per-endpoint custom adapter
71
- 2. **Store adapter** - Store-level adapter option
72
- 3. **Module adapter** - Global config in `nuxt.config.ts`
73
- 4. **Default adapter** - Built-in fetch adapter
74
-
75
- ### Built-in Adapter
76
-
77
- Use `defineApiAdapter` to create an adapter with custom options:
78
-
79
- ```typescript
80
- import { defineApiAdapter } from "@diphyx/harlemify";
81
-
82
- const customAdapter = defineApiAdapter({
83
- baseURL: "/api",
84
- timeout: 5000,
85
- retry: 3,
86
- retryDelay: 1000,
87
- retryStatusCodes: [500, 502, 503],
88
- });
89
- ```
90
-
91
- ### Custom Adapter
92
-
93
- Create a fully custom adapter for advanced scenarios:
94
-
95
- ```typescript
96
- import type { ApiAdapter, ApiAdapterRequest } from "@diphyx/harlemify";
97
-
98
- const streamingAdapter: ApiAdapter<MyType> = async (request: ApiAdapterRequest) => {
99
- // Custom fetch logic with streaming, progress, etc.
100
- const response = await fetch(request.url, {
101
- method: request.method,
102
- headers: request.headers,
103
- body: JSON.stringify(request.body),
104
- });
105
-
106
- const data = await response.json();
107
- return { data, status: response.status };
108
- };
109
- ```
110
-
111
- ### Using Adapters
112
-
113
- **Store-level adapter:**
114
-
115
- ```typescript
116
- export const userStore = createStore(
117
- "user",
118
- UserSchema,
119
- {
120
- [Endpoint.GET_UNITS]: { method: EndpointMethod.GET, url: "/users" },
53
+ export const userStore = createStore("user", userSchema, {
54
+ [UserAction.LIST]: {
55
+ endpoint: Endpoint.get("/users"),
56
+ memory: Memory.units(),
121
57
  },
122
- {
123
- adapter: customAdapter, // Used for all endpoints in this store
124
- },
125
- );
126
- ```
127
-
128
- **Endpoint-level adapter:**
129
-
130
- ```typescript
131
- export const userStore = createStore("user", UserSchema, {
132
- [Endpoint.GET_UNIT]: {
133
- method: EndpointMethod.GET,
134
- url: (p) => `/users/${p.id}`,
135
- adapter: detailAdapter, // Custom adapter for this endpoint only
136
- },
137
- [Endpoint.GET_UNITS]: {
138
- method: EndpointMethod.GET,
139
- url: "/users",
140
- // Uses store or global adapter
58
+ [UserAction.CREATE]: {
59
+ endpoint: Endpoint.post("/users"),
60
+ memory: Memory.units().add(),
141
61
  },
142
62
  });
143
63
  ```
144
64
 
145
- ## Why Harlemify?
65
+ ```vue
66
+ <script setup>
67
+ const { users, listUser, userMonitor } = useStoreAlias(userStore);
146
68
 
147
- | | |
148
- | --------------- | ------------------------------------------------- |
149
- | **Type-Safe** | Full TypeScript support with Zod schema inference |
150
- | **Declarative** | Define schema once, derive everything else |
151
- | **Reactive** | Powered by Vue's reactivity through Harlem |
152
- | **Extensible** | Custom adapters for any HTTP requirement |
69
+ await listUser();
70
+ </script>
71
+
72
+ <template>
73
+ <div v-if="userMonitor.list.pending()">Loading...</div>
74
+ <ul v-else>
75
+ <li v-for="user in users" :key="user.id">{{ user.name }}</li>
76
+ </ul>
77
+ </template>
78
+ ```
153
79
 
154
80
  ## Documentation
155
81
 
156
- Full documentation available at [https://diphyx.github.io/harlemify/](https://diphyx.github.io/harlemify/)
82
+ [https://diphyx.github.io/harlemify/](https://diphyx.github.io/harlemify/)
157
83
 
158
84
  ## License
159
85
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0 || >=4.0.0"
6
6
  },
7
- "version": "2.0.0",
7
+ "version": "3.0.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "unknown"
@@ -1,30 +1,22 @@
1
1
  import type { ComputedRef } from "vue";
2
- import { EndpointMethod } from "../utils/endpoint.js";
3
- import type { Store } from "../core/store.js";
4
- import type { Pluralize, Capitalize } from "../utils/transform.js";
5
- import type { EndpointStatusFlag } from "../utils/endpoint.js";
6
- type Indicator<T, I extends keyof T> = Required<Pick<T, I>>;
7
- type PartialWithIndicator<T, I extends keyof T> = Indicator<T, I> & Partial<T>;
2
+ import type { ActionFunction, ActionStatus, ActionsConfig, Store, StoreMemory } from "../core/store.js";
3
+ import type { Capitalize, Pluralize } from "../utils/transform.js";
8
4
  type MemoryState<E extends string, T> = {
9
5
  [P in E]: ComputedRef<T | null>;
10
6
  } & {
11
7
  [P in Pluralize<E>]: ComputedRef<T[]>;
12
8
  };
13
- type MemoryAction<A extends string, E extends string, U> = {
14
- [K in `${A}${Capitalize<E>}`]: (unit: U) => void;
15
- } & {
16
- [K in `${A}${Capitalize<Pluralize<E>>}`]: (units: U[]) => void;
9
+ type AliasActions<E extends string, A extends ActionsConfig<S>, S> = {
10
+ [K in keyof A as `${K & string}${Capitalize<E>}`]: ActionFunction<S>;
17
11
  };
18
- type EndpointAction<E extends string, T> = {
19
- [M in EndpointMethod as `${M}${Capitalize<E>}`]: (unit?: Partial<T>) => Promise<T>;
20
- } & {
21
- [M in EndpointMethod as `${M}${Capitalize<Pluralize<E>>}`]: (units?: Partial<T>[]) => Promise<T[]>;
12
+ type AliasMemory<E extends string, S, I extends keyof S> = {
13
+ [K in `${E}Memory`]: StoreMemory<S, I>;
22
14
  };
23
- type EndpointMonitor<E extends string> = {
24
- [M in EndpointMethod as `${M}${Capitalize<E>}${EndpointStatusFlag}`]: ComputedRef<boolean>;
25
- } & {
26
- [M in EndpointMethod as `${M}${Capitalize<Pluralize<E>>}${EndpointStatusFlag}`]: ComputedRef<boolean>;
15
+ type AliasMonitor<E extends string, A extends ActionsConfig<any>> = {
16
+ [K in `${E}Monitor`]: {
17
+ [ActionName in keyof A]: ActionStatus;
18
+ };
27
19
  };
28
- type StoreAlias<E extends string, T, I extends keyof T = keyof T> = MemoryState<E, T> & MemoryAction<"set", E, T | null> & MemoryAction<"edit", E, PartialWithIndicator<T, I>> & MemoryAction<"drop", E, PartialWithIndicator<T, I>> & EndpointAction<E, T> & EndpointMonitor<E>;
29
- export declare function useStoreAlias<E extends string, T, I extends keyof T = keyof T>(store: Store<E, T, I>): StoreAlias<E, T, I>;
20
+ export type StoreAlias<E extends string, S, I extends keyof S, A extends ActionsConfig<S>> = MemoryState<E, S> & AliasActions<E, A, S> & AliasMemory<E, S, I> & AliasMonitor<E, A>;
21
+ export declare function useStoreAlias<E extends string, S, I extends keyof S, A extends ActionsConfig<S>>(store: Store<E, S, I, A>): StoreAlias<E, S, I, A>;
30
22
  export {};
@@ -1,25 +1,14 @@
1
1
  import { capitalize } from "../utils/transform.js";
2
- import { EndpointMethod, EndpointStatus, makeEndpointStatusFlag } from "../utils/endpoint.js";
3
- import { StoreMemoryAction } from "../core/store.js";
4
2
  export function useStoreAlias(store) {
5
- const capitalizedUnit = capitalize(store.alias.unit);
6
- const capitalizedUnits = capitalize(store.alias.units);
3
+ const capitalizedEntity = capitalize(store.alias.unit);
7
4
  const output = {
8
5
  [store.alias.unit]: store.unit,
9
- [store.alias.units]: store.units
6
+ [store.alias.units]: store.units,
7
+ [`${store.alias.unit}Memory`]: store.memory,
8
+ [`${store.alias.unit}Monitor`]: store.monitor
10
9
  };
11
- for (const action of Object.values(StoreMemoryAction)) {
12
- output[`${action}${capitalizedUnit}`] = store.memory[`${action}Unit`];
13
- output[`${action}${capitalizedUnits}`] = store.memory[`${action}Units`];
14
- }
15
- for (const method of Object.values(EndpointMethod)) {
16
- output[`${method}${capitalizedUnit}`] = store.endpoint[`${method}Unit`];
17
- output[`${method}${capitalizedUnits}`] = store.endpoint[`${method}Units`];
18
- for (const status of Object.values(EndpointStatus)) {
19
- const statusFlag = makeEndpointStatusFlag(status);
20
- output[`${method}${capitalizedUnit}${statusFlag}`] = store.monitor[`${method}Unit${statusFlag}`];
21
- output[`${method}${capitalizedUnits}${statusFlag}`] = store.monitor[`${method}Units${statusFlag}`];
22
- }
10
+ for (const actionName in store.action) {
11
+ output[`${actionName}${capitalizedEntity}`] = store.action[actionName];
23
12
  }
24
13
  return output;
25
14
  }
@@ -1,6 +1,6 @@
1
1
  import type { MaybeRefOrGetter } from "vue";
2
2
  import { EndpointMethod } from "../utils/endpoint.js";
3
- import { type ApiAdapter } from "./adapter.js";
3
+ import type { ApiAdapter } from "../utils/adapter.js";
4
4
  export type ApiRequestHeader = MaybeRefOrGetter<Record<string, unknown>>;
5
5
  export type ApiRequestQuery = MaybeRefOrGetter<Record<string, unknown>>;
6
6
  export type ApiRequestBody = MaybeRefOrGetter<string | number | ArrayBuffer | FormData | Blob | Record<string, any>>;
@@ -1,6 +1,6 @@
1
1
  import { toValue } from "vue";
2
+ import { defineApiAdapter } from "../utils/adapter.js";
2
3
  import { EndpointMethod } from "../utils/endpoint.js";
3
- import { defineApiAdapter } from "./adapter.js";
4
4
  export function createApi(options) {
5
5
  const defaultAdapter = options?.adapter ?? defineApiAdapter();
6
6
  async function request(url, requestOptions) {
@@ -9,14 +9,8 @@ export function createApi(options) {
9
9
  method: requestOptions?.action ?? EndpointMethod.GET,
10
10
  url,
11
11
  body: toValue(requestOptions?.body),
12
- query: {
13
- ...toValue(options?.query),
14
- ...toValue(requestOptions?.query)
15
- },
16
- headers: {
17
- ...toValue(options?.headers),
18
- ...toValue(requestOptions?.headers)
19
- },
12
+ query: Object.assign({}, toValue(options?.query), toValue(requestOptions?.query)),
13
+ headers: Object.assign({}, toValue(options?.headers), toValue(requestOptions?.headers)),
20
14
  signal: requestOptions?.signal
21
15
  };
22
16
  const response = await adapter(adapterRequest);
@@ -1,75 +1,69 @@
1
1
  import type { z } from "zod";
2
2
  import type { ComputedRef } from "vue";
3
3
  import type { Extension, BaseState } from "@harlem/core";
4
- import { Endpoint, EndpointStatus } from "../utils/endpoint.js";
5
- import type { EndpointMethodOptions } from "./api.js";
6
- import type { ApiAdapter } from "./adapter.js";
7
- import type { EndpointDefinition, EndpointStatusName } from "../utils/endpoint.js";
4
+ import { EndpointStatus } from "../utils/endpoint.js";
5
+ import type { ApiAdapter } from "../utils/adapter.js";
8
6
  import type { Pluralize } from "../utils/transform.js";
9
- export declare enum StoreMemoryAction {
10
- SET = "set",
11
- EDIT = "edit",
12
- DROP = "drop"
7
+ import type { EndpointDefinition } from "../utils/endpoint.js";
8
+ import type { MemoryDefinition } from "../utils/memory.js";
9
+ export interface ActionDefinition<S = Record<string, unknown>> {
10
+ endpoint: EndpointDefinition<S>;
11
+ memory?: MemoryDefinition;
13
12
  }
14
- export declare enum StoreMemoryPosition {
15
- FIRST = "first",
16
- LAST = "last"
13
+ export interface ActionOptions {
14
+ query?: Record<string, unknown>;
15
+ headers?: Record<string, string>;
16
+ body?: unknown;
17
+ signal?: AbortSignal;
18
+ validate?: boolean;
19
+ adapter?: ApiAdapter<any>;
20
+ }
21
+ export interface ActionStatus {
22
+ current: () => EndpointStatus;
23
+ pending: () => boolean;
24
+ success: () => boolean;
25
+ failed: () => boolean;
26
+ idle: () => boolean;
17
27
  }
18
28
  export interface StoreHooks {
19
29
  before?: () => Promise<void> | void;
20
30
  after?: (error?: Error) => Promise<void> | void;
21
31
  }
22
- export interface StoreOptions {
32
+ export interface StoreOptions<_A extends ActionsConfig<any> = ActionsConfig<any>> {
23
33
  adapter?: ApiAdapter<any>;
24
34
  indicator?: string;
25
35
  hooks?: StoreHooks;
26
36
  extensions?: Extension<BaseState>[];
27
37
  }
28
- type Indicator<T, I extends keyof T> = Required<Pick<T, I>>;
29
- type WithIndicator<T, I extends keyof T> = Indicator<T, I> & T;
30
- type PartialWithIndicator<T, I extends keyof T> = Indicator<T, I> & Partial<T>;
31
- type StoreEndpointReadOptions = Omit<EndpointMethodOptions<any>, "body">;
32
- type StoreEndpointWriteOptions = EndpointMethodOptions<any> & {
33
- validate?: boolean;
34
- };
35
- type StoreEndpointWriteMultipleOptions = StoreEndpointWriteOptions & {
36
- position?: StoreMemoryPosition;
37
- };
38
- type StoreMemory<T = unknown, I extends keyof T = keyof T> = {
39
- setUnit: (unit: T | null) => void;
40
- setUnits: (units: T[]) => void;
41
- editUnit: (unit: PartialWithIndicator<T, I>) => void;
42
- editUnits: (units: PartialWithIndicator<T, I>[]) => void;
43
- dropUnit: (unit: PartialWithIndicator<T, I>) => void;
44
- dropUnits: (units: PartialWithIndicator<T, I>[]) => void;
38
+ export type ActionsConfig<S = Record<string, unknown>> = Record<string, ActionDefinition<S>>;
39
+ export type ActionFunction<S> = (params?: Partial<S>, options?: ActionOptions) => Promise<S | S[] | boolean>;
40
+ export type StoreActions<A extends ActionsConfig<S>, S> = {
41
+ [K in keyof A]: ActionFunction<S>;
45
42
  };
46
- type StoreEndpoint<T = unknown, I extends keyof T = keyof T> = {
47
- getUnit: (unit?: PartialWithIndicator<T, I>, options?: StoreEndpointReadOptions) => Promise<T>;
48
- getUnits: (options?: StoreEndpointReadOptions) => Promise<T[]>;
49
- postUnit: (unit: WithIndicator<T, I>, options?: StoreEndpointWriteOptions) => Promise<T>;
50
- postUnits: (units: WithIndicator<T, I>[], options?: StoreEndpointWriteMultipleOptions) => Promise<T[]>;
51
- putUnit: (unit: WithIndicator<T, I>, options?: StoreEndpointWriteOptions) => Promise<T>;
52
- putUnits: (units: WithIndicator<T, I>[], options?: StoreEndpointWriteOptions) => Promise<T[]>;
53
- patchUnit: (unit: PartialWithIndicator<T, I>, options?: StoreEndpointWriteOptions) => Promise<Partial<T>>;
54
- patchUnits: (units: PartialWithIndicator<T, I>[], options?: StoreEndpointWriteOptions) => Promise<Partial<T>[]>;
55
- deleteUnit: (unit: PartialWithIndicator<T, I>, options?: StoreEndpointReadOptions) => Promise<boolean>;
56
- deleteUnits: (units: PartialWithIndicator<T, I>[], options?: StoreEndpointReadOptions) => Promise<boolean>;
43
+ export type StoreMonitor<A extends ActionsConfig<any>> = {
44
+ [K in keyof A]: ActionStatus;
57
45
  };
58
- type StoreMonitor = {
59
- [K in Endpoint as EndpointStatusName<K, EndpointStatus>]: ComputedRef<boolean>;
46
+ export type StoreMemory<T, I extends keyof T> = {
47
+ set: (data: T | T[] | null) => void;
48
+ edit: (data: PartialWithIndicator<T, I> | PartialWithIndicator<T, I>[], options?: {
49
+ deep?: boolean;
50
+ }) => void;
51
+ drop: (data: PartialWithIndicator<T, I> | PartialWithIndicator<T, I>[]) => void;
60
52
  };
61
- export type Store<E extends string = string, T = unknown, I extends keyof T = keyof T> = {
53
+ export type Store<E extends string = string, S = unknown, I extends keyof S = keyof S, A extends ActionsConfig<S> = ActionsConfig<S>> = {
62
54
  store: any;
63
55
  alias: {
64
56
  unit: E;
65
57
  units: Pluralize<E>;
66
58
  };
67
59
  indicator: I;
68
- unit: ComputedRef<T | null>;
69
- units: ComputedRef<T[]>;
70
- memory: StoreMemory<T, I>;
71
- endpoint: StoreEndpoint<T, I>;
72
- monitor: StoreMonitor;
60
+ unit: ComputedRef<S | null>;
61
+ units: ComputedRef<S[]>;
62
+ action: StoreActions<A, S>;
63
+ memory: StoreMemory<S, I>;
64
+ monitor: StoreMonitor<A>;
73
65
  };
74
- export declare function createStore<E extends string, T extends z.ZodRawShape, S extends z.infer<z.ZodObject<T>> = z.infer<z.ZodObject<T>>, I extends keyof S = "id" & keyof S>(entity: E, schema: z.ZodObject<T>, endpoints?: Partial<Record<Endpoint, EndpointDefinition<Partial<S>>>>, options?: StoreOptions): Store<E, S, I>;
66
+ type Indicator<T, I extends keyof T> = Required<Pick<T, I>>;
67
+ type PartialWithIndicator<T, I extends keyof T> = Indicator<T, I> & Partial<T>;
68
+ export declare function createStore<E extends string, T extends z.ZodRawShape, A extends ActionsConfig<z.infer<z.ZodObject<T>>>, S extends z.infer<z.ZodObject<T>> = z.infer<z.ZodObject<T>>, I extends keyof S = "id" & keyof S>(entity: E, schema: z.ZodObject<T>, actions: A, options?: StoreOptions<A>): Store<E, S, I, A>;
75
69
  export {};