@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 CHANGED
@@ -1,5 +1,4 @@
1
- import type { CollectionContractDescriptor, CollectionDescription, CollectionTypeDescriptor, JsonObject, RecordDocument } from "@mdbase-dev/connect-protocol";
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
- list(): Promise<PickleRequest[]>;
100
- respond(request: PickleRequest, payload: JsonObject, options?: RespondOptions): Promise<RecordDocument<PickleFrontmatter>>;
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-protocol";
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.type_name);
25
+ const requestType = description.types.find((candidate) => candidate.name === implementation.typeName);
27
26
  if (!requestType)
28
- throw new PickleContractError(`The Pickle request type ${implementation.type_name} is missing from this collection.`);
27
+ throw new PickleContractError(`The Pickle request type ${implementation.typeName} is missing from this collection.`);
29
28
  return {
30
- typeName: implementation.type_name,
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 ??= unwrapConnectOutcome(await this.connect.describe());
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 = unwrapConnectOutcome(await this.connect.queryAll({
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
- include_body: true,
60
- frontmatter_mode: "effective"
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.effective_frontmatter, role(implementation, "response_type", "response_type"));
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
- ? (unwrapConnectOutcome(await this.connect.queryAll({
71
+ ? (requireOutcome(await this.connect.queryAll({
73
72
  types: responseTypes,
74
- include_body: true,
75
- frontmatter_mode: "effective"
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: options.responder?.trim() || "human"
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 unwrapConnectOutcome(created);
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.effective_frontmatter, role(implementation, "response_type", "response_type")) || "pickle_response_approval";
114
- const linked = responseRecords.filter((candidate) => linkTargets(candidate.effective_frontmatter.request, record.path));
115
- const status = stringField(record.effective_frontmatter, role(implementation, "status", "status"));
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.effective_frontmatter, role(implementation, "id", "id")) ||
151
+ id: stringField(record.effectiveFrontmatter, role(implementation, "id", "id")) ||
126
152
  record.path,
127
153
  path: record.path,
128
- title: stringField(record.effective_frontmatter, role(implementation, "title", "title")) ||
154
+ title: stringField(record.effectiveFrontmatter, role(implementation, "title", "title")) ||
129
155
  record.path,
130
- source: stringField(record.effective_frontmatter, role(implementation, "source", "source")) ||
156
+ source: stringField(record.effectiveFrontmatter, role(implementation, "source", "source")) ||
131
157
  "agent",
132
- message: stringField(record.effective_frontmatter, role(implementation, "message", "message")),
158
+ message: stringField(record.effectiveFrontmatter, role(implementation, "message", "message")),
133
159
  body: record.body ?? "",
134
- kind: stringField(record.effective_frontmatter, role(implementation, "kind", "kind")) ||
160
+ kind: stringField(record.effectiveFrontmatter, role(implementation, "kind", "kind")) ||
135
161
  "approval",
136
- priority: stringField(record.effective_frontmatter, role(implementation, "priority", "priority")) || "normal",
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.effective_frontmatter, role(implementation, "created_at", "created_at")) || undefined,
143
- dueAt: stringField(record.effective_frontmatter, "due_at") || undefined,
144
- tags: stringList(getField(record.effective_frontmatter, role(implementation, "tags", "tags"))),
145
- links: links(getField(record.effective_frontmatter, role(implementation, "links", "links"))),
146
- attachments: attachments(getField(record.effective_frontmatter, role(implementation, "attachment_paths", "attachment_paths"))),
147
- metadata: asObject(getField(record.effective_frontmatter, role(implementation, "metadata", "metadata"))) ?? {},
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.effective_frontmatter
175
+ frontmatter: record.effectiveFrontmatter
150
176
  };
151
177
  }
152
178
  function normalizeResponse(record) {
153
- const frontmatter = record.effective_frontmatter;
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.effective_frontmatter) {
165
- throw new PickleContractError(`Query result ${record.path} omitted effective_frontmatter.`);
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.31",
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.31",
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-dev": "0.1.0-beta.31"
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",