@rnaga/wp-node 1.1.2 → 1.1.4

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
@@ -11,7 +11,7 @@ WP-Node is a Node.js project written in TypeScript that mirrors the WordPress da
11
11
  Key benefits include:
12
12
 
13
13
  - No need to run WordPress PHP
14
- - Type-safe interaction with WordPress tables (`posts`, `users`, `terms`, etc.)
14
+ - Type-safe interaction with WordPress tables (`posts`, `users`, `terms`, `comments`, `etc.`)
15
15
  - Utility classes for querying posts, terms, users, comments, and metadata
16
16
  - Supports both Single Site and Multi Site WordPress setups
17
17
  - CLI tools to seed databases and run custom commands
@@ -33,20 +33,11 @@ WP-Node is ideal for scenarios where you need direct access to WordPress databas
33
33
  - **Debugging or inspecting database records** from a modern TypeScript environment
34
34
  - **Creating a web app** (e.g., using Next.js) that needs to pull or push data from a WordPress database, without relying on PHP codebase
35
35
 
36
- ## Limitations
37
-
38
- **WP-Node Core** is designed specifically to interact with the WordPress database. It does not support traditional WordPress features such as:
39
-
40
- - Themes and appearance settings, including updating styling
41
- - WordPress Template rendering or theming APIs
42
- - WordPress plugins
43
-
44
- Its scope is intentionally limited to providing a type-safe, programmatic interface to WordPress data — not replicating the full behavior of the WordPress runtime.
45
-
46
36
  ## Requirements
47
37
 
48
38
  - **Node.js** `>=22.0.0`
49
39
  - **MySQL** or **MariaDB**
