lazypock 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -9
- package/dist/{chunk-JYKRCEC3.js → chunk-LVEOMG6A.js} +36 -5
- package/dist/chunk-LVEOMG6A.js.map +1 -0
- package/dist/cli.cjs +35 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.global.js +35 -4
- package/dist/cli.global.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +35 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -40
- package/dist/index.d.ts +154 -40
- package/dist/index.global.js +35 -4
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen.ts +50 -4
- package/src/collection.ts +73 -25
- package/src/types.ts +261 -38
- package/dist/chunk-JYKRCEC3.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -105,16 +105,106 @@ type FieldKey<T> = Extract<keyof T, string> extends never ? string : Extract<key
|
|
|
105
105
|
type FilterOp = "=" | "!=" | "~" | "!~" | ">" | ">=" | "<" | "<=";
|
|
106
106
|
/** Optional whitespace around a filter operator (`project=x` and `project = x` both compile). */
|
|
107
107
|
type FilterWs = "" | " ";
|
|
108
|
+
/** Trim one-or-more leading/trailing spaces off a string literal. */
|
|
109
|
+
type Trim<S extends string> = S extends ` ${infer R}` ? Trim<R> : S extends `${infer L} ` ? Trim<L> : S;
|
|
110
|
+
/** `never` when the string contains a single-quote. */
|
|
111
|
+
type NoQuote<S extends string> = S extends `${string}'${string}` ? never : S;
|
|
112
|
+
/** `never` when the string contains a double-quote. */
|
|
113
|
+
type NoDQuote<S extends string> = S extends `${string}"${string}` ? never : S;
|
|
114
|
+
/** Single decimal digits — used to consume numeric values char-by-char. */
|
|
115
|
+
type Digits = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
108
116
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
117
|
+
* Consume a leading number (digits plus an optional decimal part) and
|
|
118
|
+
* return the remainder. `\`${number}\`` is NOT used in template positions:
|
|
119
|
+
* it matches a single digit, leaving `"0"` behind for `"10"`.
|
|
111
120
|
*/
|
|
112
|
-
type
|
|
121
|
+
type ConsumeNum<S extends string> = S extends `${infer C}${infer R}` ? C extends Digits ? ConsumeNum<R> : C extends "." ? R extends `${Digits}${string}` ? ConsumeNum<R> : S : S : S;
|
|
113
122
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
123
|
+
* Consume chars up to (not including) the first banned char and return the
|
|
124
|
+
* remainder. Bounds dot-paths so a path can't swallow a following clause.
|
|
116
125
|
*/
|
|
117
|
-
type
|
|
126
|
+
type ConsumeUntil<S extends string, Ban extends string> = S extends `${infer C}${infer R}` ? C extends Ban ? S : ConsumeUntil<R, Ban> : S;
|
|
127
|
+
/** Chars that terminate a relation dot-path. */
|
|
128
|
+
type PathBan = " " | "&" | "|" | "(" | ")" | "'" | "\"";
|
|
129
|
+
/**
|
|
130
|
+
* A single expandable token: a top-level key, or a dot-path into a relation
|
|
131
|
+
* (`author.user.profile`). Only the first segment is validated — nested
|
|
132
|
+
* segments reference fields of the *target* collection, which `T` doesn't
|
|
133
|
+
* describe.
|
|
134
|
+
*/
|
|
135
|
+
type ExpandField<T> = `${FieldKey<T>}${"" | `.${string}`}`;
|
|
136
|
+
/**
|
|
137
|
+
* Validate every comma-separated expand token; returns the original literal
|
|
138
|
+
* `O` when all tokens are valid, `never` otherwise. Spaces around commas are
|
|
139
|
+
* tolerated (the runtime trims tokens too).
|
|
140
|
+
*/
|
|
141
|
+
type ValidExpand<T, E extends string, O extends string = E> = string extends FieldKey<T> ? O : E extends `${infer Head},${infer Tail}` ? Trim<Head> extends ExpandField<T> ? ValidExpand<T, Trim<Tail>, O> : never : Trim<E> extends ExpandField<T> ? O : never;
|
|
142
|
+
/**
|
|
143
|
+
* Type-checked expand string: comma-separated relation field names,
|
|
144
|
+
* including nested dot-paths (e.g. `"author"`, `"author,category"`,
|
|
145
|
+
* `"author.user.profile"`). **Every** comma-separated token is validated —
|
|
146
|
+
* `expand: "author, nope"` is a compile error, not just the first token.
|
|
147
|
+
* Non-relation fields are warned about at runtime when a schema is
|
|
148
|
+
* available.
|
|
149
|
+
*/
|
|
150
|
+
type ExpandString<T, E extends string = never> = E & (E extends ValidExpand<T, E, E> ? E : never);
|
|
151
|
+
/** A single `[+|-]field` sort token. */
|
|
152
|
+
type SortField<T> = `${"" | "-" | "+"}${FieldKey<T>}`;
|
|
153
|
+
/**
|
|
154
|
+
* Validate every comma-separated sort token; returns the original literal
|
|
155
|
+
* `O` when all tokens are valid, `never` otherwise.
|
|
156
|
+
*/
|
|
157
|
+
type ValidSort<T, E extends string, O extends string = E> = string extends FieldKey<T> ? O : E extends `${infer Head},${infer Tail}` ? Trim<Head> extends SortField<T> ? ValidSort<T, Trim<Tail>, O> : never : Trim<E> extends SortField<T> ? O : never;
|
|
158
|
+
/**
|
|
159
|
+
* Type-checked sort string: `field`, `-field` (desc), `+field`, or
|
|
160
|
+
* comma-separated combinations (e.g. `"-created,title"`). **Every** token is
|
|
161
|
+
* validated — `sort: "title, nope"` is a compile error.
|
|
162
|
+
*/
|
|
163
|
+
type SortString<T, E extends string = never> = E & (E extends ValidSort<T, E, E> ? E : never);
|
|
164
|
+
/**
|
|
165
|
+
* A string value with no quotes, `&&`/`||`, or parens — the "bare" fallback
|
|
166
|
+
* for filter values (numbers, `true`/`false`/`null`, unquoted text).
|
|
167
|
+
* Applied against the *same* literal (`S extends SimpleVal<S>`) so the
|
|
168
|
+
* banned-char checks run on the concrete substring, not on the abstract
|
|
169
|
+
* `string` type.
|
|
170
|
+
*/
|
|
171
|
+
type SimpleVal<S extends string> = (S extends `${string}'${string}` ? never : S) & (S extends `${string}"${string}` ? never : S) & (S extends `${string}&&${string}` ? never : S) & (S extends `${string}||${string}` ? never : S) & (S extends `${string}(${string}` ? never : S) & (S extends `${string})${string}` ? never : S);
|
|
172
|
+
/**
|
|
173
|
+
* Field-reference value (`title = author` / `title = author.email`): a
|
|
174
|
+
* top-level key, optionally followed by a bounded dot-path. Returns the
|
|
175
|
+
* remainder after the value.
|
|
176
|
+
*/
|
|
177
|
+
type FFieldRef<T, S extends string> = S extends `${FieldKey<T>}${infer R}` ? R extends `.${infer Rest}` ? ConsumeUntil<Rest, PathBan> : R : never;
|
|
178
|
+
/**
|
|
179
|
+
* Consume one filter value (quoted string, number, boolean/null, field
|
|
180
|
+
* reference, or bare text) and return the remainder — the input left for the
|
|
181
|
+
* `&&`/`||`/`)`/end checks upstream.
|
|
182
|
+
*/
|
|
183
|
+
type FValue<T, S extends string> = (S extends `'${infer V}'${infer R}` ? (V extends NoQuote<V> ? R : never) : never) | (S extends `"${infer V}"${infer R}` ? (V extends NoDQuote<V> ? R : never) : never) | (S extends `${"true" | "false" | "null"}${infer R}` ? R : never) | (S extends `-${infer Rest}` ? Rest extends `${Digits}${string}` ? ConsumeNum<Rest> : never : S extends `${Digits}${string}` ? ConsumeNum<S> : never) | FFieldRef<T, S> | (S extends SimpleVal<S> ? "" : never);
|
|
184
|
+
/**
|
|
185
|
+
* Parse one `field op value` clause — either a plain top-level field or a
|
|
186
|
+
* bounded relation dot-path — and return the remainder. Spaces around the
|
|
187
|
+
* operator are optional (`project=x` / `project = x`).
|
|
188
|
+
*/
|
|
189
|
+
type FClause<T, S extends string> = S extends `${FieldKey<T>}${infer R}` ? R extends `${FilterWs}${FilterOp}${FilterWs}${infer R2}` ? FValue<T, Trim<R2>> : R extends `.${infer Rest}` ? ConsumeUntil<Rest, PathBan> extends infer R2 ? R2 extends `${FilterWs}${FilterOp}${FilterWs}${infer R3}` ? FValue<T, Trim<R3>> : never : never : never : never;
|
|
190
|
+
/** A clause or a parenthesised group; parens must balance and close. */
|
|
191
|
+
type FAtom<T, S extends string> = FClause<T, S> | (S extends `(${infer Inner}` ? FOr<T, Inner> extends infer R ? R extends `)${infer Tail}` ? Tail : never : never : never);
|
|
192
|
+
/** Optional `!` negation in front of an atom. */
|
|
193
|
+
type FNot<T, S extends string> = S extends `!${infer Tail}` ? FAtom<T, Tail> : FAtom<T, S>;
|
|
194
|
+
/**
|
|
195
|
+
* One or more atoms joined by `&&`. Returns `never` on parse failure (a
|
|
196
|
+
* failed atom must not masquerade as an empty remainder — `never extends ""`
|
|
197
|
+
* is true), the empty string on success, or the unconsumed remainder.
|
|
198
|
+
*/
|
|
199
|
+
type FAnd<T, S extends string> = FNot<T, S> extends infer R ? [R] extends [never] ? never : R extends "" ? "" : R extends `${FilterWs}&&${FilterWs}${infer Tail}` ? FAnd<T, Tail> : R : never;
|
|
200
|
+
/** One or more `&&`-chains joined by `||` — the top-level expression. */
|
|
201
|
+
type FOr<T, S extends string> = FAnd<T, S> extends infer R ? [R] extends [never] ? never : R extends "" ? "" : R extends `${FilterWs}||${FilterWs}${infer Tail}` ? FOr<T, Tail> : R : never;
|
|
202
|
+
/**
|
|
203
|
+
* Validate a full filter expression: the parser must consume the entire
|
|
204
|
+
* (trimmed) string. Compares the *literal* against the parse result rather
|
|
205
|
+
* than `extends ""` so a `never` parse failure can't satisfy the check.
|
|
206
|
+
*/
|
|
207
|
+
type ValidFilter<T, F extends string, O extends string = F> = string extends FieldKey<T> ? O : "" extends FOr<T, Trim<F>> ? O : never;
|
|
118
208
|
/**
|
|
119
209
|
* Type-checked filter expression (PocketBase syntax).
|
|
120
210
|
*
|
|
@@ -125,52 +215,62 @@ type FilterClause<T> = `${FilterField<T>}${FilterWs}${FilterOp}${FilterWs}${stri
|
|
|
125
215
|
* getList(1, 20, { filter: "author.email = 'x'" }) // relation dot-path
|
|
126
216
|
* ```
|
|
127
217
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type SortField<T> = `${"" | "-" | "+"}${FieldKey<T>}`;
|
|
136
|
-
/**
|
|
137
|
-
* Type-checked sort string: `field`, `-field` (desc), `+field`, or
|
|
138
|
-
* comma-separated combinations (e.g. `"-created,title"`).
|
|
218
|
+
* **Every** `field op value` clause is validated — the field name, the
|
|
219
|
+
* operator, and the clause structure (`&&`, `||`, `!`, parens, and nested
|
|
220
|
+
* groups all typecheck). Quoted values may contain `&&`/`||` (e.g.
|
|
221
|
+
* `title ~ 'a && b'`); bare unquoted values are bounded so they can't hide a
|
|
222
|
+
* following clause. When the expression is a plain dynamic `string` (e.g.
|
|
223
|
+
* `filter: \`title=${search}\``), the guard falls back to accepting it
|
|
224
|
+
* verbatim — the server decides at runtime.
|
|
139
225
|
*/
|
|
140
|
-
type
|
|
226
|
+
type FilterString<T, F extends string = never> = F & (F extends ValidFilter<T, F, F> ? F : string extends F ? F : never);
|
|
141
227
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* `"author.user.profile"`). Non-relation fields are warned about at
|
|
145
|
-
* runtime when a schema is available.
|
|
228
|
+
* First segment of a single expand token (`"author.user"` → `"author"`) —
|
|
229
|
+
* the key PocketBase uses on the expanded record.
|
|
146
230
|
*/
|
|
147
|
-
type
|
|
231
|
+
type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
|
|
232
|
+
/** Top-level keys of a comma-separated expand literal (`"a,b"` → `"a" | "b"`). */
|
|
233
|
+
type ExpandKeys<S extends string> = S extends "" ? never : S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
|
|
148
234
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* `
|
|
235
|
+
* The `expand` property attached to records when `expand` is used. Keys are
|
|
236
|
+
* parsed from the caller's expand literal (dot-paths collapse to their first
|
|
237
|
+
* segment), so `record.expand.user` typechecks and the nested record is
|
|
238
|
+
* `unknown` (its shape belongs to the target collection). Falls back to
|
|
239
|
+
* `Record<string, unknown>` when the expand value is dynamic or absent.
|
|
152
240
|
*/
|
|
153
|
-
type
|
|
241
|
+
type ExpandObj<E extends string> = [
|
|
242
|
+
E
|
|
243
|
+
] extends [never] ? Record<string, unknown> : [
|
|
244
|
+
string
|
|
245
|
+
] extends [E] ? Record<string, unknown> : Partial<Record<ExpandKeys<E>, unknown>>;
|
|
154
246
|
/**
|
|
155
247
|
* Query options for list/read operations, typed against a record shape `T`.
|
|
156
248
|
*
|
|
157
249
|
* `filter`, `sort`, `expand` and `fields` are recognized; any other key is
|
|
158
250
|
* passed through as a raw query parameter (PocketBase-compatible).
|
|
251
|
+
*
|
|
252
|
+
* The `E`/`S`/`F` generics are inferred from the caller's literals; the
|
|
253
|
+
* {@link CollectionService} methods constrain them with the validated
|
|
254
|
+
* {@link ExpandString} / {@link SortString} / {@link FilterString} guards.
|
|
255
|
+
*/
|
|
256
|
+
/**
|
|
257
|
+
* `_T` is kept for backward compatibility (`ListOptions<Post>`) but the
|
|
258
|
+
* per-field validation now runs through the `E`/`S`/`F` generics.
|
|
159
259
|
*/
|
|
160
|
-
interface ListOptions<
|
|
260
|
+
interface ListOptions<_T = ApiRecord, E extends string = never, S extends string = never, F extends string = never> {
|
|
161
261
|
/**
|
|
162
262
|
* PocketBase filter expression. Field names + operators are type-checked
|
|
163
263
|
* when `T` is a concrete shape.
|
|
164
264
|
*/
|
|
165
|
-
filter?: FilterString<
|
|
265
|
+
filter?: FilterString<_T, F>;
|
|
166
266
|
/**
|
|
167
267
|
* Sort field(s): `field`, `-field` (descending), comma-separated.
|
|
168
268
|
*/
|
|
169
|
-
sort?: SortString<
|
|
269
|
+
sort?: SortString<_T, S>;
|
|
170
270
|
/**
|
|
171
271
|
* Comma-separated relation field names to expand.
|
|
172
272
|
*/
|
|
173
|
-
expand?: ExpandString<
|
|
273
|
+
expand?: ExpandString<_T, E>;
|
|
174
274
|
/**
|
|
175
275
|
* Explicit field projection (overrides {@link CollectionService.select}).
|
|
176
276
|
*/
|
|
@@ -183,9 +283,9 @@ interface ListOptions<T = ApiRecord> {
|
|
|
183
283
|
/**
|
|
184
284
|
* Options for single-record reads (`getOne`): expand + explicit fields.
|
|
185
285
|
*/
|
|
186
|
-
interface ReadOptions<
|
|
286
|
+
interface ReadOptions<_T = ApiRecord, E extends string = never> {
|
|
187
287
|
/** Comma-separated relation field names to expand. */
|
|
188
|
-
expand?: ExpandString<
|
|
288
|
+
expand?: ExpandString<_T, E>;
|
|
189
289
|
/** Explicit field projection (overrides {@link CollectionService.select}). */
|
|
190
290
|
fields?: string;
|
|
191
291
|
}
|
|
@@ -534,8 +634,14 @@ interface RealtimeSubscribeOptions {
|
|
|
534
634
|
* Get an instance via {@link LazypockClient.collection}.
|
|
535
635
|
*
|
|
536
636
|
* @typeParam T — The record shape for this collection. Defaults to {@link ApiRecord}.
|
|
637
|
+
* @typeParam TData — Write-only create/update payload shape (defaults to `never`,
|
|
638
|
+
* which falls back to `T`-derived create data).
|
|
639
|
+
* @typeParam TFields — The key set accepted by `filter`/`sort`/`expand`/`select`.
|
|
640
|
+
* Defaults to `T`. The codegen client binds a *QueryFields type that includes
|
|
641
|
+
* hidden fields — excluded from the read model but still expandable/filterable
|
|
642
|
+
* at runtime — so `expand: "hiddenRelation"` typechecks.
|
|
537
643
|
*/
|
|
538
|
-
declare class CollectionService<T = ApiRecord, TData = never> {
|
|
644
|
+
declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
539
645
|
private http;
|
|
540
646
|
private collectionName;
|
|
541
647
|
private authStore?;
|
|
@@ -574,7 +680,7 @@ declare class CollectionService<T = ApiRecord, TData = never> {
|
|
|
574
680
|
* `fields` param — the server returns every non-password field.
|
|
575
681
|
* Pass `select("*")` to restore the default after a projection.
|
|
576
682
|
*/
|
|
577
|
-
select<K extends FieldKey<
|
|
683
|
+
select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields>;
|
|
578
684
|
/**
|
|
579
685
|
* Fields to send with reads:
|
|
580
686
|
* 1. explicit `options.fields` (caller wins)
|
|
@@ -607,27 +713,35 @@ declare class CollectionService<T = ApiRecord, TData = never> {
|
|
|
607
713
|
* @param perPage Records per page (default 30).
|
|
608
714
|
* @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
|
|
609
715
|
*/
|
|
610
|
-
getList<T2 = T>(page?: number, perPage?: number, options?: ListOptions<
|
|
716
|
+
getList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(page?: number, perPage?: number, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<ListResult<T2 & {
|
|
717
|
+
expand?: ExpandObj<E>;
|
|
718
|
+
}> | null>;
|
|
611
719
|
/**
|
|
612
720
|
* Fetch all records at once (auto-paginates). Mirrors PocketBase's
|
|
613
721
|
* `pb.collection(name).getFullList()`.
|
|
614
722
|
*
|
|
615
723
|
* @param options Query params (`sort`, `filter`, `batch`, etc.) + request options.
|
|
616
724
|
*/
|
|
617
|
-
getFullList<T2 = T>(options?: ListOptions<
|
|
725
|
+
getFullList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<Array<T2 & {
|
|
726
|
+
expand?: ExpandObj<E>;
|
|
727
|
+
}>>;
|
|
618
728
|
/**
|
|
619
729
|
* Fetch the first record matching a filter (PocketBase `getFirstListItem`).
|
|
620
730
|
*
|
|
621
731
|
* @param filter Filter expression (e.g. `title = 'x'`).
|
|
622
732
|
* @param options Optional request options.
|
|
623
733
|
*/
|
|
624
|
-
getFirstListItem<T2 = T>(filter: FilterString<
|
|
734
|
+
getFirstListItem<T2 = T, F extends string = never, E extends string = never, S extends string = never>(filter: FilterString<TFields, F>, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<(T2 & {
|
|
735
|
+
expand?: ExpandObj<E>;
|
|
736
|
+
}) | null>;
|
|
625
737
|
/**
|
|
626
738
|
* Get a single record by ID.
|
|
627
739
|
* @param id Record ID.
|
|
628
740
|
* @param options Optional request options.
|
|
629
741
|
*/
|
|
630
|
-
getOne(id: string, options?: ReadOptions<
|
|
742
|
+
getOne<E extends string = never>(id: string, options?: ReadOptions<TFields, E> & RequestOptions): Promise<(T & {
|
|
743
|
+
expand?: ExpandObj<E>;
|
|
744
|
+
}) | null>;
|
|
631
745
|
/**
|
|
632
746
|
* Create a new record.
|
|
633
747
|
* @param data Record fields. When `T` is a concrete shape (e.g. a generated
|
|
@@ -680,7 +794,7 @@ declare class CollectionService<T = ApiRecord, TData = never> {
|
|
|
680
794
|
* `types.schemas`). Enables schema-aware defaults: hidden fields are
|
|
681
795
|
* excluded from responses, select/expand are validated.
|
|
682
796
|
*/
|
|
683
|
-
withSchema(schema: CollectionSchema): CollectionService<T, TData>;
|
|
797
|
+
withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields>;
|
|
684
798
|
/**
|
|
685
799
|
* Subscribe to realtime changes for this collection.
|
|
686
800
|
* The event's `action` is one of `"create" | "update" | "delete"` and the
|
package/dist/index.d.ts
CHANGED
|
@@ -105,16 +105,106 @@ type FieldKey<T> = Extract<keyof T, string> extends never ? string : Extract<key
|
|
|
105
105
|
type FilterOp = "=" | "!=" | "~" | "!~" | ">" | ">=" | "<" | "<=";
|
|
106
106
|
/** Optional whitespace around a filter operator (`project=x` and `project = x` both compile). */
|
|
107
107
|
type FilterWs = "" | " ";
|
|
108
|
+
/** Trim one-or-more leading/trailing spaces off a string literal. */
|
|
109
|
+
type Trim<S extends string> = S extends ` ${infer R}` ? Trim<R> : S extends `${infer L} ` ? Trim<L> : S;
|
|
110
|
+
/** `never` when the string contains a single-quote. */
|
|
111
|
+
type NoQuote<S extends string> = S extends `${string}'${string}` ? never : S;
|
|
112
|
+
/** `never` when the string contains a double-quote. */
|
|
113
|
+
type NoDQuote<S extends string> = S extends `${string}"${string}` ? never : S;
|
|
114
|
+
/** Single decimal digits — used to consume numeric values char-by-char. */
|
|
115
|
+
type Digits = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
108
116
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
117
|
+
* Consume a leading number (digits plus an optional decimal part) and
|
|
118
|
+
* return the remainder. `\`${number}\`` is NOT used in template positions:
|
|
119
|
+
* it matches a single digit, leaving `"0"` behind for `"10"`.
|
|
111
120
|
*/
|
|
112
|
-
type
|
|
121
|
+
type ConsumeNum<S extends string> = S extends `${infer C}${infer R}` ? C extends Digits ? ConsumeNum<R> : C extends "." ? R extends `${Digits}${string}` ? ConsumeNum<R> : S : S : S;
|
|
113
122
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
123
|
+
* Consume chars up to (not including) the first banned char and return the
|
|
124
|
+
* remainder. Bounds dot-paths so a path can't swallow a following clause.
|
|
116
125
|
*/
|
|
117
|
-
type
|
|
126
|
+
type ConsumeUntil<S extends string, Ban extends string> = S extends `${infer C}${infer R}` ? C extends Ban ? S : ConsumeUntil<R, Ban> : S;
|
|
127
|
+
/** Chars that terminate a relation dot-path. */
|
|
128
|
+
type PathBan = " " | "&" | "|" | "(" | ")" | "'" | "\"";
|
|
129
|
+
/**
|
|
130
|
+
* A single expandable token: a top-level key, or a dot-path into a relation
|
|
131
|
+
* (`author.user.profile`). Only the first segment is validated — nested
|
|
132
|
+
* segments reference fields of the *target* collection, which `T` doesn't
|
|
133
|
+
* describe.
|
|
134
|
+
*/
|
|
135
|
+
type ExpandField<T> = `${FieldKey<T>}${"" | `.${string}`}`;
|
|
136
|
+
/**
|
|
137
|
+
* Validate every comma-separated expand token; returns the original literal
|
|
138
|
+
* `O` when all tokens are valid, `never` otherwise. Spaces around commas are
|
|
139
|
+
* tolerated (the runtime trims tokens too).
|
|
140
|
+
*/
|
|
141
|
+
type ValidExpand<T, E extends string, O extends string = E> = string extends FieldKey<T> ? O : E extends `${infer Head},${infer Tail}` ? Trim<Head> extends ExpandField<T> ? ValidExpand<T, Trim<Tail>, O> : never : Trim<E> extends ExpandField<T> ? O : never;
|
|
142
|
+
/**
|
|
143
|
+
* Type-checked expand string: comma-separated relation field names,
|
|
144
|
+
* including nested dot-paths (e.g. `"author"`, `"author,category"`,
|
|
145
|
+
* `"author.user.profile"`). **Every** comma-separated token is validated —
|
|
146
|
+
* `expand: "author, nope"` is a compile error, not just the first token.
|
|
147
|
+
* Non-relation fields are warned about at runtime when a schema is
|
|
148
|
+
* available.
|
|
149
|
+
*/
|
|
150
|
+
type ExpandString<T, E extends string = never> = E & (E extends ValidExpand<T, E, E> ? E : never);
|
|
151
|
+
/** A single `[+|-]field` sort token. */
|
|
152
|
+
type SortField<T> = `${"" | "-" | "+"}${FieldKey<T>}`;
|
|
153
|
+
/**
|
|
154
|
+
* Validate every comma-separated sort token; returns the original literal
|
|
155
|
+
* `O` when all tokens are valid, `never` otherwise.
|
|
156
|
+
*/
|
|
157
|
+
type ValidSort<T, E extends string, O extends string = E> = string extends FieldKey<T> ? O : E extends `${infer Head},${infer Tail}` ? Trim<Head> extends SortField<T> ? ValidSort<T, Trim<Tail>, O> : never : Trim<E> extends SortField<T> ? O : never;
|
|
158
|
+
/**
|
|
159
|
+
* Type-checked sort string: `field`, `-field` (desc), `+field`, or
|
|
160
|
+
* comma-separated combinations (e.g. `"-created,title"`). **Every** token is
|
|
161
|
+
* validated — `sort: "title, nope"` is a compile error.
|
|
162
|
+
*/
|
|
163
|
+
type SortString<T, E extends string = never> = E & (E extends ValidSort<T, E, E> ? E : never);
|
|
164
|
+
/**
|
|
165
|
+
* A string value with no quotes, `&&`/`||`, or parens — the "bare" fallback
|
|
166
|
+
* for filter values (numbers, `true`/`false`/`null`, unquoted text).
|
|
167
|
+
* Applied against the *same* literal (`S extends SimpleVal<S>`) so the
|
|
168
|
+
* banned-char checks run on the concrete substring, not on the abstract
|
|
169
|
+
* `string` type.
|
|
170
|
+
*/
|
|
171
|
+
type SimpleVal<S extends string> = (S extends `${string}'${string}` ? never : S) & (S extends `${string}"${string}` ? never : S) & (S extends `${string}&&${string}` ? never : S) & (S extends `${string}||${string}` ? never : S) & (S extends `${string}(${string}` ? never : S) & (S extends `${string})${string}` ? never : S);
|
|
172
|
+
/**
|
|
173
|
+
* Field-reference value (`title = author` / `title = author.email`): a
|
|
174
|
+
* top-level key, optionally followed by a bounded dot-path. Returns the
|
|
175
|
+
* remainder after the value.
|
|
176
|
+
*/
|
|
177
|
+
type FFieldRef<T, S extends string> = S extends `${FieldKey<T>}${infer R}` ? R extends `.${infer Rest}` ? ConsumeUntil<Rest, PathBan> : R : never;
|
|
178
|
+
/**
|
|
179
|
+
* Consume one filter value (quoted string, number, boolean/null, field
|
|
180
|
+
* reference, or bare text) and return the remainder — the input left for the
|
|
181
|
+
* `&&`/`||`/`)`/end checks upstream.
|
|
182
|
+
*/
|
|
183
|
+
type FValue<T, S extends string> = (S extends `'${infer V}'${infer R}` ? (V extends NoQuote<V> ? R : never) : never) | (S extends `"${infer V}"${infer R}` ? (V extends NoDQuote<V> ? R : never) : never) | (S extends `${"true" | "false" | "null"}${infer R}` ? R : never) | (S extends `-${infer Rest}` ? Rest extends `${Digits}${string}` ? ConsumeNum<Rest> : never : S extends `${Digits}${string}` ? ConsumeNum<S> : never) | FFieldRef<T, S> | (S extends SimpleVal<S> ? "" : never);
|
|
184
|
+
/**
|
|
185
|
+
* Parse one `field op value` clause — either a plain top-level field or a
|
|
186
|
+
* bounded relation dot-path — and return the remainder. Spaces around the
|
|
187
|
+
* operator are optional (`project=x` / `project = x`).
|
|
188
|
+
*/
|
|
189
|
+
type FClause<T, S extends string> = S extends `${FieldKey<T>}${infer R}` ? R extends `${FilterWs}${FilterOp}${FilterWs}${infer R2}` ? FValue<T, Trim<R2>> : R extends `.${infer Rest}` ? ConsumeUntil<Rest, PathBan> extends infer R2 ? R2 extends `${FilterWs}${FilterOp}${FilterWs}${infer R3}` ? FValue<T, Trim<R3>> : never : never : never : never;
|
|
190
|
+
/** A clause or a parenthesised group; parens must balance and close. */
|
|
191
|
+
type FAtom<T, S extends string> = FClause<T, S> | (S extends `(${infer Inner}` ? FOr<T, Inner> extends infer R ? R extends `)${infer Tail}` ? Tail : never : never : never);
|
|
192
|
+
/** Optional `!` negation in front of an atom. */
|
|
193
|
+
type FNot<T, S extends string> = S extends `!${infer Tail}` ? FAtom<T, Tail> : FAtom<T, S>;
|
|
194
|
+
/**
|
|
195
|
+
* One or more atoms joined by `&&`. Returns `never` on parse failure (a
|
|
196
|
+
* failed atom must not masquerade as an empty remainder — `never extends ""`
|
|
197
|
+
* is true), the empty string on success, or the unconsumed remainder.
|
|
198
|
+
*/
|
|
199
|
+
type FAnd<T, S extends string> = FNot<T, S> extends infer R ? [R] extends [never] ? never : R extends "" ? "" : R extends `${FilterWs}&&${FilterWs}${infer Tail}` ? FAnd<T, Tail> : R : never;
|
|
200
|
+
/** One or more `&&`-chains joined by `||` — the top-level expression. */
|
|
201
|
+
type FOr<T, S extends string> = FAnd<T, S> extends infer R ? [R] extends [never] ? never : R extends "" ? "" : R extends `${FilterWs}||${FilterWs}${infer Tail}` ? FOr<T, Tail> : R : never;
|
|
202
|
+
/**
|
|
203
|
+
* Validate a full filter expression: the parser must consume the entire
|
|
204
|
+
* (trimmed) string. Compares the *literal* against the parse result rather
|
|
205
|
+
* than `extends ""` so a `never` parse failure can't satisfy the check.
|
|
206
|
+
*/
|
|
207
|
+
type ValidFilter<T, F extends string, O extends string = F> = string extends FieldKey<T> ? O : "" extends FOr<T, Trim<F>> ? O : never;
|
|
118
208
|
/**
|
|
119
209
|
* Type-checked filter expression (PocketBase syntax).
|
|
120
210
|
*
|
|
@@ -125,52 +215,62 @@ type FilterClause<T> = `${FilterField<T>}${FilterWs}${FilterOp}${FilterWs}${stri
|
|
|
125
215
|
* getList(1, 20, { filter: "author.email = 'x'" }) // relation dot-path
|
|
126
216
|
* ```
|
|
127
217
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type SortField<T> = `${"" | "-" | "+"}${FieldKey<T>}`;
|
|
136
|
-
/**
|
|
137
|
-
* Type-checked sort string: `field`, `-field` (desc), `+field`, or
|
|
138
|
-
* comma-separated combinations (e.g. `"-created,title"`).
|
|
218
|
+
* **Every** `field op value` clause is validated — the field name, the
|
|
219
|
+
* operator, and the clause structure (`&&`, `||`, `!`, parens, and nested
|
|
220
|
+
* groups all typecheck). Quoted values may contain `&&`/`||` (e.g.
|
|
221
|
+
* `title ~ 'a && b'`); bare unquoted values are bounded so they can't hide a
|
|
222
|
+
* following clause. When the expression is a plain dynamic `string` (e.g.
|
|
223
|
+
* `filter: \`title=${search}\``), the guard falls back to accepting it
|
|
224
|
+
* verbatim — the server decides at runtime.
|
|
139
225
|
*/
|
|
140
|
-
type
|
|
226
|
+
type FilterString<T, F extends string = never> = F & (F extends ValidFilter<T, F, F> ? F : string extends F ? F : never);
|
|
141
227
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* `"author.user.profile"`). Non-relation fields are warned about at
|
|
145
|
-
* runtime when a schema is available.
|
|
228
|
+
* First segment of a single expand token (`"author.user"` → `"author"`) —
|
|
229
|
+
* the key PocketBase uses on the expanded record.
|
|
146
230
|
*/
|
|
147
|
-
type
|
|
231
|
+
type TopExpandKey<S extends string> = S extends `${infer A}.${string}` ? A : S;
|
|
232
|
+
/** Top-level keys of a comma-separated expand literal (`"a,b"` → `"a" | "b"`). */
|
|
233
|
+
type ExpandKeys<S extends string> = S extends "" ? never : S extends `${infer H},${infer T}` ? TopExpandKey<H> | ExpandKeys<T> : TopExpandKey<S>;
|
|
148
234
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* `
|
|
235
|
+
* The `expand` property attached to records when `expand` is used. Keys are
|
|
236
|
+
* parsed from the caller's expand literal (dot-paths collapse to their first
|
|
237
|
+
* segment), so `record.expand.user` typechecks and the nested record is
|
|
238
|
+
* `unknown` (its shape belongs to the target collection). Falls back to
|
|
239
|
+
* `Record<string, unknown>` when the expand value is dynamic or absent.
|
|
152
240
|
*/
|
|
153
|
-
type
|
|
241
|
+
type ExpandObj<E extends string> = [
|
|
242
|
+
E
|
|
243
|
+
] extends [never] ? Record<string, unknown> : [
|
|
244
|
+
string
|
|
245
|
+
] extends [E] ? Record<string, unknown> : Partial<Record<ExpandKeys<E>, unknown>>;
|
|
154
246
|
/**
|
|
155
247
|
* Query options for list/read operations, typed against a record shape `T`.
|
|
156
248
|
*
|
|
157
249
|
* `filter`, `sort`, `expand` and `fields` are recognized; any other key is
|
|
158
250
|
* passed through as a raw query parameter (PocketBase-compatible).
|
|
251
|
+
*
|
|
252
|
+
* The `E`/`S`/`F` generics are inferred from the caller's literals; the
|
|
253
|
+
* {@link CollectionService} methods constrain them with the validated
|
|
254
|
+
* {@link ExpandString} / {@link SortString} / {@link FilterString} guards.
|
|
255
|
+
*/
|
|
256
|
+
/**
|
|
257
|
+
* `_T` is kept for backward compatibility (`ListOptions<Post>`) but the
|
|
258
|
+
* per-field validation now runs through the `E`/`S`/`F` generics.
|
|
159
259
|
*/
|
|
160
|
-
interface ListOptions<
|
|
260
|
+
interface ListOptions<_T = ApiRecord, E extends string = never, S extends string = never, F extends string = never> {
|
|
161
261
|
/**
|
|
162
262
|
* PocketBase filter expression. Field names + operators are type-checked
|
|
163
263
|
* when `T` is a concrete shape.
|
|
164
264
|
*/
|
|
165
|
-
filter?: FilterString<
|
|
265
|
+
filter?: FilterString<_T, F>;
|
|
166
266
|
/**
|
|
167
267
|
* Sort field(s): `field`, `-field` (descending), comma-separated.
|
|
168
268
|
*/
|
|
169
|
-
sort?: SortString<
|
|
269
|
+
sort?: SortString<_T, S>;
|
|
170
270
|
/**
|
|
171
271
|
* Comma-separated relation field names to expand.
|
|
172
272
|
*/
|
|
173
|
-
expand?: ExpandString<
|
|
273
|
+
expand?: ExpandString<_T, E>;
|
|
174
274
|
/**
|
|
175
275
|
* Explicit field projection (overrides {@link CollectionService.select}).
|
|
176
276
|
*/
|
|
@@ -183,9 +283,9 @@ interface ListOptions<T = ApiRecord> {
|
|
|
183
283
|
/**
|
|
184
284
|
* Options for single-record reads (`getOne`): expand + explicit fields.
|
|
185
285
|
*/
|
|
186
|
-
interface ReadOptions<
|
|
286
|
+
interface ReadOptions<_T = ApiRecord, E extends string = never> {
|
|
187
287
|
/** Comma-separated relation field names to expand. */
|
|
188
|
-
expand?: ExpandString<
|
|
288
|
+
expand?: ExpandString<_T, E>;
|
|
189
289
|
/** Explicit field projection (overrides {@link CollectionService.select}). */
|
|
190
290
|
fields?: string;
|
|
191
291
|
}
|
|
@@ -534,8 +634,14 @@ interface RealtimeSubscribeOptions {
|
|
|
534
634
|
* Get an instance via {@link LazypockClient.collection}.
|
|
535
635
|
*
|
|
536
636
|
* @typeParam T — The record shape for this collection. Defaults to {@link ApiRecord}.
|
|
637
|
+
* @typeParam TData — Write-only create/update payload shape (defaults to `never`,
|
|
638
|
+
* which falls back to `T`-derived create data).
|
|
639
|
+
* @typeParam TFields — The key set accepted by `filter`/`sort`/`expand`/`select`.
|
|
640
|
+
* Defaults to `T`. The codegen client binds a *QueryFields type that includes
|
|
641
|
+
* hidden fields — excluded from the read model but still expandable/filterable
|
|
642
|
+
* at runtime — so `expand: "hiddenRelation"` typechecks.
|
|
537
643
|
*/
|
|
538
|
-
declare class CollectionService<T = ApiRecord, TData = never> {
|
|
644
|
+
declare class CollectionService<T = ApiRecord, TData = never, TFields = T> {
|
|
539
645
|
private http;
|
|
540
646
|
private collectionName;
|
|
541
647
|
private authStore?;
|
|
@@ -574,7 +680,7 @@ declare class CollectionService<T = ApiRecord, TData = never> {
|
|
|
574
680
|
* `fields` param — the server returns every non-password field.
|
|
575
681
|
* Pass `select("*")` to restore the default after a projection.
|
|
576
682
|
*/
|
|
577
|
-
select<K extends FieldKey<
|
|
683
|
+
select<K extends FieldKey<TFields> | "*">(...fields: K[]): CollectionService<T, TData, TFields>;
|
|
578
684
|
/**
|
|
579
685
|
* Fields to send with reads:
|
|
580
686
|
* 1. explicit `options.fields` (caller wins)
|
|
@@ -607,27 +713,35 @@ declare class CollectionService<T = ApiRecord, TData = never> {
|
|
|
607
713
|
* @param perPage Records per page (default 30).
|
|
608
714
|
* @param options Query params (`filter`, `sort`, `expand`, `fields`) + request options.
|
|
609
715
|
*/
|
|
610
|
-
getList<T2 = T>(page?: number, perPage?: number, options?: ListOptions<
|
|
716
|
+
getList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(page?: number, perPage?: number, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<ListResult<T2 & {
|
|
717
|
+
expand?: ExpandObj<E>;
|
|
718
|
+
}> | null>;
|
|
611
719
|
/**
|
|
612
720
|
* Fetch all records at once (auto-paginates). Mirrors PocketBase's
|
|
613
721
|
* `pb.collection(name).getFullList()`.
|
|
614
722
|
*
|
|
615
723
|
* @param options Query params (`sort`, `filter`, `batch`, etc.) + request options.
|
|
616
724
|
*/
|
|
617
|
-
getFullList<T2 = T>(options?: ListOptions<
|
|
725
|
+
getFullList<T2 = T, E extends string = never, S extends string = never, F extends string = never>(options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<Array<T2 & {
|
|
726
|
+
expand?: ExpandObj<E>;
|
|
727
|
+
}>>;
|
|
618
728
|
/**
|
|
619
729
|
* Fetch the first record matching a filter (PocketBase `getFirstListItem`).
|
|
620
730
|
*
|
|
621
731
|
* @param filter Filter expression (e.g. `title = 'x'`).
|
|
622
732
|
* @param options Optional request options.
|
|
623
733
|
*/
|
|
624
|
-
getFirstListItem<T2 = T>(filter: FilterString<
|
|
734
|
+
getFirstListItem<T2 = T, F extends string = never, E extends string = never, S extends string = never>(filter: FilterString<TFields, F>, options?: ListOptions<TFields, E, S, F> & RequestOptions): Promise<(T2 & {
|
|
735
|
+
expand?: ExpandObj<E>;
|
|
736
|
+
}) | null>;
|
|
625
737
|
/**
|
|
626
738
|
* Get a single record by ID.
|
|
627
739
|
* @param id Record ID.
|
|
628
740
|
* @param options Optional request options.
|
|
629
741
|
*/
|
|
630
|
-
getOne(id: string, options?: ReadOptions<
|
|
742
|
+
getOne<E extends string = never>(id: string, options?: ReadOptions<TFields, E> & RequestOptions): Promise<(T & {
|
|
743
|
+
expand?: ExpandObj<E>;
|
|
744
|
+
}) | null>;
|
|
631
745
|
/**
|
|
632
746
|
* Create a new record.
|
|
633
747
|
* @param data Record fields. When `T` is a concrete shape (e.g. a generated
|
|
@@ -680,7 +794,7 @@ declare class CollectionService<T = ApiRecord, TData = never> {
|
|
|
680
794
|
* `types.schemas`). Enables schema-aware defaults: hidden fields are
|
|
681
795
|
* excluded from responses, select/expand are validated.
|
|
682
796
|
*/
|
|
683
|
-
withSchema(schema: CollectionSchema): CollectionService<T, TData>;
|
|
797
|
+
withSchema(schema: CollectionSchema): CollectionService<T, TData, TFields>;
|
|
684
798
|
/**
|
|
685
799
|
* Subscribe to realtime changes for this collection.
|
|
686
800
|
* The event's `action` is one of `"create" | "update" | "delete"` and the
|