@dbp-wp/core 0.1.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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @dbp-wp/core
2
+
3
+ Core library for [DBP WP](https://github.com/takashi-matsuyama/dbp_wp): a WordPress REST
4
+ client, a safe formula engine, a CSV/JSON importer, and typesetting data generation.
5
+
6
+ This package is the Node layer of DBP WP. Most people will want the `dbp-wp` CLI, which
7
+ runs the full app via `npx dbp-wp`. This library is published separately so it can also be
8
+ used on its own and by the browser demo.
9
+
10
+ ```sh
11
+ npm install @dbp-wp/core
12
+ ```
13
+
14
+ ```ts
15
+ import { WpClient } from '@dbp-wp/core';
16
+
17
+ const client = new WpClient({
18
+ siteUrl: 'https://example.com',
19
+ username: 'editor',
20
+ applicationPassword: 'xxxx xxxx xxxx xxxx xxxx xxxx',
21
+ });
22
+
23
+ const posts = await client.listPosts({ perPage: 20 });
24
+ ```
25
+
26
+ Requires Node.js >= 20.
27
+
28
+ ## License
29
+
30
+ Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Connection credentials for a WordPress site.
3
+ *
4
+ * DBP WP authenticates with WordPress 5.6+ Application Passwords, which are sent as the
5
+ * password of an HTTP Basic `Authorization` header.
6
+ */
7
+ interface WpCredentials {
8
+ /** Base URL of the WordPress site, e.g. `https://example.com`. */
9
+ siteUrl: string;
10
+ /** WordPress username. */
11
+ username: string;
12
+ /** Application Password issued by WordPress (used as the Basic-auth password). */
13
+ applicationPassword: string;
14
+ }
15
+ /**
16
+ * Raw post shape as returned by the WordPress REST API (`/wp/v2/<type>`).
17
+ *
18
+ * `title` is an object; `raw` is only present in `context=edit`. `menu_order` is
19
+ * snake_case. Use {@link WpPost} for the normalized internal model.
20
+ */
21
+ interface WpPostResponse {
22
+ id: number;
23
+ type: string;
24
+ status: string;
25
+ title: {
26
+ rendered: string;
27
+ raw?: string;
28
+ };
29
+ menu_order: number;
30
+ meta: Record<string, unknown>;
31
+ /**
32
+ * Arbitrary post meta exposed by the companion plugin's `dbp_wp_meta` field.
33
+ * Present only when the connector is active; absent in restricted mode.
34
+ */
35
+ dbp_wp_meta?: Record<string, unknown>;
36
+ }
37
+ /** Normalized, internal post model used by DBP WP. */
38
+ interface WpPost {
39
+ id: number;
40
+ type: string;
41
+ status: string;
42
+ /** Editable title (the `raw` value when available, else `rendered`). */
43
+ title: string;
44
+ menuOrder: number;
45
+ /**
46
+ * Core REST post meta (only keys registered with `show_in_rest`). For arbitrary
47
+ * meta exposed by the companion plugin, see {@link WpPost.dbpWpMeta}.
48
+ */
49
+ meta: Record<string, unknown>;
50
+ /**
51
+ * Arbitrary post meta from the companion plugin (all keys, single value each).
52
+ * Present only when the connector returned `dbp_wp_meta`; `undefined` in restricted
53
+ * mode (no connector installed).
54
+ */
55
+ dbpWpMeta?: Record<string, unknown>;
56
+ }
57
+ /**
58
+ * Editable standard post fields. These map to core WordPress REST fields and need no
59
+ * companion plugin. Arbitrary meta editing is handled separately (companion plugin).
60
+ */
61
+ interface UpdatePostFields {
62
+ /** Editable title (sent as the post `title`). */
63
+ title?: string;
64
+ /** Ordering value (sent as `menu_order`). */
65
+ menuOrder?: number;
66
+ /** Post status (e.g. `publish`, `draft`). */
67
+ status?: string;
68
+ }
69
+ /** A WordPress post type available for listing/editing over REST. */
70
+ interface WpPostType {
71
+ /** Internal type slug (e.g. `post`). */
72
+ slug: string;
73
+ /** REST route base used to list/update items of this type (e.g. `posts`). */
74
+ restBase: string;
75
+ /** Human-readable name (e.g. `Posts`). */
76
+ name: string;
77
+ }
78
+ /** Result of a per-post meta delete via the companion plugin. */
79
+ interface DeleteMetaResult {
80
+ /** The post the keys were deleted from. */
81
+ postId: number;
82
+ /** Keys actually deleted (a key not present on the post is omitted). */
83
+ deleted: string[];
84
+ }
85
+ /** Parameters for listing posts. */
86
+ interface ListPostsParams {
87
+ /** REST post type slug (e.g. `posts`, `pages`). Defaults to `posts`. */
88
+ type?: string;
89
+ /** Page size (WordPress caps this at 100). Defaults to 100. */
90
+ perPage?: number;
91
+ /** 1-based page number. Defaults to 1. */
92
+ page?: number;
93
+ }
94
+
95
+ /**
96
+ * Validate and normalize a WordPress site URL into a REST base (origin + base path).
97
+ *
98
+ * Requires https, except plain http is allowed for local development hosts. Rejects
99
+ * embedded credentials, query strings, and fragments, and strips trailing slashes — so
100
+ * an Application Password is never sent over cleartext to an unexpected target.
101
+ */
102
+ declare function normalizeSiteUrl(siteUrl: string): string;
103
+ /**
104
+ * Build the HTTP Basic `Authorization` header value from Application Password
105
+ * credentials. WordPress treats the Application Password as the Basic-auth password.
106
+ */
107
+ declare function buildAuthHeader(credentials: WpCredentials): string;
108
+ /** Error thrown when the WordPress REST API returns a non-2xx response. */
109
+ declare class WpRequestError extends Error {
110
+ readonly status: number;
111
+ readonly path: string;
112
+ constructor(status: number, path: string, message: string);
113
+ }
114
+ /**
115
+ * Minimal WordPress REST client.
116
+ *
117
+ * Runs in the Node process (CLI shell), never in the browser, so Application Password
118
+ * credentials stay server-side. MVP scope: list posts, read/write post meta.
119
+ */
120
+ declare class WpClient {
121
+ private readonly credentials;
122
+ private readonly restBase;
123
+ constructor(credentials: WpCredentials);
124
+ private request;
125
+ /**
126
+ * List the REST-enabled post types on the site (edit context), so the app can offer
127
+ * a type selector. Returns each type's REST route base and display name.
128
+ */
129
+ listPostTypes(): Promise<WpPostType[]>;
130
+ /** List posts of a given type in edit context (raw fields, for editing). */
131
+ listPosts(params?: ListPostsParams): Promise<WpPost[]>;
132
+ /**
133
+ * Update post fields in a single request. Standard fields (title, menu_order,
134
+ * status) are core REST fields and need no plugin. When `meta` is supplied it rides
135
+ * the same request through the companion plugin's `dbp_wp_meta` field (ignored by
136
+ * WordPress without the connector). Pass the REST route slug as `type` (e.g.
137
+ * `posts`, `pages`) — not the object type returned on a post.
138
+ */
139
+ updatePost(id: number, fields: UpdatePostFields, type?: string, meta?: Record<string, unknown>): Promise<WpPost>;
140
+ /**
141
+ * Create a new post in a single request, symmetric to {@link WpClient.updatePost}.
142
+ * Standard fields (title, menu_order, status) are core REST fields; when `meta` is
143
+ * supplied it rides the same request through the companion plugin's `dbp_wp_meta`
144
+ * field. Pass the REST route slug as `type` (e.g. `posts`, `pages`).
145
+ */
146
+ createPost(fields: UpdatePostFields, type?: string, meta?: Record<string, unknown>): Promise<WpPost>;
147
+ /**
148
+ * Update only arbitrary post meta through the companion plugin's `dbp_wp_meta`
149
+ * field. A thin wrapper over {@link WpClient.updatePost} with no standard fields.
150
+ * Requires the connector; the connector writes scalar values only.
151
+ */
152
+ updatePostMeta(id: number, meta: Record<string, unknown>, type?: string): Promise<WpPost>;
153
+ /**
154
+ * Delete named meta keys from a single post via the companion plugin's
155
+ * `DELETE /dbp-wp/v1/posts/<id>/meta` route. This route is keyed by id only (the
156
+ * post type is irrelevant). Requires the connector.
157
+ */
158
+ deletePostMeta(id: number, keys: string[]): Promise<DeleteMetaResult>;
159
+ /**
160
+ * Detect whether the companion plugin is active by checking the REST index
161
+ * (`/wp-json/`) for the connector's namespace. Throws on a failed request; a caller
162
+ * that wants a non-fatal probe should treat a thrown error as "not available".
163
+ */
164
+ detectConnector(): Promise<boolean>;
165
+ }
166
+
167
+ /**
168
+ * Formula engine: evaluates a spreadsheet expression against named numeric cells.
169
+ *
170
+ * Implementations MUST NOT use `eval`, `Function`, or any other dynamic code execution.
171
+ */
172
+ interface FormulaEngine {
173
+ /**
174
+ * Evaluate a single expression against a map of cell references to numbers.
175
+ * Throws on invalid syntax, unknown variables, or a non-numeric result.
176
+ */
177
+ evaluate(expression: string, context: Record<string, number>): number;
178
+ }
179
+ /**
180
+ * Formula engine backed by expr-eval-fork, which parses to an AST and evaluates without
181
+ * `eval`/`Function`. Member access, assignment, and function definitions are disabled,
182
+ * the nondeterministic `random()` function is removed, and results are constrained to
183
+ * finite numbers, so expressions stay pure, deterministic, and side-effect free.
184
+ */
185
+ declare class SafeFormulaEngine implements FormulaEngine {
186
+ private readonly parser;
187
+ constructor();
188
+ evaluate(expression: string, context: Record<string, number>): number;
189
+ }
190
+
191
+ /**
192
+ * A tabular view of an import file: a header row plus data rows. Both CSV and JSON
193
+ * sources are normalized to this shape so the column-mapping logic is source-agnostic.
194
+ * Each data row is aligned to `headers` by index; a short row has missing trailing cells.
195
+ */
196
+ interface ParsedTable {
197
+ /** Column headers (CSV first row, or the union of JSON object keys). */
198
+ headers: string[];
199
+ /** Data rows; `rows[r][c]` is the cell under `headers[c]`. */
200
+ rows: string[][];
201
+ }
202
+ /**
203
+ * Where a file column is imported to. `skip` drops the column; `title`/`status`/
204
+ * `menuOrder` map to standard post fields; `meta` writes an arbitrary post-meta key
205
+ * (companion plugin required).
206
+ */
207
+ type ImportTarget = {
208
+ kind: 'skip';
209
+ } | {
210
+ kind: 'title';
211
+ } | {
212
+ kind: 'status';
213
+ } | {
214
+ kind: 'menuOrder';
215
+ } | {
216
+ kind: 'meta';
217
+ key: string;
218
+ };
219
+ /** A single new post to create, derived from one import row. */
220
+ interface ImportCreate {
221
+ /** Standard fields (title / menuOrder / status). */
222
+ fields: UpdatePostFields;
223
+ /** Arbitrary meta to write via the companion plugin (omitted when none). */
224
+ meta?: Record<string, unknown>;
225
+ }
226
+ /**
227
+ * Parse CSV text into a table, taking the first record as headers. Implements the
228
+ * RFC 4180 essentials: double-quoted fields, embedded commas/newlines, `""` escapes,
229
+ * and CRLF or LF line endings. A trailing newline does not produce an empty record.
230
+ */
231
+ declare function parseCsv(text: string): ParsedTable;
232
+ /**
233
+ * Parse JSON text (an array of objects) into a table. Headers are the union of all
234
+ * object keys in first-seen order. Object/array cell values are JSON-stringified;
235
+ * null and undefined become an empty string. Throws if the JSON is not an array.
236
+ */
237
+ declare function parseJsonRecords(text: string): ParsedTable;
238
+ /**
239
+ * Normalize a status cell to a WordPress status. Known labels/values (case-insensitive,
240
+ * e.g. `Published` → `publish`) are mapped; anything else passes through trimmed so the
241
+ * WordPress REST API can validate it and surface a per-row error if invalid.
242
+ */
243
+ declare function normalizeStatus(value: string): string;
244
+ /**
245
+ * Apply a column mapping to a parsed table, producing one {@link ImportCreate} per row.
246
+ * Empty cells contribute nothing; a row that maps to no fields and no meta is skipped.
247
+ * Non-integer `menuOrder` cells are ignored. Meta is stored on a null-prototype object
248
+ * so a header named `__proto__` is kept as data, never touching any prototype.
249
+ */
250
+ declare function buildImportPlan(table: ParsedTable, mapping: ImportTarget[]): ImportCreate[];
251
+
252
+ export { type DeleteMetaResult, type FormulaEngine, type ImportCreate, type ImportTarget, type ListPostsParams, type ParsedTable, SafeFormulaEngine, type UpdatePostFields, WpClient, type WpCredentials, type WpPost, type WpPostResponse, type WpPostType, WpRequestError, buildAuthHeader, buildImportPlan, normalizeSiteUrl, normalizeStatus, parseCsv, parseJsonRecords };
package/dist/index.js ADDED
@@ -0,0 +1,454 @@
1
+ // src/wp-client.ts
2
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
3
+ var ROUTE_SEGMENT = /^[a-z0-9_-]+$/i;
4
+ var META_FIELD = "dbp_wp_meta";
5
+ var CONNECTOR_NAMESPACE = "dbp-wp/v1";
6
+ function normalizeSiteUrl(siteUrl) {
7
+ let url;
8
+ try {
9
+ url = new URL(siteUrl);
10
+ } catch {
11
+ throw new Error(`Invalid site URL: ${siteUrl}`);
12
+ }
13
+ const isLocal = LOCAL_HOSTS.has(url.hostname);
14
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocal)) {
15
+ throw new Error(`Site URL must use https (http is allowed only for local hosts): ${siteUrl}`);
16
+ }
17
+ if (url.username !== "" || url.password !== "") {
18
+ throw new Error("Site URL must not contain embedded credentials.");
19
+ }
20
+ if (url.search !== "" || url.hash !== "") {
21
+ throw new Error("Site URL must not contain a query string or fragment.");
22
+ }
23
+ return `${url.origin}${url.pathname}`.replace(/\/+$/, "");
24
+ }
25
+ function buildAuthHeader(credentials) {
26
+ if (credentials.username.includes(":")) {
27
+ throw new Error('Username must not contain a colon (":") for HTTP Basic authentication.');
28
+ }
29
+ const token = `${credentials.username}:${credentials.applicationPassword}`;
30
+ const base64 = Buffer.from(token, "utf-8").toString("base64");
31
+ return `Basic ${base64}`;
32
+ }
33
+ var WpRequestError = class extends Error {
34
+ constructor(status, path, message) {
35
+ super(message);
36
+ this.status = status;
37
+ this.path = path;
38
+ this.name = "WpRequestError";
39
+ }
40
+ status;
41
+ path;
42
+ };
43
+ var WpClient = class {
44
+ constructor(credentials) {
45
+ this.credentials = credentials;
46
+ this.restBase = normalizeSiteUrl(credentials.siteUrl);
47
+ }
48
+ credentials;
49
+ restBase;
50
+ async request(path, init = {}) {
51
+ const response = await fetch(`${this.restBase}/wp-json${path}`, {
52
+ ...init,
53
+ headers: {
54
+ Authorization: buildAuthHeader(this.credentials),
55
+ // Only declare a JSON body when one is actually sent (GETs carry none).
56
+ ...init.body !== void 0 ? { "Content-Type": "application/json" } : {},
57
+ ...init.headers
58
+ }
59
+ });
60
+ if (!response.ok) {
61
+ throw new WpRequestError(
62
+ response.status,
63
+ path,
64
+ `WordPress REST request failed: ${response.status} ${response.statusText}`
65
+ );
66
+ }
67
+ return await response.json();
68
+ }
69
+ /**
70
+ * List the REST-enabled post types on the site (edit context), so the app can offer
71
+ * a type selector. Returns each type's REST route base and display name.
72
+ */
73
+ async listPostTypes() {
74
+ const raw = await this.request("/wp/v2/types?context=edit");
75
+ return normalizePostTypes(raw);
76
+ }
77
+ /** List posts of a given type in edit context (raw fields, for editing). */
78
+ async listPosts(params = {}) {
79
+ const type = params.type ?? "posts";
80
+ assertRouteSegment(type);
81
+ const perPage = clampInt(params.perPage ?? 100, 1, 100);
82
+ const page = clampInt(params.page ?? 1, 1, Number.MAX_SAFE_INTEGER);
83
+ const query = new URLSearchParams({
84
+ context: "edit",
85
+ per_page: String(perPage),
86
+ page: String(page)
87
+ });
88
+ const raw = await this.request(`/wp/v2/${type}?${query.toString()}`);
89
+ return raw.map(normalizePost);
90
+ }
91
+ /**
92
+ * Update post fields in a single request. Standard fields (title, menu_order,
93
+ * status) are core REST fields and need no plugin. When `meta` is supplied it rides
94
+ * the same request through the companion plugin's `dbp_wp_meta` field (ignored by
95
+ * WordPress without the connector). Pass the REST route slug as `type` (e.g.
96
+ * `posts`, `pages`) — not the object type returned on a post.
97
+ */
98
+ async updatePost(id, fields, type = "posts", meta) {
99
+ assertPostId(id);
100
+ assertRouteSegment(type);
101
+ const raw = await this.request(`/wp/v2/${type}/${String(id)}?context=edit`, {
102
+ method: "POST",
103
+ body: JSON.stringify(buildPostBody(fields, meta))
104
+ });
105
+ return normalizePost(raw);
106
+ }
107
+ /**
108
+ * Create a new post in a single request, symmetric to {@link WpClient.updatePost}.
109
+ * Standard fields (title, menu_order, status) are core REST fields; when `meta` is
110
+ * supplied it rides the same request through the companion plugin's `dbp_wp_meta`
111
+ * field. Pass the REST route slug as `type` (e.g. `posts`, `pages`).
112
+ */
113
+ async createPost(fields, type = "posts", meta) {
114
+ assertRouteSegment(type);
115
+ const raw = await this.request(`/wp/v2/${type}?context=edit`, {
116
+ method: "POST",
117
+ body: JSON.stringify(buildPostBody(fields, meta))
118
+ });
119
+ return normalizePost(raw);
120
+ }
121
+ /**
122
+ * Update only arbitrary post meta through the companion plugin's `dbp_wp_meta`
123
+ * field. A thin wrapper over {@link WpClient.updatePost} with no standard fields.
124
+ * Requires the connector; the connector writes scalar values only.
125
+ */
126
+ async updatePostMeta(id, meta, type = "posts") {
127
+ return this.updatePost(id, {}, type, meta);
128
+ }
129
+ /**
130
+ * Delete named meta keys from a single post via the companion plugin's
131
+ * `DELETE /dbp-wp/v1/posts/<id>/meta` route. This route is keyed by id only (the
132
+ * post type is irrelevant). Requires the connector.
133
+ */
134
+ async deletePostMeta(id, keys) {
135
+ assertPostId(id);
136
+ const cleanKeys = sanitizeMetaKeys(keys);
137
+ const raw = await this.request(
138
+ `/${CONNECTOR_NAMESPACE}/posts/${String(id)}/meta`,
139
+ { method: "DELETE", body: JSON.stringify({ keys: cleanKeys }) }
140
+ );
141
+ return {
142
+ postId: typeof raw.post_id === "number" ? raw.post_id : id,
143
+ deleted: Array.isArray(raw.deleted) ? raw.deleted : []
144
+ };
145
+ }
146
+ /**
147
+ * Detect whether the companion plugin is active by checking the REST index
148
+ * (`/wp-json/`) for the connector's namespace. Throws on a failed request; a caller
149
+ * that wants a non-fatal probe should treat a thrown error as "not available".
150
+ */
151
+ async detectConnector() {
152
+ const index = await this.request("/");
153
+ return hasConnectorNamespace(index.namespaces);
154
+ }
155
+ };
156
+ function buildUpdateBody(fields) {
157
+ const body = {};
158
+ if (fields.title !== void 0) {
159
+ body.title = fields.title;
160
+ }
161
+ if (fields.menuOrder !== void 0) {
162
+ body.menu_order = fields.menuOrder;
163
+ }
164
+ if (fields.status !== void 0) {
165
+ body.status = fields.status;
166
+ }
167
+ return body;
168
+ }
169
+ function assertRouteSegment(segment) {
170
+ if (!ROUTE_SEGMENT.test(segment)) {
171
+ throw new Error(`Invalid REST route segment: ${segment}`);
172
+ }
173
+ }
174
+ function assertPostId(id) {
175
+ if (!Number.isSafeInteger(id) || id <= 0) {
176
+ throw new Error(`Invalid post id: ${id}`);
177
+ }
178
+ }
179
+ function clampInt(value, min, max) {
180
+ if (!Number.isFinite(value)) {
181
+ return min;
182
+ }
183
+ return Math.min(max, Math.max(min, Math.trunc(value)));
184
+ }
185
+ function buildMetaBody(meta) {
186
+ return { [META_FIELD]: meta };
187
+ }
188
+ function buildPostBody(fields, meta) {
189
+ const body = buildUpdateBody(fields);
190
+ if (meta !== void 0) {
191
+ Object.assign(body, buildMetaBody(meta));
192
+ }
193
+ return body;
194
+ }
195
+ function sanitizeMetaKeys(keys) {
196
+ if (!Array.isArray(keys)) {
197
+ throw new Error("Meta keys must be an array of strings.");
198
+ }
199
+ const clean = keys.filter((key) => typeof key === "string" && key.length > 0);
200
+ if (clean.length === 0) {
201
+ throw new Error("At least one non-empty meta key is required.");
202
+ }
203
+ return clean;
204
+ }
205
+ function hasConnectorNamespace(namespaces) {
206
+ return Array.isArray(namespaces) && namespaces.includes(CONNECTOR_NAMESPACE);
207
+ }
208
+ function normalizePostTypes(raw) {
209
+ if (typeof raw !== "object" || raw === null) {
210
+ return [];
211
+ }
212
+ const result = [];
213
+ for (const [key, value] of Object.entries(raw)) {
214
+ if (typeof value !== "object" || value === null) {
215
+ continue;
216
+ }
217
+ const entry = value;
218
+ if (typeof entry.rest_base !== "string" || !ROUTE_SEGMENT.test(entry.rest_base)) {
219
+ continue;
220
+ }
221
+ result.push({
222
+ slug: typeof entry.slug === "string" ? entry.slug : key,
223
+ restBase: entry.rest_base,
224
+ name: typeof entry.name === "string" ? entry.name : key
225
+ });
226
+ }
227
+ return result;
228
+ }
229
+ function normalizePost(raw) {
230
+ const post = {
231
+ id: raw.id,
232
+ type: raw.type,
233
+ status: raw.status,
234
+ title: raw.title.raw ?? raw.title.rendered,
235
+ menuOrder: raw.menu_order,
236
+ meta: raw.meta
237
+ };
238
+ if (raw.dbp_wp_meta !== void 0) {
239
+ post.dbpWpMeta = raw.dbp_wp_meta;
240
+ }
241
+ return post;
242
+ }
243
+
244
+ // src/calc/index.ts
245
+ import { Parser } from "expr-eval-fork";
246
+ var SafeFormulaEngine = class {
247
+ parser;
248
+ constructor() {
249
+ this.parser = new Parser({
250
+ allowMemberAccess: false,
251
+ operators: { assignment: false, fndef: false }
252
+ });
253
+ delete this.parser.functions.random;
254
+ }
255
+ evaluate(expression, context) {
256
+ let parsed;
257
+ try {
258
+ parsed = this.parser.parse(expression);
259
+ } catch (e) {
260
+ throw new Error(`Invalid formula: ${e instanceof Error ? e.message : "parse error"}`);
261
+ }
262
+ let result;
263
+ try {
264
+ result = parsed.evaluate(context);
265
+ } catch (e) {
266
+ throw new Error(`Formula evaluation failed: ${e instanceof Error ? e.message : "error"}`);
267
+ }
268
+ if (typeof result !== "number" || !Number.isFinite(result)) {
269
+ throw new Error("Formula must evaluate to a finite number.");
270
+ }
271
+ return result;
272
+ }
273
+ };
274
+
275
+ // src/importer.ts
276
+ var MENU_ORDER_MIN = -2147483648;
277
+ var MENU_ORDER_MAX = 2147483647;
278
+ function parseCsv(text) {
279
+ const records = parseCsvRecords(text);
280
+ const headers = records[0] ?? [];
281
+ return { headers, rows: records.slice(1) };
282
+ }
283
+ function parseCsvRecords(text) {
284
+ const records = [];
285
+ let record = [];
286
+ let field = "";
287
+ let inQuotes = false;
288
+ let i = 0;
289
+ const endField = () => {
290
+ record.push(field);
291
+ field = "";
292
+ };
293
+ const endRecord = () => {
294
+ endField();
295
+ records.push(record);
296
+ record = [];
297
+ };
298
+ while (i < text.length) {
299
+ const ch = text[i];
300
+ if (inQuotes) {
301
+ if (ch === '"') {
302
+ if (text[i + 1] === '"') {
303
+ field += '"';
304
+ i += 2;
305
+ continue;
306
+ }
307
+ inQuotes = false;
308
+ i += 1;
309
+ continue;
310
+ }
311
+ field += ch;
312
+ i += 1;
313
+ continue;
314
+ }
315
+ if (ch === '"') {
316
+ inQuotes = true;
317
+ i += 1;
318
+ continue;
319
+ }
320
+ if (ch === ",") {
321
+ endField();
322
+ i += 1;
323
+ continue;
324
+ }
325
+ if (ch === "\r") {
326
+ endRecord();
327
+ i += text[i + 1] === "\n" ? 2 : 1;
328
+ continue;
329
+ }
330
+ if (ch === "\n") {
331
+ endRecord();
332
+ i += 1;
333
+ continue;
334
+ }
335
+ field += ch;
336
+ i += 1;
337
+ }
338
+ if (inQuotes) {
339
+ throw new Error("Malformed CSV: unterminated quoted field.");
340
+ }
341
+ if (field !== "" || record.length > 0) {
342
+ endRecord();
343
+ }
344
+ return records;
345
+ }
346
+ function parseJsonRecords(text) {
347
+ const data = JSON.parse(text);
348
+ if (!Array.isArray(data)) {
349
+ throw new Error("JSON import must be an array of objects.");
350
+ }
351
+ const headers = [];
352
+ const seen = /* @__PURE__ */ new Set();
353
+ for (const entry of data) {
354
+ if (isPlainRecord(entry)) {
355
+ for (const key of Object.keys(entry)) {
356
+ if (!seen.has(key)) {
357
+ seen.add(key);
358
+ headers.push(key);
359
+ }
360
+ }
361
+ }
362
+ }
363
+ const rows = data.map((entry) => {
364
+ const record = isPlainRecord(entry) ? entry : {};
365
+ return headers.map((header) => stringifyCell(record[header]));
366
+ });
367
+ return { headers, rows };
368
+ }
369
+ function isPlainRecord(value) {
370
+ return typeof value === "object" && value !== null && !Array.isArray(value);
371
+ }
372
+ function stringifyCell(value) {
373
+ if (value === null || value === void 0) {
374
+ return "";
375
+ }
376
+ if (typeof value === "object") {
377
+ return JSON.stringify(value);
378
+ }
379
+ return String(value);
380
+ }
381
+ var STATUS_LABELS = Object.assign(
382
+ /* @__PURE__ */ Object.create(null),
383
+ {
384
+ publish: "publish",
385
+ published: "publish",
386
+ draft: "draft",
387
+ pending: "pending",
388
+ private: "private",
389
+ future: "future"
390
+ }
391
+ );
392
+ function normalizeStatus(value) {
393
+ const trimmed = value.trim();
394
+ return STATUS_LABELS[trimmed.toLowerCase()] ?? trimmed;
395
+ }
396
+ function buildImportPlan(table, mapping) {
397
+ const creates = [];
398
+ for (const row of table.rows) {
399
+ const fields = {};
400
+ let meta;
401
+ for (let col = 0; col < mapping.length; col += 1) {
402
+ const target = mapping[col];
403
+ if (!target || target.kind === "skip") {
404
+ continue;
405
+ }
406
+ const value = row[col] ?? "";
407
+ if (value === "") {
408
+ continue;
409
+ }
410
+ switch (target.kind) {
411
+ case "title":
412
+ fields.title = value;
413
+ break;
414
+ case "status":
415
+ fields.status = normalizeStatus(value);
416
+ break;
417
+ case "menuOrder": {
418
+ const order = Number(value);
419
+ if (Number.isInteger(order) && order >= MENU_ORDER_MIN && order <= MENU_ORDER_MAX) {
420
+ fields.menuOrder = order;
421
+ }
422
+ break;
423
+ }
424
+ case "meta":
425
+ if (target.key.trim() === "") {
426
+ break;
427
+ }
428
+ if (!meta) {
429
+ meta = /* @__PURE__ */ Object.create(null);
430
+ }
431
+ meta[target.key] = value;
432
+ break;
433
+ }
434
+ }
435
+ if (meta !== void 0 && Object.keys(meta).length > 0) {
436
+ creates.push({ fields, meta });
437
+ } else if (Object.keys(fields).length > 0) {
438
+ creates.push({ fields });
439
+ }
440
+ }
441
+ return creates;
442
+ }
443
+ export {
444
+ SafeFormulaEngine,
445
+ WpClient,
446
+ WpRequestError,
447
+ buildAuthHeader,
448
+ buildImportPlan,
449
+ normalizeSiteUrl,
450
+ normalizeStatus,
451
+ parseCsv,
452
+ parseJsonRecords
453
+ };
454
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wp-client.ts","../src/calc/index.ts","../src/importer.ts"],"sourcesContent":["import type {\n DeleteMetaResult,\n ListPostsParams,\n UpdatePostFields,\n WpCredentials,\n WpPost,\n WpPostResponse,\n WpPostType,\n} from './types';\n\n/** Hosts for which plain http is tolerated (local development). */\nconst LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n\n/** Allowed characters for a REST route segment (post type slug). No dots: a `.`/`..`\n * segment would be resolved by the URL parser and traverse the REST path. */\nconst ROUTE_SEGMENT = /^[a-z0-9_-]+$/i;\n\n/** REST field added by the companion plugin to carry arbitrary post meta. */\nconst META_FIELD = 'dbp_wp_meta';\n\n/** REST namespace registered by the companion plugin. */\nconst CONNECTOR_NAMESPACE = 'dbp-wp/v1';\n\n/**\n * Validate and normalize a WordPress site URL into a REST base (origin + base path).\n *\n * Requires https, except plain http is allowed for local development hosts. Rejects\n * embedded credentials, query strings, and fragments, and strips trailing slashes — so\n * an Application Password is never sent over cleartext to an unexpected target.\n */\nexport function normalizeSiteUrl(siteUrl: string): string {\n let url: URL;\n try {\n url = new URL(siteUrl);\n } catch {\n throw new Error(`Invalid site URL: ${siteUrl}`);\n }\n\n const isLocal = LOCAL_HOSTS.has(url.hostname);\n if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLocal)) {\n throw new Error(`Site URL must use https (http is allowed only for local hosts): ${siteUrl}`);\n }\n if (url.username !== '' || url.password !== '') {\n throw new Error('Site URL must not contain embedded credentials.');\n }\n if (url.search !== '' || url.hash !== '') {\n throw new Error('Site URL must not contain a query string or fragment.');\n }\n\n return `${url.origin}${url.pathname}`.replace(/\\/+$/, '');\n}\n\n/**\n * Build the HTTP Basic `Authorization` header value from Application Password\n * credentials. WordPress treats the Application Password as the Basic-auth password.\n */\nexport function buildAuthHeader(credentials: WpCredentials): string {\n if (credentials.username.includes(':')) {\n throw new Error('Username must not contain a colon (\":\") for HTTP Basic authentication.');\n }\n const token = `${credentials.username}:${credentials.applicationPassword}`;\n const base64 = Buffer.from(token, 'utf-8').toString('base64');\n return `Basic ${base64}`;\n}\n\n/** Error thrown when the WordPress REST API returns a non-2xx response. */\nexport class WpRequestError extends Error {\n constructor(\n readonly status: number,\n readonly path: string,\n message: string,\n ) {\n super(message);\n this.name = 'WpRequestError';\n }\n}\n\n/**\n * Minimal WordPress REST client.\n *\n * Runs in the Node process (CLI shell), never in the browser, so Application Password\n * credentials stay server-side. MVP scope: list posts, read/write post meta.\n */\nexport class WpClient {\n private readonly restBase: string;\n\n constructor(private readonly credentials: WpCredentials) {\n this.restBase = normalizeSiteUrl(credentials.siteUrl);\n }\n\n private async request<T>(path: string, init: RequestInit = {}): Promise<T> {\n const response = await fetch(`${this.restBase}/wp-json${path}`, {\n ...init,\n headers: {\n Authorization: buildAuthHeader(this.credentials),\n // Only declare a JSON body when one is actually sent (GETs carry none).\n ...(init.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...init.headers,\n },\n });\n\n if (!response.ok) {\n throw new WpRequestError(\n response.status,\n path,\n `WordPress REST request failed: ${response.status} ${response.statusText}`,\n );\n }\n\n return (await response.json()) as T;\n }\n\n /**\n * List the REST-enabled post types on the site (edit context), so the app can offer\n * a type selector. Returns each type's REST route base and display name.\n */\n async listPostTypes(): Promise<WpPostType[]> {\n const raw = await this.request<unknown>('/wp/v2/types?context=edit');\n return normalizePostTypes(raw);\n }\n\n /** List posts of a given type in edit context (raw fields, for editing). */\n async listPosts(params: ListPostsParams = {}): Promise<WpPost[]> {\n const type = params.type ?? 'posts';\n assertRouteSegment(type);\n const perPage = clampInt(params.perPage ?? 100, 1, 100);\n const page = clampInt(params.page ?? 1, 1, Number.MAX_SAFE_INTEGER);\n const query = new URLSearchParams({\n context: 'edit',\n per_page: String(perPage),\n page: String(page),\n });\n const raw = await this.request<WpPostResponse[]>(`/wp/v2/${type}?${query.toString()}`);\n return raw.map(normalizePost);\n }\n\n /**\n * Update post fields in a single request. Standard fields (title, menu_order,\n * status) are core REST fields and need no plugin. When `meta` is supplied it rides\n * the same request through the companion plugin's `dbp_wp_meta` field (ignored by\n * WordPress without the connector). Pass the REST route slug as `type` (e.g.\n * `posts`, `pages`) — not the object type returned on a post.\n */\n async updatePost(\n id: number,\n fields: UpdatePostFields,\n type = 'posts',\n meta?: Record<string, unknown>,\n ): Promise<WpPost> {\n assertPostId(id);\n assertRouteSegment(type);\n const raw = await this.request<WpPostResponse>(`/wp/v2/${type}/${String(id)}?context=edit`, {\n method: 'POST',\n body: JSON.stringify(buildPostBody(fields, meta)),\n });\n return normalizePost(raw);\n }\n\n /**\n * Create a new post in a single request, symmetric to {@link WpClient.updatePost}.\n * Standard fields (title, menu_order, status) are core REST fields; when `meta` is\n * supplied it rides the same request through the companion plugin's `dbp_wp_meta`\n * field. Pass the REST route slug as `type` (e.g. `posts`, `pages`).\n */\n async createPost(\n fields: UpdatePostFields,\n type = 'posts',\n meta?: Record<string, unknown>,\n ): Promise<WpPost> {\n assertRouteSegment(type);\n const raw = await this.request<WpPostResponse>(`/wp/v2/${type}?context=edit`, {\n method: 'POST',\n body: JSON.stringify(buildPostBody(fields, meta)),\n });\n return normalizePost(raw);\n }\n\n /**\n * Update only arbitrary post meta through the companion plugin's `dbp_wp_meta`\n * field. A thin wrapper over {@link WpClient.updatePost} with no standard fields.\n * Requires the connector; the connector writes scalar values only.\n */\n async updatePostMeta(\n id: number,\n meta: Record<string, unknown>,\n type = 'posts',\n ): Promise<WpPost> {\n return this.updatePost(id, {}, type, meta);\n }\n\n /**\n * Delete named meta keys from a single post via the companion plugin's\n * `DELETE /dbp-wp/v1/posts/<id>/meta` route. This route is keyed by id only (the\n * post type is irrelevant). Requires the connector.\n */\n async deletePostMeta(id: number, keys: string[]): Promise<DeleteMetaResult> {\n assertPostId(id);\n const cleanKeys = sanitizeMetaKeys(keys);\n const raw = await this.request<{ post_id?: unknown; deleted?: string[] }>(\n `/${CONNECTOR_NAMESPACE}/posts/${String(id)}/meta`,\n { method: 'DELETE', body: JSON.stringify({ keys: cleanKeys }) },\n );\n // Trust our own request `id` over a malformed connector `post_id`.\n return {\n postId: typeof raw.post_id === 'number' ? raw.post_id : id,\n deleted: Array.isArray(raw.deleted) ? raw.deleted : [],\n };\n }\n\n /**\n * Detect whether the companion plugin is active by checking the REST index\n * (`/wp-json/`) for the connector's namespace. Throws on a failed request; a caller\n * that wants a non-fatal probe should treat a thrown error as \"not available\".\n */\n async detectConnector(): Promise<boolean> {\n const index = await this.request<{ namespaces?: unknown }>('/');\n return hasConnectorNamespace(index.namespaces);\n }\n}\n\n/** Map editable fields to the WordPress REST request body (camelCase → snake_case). */\nexport function buildUpdateBody(fields: UpdatePostFields): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n if (fields.title !== undefined) {\n body.title = fields.title;\n }\n if (fields.menuOrder !== undefined) {\n body.menu_order = fields.menuOrder;\n }\n if (fields.status !== undefined) {\n body.status = fields.status;\n }\n return body;\n}\n\nfunction assertRouteSegment(segment: string): void {\n if (!ROUTE_SEGMENT.test(segment)) {\n throw new Error(`Invalid REST route segment: ${segment}`);\n }\n}\n\nfunction assertPostId(id: number): void {\n if (!Number.isSafeInteger(id) || id <= 0) {\n throw new Error(`Invalid post id: ${id}`);\n }\n}\n\nfunction clampInt(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) {\n return min;\n }\n return Math.min(max, Math.max(min, Math.trunc(value)));\n}\n\n/** Wrap arbitrary meta in the companion plugin's REST field for a write request. */\nexport function buildMetaBody(meta: Record<string, unknown>): Record<string, unknown> {\n return { [META_FIELD]: meta };\n}\n\n/**\n * Build a post-update body, folding in connector meta under `dbp_wp_meta` when given.\n * A provided `meta` is included as-is, even when empty — callers that should skip empty\n * meta (e.g. the CLI batch parser) omit it before calling.\n */\nexport function buildPostBody(\n fields: UpdatePostFields,\n meta?: Record<string, unknown>,\n): Record<string, unknown> {\n const body = buildUpdateBody(fields);\n if (meta !== undefined) {\n Object.assign(body, buildMetaBody(meta));\n }\n return body;\n}\n\n/** Validate and clean a list of meta keys for deletion (non-empty strings only). */\nexport function sanitizeMetaKeys(keys: unknown): string[] {\n if (!Array.isArray(keys)) {\n throw new Error('Meta keys must be an array of strings.');\n }\n const clean = keys.filter((key): key is string => typeof key === 'string' && key.length > 0);\n if (clean.length === 0) {\n throw new Error('At least one non-empty meta key is required.');\n }\n return clean;\n}\n\n/** True when a REST index `namespaces` list includes the connector namespace. */\nexport function hasConnectorNamespace(namespaces: unknown): boolean {\n return Array.isArray(namespaces) && namespaces.includes(CONNECTOR_NAMESPACE);\n}\n\n/**\n * Normalize the `/wp/v2/types` response (an object keyed by type name) into a list.\n * Skips entries without a string `rest_base` (not addressable over REST).\n */\nexport function normalizePostTypes(raw: unknown): WpPostType[] {\n if (typeof raw !== 'object' || raw === null) {\n return [];\n }\n const result: WpPostType[] = [];\n for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof value !== 'object' || value === null) {\n continue;\n }\n const entry = value as Record<string, unknown>;\n // Validate rest_base at ingestion so a malformed slug can't become a broken option.\n if (typeof entry.rest_base !== 'string' || !ROUTE_SEGMENT.test(entry.rest_base)) {\n continue;\n }\n result.push({\n slug: typeof entry.slug === 'string' ? entry.slug : key,\n restBase: entry.rest_base,\n name: typeof entry.name === 'string' ? entry.name : key,\n });\n }\n return result;\n}\n\nfunction normalizePost(raw: WpPostResponse): WpPost {\n const post: WpPost = {\n id: raw.id,\n type: raw.type,\n status: raw.status,\n title: raw.title.raw ?? raw.title.rendered,\n menuOrder: raw.menu_order,\n meta: raw.meta,\n };\n if (raw.dbp_wp_meta !== undefined) {\n post.dbpWpMeta = raw.dbp_wp_meta;\n }\n return post;\n}\n","import { Parser, type Expression } from 'expr-eval-fork';\n\n/**\n * Formula engine: evaluates a spreadsheet expression against named numeric cells.\n *\n * Implementations MUST NOT use `eval`, `Function`, or any other dynamic code execution.\n */\nexport interface FormulaEngine {\n /**\n * Evaluate a single expression against a map of cell references to numbers.\n * Throws on invalid syntax, unknown variables, or a non-numeric result.\n */\n evaluate(expression: string, context: Record<string, number>): number;\n}\n\n/**\n * Formula engine backed by expr-eval-fork, which parses to an AST and evaluates without\n * `eval`/`Function`. Member access, assignment, and function definitions are disabled,\n * the nondeterministic `random()` function is removed, and results are constrained to\n * finite numbers, so expressions stay pure, deterministic, and side-effect free.\n */\nexport class SafeFormulaEngine implements FormulaEngine {\n private readonly parser: Parser;\n\n constructor() {\n this.parser = new Parser({\n allowMemberAccess: false,\n operators: { assignment: false, fndef: false },\n });\n // The `operators.random` flag does not remove the random() function; delete it so\n // evaluation stays deterministic.\n delete this.parser.functions.random;\n }\n\n evaluate(expression: string, context: Record<string, number>): number {\n let parsed: Expression;\n try {\n parsed = this.parser.parse(expression);\n } catch (e) {\n throw new Error(`Invalid formula: ${e instanceof Error ? e.message : 'parse error'}`);\n }\n\n let result: unknown;\n try {\n result = parsed.evaluate(context);\n } catch (e) {\n throw new Error(`Formula evaluation failed: ${e instanceof Error ? e.message : 'error'}`);\n }\n\n if (typeof result !== 'number' || !Number.isFinite(result)) {\n throw new Error('Formula must evaluate to a finite number.');\n }\n return result;\n }\n}\n","import type { UpdatePostFields } from './types';\n\n// WordPress stores menu_order in a signed 32-bit column; ignore cells outside that range\n// so one bad value does not get the whole server-side chunk rejected.\nconst MENU_ORDER_MIN = -2_147_483_648;\nconst MENU_ORDER_MAX = 2_147_483_647;\n\n/**\n * A tabular view of an import file: a header row plus data rows. Both CSV and JSON\n * sources are normalized to this shape so the column-mapping logic is source-agnostic.\n * Each data row is aligned to `headers` by index; a short row has missing trailing cells.\n */\nexport interface ParsedTable {\n /** Column headers (CSV first row, or the union of JSON object keys). */\n headers: string[];\n /** Data rows; `rows[r][c]` is the cell under `headers[c]`. */\n rows: string[][];\n}\n\n/**\n * Where a file column is imported to. `skip` drops the column; `title`/`status`/\n * `menuOrder` map to standard post fields; `meta` writes an arbitrary post-meta key\n * (companion plugin required).\n */\nexport type ImportTarget =\n | { kind: 'skip' }\n | { kind: 'title' }\n | { kind: 'status' }\n | { kind: 'menuOrder' }\n | { kind: 'meta'; key: string };\n\n/** A single new post to create, derived from one import row. */\nexport interface ImportCreate {\n /** Standard fields (title / menuOrder / status). */\n fields: UpdatePostFields;\n /** Arbitrary meta to write via the companion plugin (omitted when none). */\n meta?: Record<string, unknown>;\n}\n\n/**\n * Parse CSV text into a table, taking the first record as headers. Implements the\n * RFC 4180 essentials: double-quoted fields, embedded commas/newlines, `\"\"` escapes,\n * and CRLF or LF line endings. A trailing newline does not produce an empty record.\n */\nexport function parseCsv(text: string): ParsedTable {\n const records = parseCsvRecords(text);\n const headers = records[0] ?? [];\n return { headers, rows: records.slice(1) };\n}\n\nfunction parseCsvRecords(text: string): string[][] {\n const records: string[][] = [];\n let record: string[] = [];\n let field = '';\n let inQuotes = false;\n let i = 0;\n\n const endField = (): void => {\n record.push(field);\n field = '';\n };\n const endRecord = (): void => {\n endField();\n records.push(record);\n record = [];\n };\n\n while (i < text.length) {\n const ch = text[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (text[i + 1] === '\"') {\n field += '\"';\n i += 2;\n continue;\n }\n inQuotes = false;\n i += 1;\n continue;\n }\n field += ch;\n i += 1;\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n i += 1;\n continue;\n }\n if (ch === ',') {\n endField();\n i += 1;\n continue;\n }\n if (ch === '\\r') {\n endRecord();\n i += text[i + 1] === '\\n' ? 2 : 1;\n continue;\n }\n if (ch === '\\n') {\n endRecord();\n i += 1;\n continue;\n }\n field += ch;\n i += 1;\n }\n // An unclosed quote means the file is malformed; surface it rather than silently\n // merging the rest of the file into one field and writing corrupted data.\n if (inQuotes) {\n throw new Error('Malformed CSV: unterminated quoted field.');\n }\n // Flush a final record only if there is pending content (no trailing-newline ghost row).\n if (field !== '' || record.length > 0) {\n endRecord();\n }\n return records;\n}\n\n/**\n * Parse JSON text (an array of objects) into a table. Headers are the union of all\n * object keys in first-seen order. Object/array cell values are JSON-stringified;\n * null and undefined become an empty string. Throws if the JSON is not an array.\n */\nexport function parseJsonRecords(text: string): ParsedTable {\n const data: unknown = JSON.parse(text);\n if (!Array.isArray(data)) {\n throw new Error('JSON import must be an array of objects.');\n }\n const headers: string[] = [];\n const seen = new Set<string>();\n for (const entry of data) {\n if (isPlainRecord(entry)) {\n for (const key of Object.keys(entry)) {\n if (!seen.has(key)) {\n seen.add(key);\n headers.push(key);\n }\n }\n }\n }\n const rows = data.map((entry) => {\n const record = isPlainRecord(entry) ? entry : {};\n return headers.map((header) => stringifyCell(record[header]));\n });\n return { headers, rows };\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringifyCell(value: unknown): string {\n if (value === null || value === undefined) {\n return '';\n }\n if (typeof value === 'object') {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\n/**\n * Known post-status values and English/value labels, mapped to the WordPress status.\n * A null-prototype map so inherited keys (`constructor`, `toString`, `__proto__`, …) do\n * not accidentally resolve to a function/object instead of falling back to the raw value.\n */\nconst STATUS_LABELS: Record<string, string> = Object.assign(\n Object.create(null) as Record<string, string>,\n {\n publish: 'publish',\n published: 'publish',\n draft: 'draft',\n pending: 'pending',\n private: 'private',\n future: 'future',\n },\n);\n\n/**\n * Normalize a status cell to a WordPress status. Known labels/values (case-insensitive,\n * e.g. `Published` → `publish`) are mapped; anything else passes through trimmed so the\n * WordPress REST API can validate it and surface a per-row error if invalid.\n */\nexport function normalizeStatus(value: string): string {\n const trimmed = value.trim();\n return STATUS_LABELS[trimmed.toLowerCase()] ?? trimmed;\n}\n\n/**\n * Apply a column mapping to a parsed table, producing one {@link ImportCreate} per row.\n * Empty cells contribute nothing; a row that maps to no fields and no meta is skipped.\n * Non-integer `menuOrder` cells are ignored. Meta is stored on a null-prototype object\n * so a header named `__proto__` is kept as data, never touching any prototype.\n */\nexport function buildImportPlan(table: ParsedTable, mapping: ImportTarget[]): ImportCreate[] {\n const creates: ImportCreate[] = [];\n for (const row of table.rows) {\n const fields: UpdatePostFields = {};\n let meta: Record<string, unknown> | undefined;\n\n for (let col = 0; col < mapping.length; col += 1) {\n const target = mapping[col];\n if (!target || target.kind === 'skip') {\n continue;\n }\n const value = row[col] ?? '';\n if (value === '') {\n continue;\n }\n switch (target.kind) {\n case 'title':\n fields.title = value;\n break;\n case 'status':\n fields.status = normalizeStatus(value);\n break;\n case 'menuOrder': {\n const order = Number(value);\n if (Number.isInteger(order) && order >= MENU_ORDER_MIN && order <= MENU_ORDER_MAX) {\n fields.menuOrder = order;\n }\n break;\n }\n case 'meta':\n // An empty/whitespace meta key would be rejected by the server (empty key),\n // failing the whole chunk; skip it rather than emit `meta[\"\"]`.\n if (target.key.trim() === '') {\n break;\n }\n if (!meta) {\n meta = Object.create(null) as Record<string, unknown>;\n }\n meta[target.key] = value;\n break;\n }\n }\n\n if (meta !== undefined && Object.keys(meta).length > 0) {\n creates.push({ fields, meta });\n } else if (Object.keys(fields).length > 0) {\n creates.push({ fields });\n }\n }\n return creates;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@dbp-wp/core",
3
+ "version": "0.1.0",
4
+ "description": "Core library for DBP WP: WordPress REST client, formula engine, importer, and typesetting data generation.",
5
+ "license": "Apache-2.0",
6
+ "author": "Takashi Matsuyama",
7
+ "homepage": "https://wp.dbp.jp",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/takashi-matsuyama/dbp_wp.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/takashi-matsuyama/dbp_wp/issues"
15
+ },
16
+ "keywords": [
17
+ "wordpress",
18
+ "wordpress-rest-api",
19
+ "bulk-edit"
20
+ ],
21
+ "type": "module",
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "typecheck": "tsc --noEmit"
42
+ },
43
+ "dependencies": {
44
+ "expr-eval-fork": "^3.0.3"
45
+ },
46
+ "devDependencies": {
47
+ "tsup": "^8.3.0"
48
+ }
49
+ }