40
+ - **nvm**: Make sure you have [`nvm`](https://github.com/nvm-sh/nvm) command installed on your local machine.
50
41
  - Optional: Docker for local WordPress database setup
51
42
 
52
43
  ## Installation
@@ -69,43 +60,131 @@ docker run -d --name wp --network wpnet -p 8080:80 \
69
60
  wordpress
70
61
  ```
71
62
 
72
- ## Configuration
63
+ Visit http://localhost:8080 in your browser to complete the WordPress setup.
64
+
65
+ ### Initialize WP-Node Project
66
+
67
+ To get started, create a new folder for your project. This folder will serve as the root directory for your WP-Node application.
68
+
69
+ ```sh
70
+ mkdir wp-node
71
+ cd wp-node
72
+ ```
73
+
74
+ Then, run the command to initialize the project and follow the prompts:
73
75
 
74
- Create your config:
76
+ ```sh
77
+ npx @rnaga/wp-node-cli -- init
78
+ ```
75
79
 
76
80
  ```sh
77
- npx @rnaga/wp-node-cli -- config
81
+ Enter your database hostname: · localhost
82
+ ✔ Enter your database port: · 33306
83
+ ✔ Enter your database username: · wp
84
+ ✔ Enter your database password: · **
85
+ ✔ Enter your database name: · wordpress
86
+ ✔ Is it a multi-site? · No
87
+ ✔ Enter your static assets path: · public
78
88
  ```
79
89
 
80
- It will prompt for database settings and generate:
90
+ ### Project Structure
91
+
92
+ After initialization, your project will look like this:
81
93
 
82
94
  ```
83
- _wp/
84
- └── config/wp.json
85
- .env
95
+ ./
96
+ ├── _wp
97
+ │   ├── config
98
+ │   │   ├── index.d.ts
99
+ │   │   └── wp.json
100
+ │   └── settings.ts
101
+ ├── .env
102
+ ├── index.ts
103
+ ├── package-lock.json
104
+ ├── package.json
105
+ └── tsconfig.json
86
106
  ```
87
107
 
88
- You can configure additional settings such as:
108
+ **Key files**
89
109
 
90
- - `staticAssetsPath`: Path for media references
91
- - `multisite.enabled`: Enable or disable multisite support
92
- - Custom post types, taxonomies, statuses
110
+ - `_wp/config/wp.json`: Holds configuration for WP-Node such as public path and multisite info. This file is imported by settings.ts.
111
+ - `_wp/settings.ts`: Initializes the WP-Node Context, including config, database access and hooks.
112
+ - `index.ts`: The main entry point for your WP-Node app. A basic sample is provided.
113
+ - `.env`: Stores sensitive environment variables, including your database credentials and other configuration values required at runtime.
93
114
 
94
- ## CLI Usage
115
+ ### Run the App
95
116
 
96
- Install CLI tools:
117
+ Once the config is initialized, run the app using:
97
118
 
98
119
  ```sh
99
- npm i -S @rnaga/wp-node-cli -- -h
120
+ mvn use 22
121
+ npx ts-node ./index.ts
100
122
  ```
101
123
 
102
- Basic command to list posts:
124
+ If everything is working correctly, you’ll see SQL output like:
103
125
 
104
126
  ```sh
105
- npx @rnaga/wp-node-cli -- post list
127
+ select * from `wp_posts` as `posts_5` where `posts_5`.`ID` = 1
128
+ [
129
+ {
130
+ ID: 1,
131
+ post_author: 1,
132
+ post_title: 'Hello world!',
133
+ ...
134
+ }
135
+ ]
106
136
  ```
107
137
 
108
- Develop your own commands using decorators:
138
+ ## CLI
139
+
140
+ WP-Node CLI provides a convenient way to interact with WordPress data without writing any code.
141
+
142
+ To query a post (e.g. ID = 1), run:
143
+
144
+ ```sh
145
+ npx @rnaga/wp-node-cli -- post get 1 -Z table -F ID,post_title,post_type
146
+
147
+ ┌────────────┬────────────────┐
148
+ │ (index) │ Values │
149
+ ├────────────┼────────────────┤
150
+ │ ID │ 1 │
151
+ │ post_title │ 'Hello world!' │
152
+ │ post_type │ 'post' │
153
+ └────────────┴────────────────┘
154
+ ```
155
+
156
+ ### Listing Available Commands
157
+
158
+ To view all available CLI commands, run:
159
+
160
+ ```
161
+ npx @rnaga/wp-node-cli -- -h
162
+ ```
163
+
164
+ output:
165
+
166
+ ```sh
167
+ Usage: <command> <subcommand> [options]
168
+
169
+ Commands:
170
+ blog Blog commands
171
+ comment Comment commands
172
+ config Generate WP config files
173
+ init Initialize WP with Node. (Generate wp.json and install dependencies)
174
+ install Initialize a new blog and create a user
175
+ meta Meta commands (post, comment, blog, term, user, site)
176
+ option Options commands
177
+ post Post commands
178
+ repl Start a REPL
179
+ role Role commands
180
+ site Site commands
181
+ term Term commands
182
+ user User commands
183
+ ```
184
+
185
+ ### Develop your CLI using decorators:
186
+
187
+ **Example**:
109
188
 
110
189
  ```ts
111
190
  @command("page", { description: "Page commands" })
@@ -144,6 +223,16 @@ export class Post {
144
223
  - **Filter**: Modify data in chainable handlers (similar to `apply_filters`)
145
224
  - **Action**: Fire off side effects (`do_action` equivalent)
146
225
 
226
+ ## Limitations
227
+
228
+ **WP-Node Core** is designed specifically to interact with the WordPress database. It does not support traditional WordPress features such as:
229
+
230
+ - Themes and appearance settings, including updating styling
231
+ - WordPress Template rendering or theming APIs
232
+ - WordPress plugins
233
+
234
+ Its scope is intentionally limited to providing a type-safe, programmatic interface to WordPress data — not replicating the full behavior of the WordPress runtime.
235
+
147
236
  ## Contributing
148
237
 
149
238
  Feel free to fork, open issues, or suggest improvements. This project is in active development.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rnaga/wp-node",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build": "rm -rf ./dist && tsc --project tsconfig.build.json && npm run copyfiles && cp package.json ./dist/",
@@ -1,4 +1,5 @@
1
1
  import * as val from "../validators";
2
+ import { z } from "zod";
2
3
  export type Parser = {
3
4
  parse: (v: any, ...args: any) => any;
4
5
  safeParse: (v: any, ...args: any) => any;
@@ -6,4 +7,8 @@ export type Parser = {
6
7
  export type ParserReturnType<T> = T extends Parser ? ReturnType<T["parse"]> : any;
7
8
  export type Tables = keyof typeof val.database.wpTables;
8
9
  export type Field<T extends Tables> = keyof (typeof val.database.wpTables)[T]["shape"];
10
+ export type PickZodObjectKey<T extends z.ZodType<any, any, any>, K extends keyof z.infer<T>> = {
11
+ [P in K]: z.infer<T>[P];
12
+ };
13
+ export type PickZodObjectKeyInArray<T extends z.ZodType<any, any, any>, K extends keyof z.infer<T>[number]> = PickZodObjectKey<T, K>[];
9
14
  //# sourceMappingURL=validating.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validating.d.ts","sourceRoot":"","sources":["../../src/types/validating.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AAErC,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IACrC,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC9C,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtB,GAAG,CAAC;AAER,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;AACxD,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM,IAChC,MAAM,CAAC,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"validating.d.ts","sourceRoot":"","sources":["../../src/types/validating.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;IACrC,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,GAAG,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC9C,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GACtB,GAAG,CAAC;AAER,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;AACxD,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM,IAChC,MAAM,CAAC,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAEnD,MAAM,MAAM,gBAAgB,CAC1B,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAClC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IACxB;KACD,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,uBAAuB,CACjC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAClC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAChC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC"}
@@ -13,4 +13,12 @@ export declare const stringZeroOrOne: z.ZodUnion<[z.ZodDefault<z.ZodOptional<z.Z
13
13
  export declare const stringMetaTable: z.ZodEnum<["post", "comment", "blog", "term", "user", "site"]>;
14
14
  export declare const userRef: z.ZodEffects<z.ZodString, string | number, string>;
15
15
  export declare const blogFlag: z.ZodEnum<["public", "archived", "mature", "spam", "deleted"]>;
16
+ export declare const deepRemoveDefaults: <T>(schema: T) => T;
17
+ export declare const filterRecordByFields: (data: string | Record<string, unknown> | Record<string, unknown>[], fields: string | string[] | undefined) => string | Record<string, any> | Record<string, any>[];
18
+ export declare const recordByField: <T extends z.ZodObject<any, any, any, any, any>>(schema: T, fields: string[]) => z.ZodObject<Record<string, z.ZodTypeAny>, "strip", z.ZodTypeAny, {
19
+ [x: string]: any;
20
+ }, {
21
+ [x: string]: any;
22
+ }>;
23
+ export declare const arrayRecordByField: <T extends z.ZodArray<z.ZodObject<any, any, any, any, any>, any>>(schema: T, fields: string[]) => z.ZodArray<any>;
16
24
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/validators/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACvD,SAAS,EAAE,SAAS,CAAC,EAAE,qEAQxB;AAED,eAAO,MAAM,sBAAsB,GAAI,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,uBAC/B,CAAC;AAEtC,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,uFAQ1C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,GAAG,MAAM,GAAG,OAAO,iIAKlD,CAAC;AAEL,eAAO,MAAM,MAAM,sEAMjB,CAAC;AAEH,eAAO,MAAM,SAAS,4FAGpB,CAAC;AAEH,eAAO,MAAM,SAAS,4FAGpB,CAAC;AAEH,eAAO,MAAM,OAAO,yHAKlB,CAAC;AAEH,eAAO,MAAM,IAAI,2CAKkB,CAAC;AAEpC,eAAO,MAAM,MAAM,qCAAmC,CAAC;AAEvD,eAAO,MAAM,eAAe,0LAQ1B,CAAC;AAEH,eAAO,MAAM,eAAe,gEAO1B,CAAC;AAEH,eAAO,MAAM,OAAO,oDAKlB,CAAC;AAEH,eAAO,MAAM,QAAQ,gEAMnB,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/validators/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACvD,SAAS,EAAE,SAAS,CAAC,EAAE,qEAQxB;AAED,eAAO,MAAM,sBAAsB,GAAI,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,uBAC/B,CAAC;AAEtC,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,uFAQ1C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,GAAG,MAAM,GAAG,OAAO,iIAKlD,CAAC;AAEL,eAAO,MAAM,MAAM,sEAMjB,CAAC;AAEH,eAAO,MAAM,SAAS,4FAGpB,CAAC;AAEH,eAAO,MAAM,SAAS,4FAGpB,CAAC;AAEH,eAAO,MAAM,OAAO,yHAKlB,CAAC;AAEH,eAAO,MAAM,IAAI,2CAKkB,CAAC;AAEpC,eAAO,MAAM,MAAM,qCAAmC,CAAC;AAEvD,eAAO,MAAM,eAAe,0LAQ1B,CAAC;AAEH,eAAO,MAAM,eAAe,gEAO1B,CAAC;AAEH,eAAO,MAAM,OAAO,oDAKlB,CAAC;AAEH,eAAO,MAAM,QAAQ,gEAMnB,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAAE,QAAQ,CAAC,KAAG,CAgCjD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClE,QAAQ,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,yDAmDtC,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC1E,QAAQ,CAAC,EACT,QAAQ,MAAM,EAAE;;;;EAWjB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,EAE/D,QAAQ,CAAC,EACT,QAAQ,MAAM,EAAE,KACf,CAAC,CAAC,QAAQ,CAAC,GAAG,CAKhB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.blogFlag = exports.userRef = exports.stringMetaTable = exports.stringZeroOrOne = exports.string = exports.path = exports.boolean = exports.stringArr = exports.numberArr = exports.number = exports.booleanWithDefault = exports.numberWithDefault = exports.undefinedIfEmptyString = void 0;
3
+ exports.arrayRecordByField = exports.recordByField = exports.filterRecordByFields = exports.deepRemoveDefaults = exports.blogFlag = exports.userRef = exports.stringMetaTable = exports.stringZeroOrOne = exports.string = exports.path = exports.boolean = exports.stringArr = exports.numberArr = exports.number = exports.booleanWithDefault = exports.numberWithDefault = exports.undefinedIfEmptyString = void 0;
4
4
  exports.unionOfLiterals = unionOfLiterals;
5
5
  const zod_1 = require("zod");
6
6
  // https://github.com/colinhacks/zod/discussions/2790
@@ -84,3 +84,84 @@ exports.blogFlag = zod_1.z.enum([
84
84
  "spam",
85
85
  "deleted",
86
86
  ]);
87
+ const deepRemoveDefaults = (schema) => {
88
+ if (schema instanceof zod_1.z.ZodDefault)
89
+ return (0, exports.deepRemoveDefaults)(schema.removeDefault());
90
+ if (schema instanceof zod_1.z.ZodObject) {
91
+ const newShape = {};
92
+ for (const key in schema.shape) {
93
+ const fieldSchema = schema.shape[key];
94
+ newShape[key] = zod_1.z.ZodOptional.create((0, exports.deepRemoveDefaults)(fieldSchema));
95
+ }
96
+ return new zod_1.z.ZodObject({
97
+ ...schema._def,
98
+ shape: () => newShape,
99
+ });
100
+ }
101
+ if (schema instanceof zod_1.z.ZodArray)
102
+ return zod_1.z.ZodArray.create((0, exports.deepRemoveDefaults)(schema.element));
103
+ if (schema instanceof zod_1.z.ZodOptional)
104
+ return zod_1.z.ZodOptional.create((0, exports.deepRemoveDefaults)(schema.unwrap()));
105
+ if (schema instanceof zod_1.z.ZodNullable)
106
+ return zod_1.z.ZodNullable.create((0, exports.deepRemoveDefaults)(schema.unwrap()));
107
+ if (schema instanceof zod_1.z.ZodTuple)
108
+ return zod_1.z.ZodTuple.create(schema.items.map((item) => (0, exports.deepRemoveDefaults)(item)));
109
+ return schema;
110
+ };
111
+ exports.deepRemoveDefaults = deepRemoveDefaults;
112
+ const filterRecordByFields = (data, fields) => {
113
+ // If data is a string, return it as is
114
+ if (typeof data === "string") {
115
+ return data;
116
+ }
117
+ const fieldsSchema = zod_1.z
118
+ .string()
119
+ .optional()
120
+ .transform((val) => val
121
+ ? val
122
+ .split(",")
123
+ .map((field) => field.trim())
124
+ .filter((field) => field.length > 0)
125
+ : []);
126
+ const parsedFields = fieldsSchema.parse(Array.isArray(fields) ? fields.join(",") : fields);
127
+ // If fields are specified, filter the result data
128
+ if (0 == parsedFields.length) {
129
+ return data;
130
+ }
131
+ const filterObject = (record) => {
132
+ const filteredRecord = {};
133
+ parsedFields.forEach((field) => {
134
+ if (field in record) {
135
+ filteredRecord[field] = record[field];
136
+ }
137
+ });
138
+ return filteredRecord;
139
+ };
140
+ // If data is an object, filter it
141
+ if (typeof data === "object" && !Array.isArray(data)) {
142
+ return filterObject(data);
143
+ }
144
+ // If data is an array, filter each item
145
+ if (Array.isArray(data)) {
146
+ return data.map((item) => filterObject(item));
147
+ }
148
+ return data;
149
+ };
150
+ exports.filterRecordByFields = filterRecordByFields;
151
+ const recordByField = (schema, fields) => {
152
+ // Filter the schema to only include the specified fields
153
+ const filteredShape = {};
154
+ for (const field of fields) {
155
+ if (field in schema.shape) {
156
+ filteredShape[field] = schema.shape[field];
157
+ }
158
+ }
159
+ return zod_1.z.object(filteredShape);
160
+ };
161
+ exports.recordByField = recordByField;
162
+ const arrayRecordByField = (schema, fields) => {
163
+ // Filter the schema to only include the specified fields
164
+ const filteredShape = (0, exports.recordByField)(schema.element, fields);
165
+ return zod_1.z.array(filteredShape);
166
+ };
167
+ exports.arrayRecordByField = arrayRecordByField;