@lemonadecms/client 0.1.0 → 0.2.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/dist/types.d.ts CHANGED
@@ -4,7 +4,7 @@ export type ModelMap<Models> = {
4
4
  export type UntypedModels = Record<string, Record<string, unknown>>;
5
5
  export type FieldName<Fields> = Extract<keyof Fields, string>;
6
6
  export interface CMSBaseOptions {
7
- /** Requests always use https://lemonadecms.com. Custom CMS hosts are unsupported. */
7
+ /** Requests always use https://api.lemonadecms.com. Custom CMS hosts are unsupported. */
8
8
  url?: never;
9
9
  projectId: string;
10
10
  /** Compatible transport for SSR, testing or polyfills; receives production URLs. */
@@ -28,11 +28,11 @@ export type ScalarField<Fields> = {
28
28
  type ScalarContentFilter<Fields> = {
29
29
  [Key in ScalarField<Fields>]: {
30
30
  field: Key;
31
- op: "eq";
31
+ op: "eq" | "ne";
32
32
  value: Extract<Fields[Key], Scalar> | (unknown extends Fields[Key] ? Scalar : never) | null;
33
33
  } | {
34
34
  field: Key;
35
- op: "in";
35
+ op: "in" | "not_in";
36
36
  value: readonly (Extract<Fields[Key], Scalar> | (unknown extends Fields[Key] ? Scalar : never))[];
37
37
  } | (NonNullable<Fields[Key]> extends boolean ? never : {
38
38
  field: Key;
@@ -51,7 +51,28 @@ type ChoicesContentFilter<Fields> = {
51
51
  value: readonly ChoicesValue<Fields[Key]>[];
52
52
  };
53
53
  }[ChoicesField<Fields>];
54
- export type ContentFilter<Fields> = ScalarContentFilter<Fields> | ChoicesContentFilter<Fields>;
54
+ export type TextField<Fields> = {
55
+ [Key in FieldName<Fields>]: unknown extends Fields[Key] ? Key : NonNullable<Fields[Key]> extends string ? Key : never;
56
+ }[FieldName<Fields>];
57
+ type TextContentFilter<Fields> = {
58
+ [Key in TextField<Fields>]: {
59
+ field: Key;
60
+ op: "eqi" | "contains" | "containsi" | "starts_with" | "starts_withi" | "ends_with" | "ends_withi";
61
+ value: string;
62
+ };
63
+ }[TextField<Fields>];
64
+ export type ContentFilter<Fields> = ScalarContentFilter<Fields> | ChoicesContentFilter<Fields> | TextContentFilter<Fields> | {
65
+ and: readonly ContentFilter<Fields>[];
66
+ } | {
67
+ or: readonly ContentFilter<Fields>[];
68
+ } | {
69
+ not: ContentFilter<Fields>;
70
+ };
71
+ export interface ContentSearch<Fields = Record<string, unknown>> {
72
+ query: string;
73
+ /** Top-level text/long_text/markdown fields; the backend validates stored types. */
74
+ fields?: readonly TextField<Fields>[];
75
+ }
55
76
  export interface ContentSort<Fields> {
56
77
  field: ScalarField<Fields>;
57
78
  direction: "asc" | "desc";
@@ -60,9 +81,11 @@ interface BaseListOptions<Key extends string, Fields> {
60
81
  select?: readonly Key[];
61
82
  locale?: string;
62
83
  limit?: number;
63
- /** Up to ten AND clauses, validated against the workspace schema by the server. */
64
- filters?: readonly ContentFilter<Fields>[];
65
- sort?: ContentSort<Fields>;
84
+ /** Ten leaves, depth four, 31 nodes and 4 KiB. Arrays remain implicit AND. */
85
+ filters?: readonly ContentFilter<Fields>[] | ContentFilter<Fields>;
86
+ /** One object or 1–3 unique ordered fields; the UUID breaks remaining ties. */
87
+ sort?: ContentSort<Fields> | readonly ContentSort<Fields>[];
88
+ search?: ContentSearch<Fields>;
66
89
  signal?: AbortSignal;
67
90
  /** Explicit live enrichment, independently cached from the saved publication. */
68
91
  includeRemote?: boolean;
@@ -85,16 +108,44 @@ export type ListOptions<Key extends string = string, Fields = Record<string, unk
85
108
  after?: never;
86
109
  before?: never;
87
110
  });
111
+ /** One first match, with no caller-controlled page size or boundary. */
112
+ export type FindOneOptions<Key extends string = string, Fields = Record<string, unknown>> = Pick<BaseListOptions<Key, Fields>, "select" | "locale" | "filters" | "sort" | "search" | "signal" | "includeRemote">;
113
+ /** Direct statistics have no pagination, projection, sorting or remote enrichment. */
114
+ export type CountOptions<Fields = Record<string, unknown>> = Pick<BaseListOptions<string, Fields>, "locale" | "filters" | "search" | "signal">;
115
+ export type GetManyOptions<Key extends string = string> = Pick<BaseListOptions<Key, Record<string, unknown>>, "select" | "locale" | "signal" | "includeRemote">;
116
+ /** A paginated search; fields scopes matching, select projects the returned fields. */
117
+ export type SearchOptions<Key extends string = string, Fields = Record<string, unknown>> = ListOptions<Key, Fields> & {
118
+ fields?: readonly TextField<Fields>[];
119
+ search?: never;
120
+ };
88
121
  /** Lazy forward traversal, capped at 1,000 pages by default (maximum 10,000). */
89
122
  export type PaginationOptions<Key extends string = string, Fields = Record<string, unknown>> = Omit<ListOptions<Key, Fields>, "before" | "page" | "pageSize"> & {
90
123
  page?: never;
91
124
  pageSize?: never;
92
125
  maxPages?: number;
93
126
  };
94
- export interface ContentEntry<Fields> {
127
+ /** Identity of the selected model; ID/name can be absent on older CMS responses. */
128
+ export interface ContentTypeIdentity<Model extends string = string> {
129
+ key: Model;
130
+ id?: string;
131
+ name?: string;
132
+ }
133
+ export interface ContentEnvironment {
134
+ id: string;
135
+ key: string;
136
+ }
137
+ /** Metadata sits beside modeled fields and is retained by field projections. */
138
+ export interface ContentEntry<Fields, Model extends string = string> {
95
139
  id: string;
96
140
  fields: Fields;
97
141
  publishedAt: string | null;
142
+ /** Optional in this interface to preserve existing manually constructed entries. */
143
+ contentType?: ContentTypeIdentity<Model>;
144
+ projectId?: string;
145
+ /** Resolved request locale; fallback fields can originate in the base language. */
146
+ locale?: string;
147
+ /** Absent when the CMS response predates environment metadata. */
148
+ environment?: ContentEnvironment;
98
149
  remote?: RemoteValue | null;
99
150
  }
100
151
  export interface RemoteValue<Data = Record<string, string | number | boolean | null>> {
@@ -107,8 +158,8 @@ export interface RemoteValue<Data = Record<string, string | number | boolean | n
107
158
  fetchedAt: string | null;
108
159
  expiresAt: string | null;
109
160
  }
110
- export interface ContentPage<Fields> {
111
- data: ContentEntry<Fields>[];
161
+ export interface ContentPage<Fields, Model extends string = string> {
162
+ data: ContentEntry<Fields, Model>[];
112
163
  meta: {
113
164
  nextCursor: string | null;
114
165
  limit: number;
@@ -119,16 +170,28 @@ export interface ContentPage<Fields> {
119
170
  hasPreviousPage?: boolean;
120
171
  };
121
172
  }
122
- export interface ContentCollection<Fields extends object> {
123
- list<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: ListOptions<Key, Fields>): Promise<ContentPage<Pick<Fields, Key>>>;
173
+ export interface ContentCollection<Fields extends object, Model extends string = string> {
174
+ list<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: ListOptions<Key, Fields>): Promise<ContentPage<Pick<Fields, Key>, Model>>;
175
+ /** One page of plain text matches; relevance order unless an explicit sort is supplied. */
176
+ search<const Key extends FieldName<Fields> = FieldName<Fields>>(query: string, options?: SearchOptions<Key, Fields>): Promise<ContentPage<Pick<Fields, Key>, Model>>;
177
+ /** First matching entry or null; all request and response errors still reject. */
178
+ findOne<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: FindOneOptions<Key, Fields>): Promise<ContentEntry<Pick<Fields, Key>, Model> | null>;
179
+ /** Required first match; CMSNotFoundError only for a successful empty query. */
180
+ findOneOrThrow<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: FindOneOptions<Key, Fields>): Promise<ContentEntry<Pick<Fields, Key>, Model>>;
181
+ /** Matching entries across all pages, counted by the backend without downloading fields. */
182
+ count(options?: CountOptions<Fields>): Promise<number>;
183
+ /** Whether any entry matches; backend stops after the first match. */
184
+ exists(options?: CountOptions<Fields>): Promise<boolean>;
185
+ /** At most 100 UUIDs in one request; preserves order/duplicates, missing entries become null. */
186
+ getMany<const Key extends FieldName<Fields> = FieldName<Fields>>(ids: readonly string[], options?: GetManyOptions<Key>): Promise<(ContentEntry<Pick<Fields, Key>, Model> | null)[]>;
124
187
  /** Fetch only as the caller requests another page. Breaking stops network work. */
125
- pages<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: PaginationOptions<Key, Fields>): AsyncIterableIterator<ContentPage<Pick<Fields, Key>>>;
188
+ pages<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: PaginationOptions<Key, Fields>): AsyncIterableIterator<ContentPage<Pick<Fields, Key>, Model>>;
126
189
  /** Flatten pages lazily, retaining at most one page of entries. */
127
- entries<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: PaginationOptions<Key, Fields>): AsyncIterableIterator<ContentEntry<Pick<Fields, Key>>>;
128
- get<const Key extends FieldName<Fields> = FieldName<Fields>>(id: string, options?: Pick<ListOptions<Key, Fields>, "select" | "locale" | "signal" | "includeRemote">): Promise<ContentEntry<Pick<Fields, Key>>>;
190
+ entries<const Key extends FieldName<Fields> = FieldName<Fields>>(options?: PaginationOptions<Key, Fields>): AsyncIterableIterator<ContentEntry<Pick<Fields, Key>, Model>>;
191
+ get<const Key extends FieldName<Fields> = FieldName<Fields>>(id: string, options?: Pick<ListOptions<Key, Fields>, "select" | "locale" | "signal" | "includeRemote">): Promise<ContentEntry<Pick<Fields, Key>, Model>>;
129
192
  }
130
193
  export interface CMSClient<Models extends ModelMap<Models> = UntypedModels> {
131
- content<Model extends Extract<keyof Models, string>>(model: Model): ContentCollection<Models[Model]>;
194
+ content<Model extends Extract<keyof Models, string>>(model: Model): ContentCollection<Models[Model], Model>;
132
195
  }
133
196
  /** A draft may omit required fields or contain nulls until it is published. */
134
197
  export type PreviewModels<Models> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemonadecms/client",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-independent content delivery and developer tooling for Lemonade CMS",
5
5
  "license": "MIT",
6
6
  "homepage": "https://lemonadecms.com/guides/sdk",
@@ -44,6 +44,10 @@
44
44
  "./preview": {
45
45
  "types": "./dist/preview.d.ts",
46
46
  "default": "./dist/preview.js"
47
+ },
48
+ "./composer": {
49
+ "types": "./dist/composer.d.ts",
50
+ "default": "./dist/composer.js"
47
51
  }
48
52
  },
49
53
  "bin": {