@anchrd/intel-contract 0.14.0 → 0.16.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 +80 -0
- package/dist/contract/flow.d.ts +9 -0
- package/dist/contract/flow.js +10 -0
- package/dist/contract/node.d.ts +11 -2
- package/dist/contract/node.js +44 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,86 @@ Mutation inputs are strict: an unknown field is rejected rather than silently dr
|
|
|
26
26
|
a client never becomes a value that quietly fails to apply. Types are inferred from the schemas, so
|
|
27
27
|
`z.infer` gives you the same shape the server validates against.
|
|
28
28
|
|
|
29
|
+
Each domain has its own subpath — `/node`, `/table`, `/share`, `/tool`, `/flow`, `/flow-run`,
|
|
30
|
+
`/bundle` — and the root export carries only the shared primitives. Import from the narrowest
|
|
31
|
+
subpath that has the symbol.
|
|
32
|
+
|
|
33
|
+
## Six things that cost a first-time caller an afternoon
|
|
34
|
+
|
|
35
|
+
Every one of these was found by somebody writing against the real API and guessing.
|
|
36
|
+
|
|
37
|
+
### A refusal hides in one of two fields, never both
|
|
38
|
+
|
|
39
|
+
Intel answers failures as RFC 9457 problem details, and **which field carries the sentence depends
|
|
40
|
+
on who refused**:
|
|
41
|
+
|
|
42
|
+
| Kind of refusal | `title` | `detail` |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Intel itself — a permission, a conflict, an invalid flow graph | the actual reason | absent |
|
|
45
|
+
| Schema validation — a bad or missing field | `Request validation failed` | the actual reason, field by field |
|
|
46
|
+
|
|
47
|
+
A client that logs only `detail` sees nothing for the first kind; one that logs only `title` sees
|
|
48
|
+
`Request validation failed` and nothing about which field. **Read both.** `code` is the stable
|
|
49
|
+
machine-readable half and is always present.
|
|
50
|
+
|
|
51
|
+
### `baseVersionId` takes `null`, and it is still required
|
|
52
|
+
|
|
53
|
+
Fields declared `nullable()` and not `optional()` are **required**: `null` is a value they take, and
|
|
54
|
+
leaving them out is refused. That is deliberate everywhere a write names the version it was made
|
|
55
|
+
against — `SaveNodeVersionInput`, `SaveAttachmentInput`, `SaveFlowVersionInput`. `null` is the only
|
|
56
|
+
way to write the first content of a node that has none, and the field stays required so a caller who
|
|
57
|
+
simply forgot it is refused instead of overwriting whatever somebody else wrote in between.
|
|
58
|
+
|
|
59
|
+
Neither `undefined` nor `""` works. Send the `currentVersionId` from `node_get`, or `null`.
|
|
60
|
+
|
|
61
|
+
### `mediaType` decides whether the browser can open what you wrote
|
|
62
|
+
|
|
63
|
+
`SaveNodeVersionInput.mediaType` defaults to `text/markdown`, and for plain text that is right. It is
|
|
64
|
+
**wrong for anything the node editor should be able to open**: the editor stores and reads
|
|
65
|
+
`BlockNoteMediaType` (`application/vnd.anchrd.intel.blocknote+json`) with a `BlockNoteDocument` body.
|
|
66
|
+
|
|
67
|
+
Content in any other shape is stored intact, indexed and searchable — and the editor opens **empty**
|
|
68
|
+
on it, because it cannot parse it. Nothing reports an error, and a save from that empty editor
|
|
69
|
+
replaces what was written. If a person is meant to edit the document afterwards, write it as a
|
|
70
|
+
`BlockNoteDocument`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { BlockNoteDocument, BlockNoteMediaType } from "@anchrd/intel-contract/node";
|
|
74
|
+
|
|
75
|
+
const content = JSON.stringify(
|
|
76
|
+
BlockNoteDocument.parse({ format: "blocknote", schemaVersion: 1, blocks, markdown }),
|
|
77
|
+
);
|
|
78
|
+
// … then save with mediaType: BlockNoteMediaType
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### In a flow graph, a tool is a link, not a step
|
|
82
|
+
|
|
83
|
+
Flow nodes sit in three layers. `trigger` and `output` are markers, `instruction`, `condition` and
|
|
84
|
+
`subflow` are steps — and `folder`, `document`, `upload`, `table` and `tool` are **links**.
|
|
85
|
+
|
|
86
|
+
A link never stands in the chain of work. It hangs off an `instruction` or a `condition` — not off a
|
|
87
|
+
marker and not off a `subflow` — and the edge that attaches it must carry `kind: "context"`:
|
|
88
|
+
|
|
89
|
+
```jsonc
|
|
90
|
+
{ "source": "<instruction id>", "target": "<tool id>", "kind": "context" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
⚠️ `FlowEdge.kind` **defaults to `"flow"`**, so an edge written without it becomes an order-of-work
|
|
94
|
+
edge and the tool ends up in the chain. The graph is then refused with
|
|
95
|
+
`Node <id> is a link and must be attached to a step` — in `title`, per the first item above. A step
|
|
96
|
+
may hold several links; one link belongs to exactly one step.
|
|
97
|
+
|
|
98
|
+
### A flow is not a node, and carries no grants of its own
|
|
99
|
+
|
|
100
|
+
`node_grant_create` addresses nodes. A flow has no grants; it inherits those of the folder it lives
|
|
101
|
+
in. To change who reaches a flow, share its folder — or move the flow.
|
|
102
|
+
|
|
103
|
+
### Import takes its idempotency key as a header
|
|
104
|
+
|
|
105
|
+
Every mutation in this contract carries an `idempotencyKey` field. Bundle import is the one
|
|
106
|
+
exception, because its **body is the zip**: the key travels as the `idempotency-key` HTTP header
|
|
107
|
+
instead, and a request without it is refused.
|
|
108
|
+
|
|
29
109
|
## License
|
|
30
110
|
|
|
31
111
|
Proprietary. Published publicly for installation convenience; this is not an open-source license and
|
package/dist/contract/flow.d.ts
CHANGED
|
@@ -709,6 +709,15 @@ export declare const ArchiveFlowInput: z.ZodObject<{
|
|
|
709
709
|
idempotencyKey: z.ZodString;
|
|
710
710
|
}, z.core.$strict>;
|
|
711
711
|
export type ArchiveFlowInput = z.infer<typeof ArchiveFlowInput>;
|
|
712
|
+
export declare const PurgeFlowInput: z.ZodObject<{
|
|
713
|
+
flowId: z.ZodString;
|
|
714
|
+
}, z.core.$strict>;
|
|
715
|
+
export type PurgeFlowInput = z.infer<typeof PurgeFlowInput>;
|
|
716
|
+
export declare const PurgeFlowResult: z.ZodObject<{
|
|
717
|
+
purged: z.ZodLiteral<true>;
|
|
718
|
+
title: z.ZodString;
|
|
719
|
+
}, z.core.$strict>;
|
|
720
|
+
export type PurgeFlowResult = z.infer<typeof PurgeFlowResult>;
|
|
712
721
|
export declare const ListFlowsInput: z.ZodObject<{
|
|
713
722
|
parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
714
723
|
includeArchived: z.ZodOptional<z.ZodBoolean>;
|
package/dist/contract/flow.js
CHANGED
|
@@ -292,6 +292,16 @@ export const ArchiveFlowInput = z.strictObject({
|
|
|
292
292
|
.describe("`true` archives the flow, `false` restores it. Nothing is deleted and every version stays readable."),
|
|
293
293
|
idempotencyKey: IdempotencyKey,
|
|
294
294
|
});
|
|
295
|
+
// ⚠️ The flow half of the one irreversible act in Intel (#457). No `baseUpdatedAt`, no
|
|
296
|
+
// `idempotencyKey`, for the same two reasons the node's has neither: an archived flow is not being
|
|
297
|
+
// edited, and a replay has nothing to replay — deleting twice deletes once and then answers 404.
|
|
298
|
+
export const PurgeFlowInput = z.strictObject({
|
|
299
|
+
flowId: IntelId.describe("Flow to delete for good. It must already be archived — a living flow has no path into nothing."),
|
|
300
|
+
});
|
|
301
|
+
export const PurgeFlowResult = z.strictObject({
|
|
302
|
+
purged: z.literal(true),
|
|
303
|
+
title: z.string(),
|
|
304
|
+
});
|
|
295
305
|
// Three answers, not two: an absent `parentId` lists every visible flow (the search dialog asks
|
|
296
306
|
// that), `null` lists the root of the shared tree and an ID lists one folder (the sidebar tree asks
|
|
297
307
|
// per level, which is what keeps the tree off the N+1 it used to load with).
|
package/dist/contract/node.d.ts
CHANGED
|
@@ -75,7 +75,7 @@ export declare const CreateNodeInput: z.ZodObject<{
|
|
|
75
75
|
export type CreateNodeInput = z.infer<typeof CreateNodeInput>;
|
|
76
76
|
export declare const SaveNodeVersionInput: z.ZodObject<{
|
|
77
77
|
nodeId: z.ZodString;
|
|
78
|
-
baseVersionId: z.
|
|
78
|
+
baseVersionId: z.ZodUnion<readonly [z.ZodString, z.ZodNull]>;
|
|
79
79
|
content: z.ZodString;
|
|
80
80
|
mediaType: z.ZodDefault<z.ZodString>;
|
|
81
81
|
idempotencyKey: z.ZodString;
|
|
@@ -83,7 +83,7 @@ export declare const SaveNodeVersionInput: z.ZodObject<{
|
|
|
83
83
|
export type SaveNodeVersionInput = z.infer<typeof SaveNodeVersionInput>;
|
|
84
84
|
export declare const SaveAttachmentInput: z.ZodObject<{
|
|
85
85
|
nodeId: z.ZodString;
|
|
86
|
-
baseVersionId: z.
|
|
86
|
+
baseVersionId: z.ZodUnion<readonly [z.ZodString, z.ZodNull]>;
|
|
87
87
|
contentBase64: z.ZodString;
|
|
88
88
|
mediaType: z.ZodString;
|
|
89
89
|
idempotencyKey: z.ZodString;
|
|
@@ -105,6 +105,15 @@ export declare const ArchiveNodeInput: z.ZodObject<{
|
|
|
105
105
|
idempotencyKey: z.ZodString;
|
|
106
106
|
}, z.core.$strict>;
|
|
107
107
|
export type ArchiveNodeInput = z.infer<typeof ArchiveNodeInput>;
|
|
108
|
+
export declare const PurgeNodeInput: z.ZodObject<{
|
|
109
|
+
nodeId: z.ZodString;
|
|
110
|
+
}, z.core.$strict>;
|
|
111
|
+
export type PurgeNodeInput = z.infer<typeof PurgeNodeInput>;
|
|
112
|
+
export declare const PurgeNodeResult: z.ZodObject<{
|
|
113
|
+
purged: z.ZodLiteral<true>;
|
|
114
|
+
title: z.ZodString;
|
|
115
|
+
}, z.core.$strict>;
|
|
116
|
+
export type PurgeNodeResult = z.infer<typeof PurgeNodeResult>;
|
|
108
117
|
export declare const NodeList: z.ZodObject<{
|
|
109
118
|
items: z.ZodArray<z.ZodObject<{
|
|
110
119
|
id: z.ZodString;
|
package/dist/contract/node.js
CHANGED
|
@@ -95,9 +95,33 @@ export const CreateNodeInput = z.strictObject({
|
|
|
95
95
|
.describe("Optional sentence about the node, for readers rather than for search ranking."),
|
|
96
96
|
idempotencyKey: IdempotencyKey,
|
|
97
97
|
});
|
|
98
|
+
/**
|
|
99
|
+
* The version a write is made against, in the one form both writers of node content take it.
|
|
100
|
+
*
|
|
101
|
+
* ⚠️ Nullable and required are not the same thing, and here they stand together (#437). `null` is
|
|
102
|
+
* how a node that has no version yet is addressed, and it is the only way to write its first
|
|
103
|
+
* content; leaving the field out says nothing at all and is refused. Whoever leaves it out is
|
|
104
|
+
* almost always holding a node created one call earlier, and the plain type error they used to get
|
|
105
|
+
* — "expected string, received undefined" — reads as "this field must be an id", about a field that
|
|
106
|
+
* cannot have one yet. Both refusals therefore name both ways instead.
|
|
107
|
+
*
|
|
108
|
+
* ⚠️ It stays required once a version exists, and that is what it is for: a mismatch is answered
|
|
109
|
+
* with a `409` rather than by overwriting somebody else's newer version.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ The bounds remain `IntelId`'s. The repeated `min(1)` adds no rule — it only puts the sentence
|
|
112
|
+
* on the second value that gets tried after `undefined`, the empty string.
|
|
113
|
+
*
|
|
114
|
+
* ⚠️ The sentence says what the field TAKES, not what the caller did wrong. It answers a wrong type
|
|
115
|
+
* as well as an absent field, and "is required" would be false about the first — which would be
|
|
116
|
+
* this same ticket one corner further on. A value that is merely too long keeps Zod's own `Too
|
|
117
|
+
* big`: that one is already a true statement about what was sent.
|
|
118
|
+
*/
|
|
119
|
+
function baseVersion(rule) {
|
|
120
|
+
return z.union([IntelId.min(1, { error: rule }), z.null()], { error: rule });
|
|
121
|
+
}
|
|
98
122
|
export const SaveNodeVersionInput = z.strictObject({
|
|
99
123
|
nodeId: IntelId.describe("Document node to append a version to."),
|
|
100
|
-
baseVersionId:
|
|
124
|
+
baseVersionId: baseVersion("baseVersionId takes the `currentVersionId` from node_get, or null when the node has no version yet — and it has to be sent.").describe("The version this edit was made against — `currentVersionId` from node_get. If the node has moved on since, the call is refused rather than overwriting the newer version; pass `null` only for a node that has no version yet."),
|
|
101
125
|
content: z
|
|
102
126
|
.string()
|
|
103
127
|
.max(10_000_000)
|
|
@@ -112,7 +136,7 @@ export const SaveNodeVersionInput = z.strictObject({
|
|
|
112
136
|
});
|
|
113
137
|
export const SaveAttachmentInput = z.strictObject({
|
|
114
138
|
nodeId: IntelId.describe("Attachment node to append bytes to."),
|
|
115
|
-
baseVersionId:
|
|
139
|
+
baseVersionId: baseVersion("baseVersionId takes the `currentVersionId` from node_get, or null for the first upload — and it has to be sent.").describe("The version these bytes replace — `currentVersionId` from node_get, or `null` for the first upload. A stale value is refused rather than overwriting."),
|
|
116
140
|
contentBase64: z
|
|
117
141
|
.string()
|
|
118
142
|
.min(1)
|
|
@@ -157,6 +181,24 @@ export const ArchiveNodeInput = z.strictObject({
|
|
|
157
181
|
.describe("`true` archives the node, `false` restores it. Archiving hides a node from listings and search; nothing is deleted and every version stays readable."),
|
|
158
182
|
idempotencyKey: IdempotencyKey,
|
|
159
183
|
});
|
|
184
|
+
// ⚠️ The one input in this contract that describes an IRREVERSIBLE act (#457). Everything else
|
|
185
|
+
// called "delete" in Intel means `archived_at`.
|
|
186
|
+
//
|
|
187
|
+
// It carries no `baseUpdatedAt` and no `idempotencyKey`, and both absences are decisions:
|
|
188
|
+
// · There is nothing to conflict with. The node must already be archived — an archived node is
|
|
189
|
+
// not being edited by anybody, and a restore between the read and the call is caught by the
|
|
190
|
+
// statement itself rather than by a timestamp the caller had to carry.
|
|
191
|
+
// · A replay has nothing to replay. Deleting twice is deleting once and then a 404, which is the
|
|
192
|
+
// honest answer: the second caller really did not delete anything.
|
|
193
|
+
export const PurgeNodeInput = z.strictObject({
|
|
194
|
+
nodeId: IntelId.describe("Node to delete for good. It must already be archived — a living node has no path into nothing."),
|
|
195
|
+
});
|
|
196
|
+
// The title travels back because after this call nothing can look it up any more — not the row, not
|
|
197
|
+
// a version, not the index. A caller that wants to say what it just deleted has this one chance.
|
|
198
|
+
export const PurgeNodeResult = z.strictObject({
|
|
199
|
+
purged: z.literal(true),
|
|
200
|
+
title: z.string(),
|
|
201
|
+
});
|
|
160
202
|
// ⚠️ `withChildren` belongs to the LEVEL, not to the node (#59). Whether something has children
|
|
161
203
|
// THIS reader may see is not a property of the thing — two readers get different answers. As a
|
|
162
204
|
// field on the node, every other place that returns a node would have to compute it as well or
|