@mdbase-dev/pickle 0.1.0-beta.31 → 0.1.0-beta.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +23 -8
- package/dist/index.js +71 -40
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type MdbaseConnection } from "@mdbase-dev/connect";
|
|
1
|
+
import { type CollectionContractDescriptor, type CollectionDescription, type CollectionTypeDescriptor, type JsonObject, type RecordDocument, type ConnectRequestOptions, type MdbaseConnection, type PendingMutation } from "@mdbase-dev/connect";
|
|
3
2
|
export { PICKLE_ACK_RESPONSE_TYPE_DOCUMENT, PICKLE_APPROVAL_RESPONSE_TYPE_DOCUMENT, PICKLE_REQUEST_CONTRACT_DOCUMENT, PICKLE_REQUEST_CONTRACT_DIGEST, PICKLE_TYPE_PACK_PROVISION, PICKLE_REQUEST_TYPE_DOCUMENT } from "./resources.js";
|
|
4
3
|
export declare const PICKLE_REQUEST_CONTRACT = "pickle.request";
|
|
5
4
|
export declare const PICKLE_REQUEST_CONTRACT_VERSION = "1.0.0";
|
|
@@ -78,13 +77,23 @@ export interface PickleRequest {
|
|
|
78
77
|
response?: PickleResponse;
|
|
79
78
|
frontmatter: PickleFrontmatter;
|
|
80
79
|
}
|
|
81
|
-
export interface RespondOptions {
|
|
80
|
+
export interface RespondOptions extends ConnectRequestOptions {
|
|
82
81
|
responder?: string;
|
|
83
82
|
}
|
|
83
|
+
export type PickleResponseSubmission = {
|
|
84
|
+
kind: "recorded";
|
|
85
|
+
record: RecordDocument<PickleFrontmatter>;
|
|
86
|
+
} | {
|
|
87
|
+
kind: "pending";
|
|
88
|
+
requestId: string;
|
|
89
|
+
};
|
|
90
|
+
export type PicklePendingResponse = PendingMutation<RecordDocument<PickleFrontmatter>>;
|
|
84
91
|
export interface PickleClient {
|
|
85
|
-
describe(): ReturnType<MdbaseConnection<PickleFrontmatter>["describe"]>;
|
|
86
|
-
queryAll(input: Parameters<MdbaseConnection<PickleFrontmatter>["queryAll"]>[0]): ReturnType<MdbaseConnection<PickleFrontmatter>["queryAll"]>;
|
|
87
|
-
create(input: Parameters<MdbaseConnection<PickleFrontmatter>["create"]>[0]): ReturnType<MdbaseConnection<PickleFrontmatter>["create"]>;
|
|
92
|
+
describe(options?: ConnectRequestOptions): ReturnType<MdbaseConnection<PickleFrontmatter>["describe"]>;
|
|
93
|
+
queryAll(input: Parameters<MdbaseConnection<PickleFrontmatter>["queryAll"]>[0], options?: Parameters<MdbaseConnection<PickleFrontmatter>["queryAll"]>[1]): ReturnType<MdbaseConnection<PickleFrontmatter>["queryAll"]>;
|
|
94
|
+
create(input: Parameters<MdbaseConnection<PickleFrontmatter>["create"]>[0], options?: ConnectRequestOptions): ReturnType<MdbaseConnection<PickleFrontmatter>["create"]>;
|
|
95
|
+
pendingMutations<Result = unknown>(): readonly PendingMutation<Result>[];
|
|
96
|
+
pendingMutation<Result = unknown>(requestId: string): PendingMutation<Result> | null;
|
|
88
97
|
}
|
|
89
98
|
export declare function resolvePickleContract(description: CollectionDescription): PickleContract;
|
|
90
99
|
export declare class PickleCollection {
|
|
@@ -96,8 +105,14 @@ export declare class PickleCollection {
|
|
|
96
105
|
collection: CollectionDescription;
|
|
97
106
|
contract: PickleContract;
|
|
98
107
|
}>;
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
describe(options: ConnectRequestOptions): Promise<{
|
|
109
|
+
collection: CollectionDescription;
|
|
110
|
+
contract: PickleContract;
|
|
111
|
+
}>;
|
|
112
|
+
list(options?: ConnectRequestOptions): Promise<PickleRequest[]>;
|
|
113
|
+
respond(request: PickleRequest, payload: JsonObject, options?: RespondOptions): Promise<PickleResponseSubmission>;
|
|
114
|
+
pendingResponses(): readonly PicklePendingResponse[];
|
|
115
|
+
recoverResponse(requestId: string, options?: ConnectRequestOptions): Promise<PickleResponseSubmission>;
|
|
101
116
|
}
|
|
102
117
|
export declare class PickleContractError extends Error {
|
|
103
118
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { MDBASE_RECORD_CREATED_CONTRACT } from "@mdbase-dev/connect
|
|
2
|
-
import { unwrapConnectOutcome } from "@mdbase-dev/connect";
|
|
1
|
+
import { MDBASE_RECORD_CREATED_CONTRACT, MdbaseConnectError } from "@mdbase-dev/connect";
|
|
3
2
|
import { PICKLE_REQUEST_CONTRACT_DIGEST } from "./resources.js";
|
|
4
3
|
export { PICKLE_ACK_RESPONSE_TYPE_DOCUMENT, PICKLE_APPROVAL_RESPONSE_TYPE_DOCUMENT, PICKLE_REQUEST_CONTRACT_DOCUMENT, PICKLE_REQUEST_CONTRACT_DIGEST, PICKLE_TYPE_PACK_PROVISION, PICKLE_REQUEST_TYPE_DOCUMENT } from "./resources.js";
|
|
5
4
|
export const PICKLE_REQUEST_CONTRACT = "pickle.request";
|
|
@@ -23,11 +22,11 @@ export function resolvePickleContract(description) {
|
|
|
23
22
|
throw new PickleContractError("This collection does not provide the Pickle request contract.");
|
|
24
23
|
}
|
|
25
24
|
const implementations = descriptor.implementations.map((implementation) => {
|
|
26
|
-
const requestType = description.types.find((candidate) => candidate.name === implementation.
|
|
25
|
+
const requestType = description.types.find((candidate) => candidate.name === implementation.typeName);
|
|
27
26
|
if (!requestType)
|
|
28
|
-
throw new PickleContractError(`The Pickle request type ${implementation.
|
|
27
|
+
throw new PickleContractError(`The Pickle request type ${implementation.typeName} is missing from this collection.`);
|
|
29
28
|
return {
|
|
30
|
-
typeName: implementation.
|
|
29
|
+
typeName: implementation.typeName,
|
|
31
30
|
fields: parseFields(implementation.fields),
|
|
32
31
|
binding: implementation.binding,
|
|
33
32
|
requestType
|
|
@@ -47,33 +46,33 @@ export class PickleCollection {
|
|
|
47
46
|
constructor(connect) {
|
|
48
47
|
this.connect = connect;
|
|
49
48
|
}
|
|
50
|
-
async describe() {
|
|
51
|
-
this.description ??=
|
|
49
|
+
async describe(options = {}) {
|
|
50
|
+
this.description ??= requireOutcome(await this.connect.describe(options));
|
|
52
51
|
this.contract ??= resolvePickleContract(this.description);
|
|
53
52
|
return { collection: this.description, contract: this.contract };
|
|
54
53
|
}
|
|
55
|
-
async list() {
|
|
56
|
-
const { collection, contract } = await this.describe();
|
|
57
|
-
const requestQuery =
|
|
54
|
+
async list(options = {}) {
|
|
55
|
+
const { collection, contract } = await this.describe(options);
|
|
56
|
+
const requestQuery = requireOutcome(await this.connect.queryAll({
|
|
58
57
|
types: contract.implementations.map(({ typeName }) => typeName),
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}));
|
|
58
|
+
includeBody: true,
|
|
59
|
+
frontmatterMode: "effective"
|
|
60
|
+
}, options));
|
|
62
61
|
const requests = requestQuery.results.map(requireEffectiveFrontmatter);
|
|
63
62
|
const responseTypes = [
|
|
64
63
|
...new Set(requests
|
|
65
64
|
.map((record) => {
|
|
66
65
|
const implementation = implementationForRecord(contract, record);
|
|
67
|
-
return stringField(record.
|
|
66
|
+
return stringField(record.effectiveFrontmatter, role(implementation, "response_type", "response_type"));
|
|
68
67
|
})
|
|
69
68
|
.filter(Boolean))
|
|
70
69
|
];
|
|
71
70
|
const responses = responseTypes.length
|
|
72
|
-
? (
|
|
71
|
+
? (requireOutcome(await this.connect.queryAll({
|
|
73
72
|
types: responseTypes,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}))).results.map(requireEffectiveFrontmatter)
|
|
73
|
+
includeBody: true,
|
|
74
|
+
frontmatterMode: "effective"
|
|
75
|
+
}, options))).results.map(requireEffectiveFrontmatter)
|
|
77
76
|
: [];
|
|
78
77
|
return requests
|
|
79
78
|
.map((record) => normalizeRequest(record, responses, collection, contract))
|
|
@@ -91,28 +90,55 @@ export class PickleCollection {
|
|
|
91
90
|
}
|
|
92
91
|
const responseFolder = stringField(asObject(responseType.collection?.path), "folder") ||
|
|
93
92
|
"responses";
|
|
93
|
+
const { responder, ...requestOptions } = options;
|
|
94
94
|
const frontmatter = {
|
|
95
95
|
...payload,
|
|
96
96
|
type: request.responseType,
|
|
97
97
|
request: requestLink(request.path),
|
|
98
|
-
responder:
|
|
98
|
+
responder: responder?.trim() || "human"
|
|
99
99
|
};
|
|
100
100
|
const created = await this.connect.create({
|
|
101
101
|
type: request.responseType,
|
|
102
102
|
path: `${trimSlashes(responseFolder)}/${crypto.randomUUID()}.md`,
|
|
103
103
|
frontmatter,
|
|
104
104
|
body: ""
|
|
105
|
-
});
|
|
106
|
-
return
|
|
105
|
+
}, requestOptions);
|
|
106
|
+
return responseSubmission(created);
|
|
107
|
+
}
|
|
108
|
+
pendingResponses() {
|
|
109
|
+
return this.connect
|
|
110
|
+
.pendingMutations()
|
|
111
|
+
.filter((mutation) => mutation.operation === "create");
|
|
112
|
+
}
|
|
113
|
+
async recoverResponse(requestId, options = {}) {
|
|
114
|
+
const pending = this.connect.pendingMutation(requestId);
|
|
115
|
+
if (!pending || pending.operation !== "create") {
|
|
116
|
+
throw new PickleContractError("This pending Pickle response is no longer available for recovery.");
|
|
117
|
+
}
|
|
118
|
+
return responseSubmission(await pending.recover(options));
|
|
107
119
|
}
|
|
108
120
|
}
|
|
109
121
|
export class PickleContractError extends Error {
|
|
110
122
|
}
|
|
123
|
+
function responseSubmission(outcome) {
|
|
124
|
+
if (outcome.ok)
|
|
125
|
+
return { kind: "recorded", record: outcome.value };
|
|
126
|
+
const requestId = outcome.problem.code === "operation_outcome_unknown" &&
|
|
127
|
+
outcome.problem.details &&
|
|
128
|
+
typeof outcome.problem.details === "object" &&
|
|
129
|
+
"request_id" in outcome.problem.details &&
|
|
130
|
+
typeof outcome.problem.details.request_id === "string"
|
|
131
|
+
? outcome.problem.details.request_id
|
|
132
|
+
: null;
|
|
133
|
+
if (requestId)
|
|
134
|
+
return { kind: "pending", requestId };
|
|
135
|
+
throw new MdbaseConnectError(outcome.problem);
|
|
136
|
+
}
|
|
111
137
|
function normalizeRequest(record, responseRecords, description, contract) {
|
|
112
138
|
const implementation = implementationForRecord(contract, record);
|
|
113
|
-
const responseType = stringField(record.
|
|
114
|
-
const linked = responseRecords.filter((candidate) => linkTargets(candidate.
|
|
115
|
-
const status = stringField(record.
|
|
139
|
+
const responseType = stringField(record.effectiveFrontmatter, role(implementation, "response_type", "response_type")) || "pickle_response_approval";
|
|
140
|
+
const linked = responseRecords.filter((candidate) => linkTargets(candidate.effectiveFrontmatter.request, record.path));
|
|
141
|
+
const status = stringField(record.effectiveFrontmatter, role(implementation, "status", "status"));
|
|
116
142
|
const state = status === "cancelled"
|
|
117
143
|
? "cancelled"
|
|
118
144
|
: linked.length === 0
|
|
@@ -122,35 +148,35 @@ function normalizeRequest(record, responseRecords, description, contract) {
|
|
|
122
148
|
: "conflict";
|
|
123
149
|
const response = linked.length === 1 ? normalizeResponse(linked[0]) : undefined;
|
|
124
150
|
return {
|
|
125
|
-
id: stringField(record.
|
|
151
|
+
id: stringField(record.effectiveFrontmatter, role(implementation, "id", "id")) ||
|
|
126
152
|
record.path,
|
|
127
153
|
path: record.path,
|
|
128
|
-
title: stringField(record.
|
|
154
|
+
title: stringField(record.effectiveFrontmatter, role(implementation, "title", "title")) ||
|
|
129
155
|
record.path,
|
|
130
|
-
source: stringField(record.
|
|
156
|
+
source: stringField(record.effectiveFrontmatter, role(implementation, "source", "source")) ||
|
|
131
157
|
"agent",
|
|
132
|
-
message: stringField(record.
|
|
158
|
+
message: stringField(record.effectiveFrontmatter, role(implementation, "message", "message")),
|
|
133
159
|
body: record.body ?? "",
|
|
134
|
-
kind: stringField(record.
|
|
160
|
+
kind: stringField(record.effectiveFrontmatter, role(implementation, "kind", "kind")) ||
|
|
135
161
|
"approval",
|
|
136
|
-
priority: stringField(record.
|
|
162
|
+
priority: stringField(record.effectiveFrontmatter, role(implementation, "priority", "priority")) || "normal",
|
|
137
163
|
status: status || undefined,
|
|
138
164
|
state,
|
|
139
165
|
responseCount: linked.length,
|
|
140
166
|
responseType,
|
|
141
167
|
responseTypeDefinition: description.types.find((candidate) => candidate.name === responseType),
|
|
142
|
-
createdAt: stringField(record.
|
|
143
|
-
dueAt: stringField(record.
|
|
144
|
-
tags: stringList(getField(record.
|
|
145
|
-
links: links(getField(record.
|
|
146
|
-
attachments: attachments(getField(record.
|
|
147
|
-
metadata: asObject(getField(record.
|
|
168
|
+
createdAt: stringField(record.effectiveFrontmatter, role(implementation, "created_at", "created_at")) || undefined,
|
|
169
|
+
dueAt: stringField(record.effectiveFrontmatter, "due_at") || undefined,
|
|
170
|
+
tags: stringList(getField(record.effectiveFrontmatter, role(implementation, "tags", "tags"))),
|
|
171
|
+
links: links(getField(record.effectiveFrontmatter, role(implementation, "links", "links"))),
|
|
172
|
+
attachments: attachments(getField(record.effectiveFrontmatter, role(implementation, "attachment_paths", "attachment_paths"))),
|
|
173
|
+
metadata: asObject(getField(record.effectiveFrontmatter, role(implementation, "metadata", "metadata"))) ?? {},
|
|
148
174
|
response,
|
|
149
|
-
frontmatter: record.
|
|
175
|
+
frontmatter: record.effectiveFrontmatter
|
|
150
176
|
};
|
|
151
177
|
}
|
|
152
178
|
function normalizeResponse(record) {
|
|
153
|
-
const frontmatter = record.
|
|
179
|
+
const frontmatter = record.effectiveFrontmatter;
|
|
154
180
|
return {
|
|
155
181
|
path: record.path,
|
|
156
182
|
type: stringField(frontmatter, "type") || record.types[0] || "pickle_response",
|
|
@@ -161,11 +187,16 @@ function normalizeResponse(record) {
|
|
|
161
187
|
};
|
|
162
188
|
}
|
|
163
189
|
function requireEffectiveFrontmatter(record) {
|
|
164
|
-
if (!record.
|
|
165
|
-
throw new PickleContractError(`Query result ${record.path} omitted
|
|
190
|
+
if (!record.effectiveFrontmatter) {
|
|
191
|
+
throw new PickleContractError(`Query result ${record.path} omitted effectiveFrontmatter.`);
|
|
166
192
|
}
|
|
167
193
|
return record;
|
|
168
194
|
}
|
|
195
|
+
function requireOutcome(outcome) {
|
|
196
|
+
if (!outcome.ok)
|
|
197
|
+
throw new MdbaseConnectError(outcome.problem);
|
|
198
|
+
return outcome.value;
|
|
199
|
+
}
|
|
169
200
|
function parseFields(value) {
|
|
170
201
|
if (!Object.values(value).every((field) => typeof field === "string" && validFieldPath(field)))
|
|
171
202
|
throw new PickleContractError("The Pickle request contract is malformed.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/pickle",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.33",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@mdbase-dev/connect": "0.1.0-beta.
|
|
26
|
-
"@mdbase-dev/connect-protocol": "0.1.0-beta.31"
|
|
25
|
+
"@mdbase-dev/connect": "0.1.0-beta.33"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
28
|
"typescript": "^7.0.2",
|
|
30
29
|
"vitest": "^4.1.10",
|
|
31
|
-
"@mdbase-dev/connect-
|
|
30
|
+
"@mdbase-dev/connect-testing": "0.1.0-beta.33",
|
|
31
|
+
"@mdbase-dev/connect-dev": "0.1.0-beta.33"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|