@lemonadecms/client 0.0.0-stage → 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,84 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.1 — prepared release
6
+
7
+ - Add a native terminal environment picker for online `types generate` using
8
+ validated configured targets and their credential variables. Explicit settings,
9
+ CI, JSON/check/offline and piped runs stay noninteractive. Cancellation restores
10
+ the terminal and preserves files without network work. No dependencies added.
11
+
12
+ - Add bounded nested `and`/`or`/`not` filters, `ne`/`not_in`, literal text patterns
13
+ and case-insensitive matching. Existing arrays and scalar/choice operators stay
14
+ supported. Sort by 1–3 ordered fields with stable NULL/tie/backward pagination.
15
+ - Add typed paginated `search()` and reusable search options for reads, counts,
16
+ existence and lazy iteration. Uses the requested publication/locale snapshot
17
+ and PostgreSQL plain full-text relevance, with additive REST/RPC/GraphQL support.
18
+ Existing cursor forms and single-sort syntax remain compatible. No runtime,
19
+ optional or peer dependency was added; the updated CMS backend is required.
20
+
21
+ - `count()` and `exists()` query matching authorized snapshots directly without
22
+ downloading entries. `getMany()` reads at most 100 IDs in one request and
23
+ preserves order/duplicates with nullable missing entries. `findOneOrThrow()`
24
+ raises the exported `CMSNotFoundError` only for a successful empty match. All
25
+ support public/server-preview reads, typed options and existing cancellation,
26
+ deadline and error behavior; the updated CMS REST endpoints are required.
27
+
28
+ - `content(model).findOne()` returns the first typed matching entry or null using
29
+ one bounded query; preserves projections, metadata, filters, preview isolation
30
+ and existing error/cancellation/deadline behavior.
31
+ - `richTextToHtml()` and `richTextToPlainText()` render version-1 rich-text values
32
+ in browsers/servers with bounded traversal, escaped HTML, safe URLs and matching
33
+ expanded resources. Plain text excludes embeds and link URLs.
34
+
35
+ - Staff Admin manages release versions and notes in this Markdown file, with
36
+ exact-source preparation, durable activity and explicit npm publication through
37
+ a protected GitHub Actions job. Operator setup and live publishing qualification
38
+ remain separate; `--from-changelog` also selects this version locally.
39
+
40
+ - Repository maintainers can run `npm run release:sdk` to manage stable versions,
41
+ changelog notes, validation and exact archive preparation. `--publish` explicitly
42
+ enables npm upload and installed-registry verification; `--current` resumes the
43
+ selected version. The source package remains private.
44
+
45
+ - Entries include content-type identity, workspace/environment and locale beside
46
+ their existing ID, modeled fields and publication time. Optional TypeScript
47
+ metadata preserves existing entry fixtures; request identity supports older
48
+ CMS servers, while malformed/conflicting metadata is rejected safely.
49
+ - Expanded reference types describe additive raw REST context metadata. The
50
+ generated `Models` map still describes only custom fields, not entry envelopes.
51
+
52
+ - `lemonade types generate` loads the current project's `.env`, defaults to
53
+ `CMS_PROJECT_ID` / `CMS_PUBLIC_KEY`, Production and `src/types/cms-models.ts`.
54
+ Explicit flags, named targets and legacy `LEMONADE_*` settings still take
55
+ precedence; offline/management commands retain their credential boundaries.
56
+ C# and Python defaults use matching filenames in `src/types`.
57
+
58
+ - Keep CLI request deadlines bounded when Node 22's response-body transport
59
+ ignores native abort; best-effort stream cancellation cannot delay completion.
60
+
61
+ - Release preparation reads the selected version from package metadata, checks
62
+ package-lock/changelog agreement, and supports future stable/prerelease versions.
63
+ - Explicit public-registry verification checks archive integrity and isolated
64
+ npm/pnpm installations, runtime/types, both CLIs and browser-preview guards.
65
+ - Current installation documentation uses the published npm package. These
66
+ checkout changes do not replace the existing immutable registry artifact.
67
+
68
+ ## 0.1.0 — published package
69
+
70
+ - Framework-independent ESM client and TypeScript declarations for published REST
71
+ collection/item reads, field selection, bounded pagination, filters, sorting,
72
+ localization, references and media helpers.
73
+ - Separate server-only draft-preview client with browser import/runtime guards.
74
+ - Schema-based model generation and the `lemonade` / `cms-codegen` commands.
75
+ - Fixed `https://lemonadecms.com` destination for customer reads; configure the
76
+ workspace ID and appropriately scoped credential. URL/host/environment overrides
77
+ fail before I/O. The credential selects the content environment.
78
+ - Optional request deadlines and cancellation. No automatic cache or retry policy.
79
+ - MIT license for this client package. This does not license the rest of the repository.
80
+
81
+ New applications import `@lemonadecms/client`. Existing local `@cms/client` imports
82
+ can use the documented file alias to a reviewed archive. Public availability of
83
+ 0.1.0 is verified separately from publisher access, provenance and authorization
84
+ for a future release. Local preparation never publishes.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lemonade CMS contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,305 @@
1
- # Temporary Holding Version
1
+ # @lemonadecms/client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-independent JavaScript and TypeScript SDK for reading Lemonade CMS
4
+ content. Published-content requests use REST. The package includes TypeScript
5
+ declarations, schema type generation and the `lemonade` developer CLI.
6
+
7
+ ## Install
8
+
9
+ Install the published package in your application:
10
+
11
+ ```sh
12
+ npm install @lemonadecms/client
13
+ # or
14
+ pnpm add @lemonadecms/client
15
+ ```
16
+
17
+ Maintainers can test a future version using its reviewed candidate archive.
18
+ Check the supplied `SHA256SUMS`, then use the actual archive path:
19
+
20
+ ```sh
21
+ npm install --ignore-scripts /absolute/path/to/reviewed-client.tgz
22
+ # or
23
+ pnpm add /absolute/path/to/reviewed-client.tgz
24
+ ```
25
+
26
+ The package uses ESM and declares Node.js 22+. TypeScript consumers need
27
+ TypeScript 5+. Browser reads require standard `fetch`, `URL`, `URLSearchParams`
28
+ and `AbortController`; browser builds must use public credentials only. No
29
+ runtime library or framework dependency is required.
30
+
31
+ ## Configure and read content
32
+
33
+ The SDK connects to `https://lemonadecms.com` automatically. Configure your
34
+ workspace and delivery key; there is no URL or local CMS option. Your website
35
+ can run locally while reading content from Lemonade production. Installing the
36
+ package does not create an account, workspace or delivery key.
37
+
38
+ Replace the workspace and key placeholders with values from your Lemonade workspace. Replace `content_item` and `title` with your content type and field
39
+ API identifiers, and publish an entry before fetching it.
40
+
41
+ ```js
42
+ import { createCMSClient } from '@lemonadecms/client';
43
+
44
+ const cms = createCMSClient({
45
+ projectId: 'YOUR_WORKSPACE_UUID',
46
+ publicKey: 'YOUR_PUBLIC_DELIVERY_KEY',
47
+ });
48
+
49
+ const page = await cms.content('content_item').list({
50
+ select: ['title'],
51
+ limit: 10,
52
+ });
53
+
54
+ for (const entry of page.data) {
55
+ console.log(entry.id, entry.fields.title);
56
+ }
57
+
58
+ const first = page.data[0];
59
+ if (first) {
60
+ const entry = await cms.content('content_item').get(first.id);
61
+ console.log(entry.fields, entry.publishedAt);
62
+ }
63
+ ```
64
+
65
+ Entry values are under `fields`; metadata includes `id` and `publishedAt`.
66
+ Unreleased source builds also expose `contentType` (ID/key/name), `projectId`,
67
+ `environment` (ID/key) and resolved `locale`. Field projections retain metadata.
68
+ For older CMS responses, the SDK derives only the request's model key/workspace
69
+ and explicit locale; unavailable metadata stays absent. Metadata properties are
70
+ optional in TypeScript for compatibility with existing manually constructed
71
+ entries. Use `entry.contentType?.key`, not a custom `fields.contentType` field.
72
+ Generated `Models` describes fields; `ContentEntry<Models['content_item'],
73
+ 'content_item'>` from the SDK describes the complete entry. Expanded reference
74
+ metadata remains raw snake_case. Published `0.1.0` does not include these additions.
75
+ An empty collection is `data: []`. Historical or selected fields can be missing
76
+ or `null`, so handle both when rendering. Continue a collection using
77
+ `page.meta.nextCursor` as the next request's `after` value while keeping the
78
+ other query options unchanged.
79
+
80
+ Use an existing `pk_` public delivery key or an `lmd_pk_` application token with
81
+ the required published-content grants. Anyone with that credential can read
82
+ the content it permits. Browser login cookies do not authorize SDK requests.
83
+ The credential selects the content environment; there is no separate SDK
84
+ environment override. Ask your Owner for a credential belonging to the intended
85
+ environment. The server rechecks current access, expiry and revocation on every
86
+ request.
87
+
88
+ ## Find one entry
89
+
90
+ In the updated source build, `findOne()` returns the first matching complete
91
+ entry or `null`. It makes one request with `limit=1` and preserves existing
92
+ filters, search, sort, select, locale, includeRemote and signal options. It does not check
93
+ uniqueness. Pagination/limit options are rejected; HTTP/authorization, transport,
94
+ cancellation/deadline and malformed-response errors still reject. `get(id)` keeps
95
+ its existing UUID lookup and 404 behavior. Published `0.1.0` lacks these helpers.
96
+
97
+ ```js
98
+ const entry = await cms.content('content_item').findOne({
99
+ filters: [{ field: 'title', op: 'eq', value: 'Welcome' }],
100
+ select: ['title'],
101
+ });
102
+ console.log(entry?.fields.title ?? 'No matching content');
103
+ ```
104
+
105
+ ## Counts, existence and bulk reads
106
+
107
+ These unreleased methods require the updated SDK and CMS backend. They are
108
+ available in both published and server preview clients:
109
+
110
+ ```js
111
+ import { CMSNotFoundError } from '@lemonadecms/client';
112
+
113
+ const content = cms.content('content_item');
114
+ const filters = [{ field: 'title', op: 'eq', value: 'Welcome' }];
115
+ const total = await content.count({ filters });
116
+ const available = await content.exists({ filters });
117
+ try {
118
+ const required = await content.findOneOrThrow({ filters, select: ['title'] });
119
+ const entries = await content.getMany([required.id], { select: ['title'] });
120
+ console.log(total, available, entries[0]?.fields.title);
121
+ } catch (error) {
122
+ if (!(error instanceof CMSNotFoundError)) throw error;
123
+ console.log('Required content is unavailable');
124
+ }
125
+ ```
126
+
127
+ `count()`/`exists()` accept filters, search, locale and signal, using direct database
128
+ queries without downloading entries or expanding references. Count includes all
129
+ matches across pages; separate requests can see different publication states.
130
+ `getMany()` accepts up to 100 UUIDs and select/locale/includeRemote/signal. One
131
+ request returns entries in input order, preserving duplicate positions and using
132
+ `null` for missing/unpublished/foreign IDs in an otherwise authorized model.
133
+ Empty input returns `[]` without I/O after validation. Split larger ID sets into
134
+ batches; use the existing lazy `entries()`/`pages()` methods for collections.
135
+ `findOneOrThrow()` shares findOne options and throws `CMSNotFoundError` only for
136
+ a successful empty match. Authorization, HTTP, malformed-response, network,
137
+ cancellation and deadline failures retain their existing errors. Preview fields
138
+ may still be missing/null. No automatic collection-download fallback occurs.
139
+
140
+ ## Advanced filters, search and sorting
141
+
142
+ Updated source builds support nested `and`/`or`/`not`, `ne`/`not_in`, and text
143
+ `eqi`, `contains`/`containsi`, `starts_with`/`starts_withi`, `ends_with`/`ends_withi`.
144
+ Existing filter arrays remain AND. Limits are ten leaves, 31 nodes, depth four
145
+ and 4 KiB; groups are nonempty. SQL wildcard characters are treated literally.
146
+ Positive comparisons on missing values are false; NOT and ne/not_in complement
147
+ them, including missing values. Use eq/ne null for explicit null checks.
148
+
149
+ ```js
150
+ const matches = await cms.content('content_item').search('welcome', {
151
+ fields: ['title'],
152
+ select: ['title'],
153
+ filters: {
154
+ or: [
155
+ { field: 'title', op: 'containsi', value: 'WELCOME' },
156
+ { not: { field: 'title', op: 'eq', value: 'Archived' } },
157
+ ],
158
+ },
159
+ sort: [
160
+ { field: 'position', direction: 'desc' },
161
+ { field: 'title', direction: 'asc' },
162
+ ],
163
+ limit: 10,
164
+ });
165
+ console.log(matches.data);
166
+ ```
167
+
168
+ `fields` scopes matching to 1–24 text/long_text/markdown fields; `select` projects
169
+ the returned fields. Omit fields to search all top-level fields of those types.
170
+ Query accepts 1–200 Unicode codepoints, up to 512 UTF-8 bytes and 16 whitespace
171
+ terms. PostgreSQL simple-config plain search requires every parsed term, with
172
+ cover-density relevance order by default. Explicit 1–3 unique sort fields override
173
+ relevance; NULLs stay last and UUID breaks ties. No stemming, fuzzy/prefix/phrase
174
+ search or recursive rich-text/reference search is implied. Matching and ranking
175
+ use only the authorized locale/fallback and published/preview snapshot.
176
+
177
+ `search()` makes one normal paginated collection request. Pass the same
178
+ `search: {query, fields}` object to list/findOne/findOneOrThrow/count/exists/pages/
179
+ entries to count or traverse matches. Keep the full query and ordered sort fields
180
+ unchanged when following cursors. Existing single-sort syntax remains supported.
181
+ Signed cursors have an 8 KiB limit. Unusually large Unicode text anchors that
182
+ exceed it after compression return `invalid_query`; choose more compact scalar
183
+ sort fields in that case.
184
+ These methods require the updated SDK/backend. Runtime/peer/optional dependencies
185
+ remain empty; no external search service is used.
186
+
187
+ ## Rich-text helpers
188
+
189
+ ```js
190
+ import { richTextToHtml, richTextToPlainText } from '@lemonadecms/client';
191
+
192
+ // Replace rich_body with a rich_text field from your schema.
193
+ const richEntry = await cms.content('content_item').findOne({ select: ['rich_body'] });
194
+ const html = richTextToHtml(richEntry?.fields.rich_body);
195
+ const plainText = richTextToPlainText(richEntry?.fields.rich_body);
196
+ ```
197
+
198
+ Both accept nullable/unknown version-1 rich-text JSON and work in browsers or
199
+ servers without a framework or DOM. HTML escapes text/attributes and renders only
200
+ supported nodes/marks, safe links and matching expanded image/entry-caption
201
+ resources. Unavailable embeds are omitted; entry routes are never invented.
202
+ Plain text retains authored text with block/list/hard-break newlines, omitting
203
+ embeds and link URLs. Plain text is not escaped HTML: use text interpolation.
204
+
205
+ Traversal is bounded to 500 nodes, depth 8, 200 children per node, 20 embeds,
206
+ 20,000 UTF-8 bytes per text and 64 KiB of rendered source strings. Unsupported
207
+ versions, invalid roots/containers or exhausted budgets return empty strings;
208
+ unsupported parts are omitted. No raw HTML, resource-field recursion, I/O or
209
+ automatic Markdown conversion is performed. HTML URL/alt bounds and exact
210
+ preview-image conventions are documented in the SDK guide. The helpers are also
211
+ exported from the server entry; preview authorization remains your responsibility.
212
+
213
+ ## Types, queries and framework examples
214
+
215
+ The [SDK guide](https://lemonadecms.com/guides/sdk) covers generated `Models`,
216
+ field selection, filtering, sorting, localization, cursor and numbered
217
+ pagination, errors, cancellation, images, and JavaScript/React/Vue/Nuxt examples.
218
+ Generate types from your own authorized schema and regenerate them after model
219
+ changes. Types help at compile time; they do not authenticate a request or
220
+ validate every field at runtime.
221
+
222
+ There is no automatic caching or retry policy. Your application owns rendering,
223
+ loading and error states, cache invalidation and safe HTML handling. Pass an
224
+ `AbortSignal` to cancel a read. Optional `timeoutMs` sets a per-request deadline;
225
+ combining a timeout with a caller signal requires `AbortSignal.any`.
226
+
227
+ ## Choose an environment for generated types
228
+
229
+ The updated source CLI offers an interactive picker from configured targets:
230
+
231
+ ```sh
232
+ npx lemonade types generate
233
+ ```
234
+
235
+ Create `lemonade.config.json` in your application. Replace both workspace
236
+ placeholders with your workspace UUID; the target labels distinguish environments.
237
+
238
+ ```json
239
+ {
240
+ "version": 1,
241
+ "targets": {
242
+ "production": {
243
+ "url": "https://lemonadecms.com",
244
+ "workspace": "YOUR_WORKSPACE_UUID",
245
+ "environment": "production",
246
+ "credentialEnv": "CMS_PUBLIC_KEY"
247
+ },
248
+ "dev": {
249
+ "url": "https://lemonadecms.com",
250
+ "workspace": "YOUR_WORKSPACE_UUID",
251
+ "environment": "dev",
252
+ "credentialEnv": "CMS_DEV_PUBLIC_KEY"
253
+ }
254
+ }
255
+ }
256
+ ```
257
+
258
+ Keep actual credentials in your current directory's `.env` or process environment;
259
+ process values win. A dev target needs a dev-scoped credential. The credential
260
+ selects access; the CLI verifies the returned workspace/environment before writing.
261
+ The picker lists configured targets, not environments discovered from the CMS.
262
+
263
+ Use arrows and Enter. Production is initially selected when configured; otherwise
264
+ the first target alphabetically. Escape/Ctrl+C/EOF exits 130 without network or
265
+ output-file writes. Terminal input mode is restored on success, cancellation/error.
266
+ Missing/revoked/mismatched credentials fail without replacing existing types.
267
+
268
+ Explicit `--target`, `--environment`, `--url`, `--workspace` or `--credential-env`
269
+ bypasses the picker, as do `LEMONADE_TARGET`, `LEMONADE_ENVIRONMENT` or
270
+ `CMS_ENVIRONMENT`. CI, `--json`, `--check`, piped input/output, `--file` and other
271
+ commands never prompt. `--config` can choose the target configuration file.
272
+ Without configured targets, existing defaults remain unchanged. For automation:
273
+
274
+ ```sh
275
+ npx lemonade types generate --target dev --check
276
+ ```
277
+
278
+ This requires the updated source package; published 0.1.0 lacks the picker.
279
+ It uses native Node APIs and retains zero runtime, peer or optional dependencies.
280
+
281
+ ## Private previews
282
+
283
+ For draft reads, use `createCMSPreviewClient` from
284
+ `@lemonadecms/client/server` in trusted server code with a server-only
285
+ `secretKey`. Authenticate and authorize the requesting user in your application
286
+ before fetching or returning drafts. Do not create an anonymous preview proxy.
287
+ Never put preview credentials or private responses in browser bundles, public
288
+ environment variables, shared caches, static output or logs. The server export
289
+ rejects browser use; that guard does not replace your application's authorization.
290
+
291
+ `@lemonadecms/client/preview` is a separate optional click-to-edit helper for an
292
+ already authorized website preview. It provides no preview credential or draft
293
+ fetch client.
294
+
295
+ ## Guides
296
+
297
+ - [Getting started](https://lemonadecms.com/guides)
298
+ - [SDK reference and examples](https://lemonadecms.com/guides/sdk)
299
+ - [Troubleshooting](https://lemonadecms.com/guides/troubleshooting)
300
+ - [Repository](https://github.com/ilara-booqself/lemonadecms)
301
+
302
+ ## License
303
+
304
+ This SDK package is licensed under the [MIT License](LICENSE). Its license does
305
+ not grant a license to the entire CMS repository or the separate native clients.