@org-quicko/silo-client 1.0.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/README.md +313 -0
- package/dist/collections/collection-definition.d.cts +10 -0
- package/dist/collections/collection-definition.d.ts +10 -0
- package/dist/collections/collection-handle.d.cts +54 -0
- package/dist/collections/collection-handle.d.ts +54 -0
- package/dist/collections/collection-schema.d.cts +16 -0
- package/dist/collections/collection-schema.d.ts +16 -0
- package/dist/collections/collection-summary.d.cts +10 -0
- package/dist/collections/collection-summary.d.ts +10 -0
- package/dist/collections/collections.d.cts +19 -0
- package/dist/collections/collections.d.ts +19 -0
- package/dist/collections/json-schema.d.cts +9 -0
- package/dist/collections/json-schema.d.ts +9 -0
- package/dist/collections/reserved-field-names.d.cts +9 -0
- package/dist/collections/reserved-field-names.d.ts +9 -0
- package/dist/entries/entry-base.d.cts +31 -0
- package/dist/entries/entry-base.d.ts +31 -0
- package/dist/entries/entry-list-query.d.cts +11 -0
- package/dist/entries/entry-list-query.d.ts +11 -0
- package/dist/entries/entry-mapper.d.cts +13 -0
- package/dist/entries/entry-mapper.d.ts +13 -0
- package/dist/entries/entry-page-stream.d.cts +14 -0
- package/dist/entries/entry-page-stream.d.ts +14 -0
- package/dist/entries/entry-page.d.cts +16 -0
- package/dist/entries/entry-page.d.ts +16 -0
- package/dist/entries/entry-payload.d.cts +12 -0
- package/dist/entries/entry-payload.d.ts +12 -0
- package/dist/entries/entry-reader.d.cts +26 -0
- package/dist/entries/entry-reader.d.ts +26 -0
- package/dist/entries/entry-stream.d.cts +5 -0
- package/dist/entries/entry-stream.d.ts +5 -0
- package/dist/entries/entry.d.cts +25 -0
- package/dist/entries/entry.d.ts +25 -0
- package/dist/entries/resolved-entry.d.cts +11 -0
- package/dist/entries/resolved-entry.d.ts +11 -0
- package/dist/errors/conflict-error.d.cts +6 -0
- package/dist/errors/conflict-error.d.ts +6 -0
- package/dist/errors/error-code.d.cts +4 -0
- package/dist/errors/error-code.d.ts +4 -0
- package/dist/errors/error-factory.d.cts +15 -0
- package/dist/errors/error-factory.d.ts +15 -0
- package/dist/errors/error-payload.d.cts +13 -0
- package/dist/errors/error-payload.d.ts +13 -0
- package/dist/errors/forbidden-error.d.cts +5 -0
- package/dist/errors/forbidden-error.d.ts +5 -0
- package/dist/errors/internal-error.d.cts +6 -0
- package/dist/errors/internal-error.d.ts +6 -0
- package/dist/errors/invalid-response-error.d.cts +11 -0
- package/dist/errors/invalid-response-error.d.ts +11 -0
- package/dist/errors/media-delete-stalled-error.d.cts +14 -0
- package/dist/errors/media-delete-stalled-error.d.ts +14 -0
- package/dist/errors/media-in-use-error.d.cts +21 -0
- package/dist/errors/media-in-use-error.d.ts +21 -0
- package/dist/errors/network-error.d.cts +11 -0
- package/dist/errors/network-error.d.ts +11 -0
- package/dist/errors/not-found-error.d.cts +6 -0
- package/dist/errors/not-found-error.d.ts +6 -0
- package/dist/errors/request-aborted-error.d.cts +10 -0
- package/dist/errors/request-aborted-error.d.ts +10 -0
- package/dist/errors/silo-error.d.cts +12 -0
- package/dist/errors/silo-error.d.ts +12 -0
- package/dist/errors/timeout-error.d.cts +11 -0
- package/dist/errors/timeout-error.d.ts +11 -0
- package/dist/errors/unauthorized-error.d.cts +5 -0
- package/dist/errors/unauthorized-error.d.ts +5 -0
- package/dist/errors/validation-detail.d.cts +8 -0
- package/dist/errors/validation-detail.d.ts +8 -0
- package/dist/errors/validation-failed-error.d.cts +11 -0
- package/dist/errors/validation-failed-error.d.ts +11 -0
- package/dist/index.cjs +2095 -0
- package/dist/index.d.cts +96 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.js +2053 -0
- package/dist/instance/health-report.d.cts +5 -0
- package/dist/instance/health-report.d.ts +5 -0
- package/dist/media/media-asset-mapper.d.cts +31 -0
- package/dist/media/media-asset-mapper.d.ts +31 -0
- package/dist/media/media-asset-payload.d.cts +20 -0
- package/dist/media/media-asset-payload.d.ts +20 -0
- package/dist/media/media-asset.d.cts +63 -0
- package/dist/media/media-asset.d.ts +63 -0
- package/dist/media/media-delete-failure.d.cts +16 -0
- package/dist/media/media-delete-failure.d.ts +16 -0
- package/dist/media/media-delete-options.d.cts +9 -0
- package/dist/media/media-delete-options.d.ts +9 -0
- package/dist/media/media-delete-report.d.cts +13 -0
- package/dist/media/media-delete-report.d.ts +13 -0
- package/dist/media/media-folder-delete.d.cts +10 -0
- package/dist/media/media-folder-delete.d.ts +10 -0
- package/dist/media/media-folder-move.d.cts +9 -0
- package/dist/media/media-folder-move.d.ts +9 -0
- package/dist/media/media-folders.d.cts +22 -0
- package/dist/media/media-folders.d.ts +22 -0
- package/dist/media/media-page-stream.d.cts +10 -0
- package/dist/media/media-page-stream.d.ts +10 -0
- package/dist/media/media-page.d.cts +19 -0
- package/dist/media/media-page.d.ts +19 -0
- package/dist/media/media-query.d.cts +24 -0
- package/dist/media/media-query.d.ts +24 -0
- package/dist/media/media-reference.d.cts +18 -0
- package/dist/media/media-reference.d.ts +18 -0
- package/dist/media/media-stream.d.cts +7 -0
- package/dist/media/media-stream.d.ts +7 -0
- package/dist/media/media-upload.d.cts +18 -0
- package/dist/media/media-upload.d.ts +18 -0
- package/dist/media/media-usage-page.d.cts +35 -0
- package/dist/media/media-usage-page.d.ts +35 -0
- package/dist/media/media-usage.d.cts +13 -0
- package/dist/media/media-usage.d.ts +13 -0
- package/dist/media/media.d.cts +37 -0
- package/dist/media/media.d.ts +37 -0
- package/dist/pagination/page-window.d.cts +14 -0
- package/dist/pagination/page-window.d.ts +14 -0
- package/dist/pagination/page.d.cts +27 -0
- package/dist/pagination/page.d.ts +27 -0
- package/dist/pagination/row-stream.d.cts +28 -0
- package/dist/pagination/row-stream.d.ts +28 -0
- package/dist/query/field-path.d.cts +14 -0
- package/dist/query/field-path.d.ts +14 -0
- package/dist/query/filter-expression.d.cts +15 -0
- package/dist/query/filter-expression.d.ts +15 -0
- package/dist/query/filter-field.d.cts +19 -0
- package/dist/query/filter-field.d.ts +19 -0
- package/dist/query/filter-node.d.cts +9 -0
- package/dist/query/filter-node.d.ts +9 -0
- package/dist/query/filter-operator.d.cts +7 -0
- package/dist/query/filter-operator.d.ts +7 -0
- package/dist/query/filter.d.cts +18 -0
- package/dist/query/filter.d.ts +18 -0
- package/dist/query/sort-term.d.cts +13 -0
- package/dist/query/sort-term.d.ts +13 -0
- package/dist/query/sort.d.cts +15 -0
- package/dist/query/sort.d.ts +15 -0
- package/dist/query/typed-filter.d.cts +32 -0
- package/dist/query/typed-filter.d.ts +32 -0
- package/dist/request-options.d.cts +9 -0
- package/dist/request-options.d.ts +9 -0
- package/dist/scope/delete-options.d.cts +9 -0
- package/dist/scope/delete-options.d.ts +9 -0
- package/dist/scope/environment-handle.d.cts +29 -0
- package/dist/scope/environment-handle.d.ts +29 -0
- package/dist/scope/environment.d.cts +8 -0
- package/dist/scope/environment.d.ts +8 -0
- package/dist/scope/environments.d.cts +11 -0
- package/dist/scope/environments.d.ts +11 -0
- package/dist/scope/project-handle.d.cts +22 -0
- package/dist/scope/project-handle.d.ts +22 -0
- package/dist/scope/project.d.cts +8 -0
- package/dist/scope/project.d.ts +8 -0
- package/dist/scope/projects.d.cts +10 -0
- package/dist/scope/projects.d.ts +10 -0
- package/dist/scope/rename-options.d.cts +10 -0
- package/dist/scope/rename-options.d.ts +10 -0
- package/dist/scope/rename-report.d.cts +21 -0
- package/dist/scope/rename-report.d.ts +21 -0
- package/dist/scope/scope-reference.d.cts +12 -0
- package/dist/scope/scope-reference.d.ts +12 -0
- package/dist/search/search-engine.d.cts +3 -0
- package/dist/search/search-engine.d.ts +3 -0
- package/dist/search/search-hit.d.cts +18 -0
- package/dist/search/search-hit.d.ts +18 -0
- package/dist/search/search-page.d.cts +19 -0
- package/dist/search/search-page.d.ts +19 -0
- package/dist/search/search-query.d.cts +11 -0
- package/dist/search/search-query.d.ts +11 -0
- package/dist/search/search-reach.d.cts +12 -0
- package/dist/search/search-reach.d.ts +12 -0
- package/dist/search/search-snippet.d.cts +8 -0
- package/dist/search/search-snippet.d.ts +8 -0
- package/dist/search/search.d.cts +18 -0
- package/dist/search/search.d.ts +18 -0
- package/dist/silo-options.d.cts +12 -0
- package/dist/silo-options.d.ts +12 -0
- package/dist/silo.d.cts +42 -0
- package/dist/silo.d.ts +42 -0
- package/dist/transport/abort-signals.d.cts +23 -0
- package/dist/transport/abort-signals.d.ts +23 -0
- package/dist/transport/api-path.d.cts +36 -0
- package/dist/transport/api-path.d.ts +36 -0
- package/dist/transport/fetch-function.d.cts +11 -0
- package/dist/transport/fetch-function.d.ts +11 -0
- package/dist/transport/page-payload.d.cts +15 -0
- package/dist/transport/page-payload.d.ts +15 -0
- package/dist/transport/query-string.d.cts +10 -0
- package/dist/transport/query-string.d.ts +10 -0
- package/dist/transport/response-decoder.d.cts +14 -0
- package/dist/transport/response-decoder.d.ts +14 -0
- package/dist/transport/route-inventory.d.cts +22 -0
- package/dist/transport/route-inventory.d.ts +22 -0
- package/dist/transport/transport-request.d.cts +18 -0
- package/dist/transport/transport-request.d.ts +18 -0
- package/dist/transport/transport.d.cts +45 -0
- package/dist/transport/transport.d.ts +45 -0
- package/dist/variables/environment-variables.d.cts +14 -0
- package/dist/variables/environment-variables.d.ts +14 -0
- package/dist/variables/project-variables.d.cts +38 -0
- package/dist/variables/project-variables.d.ts +38 -0
- package/dist/variables/variable-declaration.d.cts +10 -0
- package/dist/variables/variable-declaration.d.ts +10 -0
- package/dist/variables/variable.d.cts +17 -0
- package/dist/variables/variable.d.ts +17 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# silo-client
|
|
2
|
+
|
|
3
|
+
The typed client for [silo](https://github.com/org-quicko/silo). It follows
|
|
4
|
+
silo's own shape: an instance holds projects, a project holds environments, an
|
|
5
|
+
environment holds collections, and a collection holds entries.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @org-quicko/silo-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Runs on Node 18+, Bun, Deno, browsers and workers. No dependencies.
|
|
12
|
+
|
|
13
|
+
## Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Silo } from "@org-quicko/silo-client"
|
|
17
|
+
|
|
18
|
+
const silo = new Silo({ url: "http://localhost:8090", key: process.env.SILO_KEY })
|
|
19
|
+
|
|
20
|
+
const posts = silo.project("acme").environment("prod").collection("posts")
|
|
21
|
+
|
|
22
|
+
const page = await posts.list({ limit: 10 })
|
|
23
|
+
for (const post of page.entries) {
|
|
24
|
+
console.log(post.fields.title)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Handles are cheap. `silo.project("acme").environment("prod")` makes no request,
|
|
29
|
+
so nothing above needs an `await` until the read.
|
|
30
|
+
|
|
31
|
+
Both scope names are always explicit. There is no default project or
|
|
32
|
+
environment, because a client that guesses reads the wrong environment quietly.
|
|
33
|
+
`silo.scope("acme", "prod")` is the same chain in one call.
|
|
34
|
+
|
|
35
|
+
## Entries
|
|
36
|
+
|
|
37
|
+
Name your fields and the collection is typed.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
interface Post {
|
|
41
|
+
title: string
|
|
42
|
+
status: "draft" | "published"
|
|
43
|
+
tags: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const posts = silo.project("acme").environment("prod").collection<Post>("posts")
|
|
47
|
+
|
|
48
|
+
const created = await posts.create({ title: "Hello", status: "draft", tags: [] })
|
|
49
|
+
|
|
50
|
+
const draft = await posts.edit(created.id)
|
|
51
|
+
draft.fields.status = "published"
|
|
52
|
+
await draft.save()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
An entry keeps its own revision, so you never hold one. `save()` sends the
|
|
56
|
+
revision it read, adopts the one it gets back, and raises `ConflictError` if
|
|
57
|
+
somebody wrote first.
|
|
58
|
+
|
|
59
|
+
`get` and `edit` answer different things, and the difference matters.
|
|
60
|
+
|
|
61
|
+
| Call | Answers | Variables | `save()` |
|
|
62
|
+
|------|---------|-----------|----------|
|
|
63
|
+
| `posts.get(id)` | `ResolvedEntry<Post>` | substituted | not available |
|
|
64
|
+
| `posts.edit(id)` | `Entry<Post>` | left as written | available |
|
|
65
|
+
|
|
66
|
+
A `{{VARIABLE}}` in your content is substituted on the way out. Saving an entry
|
|
67
|
+
you read that way would write the substituted value over the reference, so a
|
|
68
|
+
read that substitutes has no `save()` at all, and `edit` is how you read an
|
|
69
|
+
entry you mean to write back.
|
|
70
|
+
|
|
71
|
+
`posts.editable` is the whole read surface with the same rule, for a tool that
|
|
72
|
+
edits more than one entry at a time.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
await posts.editable.get(id)
|
|
76
|
+
await posts.editable.list({ limit: 50 })
|
|
77
|
+
for await (const post of posts.editable.all()) { }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`replace(id, rev, fields)` and `delete(id, rev)` are there for when you hold an
|
|
81
|
+
id and a revision from somewhere else. Both `replace` and `save` send every
|
|
82
|
+
field, because the route is a full replace.
|
|
83
|
+
|
|
84
|
+
Five field names never survive a round trip, because silo strips them before it
|
|
85
|
+
answers: `id`, `rev`, `seq`, `created_at` and `updated_at`. `ReservedFieldNames`
|
|
86
|
+
holds the list, and creating a collection warns if its schema declares one.
|
|
87
|
+
|
|
88
|
+
## Queries
|
|
89
|
+
|
|
90
|
+
Filters are built, and a typed collection types them.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { Filter, Sort } from "@org-quicko/silo-client"
|
|
94
|
+
|
|
95
|
+
const page = await posts.list({
|
|
96
|
+
where: posts.filter.field("status").equals("published")
|
|
97
|
+
.and(posts.filter.each("tags").equals("release")),
|
|
98
|
+
sort: Sort.recentlyUpdated(),
|
|
99
|
+
limit: 20,
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`field` addresses your own fields, `each` addresses every element of an array,
|
|
104
|
+
and `meta` addresses the envelope.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
posts.filter.field("author.name").contains("ada")
|
|
108
|
+
posts.filter.each("tags").equals("release")
|
|
109
|
+
Filter.meta("updated_at").greaterThan("2026-01-01T00:00:00Z")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The operators are `equals`, `notEquals`, `contains`, `greaterThan`, `atLeast`,
|
|
113
|
+
`lessThan`, `atMost`, `oneOf` and `exists`, joined with `and`, `or` and `not`.
|
|
114
|
+
`Filter` has the same surface untyped, for a filter you assemble at runtime,
|
|
115
|
+
and `Filter.raw(node)` takes the wire AST.
|
|
116
|
+
|
|
117
|
+
Two spellings of a wildcard mean two different things, which is why `each` is
|
|
118
|
+
its own call:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
posts.filter.each("tags").notEquals("draft") // some tag is not "draft"
|
|
122
|
+
Filter.not(posts.filter.each("tags").equals("draft")) // no tag is "draft"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Pagination
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const first = await posts.list({ limit: 25 })
|
|
129
|
+
first.total // 137
|
|
130
|
+
first.pageNumber // 1
|
|
131
|
+
first.hasMore // true
|
|
132
|
+
|
|
133
|
+
const second = await first.next()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
A page reports the window silo actually used, which is not always the one you
|
|
137
|
+
asked for: silo caps `limit` at 500 and replaces a nonpositive one with 50.
|
|
138
|
+
`next()` advances by the answered window, so an oversized request pages
|
|
139
|
+
correctly instead of stepping over entries.
|
|
140
|
+
|
|
141
|
+
Every page is iterable, and two iterators page for you.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
for (const entry of page) { }
|
|
145
|
+
|
|
146
|
+
for await (const entry of posts.all({ where })) { }
|
|
147
|
+
for await (const page of posts.pages({ limit: 100 })) { }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Offset paging over data being written is not a snapshot. Sort by something
|
|
151
|
+
stable when that matters.
|
|
152
|
+
|
|
153
|
+
## Media
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { MediaReference } from "@org-quicko/silo-client"
|
|
157
|
+
|
|
158
|
+
const asset = await silo.media.upload({
|
|
159
|
+
bytes,
|
|
160
|
+
filename: "hero.png",
|
|
161
|
+
contentType: "image/png",
|
|
162
|
+
folder: "heroes",
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
await posts.create({
|
|
166
|
+
title: "Hello",
|
|
167
|
+
status: "draft",
|
|
168
|
+
tags: [],
|
|
169
|
+
cover: MediaReference.of(asset.id),
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Entries reference an asset by id, so renaming or moving a file rewrites
|
|
174
|
+
nothing. `asset.url` is the link to serve; `asset.reference` is the value to
|
|
175
|
+
store. Storing the URL is the mistake a later rename breaks.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
await asset.rename("hero-2.png")
|
|
179
|
+
await asset.moveTo("heroes/2026")
|
|
180
|
+
await asset.setTags(["banner"]) // replaces the list
|
|
181
|
+
await asset.delete() // refused while an entry references it
|
|
182
|
+
await asset.delete({ force: true })
|
|
183
|
+
|
|
184
|
+
const usage = await asset.usages()
|
|
185
|
+
usage.usages // the referrers this key may read
|
|
186
|
+
usage.total // the true count
|
|
187
|
+
usage.visible // what this key may see of it
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Folders, and a bulk delete capped at 100 ids:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
await silo.media.folders.list()
|
|
194
|
+
await silo.media.folders.create("heroes/2026")
|
|
195
|
+
await silo.media.folders.rename("heroes", "banners", { merge: true })
|
|
196
|
+
await silo.media.folders.delete("banners", { recursive: true })
|
|
197
|
+
|
|
198
|
+
const report = await silo.media.deleteMany(ids, { force: true })
|
|
199
|
+
report.deleted
|
|
200
|
+
report.failed
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Variables
|
|
204
|
+
|
|
205
|
+
A variable is declared once per project and valued per environment.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const acme = silo.project("acme")
|
|
209
|
+
await acme.variables.declare("API_URL", { environment: "prod", value: "https://api.acme.com" })
|
|
210
|
+
|
|
211
|
+
const environment = acme.environment("prod")
|
|
212
|
+
await environment.variables.list()
|
|
213
|
+
await environment.variables.set("API_URL", "https://api.acme.com")
|
|
214
|
+
await environment.variables.unset("API_URL")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`variable.value` is `null` when this environment has given it nothing, which is
|
|
218
|
+
not `""`. An empty value substitutes as empty; an unset one leaves `{{API_URL}}`
|
|
219
|
+
standing in the response.
|
|
220
|
+
|
|
221
|
+
## Search
|
|
222
|
+
|
|
223
|
+
The reach is whatever you call it on, so a missing argument cannot widen a
|
|
224
|
+
search.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
await posts.search({ query: "pricing" }) // one collection
|
|
228
|
+
await environment.search({ query: "pricing" }) // one environment
|
|
229
|
+
await silo.search({ query: "pricing" }) // everything the key can read
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A hit says where it was found and quotes why it matched.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const results = await silo.search({ query: "pricing" })
|
|
236
|
+
results.hits[0].collection // "posts"
|
|
237
|
+
results.hits[0].snippets // [{ path, before, match, after }]
|
|
238
|
+
results.engine // "fts5" when the index answered, "scan" when it walked
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Errors
|
|
242
|
+
|
|
243
|
+
One class per failure, so you branch on the type.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { ConflictError, ValidationFailedError, NetworkError } from "@org-quicko/silo-client"
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
await draft.save()
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof ConflictError) {
|
|
252
|
+
await draft.refresh() // somebody else wrote first
|
|
253
|
+
} else if (error instanceof ValidationFailedError) {
|
|
254
|
+
error.details // [{ path: "/title", message }]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`SiloError` is the base for anything silo refused: `ValidationFailedError`,
|
|
260
|
+
`UnauthorizedError`, `ForbiddenError`, `NotFoundError`, `ConflictError`,
|
|
261
|
+
`MediaInUseError`, `MediaDeleteStalledError` and `InternalError`.
|
|
262
|
+
|
|
263
|
+
`NetworkError`, `TimeoutError`, `RequestAbortedError` and `InvalidResponseError`
|
|
264
|
+
are not `SiloError`, because nothing answered. A `NetworkError` on a write does
|
|
265
|
+
not prove the write failed. Read the entry back before deciding.
|
|
266
|
+
|
|
267
|
+
## Cancellation
|
|
268
|
+
|
|
269
|
+
Every call takes the same last argument.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
await posts.list({ limit: 20 }, { signal: controller.signal })
|
|
273
|
+
await posts.get(id, { timeoutMilliseconds: 2_000 })
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`abort()` raises `RequestAbortedError` and the deadline raises `TimeoutError`.
|
|
277
|
+
Nothing is retried for you: a retried `POST` is a duplicate entry, and only the
|
|
278
|
+
caller knows whether a call was safe to repeat.
|
|
279
|
+
|
|
280
|
+
## Anonymous reads
|
|
281
|
+
|
|
282
|
+
A key is optional. Without one you reach the collections whose schema does not
|
|
283
|
+
set `x-silo-auth`.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const silo = new Silo({ url: "https://cms.example.com" })
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## What this client does not reach
|
|
290
|
+
|
|
291
|
+
Keys, claims, plugins, export and import, settings, audit and observability.
|
|
292
|
+
Those are operator surfaces, and the admin UI and the CLI own them. There is no
|
|
293
|
+
generic `request()` either: `RouteInventory` lists every route this client
|
|
294
|
+
covers and every route it leaves out, and a test holds that list against the
|
|
295
|
+
server's own registrations.
|
|
296
|
+
|
|
297
|
+
## Development
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
bun install # from the repo root
|
|
301
|
+
bun test
|
|
302
|
+
bun run typecheck
|
|
303
|
+
bun run build
|
|
304
|
+
bun run test:packaged # packs the tarball and consumes it from Node, Bun and a browser build
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The design and the reasoning behind it are in
|
|
308
|
+
[docs/design/api-client.md](../../docs/design/api-client.md). The routes are in
|
|
309
|
+
[docs/guide/http-api.md](../../docs/guide/http-api.md).
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
AGPL-3.0-or-later
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JsonSchema } from "./json-schema.cjs";
|
|
2
|
+
/** One collection with its schema, bundled so `silo://` refs are already
|
|
3
|
+
* resolved. What `create`, `schema.get()`, `schema.put()` and
|
|
4
|
+
* `environment.schemas()` all answer. No mapping needed: every key here is
|
|
5
|
+
* already the wire's own spelling. */
|
|
6
|
+
export interface CollectionDefinition {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly schema: JsonSchema;
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JsonSchema } from "./json-schema.js";
|
|
2
|
+
/** One collection with its schema, bundled so `silo://` refs are already
|
|
3
|
+
* resolved. What `create`, `schema.get()`, `schema.put()` and
|
|
4
|
+
* `environment.schemas()` all answer. No mapping needed: every key here is
|
|
5
|
+
* already the wire's own spelling. */
|
|
6
|
+
export interface CollectionDefinition {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly schema: JsonSchema;
|
|
10
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Entry } from "../entries/entry.cjs";
|
|
2
|
+
import type { EntryListQuery } from "../entries/entry-list-query.cjs";
|
|
3
|
+
import type { EntryPage } from "../entries/entry-page.cjs";
|
|
4
|
+
import type { EntryPageStream } from "../entries/entry-page-stream.cjs";
|
|
5
|
+
import { EntryReader } from "../entries/entry-reader.cjs";
|
|
6
|
+
import type { EntryStream } from "../entries/entry-stream.cjs";
|
|
7
|
+
import { ResolvedEntry } from "../entries/resolved-entry.cjs";
|
|
8
|
+
import { TypedFilter } from "../query/typed-filter.cjs";
|
|
9
|
+
import type { RequestOptions } from "../request-options.cjs";
|
|
10
|
+
import { RenameReport } from "../scope/rename-report.cjs";
|
|
11
|
+
import type { RenameOptions } from "../scope/rename-options.cjs";
|
|
12
|
+
import type { ScopeReference } from "../scope/scope-reference.cjs";
|
|
13
|
+
import type { SearchPage } from "../search/search-page.cjs";
|
|
14
|
+
import type { SearchQuery } from "../search/search-query.cjs";
|
|
15
|
+
import { CollectionSchema } from "./collection-schema.cjs";
|
|
16
|
+
/**
|
|
17
|
+
* One collection, typed to its fields:
|
|
18
|
+
* `environment.collection<Post>("posts")`.
|
|
19
|
+
*
|
|
20
|
+
* Reads here substitute every `{{NAME}}` an entry holds and answer a
|
|
21
|
+
* `ResolvedEntry`, which has no `save()`. Writing one back would replace the
|
|
22
|
+
* reference somebody typed with whatever it happened to mean, so reading for
|
|
23
|
+
* a write goes through `edit` or `editable`.
|
|
24
|
+
*/
|
|
25
|
+
export declare class CollectionHandle<Fields = Record<string, unknown>> {
|
|
26
|
+
private readonly scope;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly filter: TypedFilter<Fields>;
|
|
29
|
+
readonly schema: CollectionSchema;
|
|
30
|
+
/**
|
|
31
|
+
* The same collection read for writing back. Every read here keeps the
|
|
32
|
+
* templates as stored and answers an `Entry`, which has `save()`.
|
|
33
|
+
*/
|
|
34
|
+
readonly editable: EntryReader<Entry<Fields>>;
|
|
35
|
+
private readonly context;
|
|
36
|
+
private readonly resolved;
|
|
37
|
+
constructor(scope: ScopeReference, name: string);
|
|
38
|
+
/** One entry, with its variables substituted. Read-only: see `edit`. */
|
|
39
|
+
get(id: string, options?: RequestOptions): Promise<ResolvedEntry<Fields>>;
|
|
40
|
+
/** One entry as stored, ready to change and `save()`. */
|
|
41
|
+
edit(id: string, options?: RequestOptions): Promise<Entry<Fields>>;
|
|
42
|
+
list(query?: EntryListQuery, options?: RequestOptions): Promise<EntryPage<ResolvedEntry<Fields>>>;
|
|
43
|
+
all(query?: EntryListQuery, options?: RequestOptions): EntryStream<ResolvedEntry<Fields>>;
|
|
44
|
+
pages(query?: EntryListQuery, options?: RequestOptions): EntryPageStream<ResolvedEntry<Fields>>;
|
|
45
|
+
create(fields: Fields, options?: RequestOptions): Promise<Entry<Fields>>;
|
|
46
|
+
/** A full replace, which is what the route is: send every field. */
|
|
47
|
+
replace(id: string, rev: number, fields: Fields, options?: RequestOptions): Promise<Entry<Fields>>;
|
|
48
|
+
delete(id: string, rev: number, options?: RequestOptions): Promise<void>;
|
|
49
|
+
search(query: SearchQuery, options?: RequestOptions): Promise<SearchPage>;
|
|
50
|
+
rename(name: string, options?: RenameOptions): Promise<RenameReport>;
|
|
51
|
+
/** Both writes ask for the stored templates back, so what returns is what
|
|
52
|
+
* was sent and is safe to hold and `save()` straight away. */
|
|
53
|
+
private write;
|
|
54
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Entry } from "../entries/entry.js";
|
|
2
|
+
import type { EntryListQuery } from "../entries/entry-list-query.js";
|
|
3
|
+
import type { EntryPage } from "../entries/entry-page.js";
|
|
4
|
+
import type { EntryPageStream } from "../entries/entry-page-stream.js";
|
|
5
|
+
import { EntryReader } from "../entries/entry-reader.js";
|
|
6
|
+
import type { EntryStream } from "../entries/entry-stream.js";
|
|
7
|
+
import { ResolvedEntry } from "../entries/resolved-entry.js";
|
|
8
|
+
import { TypedFilter } from "../query/typed-filter.js";
|
|
9
|
+
import type { RequestOptions } from "../request-options.js";
|
|
10
|
+
import { RenameReport } from "../scope/rename-report.js";
|
|
11
|
+
import type { RenameOptions } from "../scope/rename-options.js";
|
|
12
|
+
import type { ScopeReference } from "../scope/scope-reference.js";
|
|
13
|
+
import type { SearchPage } from "../search/search-page.js";
|
|
14
|
+
import type { SearchQuery } from "../search/search-query.js";
|
|
15
|
+
import { CollectionSchema } from "./collection-schema.js";
|
|
16
|
+
/**
|
|
17
|
+
* One collection, typed to its fields:
|
|
18
|
+
* `environment.collection<Post>("posts")`.
|
|
19
|
+
*
|
|
20
|
+
* Reads here substitute every `{{NAME}}` an entry holds and answer a
|
|
21
|
+
* `ResolvedEntry`, which has no `save()`. Writing one back would replace the
|
|
22
|
+
* reference somebody typed with whatever it happened to mean, so reading for
|
|
23
|
+
* a write goes through `edit` or `editable`.
|
|
24
|
+
*/
|
|
25
|
+
export declare class CollectionHandle<Fields = Record<string, unknown>> {
|
|
26
|
+
private readonly scope;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly filter: TypedFilter<Fields>;
|
|
29
|
+
readonly schema: CollectionSchema;
|
|
30
|
+
/**
|
|
31
|
+
* The same collection read for writing back. Every read here keeps the
|
|
32
|
+
* templates as stored and answers an `Entry`, which has `save()`.
|
|
33
|
+
*/
|
|
34
|
+
readonly editable: EntryReader<Entry<Fields>>;
|
|
35
|
+
private readonly context;
|
|
36
|
+
private readonly resolved;
|
|
37
|
+
constructor(scope: ScopeReference, name: string);
|
|
38
|
+
/** One entry, with its variables substituted. Read-only: see `edit`. */
|
|
39
|
+
get(id: string, options?: RequestOptions): Promise<ResolvedEntry<Fields>>;
|
|
40
|
+
/** One entry as stored, ready to change and `save()`. */
|
|
41
|
+
edit(id: string, options?: RequestOptions): Promise<Entry<Fields>>;
|
|
42
|
+
list(query?: EntryListQuery, options?: RequestOptions): Promise<EntryPage<ResolvedEntry<Fields>>>;
|
|
43
|
+
all(query?: EntryListQuery, options?: RequestOptions): EntryStream<ResolvedEntry<Fields>>;
|
|
44
|
+
pages(query?: EntryListQuery, options?: RequestOptions): EntryPageStream<ResolvedEntry<Fields>>;
|
|
45
|
+
create(fields: Fields, options?: RequestOptions): Promise<Entry<Fields>>;
|
|
46
|
+
/** A full replace, which is what the route is: send every field. */
|
|
47
|
+
replace(id: string, rev: number, fields: Fields, options?: RequestOptions): Promise<Entry<Fields>>;
|
|
48
|
+
delete(id: string, rev: number, options?: RequestOptions): Promise<void>;
|
|
49
|
+
search(query: SearchQuery, options?: RequestOptions): Promise<SearchPage>;
|
|
50
|
+
rename(name: string, options?: RenameOptions): Promise<RenameReport>;
|
|
51
|
+
/** Both writes ask for the stored templates back, so what returns is what
|
|
52
|
+
* was sent and is safe to hold and `save()` straight away. */
|
|
53
|
+
private write;
|
|
54
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RequestOptions } from "../request-options.cjs";
|
|
2
|
+
import type { DeleteOptions } from "../scope/delete-options.cjs";
|
|
3
|
+
import type { ScopeReference } from "../scope/scope-reference.cjs";
|
|
4
|
+
import type { CollectionDefinition } from "./collection-definition.cjs";
|
|
5
|
+
import type { JsonSchema } from "./json-schema.cjs";
|
|
6
|
+
/** One collection's schema, bundled on every read: `get`, `put`,
|
|
7
|
+
* `delete`. Deleting the schema deletes the collection — the only path the
|
|
8
|
+
* server exposes for that. */
|
|
9
|
+
export declare class CollectionSchema {
|
|
10
|
+
private readonly scope;
|
|
11
|
+
private readonly name;
|
|
12
|
+
constructor(scope: ScopeReference, name: string);
|
|
13
|
+
get(options?: RequestOptions): Promise<CollectionDefinition>;
|
|
14
|
+
put(schema: JsonSchema, options?: RequestOptions): Promise<CollectionDefinition>;
|
|
15
|
+
delete(options?: DeleteOptions): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RequestOptions } from "../request-options.js";
|
|
2
|
+
import type { DeleteOptions } from "../scope/delete-options.js";
|
|
3
|
+
import type { ScopeReference } from "../scope/scope-reference.js";
|
|
4
|
+
import type { CollectionDefinition } from "./collection-definition.js";
|
|
5
|
+
import type { JsonSchema } from "./json-schema.js";
|
|
6
|
+
/** One collection's schema, bundled on every read: `get`, `put`,
|
|
7
|
+
* `delete`. Deleting the schema deletes the collection — the only path the
|
|
8
|
+
* server exposes for that. */
|
|
9
|
+
export declare class CollectionSchema {
|
|
10
|
+
private readonly scope;
|
|
11
|
+
private readonly name;
|
|
12
|
+
constructor(scope: ScopeReference, name: string);
|
|
13
|
+
get(options?: RequestOptions): Promise<CollectionDefinition>;
|
|
14
|
+
put(schema: JsonSchema, options?: RequestOptions): Promise<CollectionDefinition>;
|
|
15
|
+
delete(options?: DeleteOptions): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One collection as the listing answers it: no schema, which is the one
|
|
2
|
+
* thing a list of collections never needs to draw. */
|
|
3
|
+
export interface CollectionSummary {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly entries: number;
|
|
7
|
+
readonly requiresAuth: boolean;
|
|
8
|
+
readonly createdAt: Date;
|
|
9
|
+
readonly updatedAt: Date;
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One collection as the listing answers it: no schema, which is the one
|
|
2
|
+
* thing a list of collections never needs to draw. */
|
|
3
|
+
export interface CollectionSummary {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly entries: number;
|
|
7
|
+
readonly requiresAuth: boolean;
|
|
8
|
+
readonly createdAt: Date;
|
|
9
|
+
readonly updatedAt: Date;
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RequestOptions } from "../request-options.cjs";
|
|
2
|
+
import type { ScopeReference } from "../scope/scope-reference.cjs";
|
|
3
|
+
import type { CollectionDefinition } from "./collection-definition.cjs";
|
|
4
|
+
import type { CollectionSummary } from "./collection-summary.cjs";
|
|
5
|
+
import type { JsonSchema } from "./json-schema.cjs";
|
|
6
|
+
/** Collections within one environment: `list` answers summaries and no
|
|
7
|
+
* schema, `create` answers the new collection's bundled definition. */
|
|
8
|
+
export declare class Collections {
|
|
9
|
+
private readonly scope;
|
|
10
|
+
constructor(scope: ScopeReference);
|
|
11
|
+
list(options?: RequestOptions): Promise<CollectionSummary[]>;
|
|
12
|
+
create(name: string, schema: JsonSchema, options?: RequestOptions): Promise<CollectionDefinition>;
|
|
13
|
+
private static toSummary;
|
|
14
|
+
/** The only `console` use in this package: a schema declaring a reserved
|
|
15
|
+
* field still validates, so this is the one place a caller can learn
|
|
16
|
+
* about it before finding out from a field that is silently never
|
|
17
|
+
* returned. */
|
|
18
|
+
private static warnOnReservedFields;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RequestOptions } from "../request-options.js";
|
|
2
|
+
import type { ScopeReference } from "../scope/scope-reference.js";
|
|
3
|
+
import type { CollectionDefinition } from "./collection-definition.js";
|
|
4
|
+
import type { CollectionSummary } from "./collection-summary.js";
|
|
5
|
+
import type { JsonSchema } from "./json-schema.js";
|
|
6
|
+
/** Collections within one environment: `list` answers summaries and no
|
|
7
|
+
* schema, `create` answers the new collection's bundled definition. */
|
|
8
|
+
export declare class Collections {
|
|
9
|
+
private readonly scope;
|
|
10
|
+
constructor(scope: ScopeReference);
|
|
11
|
+
list(options?: RequestOptions): Promise<CollectionSummary[]>;
|
|
12
|
+
create(name: string, schema: JsonSchema, options?: RequestOptions): Promise<CollectionDefinition>;
|
|
13
|
+
private static toSummary;
|
|
14
|
+
/** The only `console` use in this package: a schema declaring a reserved
|
|
15
|
+
* field still validates, so this is the one place a caller can learn
|
|
16
|
+
* about it before finding out from a field that is silently never
|
|
17
|
+
* returned. */
|
|
18
|
+
private static warnOnReservedFields;
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** A JSON Schema document, loosely typed: this client validates nothing
|
|
2
|
+
* locally and passes a schema through to the server exactly as given. */
|
|
3
|
+
export interface JsonSchema {
|
|
4
|
+
type?: string;
|
|
5
|
+
properties?: Record<string, JsonSchema>;
|
|
6
|
+
required?: string[];
|
|
7
|
+
items?: JsonSchema | JsonSchema[];
|
|
8
|
+
[keyword: string]: unknown;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** A JSON Schema document, loosely typed: this client validates nothing
|
|
2
|
+
* locally and passes a schema through to the server exactly as given. */
|
|
3
|
+
export interface JsonSchema {
|
|
4
|
+
type?: string;
|
|
5
|
+
properties?: Record<string, JsonSchema>;
|
|
6
|
+
required?: string[];
|
|
7
|
+
items?: JsonSchema | JsonSchema[];
|
|
8
|
+
[keyword: string]: unknown;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The five field names the server strips from every entry response before
|
|
3
|
+
* answering, verified against `EntryUtils.toApiResponse`. A schema
|
|
4
|
+
* declaring one still validates and stores it; it just never comes back.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReservedFieldNames {
|
|
7
|
+
static readonly all: readonly string[];
|
|
8
|
+
static isReserved(name: string): boolean;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The five field names the server strips from every entry response before
|
|
3
|
+
* answering, verified against `EntryUtils.toApiResponse`. A schema
|
|
4
|
+
* declaring one still validates and stores it; it just never comes back.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ReservedFieldNames {
|
|
7
|
+
static readonly all: readonly string[];
|
|
8
|
+
static isReserved(name: string): boolean;
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RequestOptions } from "../request-options.cjs";
|
|
2
|
+
import type { Transport } from "../transport/transport.cjs";
|
|
3
|
+
import type { EntryPayload } from "./entry-payload.cjs";
|
|
4
|
+
/** What an entry needs to address itself: the transport, and the scope and
|
|
5
|
+
* collection its id lives in. */
|
|
6
|
+
export interface EntryContext {
|
|
7
|
+
readonly transport: Transport;
|
|
8
|
+
readonly project: string;
|
|
9
|
+
readonly environment: string;
|
|
10
|
+
readonly collection: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* What every entry has regardless of whether it is editable: identity,
|
|
14
|
+
* timestamps, its fields, a plain snapshot, and `delete()` — which needs
|
|
15
|
+
* only an id and a rev, not the content. `rev`/`createdAt`/`updatedAt`/
|
|
16
|
+
* `fields` are declared `readonly` here; {@link Entry} is the one subclass
|
|
17
|
+
* that redeclares them mutable, since it is the one subclass with something
|
|
18
|
+
* that legitimately changes them.
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class EntryBase<Fields> {
|
|
21
|
+
protected readonly context: EntryContext;
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly rev: number;
|
|
24
|
+
readonly createdAt: Date;
|
|
25
|
+
readonly updatedAt: Date;
|
|
26
|
+
readonly fields: Fields;
|
|
27
|
+
constructor(context: EntryContext, payload: EntryPayload);
|
|
28
|
+
/** The flat wire shape, for logging or React state. */
|
|
29
|
+
toJSON(): EntryPayload;
|
|
30
|
+
delete(options?: RequestOptions): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RequestOptions } from "../request-options.js";
|
|
2
|
+
import type { Transport } from "../transport/transport.js";
|
|
3
|
+
import type { EntryPayload } from "./entry-payload.js";
|
|
4
|
+
/** What an entry needs to address itself: the transport, and the scope and
|
|
5
|
+
* collection its id lives in. */
|
|
6
|
+
export interface EntryContext {
|
|
7
|
+
readonly transport: Transport;
|
|
8
|
+
readonly project: string;
|
|
9
|
+
readonly environment: string;
|
|
10
|
+
readonly collection: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* What every entry has regardless of whether it is editable: identity,
|
|
14
|
+
* timestamps, its fields, a plain snapshot, and `delete()` — which needs
|
|
15
|
+
* only an id and a rev, not the content. `rev`/`createdAt`/`updatedAt`/
|
|
16
|
+
* `fields` are declared `readonly` here; {@link Entry} is the one subclass
|
|
17
|
+
* that redeclares them mutable, since it is the one subclass with something
|
|
18
|
+
* that legitimately changes them.
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class EntryBase<Fields> {
|
|
21
|
+
protected readonly context: EntryContext;
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly rev: number;
|
|
24
|
+
readonly createdAt: Date;
|
|
25
|
+
readonly updatedAt: Date;
|
|
26
|
+
readonly fields: Fields;
|
|
27
|
+
constructor(context: EntryContext, payload: EntryPayload);
|
|
28
|
+
/** The flat wire shape, for logging or React state. */
|
|
29
|
+
toJSON(): EntryPayload;
|
|
30
|
+
delete(options?: RequestOptions): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FilterExpression } from "../query/filter-expression.cjs";
|
|
2
|
+
import type { SortTerm } from "../query/sort-term.cjs";
|
|
3
|
+
/** Everything `collection.list()` accepts, all optional. `where`
|
|
4
|
+
* builds through `collection.filter`; `sort` takes a built `SortTerm` or a
|
|
5
|
+
* raw string. */
|
|
6
|
+
export interface EntryListQuery {
|
|
7
|
+
where?: FilterExpression;
|
|
8
|
+
sort?: SortTerm | string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
offset?: number;
|
|
11
|
+
}
|