@warmhub/sdk-ts 0.139.0 → 0.141.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/CHANGELOG.md +463 -0
- package/README.md +72 -26
- package/changelog.json +31 -0
- package/dist/checkpoint.js +1 -1
- package/dist/checkpoint.js.map +1 -1
- package/dist/chunk-6QNGVCSD.js +10 -0
- package/dist/chunk-6QNGVCSD.js.map +1 -0
- package/dist/{chunk-F2BL6MQQ.js → chunk-AO5UO4EW.js} +19 -19
- package/dist/chunk-AO5UO4EW.js.map +1 -0
- package/dist/index.d.ts +1954 -1713
- package/dist/index.js +1 -1
- package/dist/react.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-F2BL6MQQ.js.map +0 -1
- package/dist/chunk-V336X6J2.js +0 -10
- package/dist/chunk-V336X6J2.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,469 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- warmhub-changelog-artifact:v1 -->
|
|
4
4
|
|
|
5
|
+
## 0.141.0
|
|
6
|
+
|
|
7
|
+
No new release notes.
|
|
8
|
+
|
|
9
|
+
## 0.140.0
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
A resumed repository export now sends the `formatVersion` of the header the download opened with, and the server refuses a resume whose format is not the one it writes: HTTP 412 `client-incompatible` with the message "Restart the export; a resume must use the format it started with." A download started before a server upgrade that changes the export format can no longer be silently finished under its old header; the resume fails and the export must be restarted.
|
|
14
|
+
|
|
15
|
+
A resume of a file that holds only the header (interrupted before the first row) sends no cursor, so the server cannot check it; the session now compares the new header's format to the saved one and raises `RepositoryExportError` (`header_invalid`) with the same restart message rather than appending new-format rows under the old header.
|
|
16
|
+
|
|
17
|
+
**Upgrade with the server.** An older SDK sends no `formatVersion` on a resume, so a server with this change refuses every resume from it, not only cross-format ones. Upgrade the SDK in the same release as the server.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
`isAllStreamOperationsFailedError(error)` reports whether a thrown value is an all-failed stream error. Unlike `error instanceof AllStreamOperationsFailedError`, it also recognizes the error after `toWarmHubError` normalization and when a different copy of the SDK threw it.
|
|
22
|
+
|
|
23
|
+
### Breaking
|
|
24
|
+
|
|
25
|
+
#### `NAME_TAKEN` names the holder by durable id only and offers no replacement name
|
|
26
|
+
|
|
27
|
+
**What it was.** A `name_taken` error's `details.takenBy` was `{ shape, durableId }`, `details.suggestedName` could carry a replacement name, and the message said `by a <Shape> Thing` and `for example "<name>"`.
|
|
28
|
+
|
|
29
|
+
**What it is now.** `details.takenBy` is `{ durableId }`; `shape` is removed. `details.suggestedName` is removed and no example name appears in the message or hint. The message names no Shape: `Name "acme" is already taken in this repository by another Thing; choose another name`. A caller who cannot read the holder still gets no `takenBy` and no `by another Thing`. The `name_taken` decoder in the streaming submission protocol requires only `takenBy.durableId`.
|
|
30
|
+
|
|
31
|
+
**Why it breaks.** This is a client-visible breaking change, accepted. Released sdk-ts 0.135.0 to 0.139.0 decode `takenBy` with `shape` required, so they refuse a `name_taken` result from a server that carries this change. It lands before the v2 floor release (0.140.0), so no v2 client sees `shape`.
|
|
32
|
+
|
|
33
|
+
**Migration.** Read the holder's Shapes with `get` on `details.takenBy.durableId`; a Thing can have several Shapes or none. Choose the replacement name yourself.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// Before
|
|
37
|
+
const { takenBy, suggestedName } = details
|
|
38
|
+
console.log(takenBy?.shape, suggestedName)
|
|
39
|
+
|
|
40
|
+
// After
|
|
41
|
+
const holder = details.takenBy && (await client.thing.get(org, repo, details.takenBy.durableId))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
The `restore` operation and `OperationBuilder.restore()` make a retracted Thing active again, by its durable ID. Pass `newName` to restore it under another name.
|
|
47
|
+
|
|
48
|
+
In these notes, "old grammar" means the reference form used before this release (`Deal/acme`, operations with `kind`), and "new grammar" the form this release introduces (`acme`, operations with `shapes`).
|
|
49
|
+
|
|
50
|
+
### Breaking
|
|
51
|
+
|
|
52
|
+
#### Write operations take `name` and `shapes`
|
|
53
|
+
|
|
54
|
+
**What it was.** Operations carried `kind`, an add name spelled
|
|
55
|
+
`Shape/name`, a top-level `about`, or collection `type`/`members`, and the
|
|
56
|
+
SDK converted them.
|
|
57
|
+
|
|
58
|
+
**What it is now.** `Operation`, `AddOperation`, `ReviseOperation`,
|
|
59
|
+
`RetractOperation`, `ReaffirmOperation` and `RenameOperation` keep their
|
|
60
|
+
names but have the new shape. An add or revise requires `name`, `shapes`
|
|
61
|
+
and `data`. `kind` and `shape` are typed `never`. The new `WriteOperation`
|
|
62
|
+
type is one operation of that form.
|
|
63
|
+
|
|
64
|
+
- A Shape is the Thing `hub/shapes/X` (`shapeName('X')`), written with
|
|
65
|
+
`shapes: []`.
|
|
66
|
+
- An assertion is a Thing one of whose declared Shapes composes `Assertion`,
|
|
67
|
+
with its target in `data.about`.
|
|
68
|
+
- A collection is a Thing of its collection Shape:
|
|
69
|
+
`{ shapes: ['Set'], data: { members } }`.
|
|
70
|
+
|
|
71
|
+
**Migration.**
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Before
|
|
75
|
+
await client.commit.apply('acme', 'world', 'seed', [
|
|
76
|
+
{ operation: 'add', kind: 'thing', name: 'Deal/acme', data: { company: 'Acme' } },
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
// After
|
|
80
|
+
await client.commit.apply('acme', 'world', 'seed', [
|
|
81
|
+
{ operation: 'add', name: 'acme', shapes: ['Deal'], data: { company: 'Acme' } },
|
|
82
|
+
])
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Old-grammar operations are refused before any request
|
|
86
|
+
|
|
87
|
+
**What it was.** The SDK converted old-grammar operations.
|
|
88
|
+
|
|
89
|
+
**What it is now.** An operation with `kind`, `shape`, `wref`,
|
|
90
|
+
`declaredShapes`, `about`, collection `type` or `members`, or with no
|
|
91
|
+
`operation`, is refused with a `WriteOperationError` before anything is
|
|
92
|
+
sent. The message shows a suggested new form, with data values replaced by
|
|
93
|
+
`"..."`. Names and `about` targets are copied as written, so check them: a
|
|
94
|
+
suggested `name: 'Deal/acme'` would address a Thing literally named
|
|
95
|
+
`Deal/acme`. An add or revise with no `shapes` is refused with a message
|
|
96
|
+
that it states no Shapes.
|
|
97
|
+
|
|
98
|
+
Old-grammar input no longer type-checks, so it reaches this refusal only
|
|
99
|
+
through a cast.
|
|
100
|
+
|
|
101
|
+
**Migration.**
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// Before: converted and sent
|
|
105
|
+
await client.commit.apply('acme', 'world', 'rate', [
|
|
106
|
+
{ operation: 'add', kind: 'assertion', name: 'Rating/r1', about: 'Deal/acme', data: { score: 5 } },
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
// After
|
|
110
|
+
await client.commit.apply('acme', 'world', 'rate', [
|
|
111
|
+
{ operation: 'add', name: 'r1', shapes: ['Rating'], data: { about: 'acme', score: 5 } },
|
|
112
|
+
])
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Other operations refused locally:
|
|
116
|
+
|
|
117
|
+
- a pinned Shape in `shapes` (`Deal@v2`), or an entry that is not a bare
|
|
118
|
+
Shape name (`hub/shapes/Deal`, anything with `/`);
|
|
119
|
+
- `shapeless` on a raw operation (spell it `shapes: []`);
|
|
120
|
+
- non-empty `shapes` on a `hub/shapes/X` name;
|
|
121
|
+
- an add or revise without `data`, and `keepShapes` on an add;
|
|
122
|
+
- `affirmedTargets` on a shapeless write;
|
|
123
|
+
- `expectedVersion` on an add, and `active` on a revise;
|
|
124
|
+
- renaming a Shape to a non-Shape name, or the reverse;
|
|
125
|
+
- `+` in an added or renamed-to name;
|
|
126
|
+
- an added or renamed-to name under the reserved `hub` root
|
|
127
|
+
(`RESERVED_NAME`);
|
|
128
|
+
- the same name added twice in one submission (`ILLEGAL_OP_SEQUENCE`);
|
|
129
|
+
- errors found by the local Shape-definition checks.
|
|
130
|
+
|
|
131
|
+
#### `OperationBuilder` methods take the name first
|
|
132
|
+
|
|
133
|
+
**What it was.** `add({ name, data })`, `revise({ name, data })`,
|
|
134
|
+
`retract(target)`, `reaffirm({ name, kind, ... })`; no `rename`.
|
|
135
|
+
|
|
136
|
+
**What it is now.**
|
|
137
|
+
|
|
138
|
+
| Method | After |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `add` | `add(name, { shapes \| shapeless, data, ... })` |
|
|
141
|
+
| `revise` | `revise(name, { shapes \| shapeless \| keepShapes, data, ... })` |
|
|
142
|
+
| `retract` | `retract(name, { reason? })` |
|
|
143
|
+
| `reaffirm` | `reaffirm(name, { add?, remove? })` |
|
|
144
|
+
| `rename` | `rename(name, newName)` (new) |
|
|
145
|
+
|
|
146
|
+
Each add and revise states its Shapes in exactly one way: `shapes: [...]`,
|
|
147
|
+
`shapeless: true`, or (revise only) `keepShapes: true`. The builder refuses
|
|
148
|
+
zero ways or more than one, `keepShapes` on an add, `shapeless` on a
|
|
149
|
+
`hub/shapes/X` name, and `shapes: []` on a non-Shape name (spell that
|
|
150
|
+
`shapeless: true`).
|
|
151
|
+
|
|
152
|
+
**Migration.**
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// Before
|
|
156
|
+
import { OperationBuilder } from '@warmhub/sdk-ts'
|
|
157
|
+
const ob = new OperationBuilder()
|
|
158
|
+
ob.add({ name: 'Deal/acme', data: { company: 'Acme' } })
|
|
159
|
+
ob.retract('Deal/old')
|
|
160
|
+
await ob.commit({ client, orgName: 'acme', repoName: 'world', message: 'seed' })
|
|
161
|
+
|
|
162
|
+
// After
|
|
163
|
+
import { createOperationEventRequestId } from '@warmhub/sdk-ts'
|
|
164
|
+
|
|
165
|
+
await client.shape.create('acme', 'world', 'Deal', { company: 'string' }, {
|
|
166
|
+
eventRequestId: createOperationEventRequestId(),
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
await client.commit.apply(
|
|
170
|
+
'acme',
|
|
171
|
+
'world',
|
|
172
|
+
'seed',
|
|
173
|
+
client
|
|
174
|
+
.ops()
|
|
175
|
+
.add('acme', { shapes: ['Deal'], data: { company: 'Acme' } })
|
|
176
|
+
.add('scratch', { shapeless: true, data: { note: 'free' } })
|
|
177
|
+
.rename('scratch', 'scratch-2')
|
|
178
|
+
.retract('old'),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// A later submission: keepShapes revises a Thing that already exists
|
|
182
|
+
await client.commit.apply(
|
|
183
|
+
'acme',
|
|
184
|
+
'world',
|
|
185
|
+
'update',
|
|
186
|
+
client.ops().revise('acme', { keepShapes: true, data: { company: 'Acme 2' } }),
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
A `keepShapes` revise of a Thing added or renamed earlier in the same
|
|
191
|
+
submission is refused; state `shapes` instead.
|
|
192
|
+
|
|
193
|
+
#### Builder errors are `WriteOperationError`, raised when an operation is queued
|
|
194
|
+
|
|
195
|
+
**What it was.** Some refusals surfaced only at commit.
|
|
196
|
+
|
|
197
|
+
**What it is now.** A per-operation refusal throws at the `.add(...)` or
|
|
198
|
+
`.revise(...)` call. Whole-batch refusals ("already submitted", "in flight")
|
|
199
|
+
have `operationIndex: undefined`. The code is usually `VALIDATION_ERROR`;
|
|
200
|
+
it can also be `RESERVED_NAME`, `ILLEGAL_OP_SEQUENCE`, a Shape-name check
|
|
201
|
+
code, or (for `keepShapes`, at submit) `NOT_FOUND`.
|
|
202
|
+
|
|
203
|
+
**Migration.**
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { WriteOperationError, type AddInput } from '@warmhub/sdk-ts'
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
client.ops().add('acme', { data: {} } as AddInput) // no Shapes stated
|
|
210
|
+
} catch (e) {
|
|
211
|
+
if (e instanceof WriteOperationError) console.log(e.code, e.operationIndex) // VALIDATION_ERROR 0
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Every reference argument uses the new grammar
|
|
216
|
+
|
|
217
|
+
**What it was.** Read and write calls took `Deal/acme`, and a Shape was
|
|
218
|
+
`Deal`.
|
|
219
|
+
|
|
220
|
+
**What it is now.** Pass the Thing name (`acme`) and `hub/shapes/Deal` for a
|
|
221
|
+
Shape where any Thing can appear. An old spelling is looked up literally: a
|
|
222
|
+
single read gets `NOT_FOUND`, and `thing.getMany` lists it in `missing`.
|
|
223
|
+
Globs (`match`) match Thing names, and `*` does not cross `/`.
|
|
224
|
+
|
|
225
|
+
**Migration.**
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// Before
|
|
229
|
+
await client.thing.get('acme', 'world', 'Deal/acme')
|
|
230
|
+
// After
|
|
231
|
+
await client.thing.get('acme', 'world', 'acme')
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### `thing.rename` takes full names
|
|
235
|
+
|
|
236
|
+
**What it was.** `rename(org, repo, shapeName, oldName, newName, opts)`.
|
|
237
|
+
|
|
238
|
+
**What it is now.** `rename(org, repo, name, newName, opts)`. A call in the
|
|
239
|
+
old argument order is refused locally with `VALIDATION_ERROR`.
|
|
240
|
+
|
|
241
|
+
**Migration.**
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
// Before
|
|
245
|
+
await client.thing.rename('acme', 'world', 'Deal', 'acme', 'acme-corp', { eventRequestId })
|
|
246
|
+
// After
|
|
247
|
+
await client.thing.rename('acme', 'world', 'acme', 'acme-corp', { eventRequestId })
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Read results drop `shape`, `shapeName` and `validatedShape`
|
|
251
|
+
|
|
252
|
+
**What it was.** Results carried a single Shape.
|
|
253
|
+
|
|
254
|
+
**What it is now.** Use `declaredShapes` (the Shapes the version states)
|
|
255
|
+
and `validatedShapes` (the Shapes it satisfies, including composed ones).
|
|
256
|
+
Both hold version-pinned names such as `Deal@v3`. The removal covers
|
|
257
|
+
`ThingDetail`, `ThingItem`, `thing.getMany` items, `thing.getWithLease`,
|
|
258
|
+
history versions and history's `thing`, refs items, component owned Things
|
|
259
|
+
and `view.evaluate` items. `SynthesizedRepoContent.shape` becomes
|
|
260
|
+
`declaredShapes` / `validatedShapes`.
|
|
261
|
+
|
|
262
|
+
`ThingDetail` allows extra keys, so `detail.shapeName` still compiles; it is
|
|
263
|
+
`undefined` at run time. Search your code for these field names.
|
|
264
|
+
|
|
265
|
+
`shape` as an input filter (`thing.head(org, repo, { shape: 'Deal' })`) is
|
|
266
|
+
unchanged.
|
|
267
|
+
|
|
268
|
+
**Migration.**
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// Before
|
|
272
|
+
const shape = detail.shapeName
|
|
273
|
+
// After
|
|
274
|
+
const shapes = (detail.declaredShapes ?? []).map((w) => w.replace(/@v\d+$/, ''))
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Token scopes use `grants`, on input and output
|
|
278
|
+
|
|
279
|
+
**What it was.** A scope entry carried `allowedMatches` and `allowedShapes`.
|
|
280
|
+
|
|
281
|
+
**What it is now.** Restrictions are a list of (paths, Shapes) pairs in
|
|
282
|
+
`grants`.
|
|
283
|
+
|
|
284
|
+
- Input (`token.create`): the entry-level fields are still typed `unknown`,
|
|
285
|
+
so old code compiles, but the server refuses them with "allowedMatches and
|
|
286
|
+
allowedShapes are no longer scope-entry fields: give each (paths, Shapes)
|
|
287
|
+
pair in grants…".
|
|
288
|
+
- Output (`TokenCreateResult`, token metadata, whoami scopes): the
|
|
289
|
+
entry-level fields are removed, and reading them is a compile error. Read
|
|
290
|
+
`grants`.
|
|
291
|
+
- `org.setMemberScopes` takes entries with `resource` and `permissions`
|
|
292
|
+
only; restrictions on member scopes are refused.
|
|
293
|
+
- A pair must carry a non-empty `allowedMatches`, or an `allowedShapes`
|
|
294
|
+
with at least one key. `grants: []`, `{}` and `{ allowedShapes: {} }` are
|
|
295
|
+
refused.
|
|
296
|
+
- `shapeless: true` in a clause is not a restriction of its own: it lets
|
|
297
|
+
Things with no Shape through. Alone it admits every Thing the pair's paths
|
|
298
|
+
match, shaped or shapeless; beside `shapes` it adds shapeless Things to
|
|
299
|
+
the Things the clause covers. No clause restricts a pair to shapeless
|
|
300
|
+
Things only.
|
|
301
|
+
|
|
302
|
+
**Migration.**
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
// Before
|
|
306
|
+
await client.token.create({ name: 'ci', scopes: [
|
|
307
|
+
{ resource: 'acme/world', permissions: ['read'], allowedMatches: ['Deal/**'] },
|
|
308
|
+
] })
|
|
309
|
+
// After
|
|
310
|
+
await client.token.create({ name: 'ci', scopes: [
|
|
311
|
+
{ resource: 'acme/world', permissions: ['read'],
|
|
312
|
+
grants: [{ allowedMatches: ['**'], allowedShapes: { shapes: ['Deal'] } }] },
|
|
313
|
+
] })
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Repository exports are format 6, and the delta fold needs formats
|
|
317
|
+
|
|
318
|
+
**What it was.** Exports were format 5, and
|
|
319
|
+
`applyRepositoryExportDelta(base, delta)` returned rows keyed by durable id.
|
|
320
|
+
|
|
321
|
+
**What it is now.**
|
|
322
|
+
|
|
323
|
+
- The server writes format 6. This SDK reads 5 and 6; older SDKs read only
|
|
324
|
+
5, so they cannot read exports made after this release reaches
|
|
325
|
+
production.
|
|
326
|
+
- Format 6 uses new-grammar references, has one row per Shape version, and
|
|
327
|
+
may carry `retracted: true` on Shape rows. A Shape tombstone in format 6
|
|
328
|
+
is rejected as `row_invalid`.
|
|
329
|
+
- `applyRepositoryExportDelta` takes a third argument, the formats. Mixing
|
|
330
|
+
format 5 and format 6 throws `RepositoryExportError` with reason
|
|
331
|
+
`format_mismatch`.
|
|
332
|
+
- In the returned map, format 6 Shape rows are keyed `durableId@vN`, not
|
|
333
|
+
`durableId`. Code that looks a Shape up by durable id, or keys its own map
|
|
334
|
+
by durable id, must account for versions.
|
|
335
|
+
|
|
336
|
+
**Migration.**
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
// Before
|
|
340
|
+
const current = applyRepositoryExportDelta(base, delta)
|
|
341
|
+
// After
|
|
342
|
+
const current = applyRepositoryExportDelta(base, delta, {
|
|
343
|
+
base: baseSession.header!.formatVersion,
|
|
344
|
+
delta: deltaSession.header!.formatVersion,
|
|
345
|
+
})
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### `stream.append` takes new-grammar operations only
|
|
349
|
+
|
|
350
|
+
**What it was.** `stream.append` took the old operation form.
|
|
351
|
+
|
|
352
|
+
**What it is now.** It takes new-grammar operations and does not accept
|
|
353
|
+
`keepShapes` (use `createKeepShapesResolver`). Its only local check is a
|
|
354
|
+
pinned Shape; everything else is checked by the server.
|
|
355
|
+
|
|
356
|
+
**Migration.** Send `{ operation, name, shapes, data }` operations.
|
|
357
|
+
|
|
358
|
+
#### Writes need a server running this release
|
|
359
|
+
|
|
360
|
+
**What it was.** The SDK wrote to any current server.
|
|
361
|
+
|
|
362
|
+
**What it is now.** Against an older server, writes fail the compatibility
|
|
363
|
+
check with "@warmhub/sdk-ts requires write contract 3, but the backend
|
|
364
|
+
provides 2."
|
|
365
|
+
|
|
366
|
+
**Migration.** Upgrade the SDK after the server.
|
|
367
|
+
|
|
368
|
+
### Added
|
|
369
|
+
|
|
370
|
+
- **`client.ops(options?)`** — returns a new `OperationBuilder`.
|
|
371
|
+
- **`shapeName(bare)`** — `'Deal'` to `'hub/shapes/Deal'`; idempotent.
|
|
372
|
+
- **`isShapeReference(value)`** — true for a bare, unpinned Shape name.
|
|
373
|
+
- **`WriteOperationError`** (extends `WarmHubError`) — with
|
|
374
|
+
`operationIndex`; its message starts `Invalid operation at index N:`.
|
|
375
|
+
- **Types** — `WriteOperation`, `KeepShapesRevise`, `AddInput`,
|
|
376
|
+
`ReviseInput`, `RetractInput`, `ReaffirmInput`, `ShapesChoice`,
|
|
377
|
+
`CommitValidateOperations`, `RepositoryExportDeltaFormats`,
|
|
378
|
+
`ThingShapesReader`.
|
|
379
|
+
- **`keepShapes: true` on a revise** — at submit the SDK reads the Thing's
|
|
380
|
+
current Shapes and version and sends both, the version as
|
|
381
|
+
`expectedVersion` (a supplied `expectedVersion` wins). Shapes are sent
|
|
382
|
+
bare, so the revise is checked against each Shape's current version. A
|
|
383
|
+
missing Thing is refused with `NOT_FOUND`. A Thing added, or renamed to or
|
|
384
|
+
from, earlier in the same submission is refused. A Thing revised earlier in
|
|
385
|
+
the same submission reuses that revise's Shapes and sends no version.
|
|
386
|
+
Because the version is pinned, resubmitting the same `submissionId` from
|
|
387
|
+
another process can conflict; explicit `shapes` is the retry-safe form.
|
|
388
|
+
```ts
|
|
389
|
+
await client.commit.apply('acme', 'world', 'm',
|
|
390
|
+
client.ops().revise('acme', { keepShapes: true, data: { company: 'Acme 2' } }))
|
|
391
|
+
```
|
|
392
|
+
- **`createKeepShapesResolver(read)`** — resolves `keepShapes` revises for
|
|
393
|
+
`stream.append`. It keeps state across calls, so use one resolver per
|
|
394
|
+
submission.
|
|
395
|
+
```ts
|
|
396
|
+
const resolve = createKeepShapesResolver((names) => client.thing.getMany('acme', 'world', names))
|
|
397
|
+
await client.stream.append({ /* ... */ operations: await resolve([
|
|
398
|
+
{ operation: 'revise', name: 'acme', keepShapes: true, data: {} },
|
|
399
|
+
]) })
|
|
400
|
+
```
|
|
401
|
+
- **Builders everywhere** — `commit.validate`, `commit.apply` and
|
|
402
|
+
`commit.applyStreaming` accept an `OperationBuilder` as well as an array.
|
|
403
|
+
- **`shapeless: true` read filter** — on `thing.head`, `thing.query`,
|
|
404
|
+
`thing.count` and `live.thingHead`: only Things with no Shape. It
|
|
405
|
+
cannot be combined with `shape`, `declaredShape`, `match`, a token
|
|
406
|
+
restricted by paths, or `sinceRepoSeq`, and is not available on search or
|
|
407
|
+
the changes feeds.
|
|
408
|
+
```ts
|
|
409
|
+
await client.thing.head('acme', 'world', { shapeless: true })
|
|
410
|
+
```
|
|
411
|
+
- **`warnings.shapeless?: true`** — on per-operation write results.
|
|
412
|
+
- **`ThingGetManyResult.missingHints`** — for a missing reference that is an
|
|
413
|
+
old `Shape/name` spelling of a Thing you can read, the text naming its new
|
|
414
|
+
form.
|
|
415
|
+
- **`declaredShapes` on refs items and history's `thing`** (history also
|
|
416
|
+
gains `validatedShapes`).
|
|
417
|
+
- **`redactedResource: true`** — may appear on `TokenCreateResult` scopes.
|
|
418
|
+
- **`details.reason` values** — `'legacy-wrefs-window-ended'` (from the
|
|
419
|
+
server) and `'legacy-wrefs-sdk'` (local).
|
|
420
|
+
- **`DEPENDENCY_FAILED`** — exported as a constant.
|
|
421
|
+
- **`LEGACY_WREFS_REFUSAL` and `WarmHubClient.clientFlags`**.
|
|
422
|
+
- **Export** — `RepositoryExportErrorReason` gains `'format_mismatch'`;
|
|
423
|
+
`RepositoryExportSession.lastShapeVersion`; an `onRow` option on
|
|
424
|
+
`restoreRepositoryExportSession`. A resume that breaks between two versions
|
|
425
|
+
of one Shape continues from the right version.
|
|
426
|
+
|
|
427
|
+
### Behavior worth knowing
|
|
428
|
+
|
|
429
|
+
- **`clientFlags: ['legacy-wrefs']` refuses every call.** The SDK does not
|
|
430
|
+
send old-grammar requests. With the flag set, reads, writes, streaming,
|
|
431
|
+
export and the compatibility check all throw `WarmHubError` with code
|
|
432
|
+
`CLIENT_INCOMPATIBLE`, `details.reason: 'legacy-wrefs-sdk'`, and the
|
|
433
|
+
message "`legacy-wrefs` is set; the SDKs are v2-only. Unset it, or use the
|
|
434
|
+
`wh` CLI for v1." Use the CLI to submit old-grammar files during the
|
|
435
|
+
compatibility window.
|
|
436
|
+
|
|
437
|
+
### Changed
|
|
438
|
+
|
|
439
|
+
- **Local checks before sending.** `commit.apply`, `commit.validate` and
|
|
440
|
+
the builder check every operation before sending when given an array or a
|
|
441
|
+
builder. An iterable, an async iterable, or `commit.applyStreaming` is
|
|
442
|
+
checked chunk by chunk, so a bad operation late in the source is refused
|
|
443
|
+
after earlier chunks were committed (`PartialStreamSubmissionError`).
|
|
444
|
+
`stream.append` checks only pinned Shapes.
|
|
445
|
+
- **Content-limit error labels.**
|
|
446
|
+
```text
|
|
447
|
+
Before: Field "Content/Readme.content" is N bytes; WarmHub content fields are limited to 65536 bytes. ...
|
|
448
|
+
After: Field "Readme.content" is N bytes; WarmHub content fields are limited to 65536 bytes. ...
|
|
449
|
+
```
|
|
450
|
+
`Agents.content` changes the same way.
|
|
451
|
+
|
|
452
|
+
### Removed
|
|
453
|
+
|
|
454
|
+
- **`AddOp`, `ReviseOp`, `RetractOp`, `CollectionAddOp`,
|
|
455
|
+
`CollectionAddType`, `OperationBuilderAddInput`** — use `WriteOperation`
|
|
456
|
+
and the builder input types.
|
|
457
|
+
```ts
|
|
458
|
+
// Before
|
|
459
|
+
const op: AddOp = { name: 'Deal/acme', data: {} }
|
|
460
|
+
// After
|
|
461
|
+
const op: WriteOperation = { operation: 'add', name: 'acme', shapes: ['Deal'], data: {} }
|
|
462
|
+
```
|
|
463
|
+
- **Automatic old-grammar conversion** (`kind`, `about`, `type`/`members`,
|
|
464
|
+
`Shape/name` splitting).
|
|
465
|
+
- **The local "Selector-backed revise requires a Set/<name> target wref"
|
|
466
|
+
check in `collection.revise`.** The server decides.
|
|
467
|
+
|
|
5
468
|
## 0.139.0
|
|
6
469
|
|
|
7
470
|
No new release notes.
|
package/README.md
CHANGED
|
@@ -65,20 +65,23 @@ const client = new WarmHubClient({
|
|
|
65
65
|
// One-time setup. Skip these three calls (or catch CONFLICT) on re-runs.
|
|
66
66
|
await client.org.create('acme')
|
|
67
67
|
await client.repo.create('acme', 'world', 'Game world')
|
|
68
|
-
await client.shape.create(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
68
|
+
await client.shape.create(
|
|
69
|
+
'acme',
|
|
70
|
+
'world',
|
|
71
|
+
'Location',
|
|
72
|
+
{ x: 'number', y: 'number', label: 'string' },
|
|
73
|
+
{ eventRequestId: crypto.randomUUID() },
|
|
74
|
+
)
|
|
73
75
|
|
|
74
|
-
await client.commit.apply(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
await client.commit.apply(
|
|
77
|
+
'acme',
|
|
78
|
+
'world',
|
|
79
|
+
'seed cave',
|
|
80
|
+
client.ops().add('cave', {
|
|
81
|
+
shapes: ['Location'],
|
|
79
82
|
data: { x: 0, y: 0, label: 'Dark Cave' },
|
|
80
|
-
},
|
|
81
|
-
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
82
85
|
|
|
83
86
|
const head = await client.thing.head('acme', 'world', { shape: 'Location' })
|
|
84
87
|
```
|
|
@@ -99,7 +102,7 @@ const client = new WarmHubClient({
|
|
|
99
102
|
|
|
100
103
|
## Entrypoints
|
|
101
104
|
|
|
102
|
-
- `@warmhub/sdk-ts`: core client, types, errors, `OperationBuilder`
|
|
105
|
+
- `@warmhub/sdk-ts`: core client, types, errors, `OperationBuilder` (`client.ops()`)
|
|
103
106
|
|
|
104
107
|
## Error Handling
|
|
105
108
|
|
|
@@ -119,24 +122,67 @@ try {
|
|
|
119
122
|
}
|
|
120
123
|
```
|
|
121
124
|
|
|
122
|
-
##
|
|
125
|
+
## Writing
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
127
|
+
A write names a Thing by its full name (taken literally, `/` included) and
|
|
128
|
+
states its Shapes. Build writes with `client.ops()`; every add and revise
|
|
129
|
+
states its Shapes in exactly one way, or the builder throws a
|
|
130
|
+
`WriteOperationError` (`VALIDATION_ERROR`, with `operationIndex`) before any
|
|
131
|
+
request:
|
|
126
132
|
|
|
127
133
|
```ts
|
|
128
|
-
import {
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
import { shapeName } from '@warmhub/sdk-ts'
|
|
135
|
+
|
|
136
|
+
const ops = client
|
|
137
|
+
.ops()
|
|
138
|
+
.add('acme', { shapes: ['Deal'], data: { company: 'Acme' } })
|
|
139
|
+
.add('scratch', { shapeless: true, data: { note: 'free' } })
|
|
140
|
+
.revise('acme', { shapes: ['Deal'], data: { company: 'Acme 2' } })
|
|
141
|
+
.add('r1', { shapes: ['Rating'], data: { about: 'acme', score: 5 } })
|
|
142
|
+
.add('crew', { shapes: ['Set'], data: { members: ['acme'] } })
|
|
143
|
+
.rename('scratch', 'scratch-2')
|
|
144
|
+
.retract('crew', { reason: 'disbanded' })
|
|
145
|
+
// A Shape is the Thing hub/shapes/X, written with shapes: [].
|
|
146
|
+
.add(shapeName('Deal'), { shapes: [], data: { fields: { company: 'string' } } })
|
|
147
|
+
|
|
148
|
+
await client.commit.apply('acme', 'world', 'seed', ops)
|
|
138
149
|
```
|
|
139
150
|
|
|
151
|
+
`commit.apply`, `commit.validate` and `commit.applyStreaming` also take plain
|
|
152
|
+
`WriteOperation[]` (`{ operation: 'add', name, shapes, data }`). A v1-shaped
|
|
153
|
+
operation (`kind`, `Shape/name` adds, top-level `about`, collection
|
|
154
|
+
`type`/`members`) is refused, and the error shows its v2 form. Raw v1 input
|
|
155
|
+
from typed TypeScript needs a cast: the v1 types are gone.
|
|
156
|
+
|
|
157
|
+
- An assertion (`r1` above) is a Thing one of whose declared Shapes composes
|
|
158
|
+
`Assertion`, with its target in `data.about`.
|
|
159
|
+
- `shapes: ['Deal', 'Contactable']`: bare Shape names. A pin (`Deal@v2`) is
|
|
160
|
+
refused: a write always certifies under each Shape's head version.
|
|
161
|
+
- `shapeless: true` (builder option): no Shape; the data is stored but not
|
|
162
|
+
validated, and the operation result carries `warnings.shapeless`. A raw
|
|
163
|
+
operation spells it `shapes: []`. A Shape (`hub/shapes/X`) is written with
|
|
164
|
+
`shapes: []`, never `shapeless`.
|
|
165
|
+
- `keepShapes: true` (revise only): the SDK reads the Thing's current Shapes
|
|
166
|
+
and version and sends both, the version as `expectedVersion` (a supplied
|
|
167
|
+
`expectedVersion` wins). A Thing revised earlier in the same submission
|
|
168
|
+
carries that revise's Shapes and no version; one added, or renamed to or
|
|
169
|
+
from, earlier in the same submission is refused before anything is sent
|
|
170
|
+
(submit it in a later request, or pass `shapes`). **keepShapes pins the version
|
|
171
|
+
it read; a cross-process resubmit of the same submissionId can conflict;
|
|
172
|
+
explicit shapes is the retry-safe form.** The Shapes are sent bare, so the
|
|
173
|
+
revise re-certifies under each Shape's head version.
|
|
174
|
+
|
|
175
|
+
A builder can also submit itself with `ops.commit({ client, orgName,
|
|
176
|
+
repoName })`. Either way it is single-use: once submitted it is sealed. Every
|
|
177
|
+
local refusal, per operation or for the whole batch ("already submitted",
|
|
178
|
+
"in flight"), is a `WriteOperationError` with code `VALIDATION_ERROR`
|
|
179
|
+
unless it names another; `operationIndex` is unset for a whole-batch one.
|
|
180
|
+
|
|
181
|
+
**`legacy-wrefs`:** the SDK is v2-only. With `clientFlags: ['legacy-wrefs']`
|
|
182
|
+
it refuses every call, read or write, before any request (the compatibility
|
|
183
|
+
probe included) with `LEGACY_WREFS_REFUSAL`. To write v1 during the
|
|
184
|
+
compatibility window, use the `wh` CLI.
|
|
185
|
+
|
|
140
186
|
## API Docs
|
|
141
187
|
|
|
142
188
|
Generated API reference is published at [docs.warmhub.ai/sdk-reference/readme/](https://docs.warmhub.ai/sdk-reference/readme/) — the landing page lists every public class, interface, type alias, variable, and function exported from the main `@warmhub/sdk-ts` entrypoint.
|