@mdbase-dev/pickle 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +350 -0
- package/dist/resources.d.ts +68 -0
- package/dist/resources.js +304 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mdbase connect contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# `@mdbase-dev/pickle`
|
|
2
|
+
|
|
3
|
+
Contract adapter and provisioning resources for Pickle collections.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const pickle = new PickleCollection(connect);
|
|
7
|
+
const requests = await pickle.list();
|
|
8
|
+
await pickle.respond(requests[0], {
|
|
9
|
+
decision: "approve",
|
|
10
|
+
comment: "Looks right."
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The adapter uses ordinary mdbase operations. Request state is derived from
|
|
15
|
+
linked response records, and no record payload is stored outside the collection.
|
|
16
|
+
|
|
17
|
+
For an inbox, use `pickle.list({ includeBody: false })`. It still waits for all
|
|
18
|
+
response links before deriving pending, answered, and conflict states, but does
|
|
19
|
+
not transfer every request's Markdown context. Fetch an opened request's context
|
|
20
|
+
with `await pickle.readBody(request, { signal, timeoutMs: 10_000 })`. Response
|
|
21
|
+
bodies are never needed for lifecycle calculation and are omitted from queries.
|
|
22
|
+
|
|
23
|
+
Pickle requests 256 records per page from the first query. Cursor authorities
|
|
24
|
+
pin that initial size; continuation requests reuse the cursor rather than asking
|
|
25
|
+
for an ineffective larger limit.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { type CollectionContractDescriptor, type CollectionDescription, type CollectionTypeDescriptor, type JsonObject, type RecordDocument, type ConnectRequestOptions, type MdbaseConnection, type PendingMutation } from "@mdbase-dev/connect";
|
|
2
|
+
export { PICKLE_ACK_RESPONSE_TYPE_DOCUMENT, PICKLE_ATTACHMENT_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";
|
|
3
|
+
export declare const PICKLE_REQUEST_CONTRACT = "pickle.request";
|
|
4
|
+
export declare const PICKLE_REQUEST_CONTRACT_VERSION = "1.0.0";
|
|
5
|
+
export declare const PICKLE_NOTIFICATION_CRITERION = "pickle.request.created";
|
|
6
|
+
export declare const PICKLE_NOTIFICATION_EVENT: {
|
|
7
|
+
readonly id: "mdbase.record.created";
|
|
8
|
+
readonly version: "1.0.0";
|
|
9
|
+
readonly digest: "sha256:7f3bed6baa356ee9389e977ae7b77a102e2bee871a7c1d9f2026fc21cacdbfc9";
|
|
10
|
+
};
|
|
11
|
+
export type PickleRequestState = "pending" | "answered" | "conflict" | "cancelled";
|
|
12
|
+
export interface PickleFrontmatter extends JsonObject {
|
|
13
|
+
type?: string;
|
|
14
|
+
id?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
source?: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
kind?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
priority?: string;
|
|
21
|
+
response_type?: string;
|
|
22
|
+
created_at?: string;
|
|
23
|
+
due_at?: string;
|
|
24
|
+
dedupe_key?: string;
|
|
25
|
+
tags?: unknown[];
|
|
26
|
+
links?: unknown[];
|
|
27
|
+
attachment_paths?: unknown[];
|
|
28
|
+
metadata?: JsonObject;
|
|
29
|
+
}
|
|
30
|
+
export interface PickleContractImplementation {
|
|
31
|
+
typeName: string;
|
|
32
|
+
fields: Record<string, string>;
|
|
33
|
+
binding?: JsonObject;
|
|
34
|
+
requestType: CollectionTypeDescriptor;
|
|
35
|
+
}
|
|
36
|
+
export interface PickleContract {
|
|
37
|
+
descriptor: CollectionContractDescriptor;
|
|
38
|
+
implementations: PickleContractImplementation[];
|
|
39
|
+
}
|
|
40
|
+
export interface PickleLink {
|
|
41
|
+
label: string;
|
|
42
|
+
url?: string;
|
|
43
|
+
path?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface PickleAttachment {
|
|
46
|
+
path: string;
|
|
47
|
+
filename: string;
|
|
48
|
+
}
|
|
49
|
+
export interface PickleAttachmentContent {
|
|
50
|
+
path: string;
|
|
51
|
+
filename: string;
|
|
52
|
+
mediaType: string;
|
|
53
|
+
size: number;
|
|
54
|
+
blob: Blob;
|
|
55
|
+
}
|
|
56
|
+
export interface PickleResponse {
|
|
57
|
+
path: string;
|
|
58
|
+
type: string;
|
|
59
|
+
responder: string;
|
|
60
|
+
respondedAt?: string;
|
|
61
|
+
payload: JsonObject;
|
|
62
|
+
frontmatter: JsonObject;
|
|
63
|
+
}
|
|
64
|
+
export interface PickleRequest {
|
|
65
|
+
id: string;
|
|
66
|
+
path: string;
|
|
67
|
+
title: string;
|
|
68
|
+
source: string;
|
|
69
|
+
message: string;
|
|
70
|
+
body: string;
|
|
71
|
+
kind: string;
|
|
72
|
+
priority: string;
|
|
73
|
+
status?: string;
|
|
74
|
+
state: PickleRequestState;
|
|
75
|
+
responseCount: number;
|
|
76
|
+
responseType: string;
|
|
77
|
+
responseTypeDefinition?: CollectionTypeDescriptor;
|
|
78
|
+
createdAt?: string;
|
|
79
|
+
/** Stable file modification time for invalidating an opened context. */
|
|
80
|
+
modifiedAt?: string;
|
|
81
|
+
dueAt?: string;
|
|
82
|
+
tags: string[];
|
|
83
|
+
links: PickleLink[];
|
|
84
|
+
attachments: PickleAttachment[];
|
|
85
|
+
metadata: JsonObject;
|
|
86
|
+
response?: PickleResponse;
|
|
87
|
+
frontmatter: PickleFrontmatter;
|
|
88
|
+
}
|
|
89
|
+
export interface RespondOptions extends ConnectRequestOptions {
|
|
90
|
+
responder?: string;
|
|
91
|
+
}
|
|
92
|
+
export type PickleResponseSubmission = {
|
|
93
|
+
kind: "recorded";
|
|
94
|
+
record: RecordDocument<PickleFrontmatter>;
|
|
95
|
+
} | {
|
|
96
|
+
kind: "pending";
|
|
97
|
+
requestId: string;
|
|
98
|
+
};
|
|
99
|
+
export type PicklePendingResponse = PendingMutation<RecordDocument<PickleFrontmatter>>;
|
|
100
|
+
export interface PickleClient {
|
|
101
|
+
describe(options?: ConnectRequestOptions): ReturnType<MdbaseConnection<PickleFrontmatter>["describe"]>;
|
|
102
|
+
queryAll(input: Parameters<MdbaseConnection<PickleFrontmatter>["queryAll"]>[0], options?: Parameters<MdbaseConnection<PickleFrontmatter>["queryAll"]>[1]): ReturnType<MdbaseConnection<PickleFrontmatter>["queryAll"]>;
|
|
103
|
+
create(input: Parameters<MdbaseConnection<PickleFrontmatter>["create"]>[0], options?: ConnectRequestOptions): ReturnType<MdbaseConnection<PickleFrontmatter>["create"]>;
|
|
104
|
+
read(input: Parameters<MdbaseConnection<PickleFrontmatter>["read"]>[0], options?: ConnectRequestOptions): ReturnType<MdbaseConnection<PickleFrontmatter>["read"]>;
|
|
105
|
+
files: MdbaseConnection<PickleFrontmatter>["files"];
|
|
106
|
+
pendingMutations<Result = unknown>(): readonly PendingMutation<Result>[];
|
|
107
|
+
pendingMutation<Result = unknown>(requestId: string): PendingMutation<Result> | null;
|
|
108
|
+
}
|
|
109
|
+
export declare function resolvePickleContract(description: CollectionDescription): PickleContract;
|
|
110
|
+
export declare class PickleCollection {
|
|
111
|
+
private readonly connect;
|
|
112
|
+
private description;
|
|
113
|
+
private contract;
|
|
114
|
+
constructor(connect: PickleClient);
|
|
115
|
+
describe(): Promise<{
|
|
116
|
+
collection: CollectionDescription;
|
|
117
|
+
contract: PickleContract;
|
|
118
|
+
}>;
|
|
119
|
+
describe(options: ConnectRequestOptions): Promise<{
|
|
120
|
+
collection: CollectionDescription;
|
|
121
|
+
contract: PickleContract;
|
|
122
|
+
}>;
|
|
123
|
+
/** Omit Markdown bodies for an inbox; load individual context with readBody. */
|
|
124
|
+
list({ includeBody, ...options }?: ConnectRequestOptions & {
|
|
125
|
+
includeBody?: boolean;
|
|
126
|
+
}): Promise<PickleRequest[]>;
|
|
127
|
+
readBody(request: Pick<PickleRequest, "path">, options?: ConnectRequestOptions): Promise<string>;
|
|
128
|
+
respond(request: PickleRequest, payload: JsonObject, options?: RespondOptions): Promise<PickleResponseSubmission>;
|
|
129
|
+
readAttachment(attachment: PickleAttachment, options?: ConnectRequestOptions): Promise<PickleAttachmentContent | null>;
|
|
130
|
+
pendingResponses(): readonly PicklePendingResponse[];
|
|
131
|
+
recoverResponse(requestId: string, options?: ConnectRequestOptions): Promise<PickleResponseSubmission>;
|
|
132
|
+
}
|
|
133
|
+
export declare class PickleContractError extends Error {
|
|
134
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { MDBASE_RECORD_CREATED_CONTRACT, MdbaseConnectError } from "@mdbase-dev/connect";
|
|
2
|
+
import { PICKLE_REQUEST_CONTRACT_DIGEST } from "./resources.js";
|
|
3
|
+
export { PICKLE_ACK_RESPONSE_TYPE_DOCUMENT, PICKLE_ATTACHMENT_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
|
+
export const PICKLE_REQUEST_CONTRACT = "pickle.request";
|
|
5
|
+
export const PICKLE_REQUEST_CONTRACT_VERSION = "1.0.0";
|
|
6
|
+
export const PICKLE_NOTIFICATION_CRITERION = "pickle.request.created";
|
|
7
|
+
export const PICKLE_NOTIFICATION_EVENT = MDBASE_RECORD_CREATED_CONTRACT;
|
|
8
|
+
const SYSTEM_RESPONSE_FIELDS = new Set([
|
|
9
|
+
"type",
|
|
10
|
+
"types",
|
|
11
|
+
"id",
|
|
12
|
+
"request",
|
|
13
|
+
"responded_at",
|
|
14
|
+
"responder",
|
|
15
|
+
"attachment_paths"
|
|
16
|
+
]);
|
|
17
|
+
export function resolvePickleContract(description) {
|
|
18
|
+
const descriptor = description.contracts.find((contract) => contract.id === PICKLE_REQUEST_CONTRACT &&
|
|
19
|
+
contract.version === PICKLE_REQUEST_CONTRACT_VERSION &&
|
|
20
|
+
contract.digest === PICKLE_REQUEST_CONTRACT_DIGEST);
|
|
21
|
+
if (!descriptor) {
|
|
22
|
+
throw new PickleContractError("This collection does not provide the Pickle request contract.");
|
|
23
|
+
}
|
|
24
|
+
const implementations = descriptor.implementations.map((implementation) => {
|
|
25
|
+
const requestType = description.types.find((candidate) => candidate.name === implementation.typeName);
|
|
26
|
+
if (!requestType)
|
|
27
|
+
throw new PickleContractError(`The Pickle request type ${implementation.typeName} is missing from this collection.`);
|
|
28
|
+
return {
|
|
29
|
+
typeName: implementation.typeName,
|
|
30
|
+
fields: parseFields(implementation.fields),
|
|
31
|
+
binding: implementation.binding,
|
|
32
|
+
requestType
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
if (!implementations.length)
|
|
36
|
+
throw new PickleContractError("The Pickle request contract has no type implementations.");
|
|
37
|
+
return {
|
|
38
|
+
descriptor,
|
|
39
|
+
implementations
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export class PickleCollection {
|
|
43
|
+
connect;
|
|
44
|
+
description = null;
|
|
45
|
+
contract = null;
|
|
46
|
+
constructor(connect) {
|
|
47
|
+
this.connect = connect;
|
|
48
|
+
}
|
|
49
|
+
async describe(options = {}) {
|
|
50
|
+
this.description ??= requireOutcome(await this.connect.describe(options));
|
|
51
|
+
this.contract ??= resolvePickleContract(this.description);
|
|
52
|
+
return { collection: this.description, contract: this.contract };
|
|
53
|
+
}
|
|
54
|
+
/** Omit Markdown bodies for an inbox; load individual context with readBody. */
|
|
55
|
+
async list({ includeBody = true, ...options } = {}) {
|
|
56
|
+
const { collection, contract } = await this.describe(options);
|
|
57
|
+
const requestQuery = requireOutcome(await this.connect.queryAll({
|
|
58
|
+
types: contract.implementations.map(({ typeName }) => typeName),
|
|
59
|
+
includeBody,
|
|
60
|
+
frontmatterMode: "effective"
|
|
61
|
+
}, { ...options, pageSize: 256 }));
|
|
62
|
+
const requests = requestQuery.results.map(requireEffectiveFrontmatter);
|
|
63
|
+
const responseTypes = [
|
|
64
|
+
...new Set(requests
|
|
65
|
+
.map((record) => {
|
|
66
|
+
const implementation = implementationForRecord(contract, record);
|
|
67
|
+
return stringField(record.effectiveFrontmatter, role(implementation, "response_type", "response_type"));
|
|
68
|
+
})
|
|
69
|
+
.filter(Boolean))
|
|
70
|
+
];
|
|
71
|
+
const responses = responseTypes.length
|
|
72
|
+
? (requireOutcome(await this.connect.queryAll({
|
|
73
|
+
types: responseTypes,
|
|
74
|
+
includeBody: false,
|
|
75
|
+
frontmatterMode: "effective"
|
|
76
|
+
}, { ...options, pageSize: 256 }))).results.map(requireEffectiveFrontmatter)
|
|
77
|
+
: [];
|
|
78
|
+
return requests
|
|
79
|
+
.map((record) => normalizeRequest(record, responses, collection, contract))
|
|
80
|
+
.sort((left, right) => (right.createdAt ?? "").localeCompare(left.createdAt ?? ""));
|
|
81
|
+
}
|
|
82
|
+
async readBody(request, options = {}) {
|
|
83
|
+
const record = requireOutcome(await this.connect.read({ path: request.path }, options));
|
|
84
|
+
return record.body ?? "";
|
|
85
|
+
}
|
|
86
|
+
async respond(request, payload, options = {}) {
|
|
87
|
+
if (request.state !== "pending") {
|
|
88
|
+
throw new PickleContractError(request.state === "conflict"
|
|
89
|
+
? "Resolve the conflicting responses in the collection first."
|
|
90
|
+
: "This request already has a response.");
|
|
91
|
+
}
|
|
92
|
+
const responseType = request.responseTypeDefinition;
|
|
93
|
+
if (!responseType) {
|
|
94
|
+
throw new PickleContractError(`The response type ${request.responseType} is unavailable.`);
|
|
95
|
+
}
|
|
96
|
+
const responseFolder = stringField(asObject(responseType.collection?.path), "folder") ||
|
|
97
|
+
"responses";
|
|
98
|
+
const { responder, ...requestOptions } = options;
|
|
99
|
+
const frontmatter = {
|
|
100
|
+
...payload,
|
|
101
|
+
type: request.responseType,
|
|
102
|
+
request: requestLink(request.path),
|
|
103
|
+
responder: responder?.trim() || "human"
|
|
104
|
+
};
|
|
105
|
+
const created = await this.connect.create({
|
|
106
|
+
type: request.responseType,
|
|
107
|
+
path: `${trimSlashes(responseFolder)}/${crypto.randomUUID()}.md`,
|
|
108
|
+
frontmatter,
|
|
109
|
+
body: ""
|
|
110
|
+
}, requestOptions);
|
|
111
|
+
return responseSubmission(created);
|
|
112
|
+
}
|
|
113
|
+
async readAttachment(attachment, options = {}) {
|
|
114
|
+
const path = attachment.path.replace(/^\/+/, "");
|
|
115
|
+
if (isMarkdownPath(path)) {
|
|
116
|
+
const outcome = await this.connect.read({ path }, options);
|
|
117
|
+
if (outcome.ok) {
|
|
118
|
+
const frontmatter = outcome.value.frontmatter;
|
|
119
|
+
if (stringField(frontmatter, "type") === "pickle_attachment") {
|
|
120
|
+
const body = outcome.value.body ?? "";
|
|
121
|
+
const filename = stringField(frontmatter, "filename") || attachment.filename;
|
|
122
|
+
const mediaType = stringField(frontmatter, "content_type") || "text/markdown";
|
|
123
|
+
return {
|
|
124
|
+
path,
|
|
125
|
+
filename,
|
|
126
|
+
mediaType,
|
|
127
|
+
size: new TextEncoder().encode(body).byteLength,
|
|
128
|
+
blob: new Blob([body], { type: mediaType })
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const separator = path.lastIndexOf("/");
|
|
134
|
+
const folder = separator < 0 ? undefined : path.slice(0, separator);
|
|
135
|
+
let match = null;
|
|
136
|
+
for await (const file of this.connect.files.list({
|
|
137
|
+
...options,
|
|
138
|
+
...(folder ? { folder } : {})
|
|
139
|
+
})) {
|
|
140
|
+
if (file.path === path) {
|
|
141
|
+
match = file;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!match)
|
|
146
|
+
return null;
|
|
147
|
+
const blob = await this.connect.files.download(match, options);
|
|
148
|
+
return {
|
|
149
|
+
path,
|
|
150
|
+
filename: attachment.filename,
|
|
151
|
+
mediaType: match.mediaType || blob.type || "application/octet-stream",
|
|
152
|
+
size: match.size,
|
|
153
|
+
blob
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
pendingResponses() {
|
|
157
|
+
return this.connect
|
|
158
|
+
.pendingMutations()
|
|
159
|
+
.filter((mutation) => mutation.operation === "create");
|
|
160
|
+
}
|
|
161
|
+
async recoverResponse(requestId, options = {}) {
|
|
162
|
+
const pending = this.connect.pendingMutation(requestId);
|
|
163
|
+
if (!pending || pending.operation !== "create") {
|
|
164
|
+
throw new PickleContractError("This pending Pickle response is no longer available for recovery.");
|
|
165
|
+
}
|
|
166
|
+
return responseSubmission(await pending.recover(options));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export class PickleContractError extends Error {
|
|
170
|
+
}
|
|
171
|
+
function responseSubmission(outcome) {
|
|
172
|
+
if (outcome.ok)
|
|
173
|
+
return { kind: "recorded", record: outcome.value };
|
|
174
|
+
const requestId = outcome.problem.code === "operation_outcome_unknown" &&
|
|
175
|
+
outcome.problem.details &&
|
|
176
|
+
typeof outcome.problem.details === "object" &&
|
|
177
|
+
"request_id" in outcome.problem.details &&
|
|
178
|
+
typeof outcome.problem.details.request_id === "string"
|
|
179
|
+
? outcome.problem.details.request_id
|
|
180
|
+
: null;
|
|
181
|
+
if (requestId)
|
|
182
|
+
return { kind: "pending", requestId };
|
|
183
|
+
throw new MdbaseConnectError(outcome.problem);
|
|
184
|
+
}
|
|
185
|
+
function normalizeRequest(record, responseRecords, description, contract) {
|
|
186
|
+
const implementation = implementationForRecord(contract, record);
|
|
187
|
+
const responseType = stringField(record.effectiveFrontmatter, role(implementation, "response_type", "response_type")) || "pickle_response_approval";
|
|
188
|
+
const linked = responseRecords.filter((candidate) => linkTargets(candidate.effectiveFrontmatter.request, record.path));
|
|
189
|
+
const status = stringField(record.effectiveFrontmatter, role(implementation, "status", "status"));
|
|
190
|
+
const state = status === "cancelled"
|
|
191
|
+
? "cancelled"
|
|
192
|
+
: linked.length === 0
|
|
193
|
+
? "pending"
|
|
194
|
+
: linked.length === 1
|
|
195
|
+
? "answered"
|
|
196
|
+
: "conflict";
|
|
197
|
+
const response = linked.length === 1 ? normalizeResponse(linked[0]) : undefined;
|
|
198
|
+
return {
|
|
199
|
+
id: stringField(record.effectiveFrontmatter, role(implementation, "id", "id")) ||
|
|
200
|
+
record.path,
|
|
201
|
+
path: record.path,
|
|
202
|
+
title: stringField(record.effectiveFrontmatter, role(implementation, "title", "title")) ||
|
|
203
|
+
record.path,
|
|
204
|
+
source: stringField(record.effectiveFrontmatter, role(implementation, "source", "source")) ||
|
|
205
|
+
"agent",
|
|
206
|
+
message: stringField(record.effectiveFrontmatter, role(implementation, "message", "message")),
|
|
207
|
+
body: record.body ?? "",
|
|
208
|
+
modifiedAt: record.file?.mtime,
|
|
209
|
+
kind: stringField(record.effectiveFrontmatter, role(implementation, "kind", "kind")) ||
|
|
210
|
+
"approval",
|
|
211
|
+
priority: stringField(record.effectiveFrontmatter, role(implementation, "priority", "priority")) || "normal",
|
|
212
|
+
status: status || undefined,
|
|
213
|
+
state,
|
|
214
|
+
responseCount: linked.length,
|
|
215
|
+
responseType,
|
|
216
|
+
responseTypeDefinition: description.types.find((candidate) => candidate.name === responseType),
|
|
217
|
+
createdAt: stringField(record.effectiveFrontmatter, role(implementation, "created_at", "created_at")) || undefined,
|
|
218
|
+
dueAt: stringField(record.effectiveFrontmatter, "due_at") || undefined,
|
|
219
|
+
tags: stringList(getField(record.effectiveFrontmatter, role(implementation, "tags", "tags"))),
|
|
220
|
+
links: links(getField(record.effectiveFrontmatter, role(implementation, "links", "links"))),
|
|
221
|
+
attachments: attachments(getField(record.effectiveFrontmatter, role(implementation, "attachment_paths", "attachment_paths"))),
|
|
222
|
+
metadata: asObject(getField(record.effectiveFrontmatter, role(implementation, "metadata", "metadata"))) ?? {},
|
|
223
|
+
response,
|
|
224
|
+
frontmatter: record.effectiveFrontmatter
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function normalizeResponse(record) {
|
|
228
|
+
const frontmatter = record.effectiveFrontmatter;
|
|
229
|
+
return {
|
|
230
|
+
path: record.path,
|
|
231
|
+
type: stringField(frontmatter, "type") || record.types[0] || "pickle_response",
|
|
232
|
+
responder: stringField(frontmatter, "responder") || "human",
|
|
233
|
+
respondedAt: stringField(frontmatter, "responded_at") || undefined,
|
|
234
|
+
payload: Object.fromEntries(Object.entries(frontmatter).filter(([key]) => !SYSTEM_RESPONSE_FIELDS.has(key))),
|
|
235
|
+
frontmatter
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function requireEffectiveFrontmatter(record) {
|
|
239
|
+
if (!record.effectiveFrontmatter) {
|
|
240
|
+
throw new PickleContractError(`Query result ${record.path} omitted effectiveFrontmatter.`);
|
|
241
|
+
}
|
|
242
|
+
return record;
|
|
243
|
+
}
|
|
244
|
+
function requireOutcome(outcome) {
|
|
245
|
+
if (!outcome.ok)
|
|
246
|
+
throw new MdbaseConnectError(outcome.problem);
|
|
247
|
+
return outcome.value;
|
|
248
|
+
}
|
|
249
|
+
function parseFields(value) {
|
|
250
|
+
if (!Object.values(value).every((field) => typeof field === "string" && validFieldPath(field)))
|
|
251
|
+
throw new PickleContractError("The Pickle request contract is malformed.");
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
function role(implementation, name, fallback) {
|
|
255
|
+
return implementation.fields[name] ?? fallback;
|
|
256
|
+
}
|
|
257
|
+
function implementationForRecord(contract, record) {
|
|
258
|
+
const matches = contract.implementations.filter((implementation) => record.types.includes(implementation.typeName));
|
|
259
|
+
if (matches.length !== 1)
|
|
260
|
+
throw new PickleContractError(`Pickle request ${record.path} matches ${matches.length} contract implementations; exactly one is required.`);
|
|
261
|
+
return matches[0];
|
|
262
|
+
}
|
|
263
|
+
function getField(value, path) {
|
|
264
|
+
let current = value;
|
|
265
|
+
for (const segment of path.split(".")) {
|
|
266
|
+
const object = asObject(current);
|
|
267
|
+
if (!object)
|
|
268
|
+
return undefined;
|
|
269
|
+
current = object[segment];
|
|
270
|
+
}
|
|
271
|
+
return current;
|
|
272
|
+
}
|
|
273
|
+
function stringField(value, path) {
|
|
274
|
+
const field = asObject(value) ? getField(value, path) : undefined;
|
|
275
|
+
return typeof field === "string" ? field.trim() : "";
|
|
276
|
+
}
|
|
277
|
+
function stringList(value) {
|
|
278
|
+
return Array.isArray(value)
|
|
279
|
+
? value.filter((item) => typeof item === "string")
|
|
280
|
+
: [];
|
|
281
|
+
}
|
|
282
|
+
function links(value) {
|
|
283
|
+
if (!Array.isArray(value))
|
|
284
|
+
return [];
|
|
285
|
+
return value.flatMap((item) => {
|
|
286
|
+
const object = asObject(item);
|
|
287
|
+
if (!object)
|
|
288
|
+
return [];
|
|
289
|
+
const label = stringField(object, "label");
|
|
290
|
+
const url = stringField(object, "url");
|
|
291
|
+
const path = stringField(object, "path");
|
|
292
|
+
return label || url || path
|
|
293
|
+
? [{ label: label || url || path, ...(url ? { url } : {}), ...(path ? { path } : {}) }]
|
|
294
|
+
: [];
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function attachments(value) {
|
|
298
|
+
return stringList(value).map((path) => ({
|
|
299
|
+
path,
|
|
300
|
+
filename: path.split("/").at(-1) || path
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
function requestLink(path) {
|
|
304
|
+
return `[[${path.replace(/\.md$/i, "")}]]`;
|
|
305
|
+
}
|
|
306
|
+
function linkTargets(value, requestPath) {
|
|
307
|
+
if (typeof value !== "string")
|
|
308
|
+
return false;
|
|
309
|
+
const target = normalizeLink(value);
|
|
310
|
+
const request = normalizeLink(requestPath);
|
|
311
|
+
if (!target || !request)
|
|
312
|
+
return false;
|
|
313
|
+
return (target === request ||
|
|
314
|
+
(!target.includes("/") && target === request.split("/").at(-1)));
|
|
315
|
+
}
|
|
316
|
+
function normalizeLink(value) {
|
|
317
|
+
let normalized = value.trim();
|
|
318
|
+
if (normalized.startsWith("[[") && normalized.endsWith("]]")) {
|
|
319
|
+
normalized = normalized.slice(2, -2);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const markdown = normalized.match(/^\[[^\]]*\]\(([^)]+)\)$/);
|
|
323
|
+
if (markdown)
|
|
324
|
+
normalized = markdown[1];
|
|
325
|
+
}
|
|
326
|
+
normalized = normalized.split("|", 1)[0].split("#", 1)[0];
|
|
327
|
+
return normalized
|
|
328
|
+
.replaceAll("\\", "/")
|
|
329
|
+
.replace(/^\.?\//, "")
|
|
330
|
+
.replace(/\.md$/i, "");
|
|
331
|
+
}
|
|
332
|
+
function validFieldPath(value) {
|
|
333
|
+
return value
|
|
334
|
+
.split(".")
|
|
335
|
+
.every((part) => part.length > 0 &&
|
|
336
|
+
part !== "__proto__" &&
|
|
337
|
+
part !== "prototype" &&
|
|
338
|
+
part !== "constructor");
|
|
339
|
+
}
|
|
340
|
+
function trimSlashes(value) {
|
|
341
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
342
|
+
}
|
|
343
|
+
function isMarkdownPath(path) {
|
|
344
|
+
return /\.(?:md|markdown|mdown|mkd|mkdn)$/i.test(path);
|
|
345
|
+
}
|
|
346
|
+
function asObject(value) {
|
|
347
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
348
|
+
? value
|
|
349
|
+
: undefined;
|
|
350
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export declare const PICKLE_REQUEST_CONTRACT_DOCUMENT = "---\nkind: mdbase.contract\ncontract_type: record\nid: pickle.request\nversion: 1.0.0\nname: Pickle request\ndescription: A portable asynchronous request that needs a human response.\nrecord_schema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [title, response_type]\n properties:\n id: { type: string }\n title: { type: string, minLength: 1 }\n source: { type: string }\n message: { type: string }\n kind: { enum: [approval, choice, input, notice, message] }\n status: { enum: [pending, answered, cancelled] }\n priority: { enum: [low, normal, high, urgent] }\n response_type: { type: string }\n created_at: { type: string, format: date-time }\n tags:\n type: array\n items: { type: string }\n links:\n type: array\n items:\n type: object\n additionalProperties: false\n properties:\n label: { type: string }\n url: { type: string }\n path: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\n metadata:\n type: object\n additionalProperties: true\n---\n";
|
|
2
|
+
/** Semantic digest of the resolved Pickle request contract, pinned by consumers. */
|
|
3
|
+
export declare const PICKLE_REQUEST_CONTRACT_DIGEST = "sha256:5a96bbda301de6ec2f71a5083f62769463b4e613ed8ba860d62170b8c810db61";
|
|
4
|
+
export declare const PICKLE_REQUEST_TYPE_DOCUMENT = "---\nkind: mdbase.type\nname: pickle_request\nversion: 1\ndescription: Async request that needs a human response.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [title, response_type]\n properties:\n type: { const: pickle_request }\n id: { type: string }\n title: { type: string, minLength: 1 }\n source: { type: string }\n message: { type: string }\n kind: { enum: [approval, choice, input, notice, message] }\n status:\n enum: [pending, answered, cancelled]\n description: Legacy lifecycle marker. Response links are authoritative for answered state.\n priority: { enum: [low, normal, high, urgent] }\n response_type: { type: string }\n created_at: { type: string, format: date-time }\n due_at: { type: string, format: date-time }\n dedupe_key: { type: string }\n tags:\n type: array\n items: { type: string }\n links:\n type: array\n items:\n type: object\n additionalProperties: false\n properties:\n label: { type: string }\n url: { type: string }\n path: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\n metadata:\n type: object\n additionalProperties: true\n context:\n type: object\n additionalProperties: false\n properties:\n cwd: { type: string }\n repo: { type: string }\n task: { type: string }\ncollection:\n display:\n name_field: title\n unique:\n - field: id\n scope: collection\nlifecycle:\n on_create:\n set:\n created_at: { now: true }\nimplements:\n - contract: pickle.request\n version: 1.0.0\n fields:\n id: id\n title: title\n source: source\n message: message\n kind: kind\n status: status\n priority: priority\n response_type: response_type\n created_at: created_at\n tags: tags\n links: links\n attachment_paths: attachment_paths\n metadata: metadata\n---\n";
|
|
5
|
+
export declare const PICKLE_APPROVAL_RESPONSE_TYPE_DOCUMENT = "---\nkind: mdbase.type\nname: pickle_response_approval\nversion: 1\ndescription: Approve, reject, or request revision for a Pickle request.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [request, decision]\n properties:\n type: { const: pickle_response_approval }\n id: { type: string }\n request: { type: string }\n decision: { enum: [approve, reject, revise] }\n comment: { type: string }\n responded_at: { type: string, format: date-time }\n responder: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\ncollection:\n display:\n name_field: decision\n links:\n request:\n target_type: pickle_request\n validate_exists: true\n unique:\n - field: id\n scope: collection\nlifecycle:\n on_create:\n set:\n id: { ulid: true }\n responded_at: { now: true }\n---\n";
|
|
6
|
+
export declare const PICKLE_ACK_RESPONSE_TYPE_DOCUMENT = "---\nkind: mdbase.type\nname: pickle_response_ack\nversion: 1\ndescription: Acknowledge that a Pickle message was read.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [request]\n properties:\n type: { const: pickle_response_ack }\n id: { type: string }\n request: { type: string }\n message: { type: string }\n responded_at: { type: string, format: date-time }\n responder: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\ncollection:\n display:\n name_field: message\n links:\n request:\n target_type: pickle_request\n validate_exists: true\n unique:\n - field: id\n scope: collection\nlifecycle:\n on_create:\n set:\n id: { ulid: true }\n responded_at: { now: true }\n---\n";
|
|
7
|
+
export declare const PICKLE_ATTACHMENT_TYPE_DOCUMENT = "---\nkind: mdbase.type\nname: pickle_attachment\nversion: 1\ndescription: Markdown content attached to a Pickle request.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [request_id, filename, content_type, size_bytes, sha256]\n properties:\n type: { const: pickle_attachment }\n request_id: { type: string, minLength: 1 }\n filename: { type: string, minLength: 1 }\n content_type: { type: string, minLength: 1 }\n size_bytes: { type: integer, minimum: 0 }\n sha256: { type: string, pattern: '^sha256:[0-9a-f]{64}$' }\n created_at: { type: string, format: date-time }\ncollection:\n display:\n name_field: filename\n---\n";
|
|
8
|
+
export declare const PICKLE_TYPE_PACK_PROVISION: {
|
|
9
|
+
readonly manifest: {
|
|
10
|
+
readonly kind: "mdbase.type-pack";
|
|
11
|
+
readonly id: "pickle.requests";
|
|
12
|
+
readonly version: "1.1.0";
|
|
13
|
+
readonly name: "Pickle requests";
|
|
14
|
+
readonly description: "Pickle request and response record types.";
|
|
15
|
+
readonly resources: readonly [{
|
|
16
|
+
readonly kind: "contract";
|
|
17
|
+
readonly mode: "managed";
|
|
18
|
+
readonly source: "contracts/pickle.request.md";
|
|
19
|
+
readonly target: "_contracts/pickle.request.md";
|
|
20
|
+
readonly digest: "sha256:caf9ee290e98ef51f871a1237b38531168aa1b74640241ce6edece4c2bff66f1";
|
|
21
|
+
}, {
|
|
22
|
+
readonly kind: "type";
|
|
23
|
+
readonly mode: "seed";
|
|
24
|
+
readonly source: "types/pickle_request.md";
|
|
25
|
+
readonly target: "_types/pickle_request.md";
|
|
26
|
+
readonly digest: "sha256:7f84e359a5a3b7d23dd3471c7557f67cb8a80eb553184db83c4750b2af5761e4";
|
|
27
|
+
}, {
|
|
28
|
+
readonly kind: "type";
|
|
29
|
+
readonly mode: "seed";
|
|
30
|
+
readonly source: "types/pickle_response_approval.md";
|
|
31
|
+
readonly target: "_types/pickle_response_approval.md";
|
|
32
|
+
readonly digest: "sha256:944d2b68c158297f5d2a035fa6ff3c81a7098531f77a9bc3342dc9435ffcb8b8";
|
|
33
|
+
}, {
|
|
34
|
+
readonly kind: "type";
|
|
35
|
+
readonly mode: "seed";
|
|
36
|
+
readonly source: "types/pickle_response_ack.md";
|
|
37
|
+
readonly target: "_types/pickle_response_ack.md";
|
|
38
|
+
readonly digest: "sha256:2530c8d21bb711889f20e0d096045cb6c9a7618583b1e2afff7ada72367de41f";
|
|
39
|
+
}, {
|
|
40
|
+
readonly kind: "type";
|
|
41
|
+
readonly mode: "seed";
|
|
42
|
+
readonly source: "types/pickle_attachment.md";
|
|
43
|
+
readonly target: "_types/pickle_attachment.md";
|
|
44
|
+
readonly digest: "sha256:67f14eeee2c126e99d8b37b7508fa148955b924367e2d2941b1793d1c814ec25";
|
|
45
|
+
}];
|
|
46
|
+
};
|
|
47
|
+
readonly resources: readonly [{
|
|
48
|
+
readonly source: "contracts/pickle.request.md";
|
|
49
|
+
readonly document: "---\nkind: mdbase.contract\ncontract_type: record\nid: pickle.request\nversion: 1.0.0\nname: Pickle request\ndescription: A portable asynchronous request that needs a human response.\nrecord_schema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [title, response_type]\n properties:\n id: { type: string }\n title: { type: string, minLength: 1 }\n source: { type: string }\n message: { type: string }\n kind: { enum: [approval, choice, input, notice, message] }\n status: { enum: [pending, answered, cancelled] }\n priority: { enum: [low, normal, high, urgent] }\n response_type: { type: string }\n created_at: { type: string, format: date-time }\n tags:\n type: array\n items: { type: string }\n links:\n type: array\n items:\n type: object\n additionalProperties: false\n properties:\n label: { type: string }\n url: { type: string }\n path: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\n metadata:\n type: object\n additionalProperties: true\n---\n";
|
|
50
|
+
}, {
|
|
51
|
+
readonly source: "types/pickle_request.md";
|
|
52
|
+
readonly document: "---\nkind: mdbase.type\nname: pickle_request\nversion: 1\ndescription: Async request that needs a human response.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [title, response_type]\n properties:\n type: { const: pickle_request }\n id: { type: string }\n title: { type: string, minLength: 1 }\n source: { type: string }\n message: { type: string }\n kind: { enum: [approval, choice, input, notice, message] }\n status:\n enum: [pending, answered, cancelled]\n description: Legacy lifecycle marker. Response links are authoritative for answered state.\n priority: { enum: [low, normal, high, urgent] }\n response_type: { type: string }\n created_at: { type: string, format: date-time }\n due_at: { type: string, format: date-time }\n dedupe_key: { type: string }\n tags:\n type: array\n items: { type: string }\n links:\n type: array\n items:\n type: object\n additionalProperties: false\n properties:\n label: { type: string }\n url: { type: string }\n path: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\n metadata:\n type: object\n additionalProperties: true\n context:\n type: object\n additionalProperties: false\n properties:\n cwd: { type: string }\n repo: { type: string }\n task: { type: string }\ncollection:\n display:\n name_field: title\n unique:\n - field: id\n scope: collection\nlifecycle:\n on_create:\n set:\n created_at: { now: true }\nimplements:\n - contract: pickle.request\n version: 1.0.0\n fields:\n id: id\n title: title\n source: source\n message: message\n kind: kind\n status: status\n priority: priority\n response_type: response_type\n created_at: created_at\n tags: tags\n links: links\n attachment_paths: attachment_paths\n metadata: metadata\n---\n";
|
|
53
|
+
}, {
|
|
54
|
+
readonly source: "types/pickle_response_approval.md";
|
|
55
|
+
readonly document: "---\nkind: mdbase.type\nname: pickle_response_approval\nversion: 1\ndescription: Approve, reject, or request revision for a Pickle request.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [request, decision]\n properties:\n type: { const: pickle_response_approval }\n id: { type: string }\n request: { type: string }\n decision: { enum: [approve, reject, revise] }\n comment: { type: string }\n responded_at: { type: string, format: date-time }\n responder: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\ncollection:\n display:\n name_field: decision\n links:\n request:\n target_type: pickle_request\n validate_exists: true\n unique:\n - field: id\n scope: collection\nlifecycle:\n on_create:\n set:\n id: { ulid: true }\n responded_at: { now: true }\n---\n";
|
|
56
|
+
}, {
|
|
57
|
+
readonly source: "types/pickle_response_ack.md";
|
|
58
|
+
readonly document: "---\nkind: mdbase.type\nname: pickle_response_ack\nversion: 1\ndescription: Acknowledge that a Pickle message was read.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [request]\n properties:\n type: { const: pickle_response_ack }\n id: { type: string }\n request: { type: string }\n message: { type: string }\n responded_at: { type: string, format: date-time }\n responder: { type: string }\n attachment_paths:\n type: array\n items: { type: string }\ncollection:\n display:\n name_field: message\n links:\n request:\n target_type: pickle_request\n validate_exists: true\n unique:\n - field: id\n scope: collection\nlifecycle:\n on_create:\n set:\n id: { ulid: true }\n responded_at: { now: true }\n---\n";
|
|
59
|
+
}, {
|
|
60
|
+
readonly source: "types/pickle_attachment.md";
|
|
61
|
+
readonly document: "---\nkind: mdbase.type\nname: pickle_attachment\nversion: 1\ndescription: Markdown content attached to a Pickle request.\nschema:\n dialect: json-schema-2020-12\n value:\n $schema: \"https://json-schema.org/draft/2020-12/schema\"\n type: object\n additionalProperties: true\n required: [request_id, filename, content_type, size_bytes, sha256]\n properties:\n type: { const: pickle_attachment }\n request_id: { type: string, minLength: 1 }\n filename: { type: string, minLength: 1 }\n content_type: { type: string, minLength: 1 }\n size_bytes: { type: integer, minimum: 0 }\n sha256: { type: string, pattern: '^sha256:[0-9a-f]{64}$' }\n created_at: { type: string, format: date-time }\ncollection:\n display:\n name_field: filename\n---\n";
|
|
62
|
+
}];
|
|
63
|
+
readonly provides: readonly [{
|
|
64
|
+
readonly id: "pickle.request";
|
|
65
|
+
readonly version: "1.0.0";
|
|
66
|
+
readonly digest: "sha256:5a96bbda301de6ec2f71a5083f62769463b4e613ed8ba860d62170b8c810db61";
|
|
67
|
+
}];
|
|
68
|
+
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
export const PICKLE_REQUEST_CONTRACT_DOCUMENT = `---
|
|
2
|
+
kind: mdbase.contract
|
|
3
|
+
contract_type: record
|
|
4
|
+
id: pickle.request
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
name: Pickle request
|
|
7
|
+
description: A portable asynchronous request that needs a human response.
|
|
8
|
+
record_schema:
|
|
9
|
+
dialect: json-schema-2020-12
|
|
10
|
+
value:
|
|
11
|
+
$schema: "https://json-schema.org/draft/2020-12/schema"
|
|
12
|
+
type: object
|
|
13
|
+
additionalProperties: true
|
|
14
|
+
required: [title, response_type]
|
|
15
|
+
properties:
|
|
16
|
+
id: { type: string }
|
|
17
|
+
title: { type: string, minLength: 1 }
|
|
18
|
+
source: { type: string }
|
|
19
|
+
message: { type: string }
|
|
20
|
+
kind: { enum: [approval, choice, input, notice, message] }
|
|
21
|
+
status: { enum: [pending, answered, cancelled] }
|
|
22
|
+
priority: { enum: [low, normal, high, urgent] }
|
|
23
|
+
response_type: { type: string }
|
|
24
|
+
created_at: { type: string, format: date-time }
|
|
25
|
+
tags:
|
|
26
|
+
type: array
|
|
27
|
+
items: { type: string }
|
|
28
|
+
links:
|
|
29
|
+
type: array
|
|
30
|
+
items:
|
|
31
|
+
type: object
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
properties:
|
|
34
|
+
label: { type: string }
|
|
35
|
+
url: { type: string }
|
|
36
|
+
path: { type: string }
|
|
37
|
+
attachment_paths:
|
|
38
|
+
type: array
|
|
39
|
+
items: { type: string }
|
|
40
|
+
metadata:
|
|
41
|
+
type: object
|
|
42
|
+
additionalProperties: true
|
|
43
|
+
---
|
|
44
|
+
`;
|
|
45
|
+
/** Semantic digest of the resolved Pickle request contract, pinned by consumers. */
|
|
46
|
+
export const PICKLE_REQUEST_CONTRACT_DIGEST = "sha256:5a96bbda301de6ec2f71a5083f62769463b4e613ed8ba860d62170b8c810db61";
|
|
47
|
+
export const PICKLE_REQUEST_TYPE_DOCUMENT = `---
|
|
48
|
+
kind: mdbase.type
|
|
49
|
+
name: pickle_request
|
|
50
|
+
version: 1
|
|
51
|
+
description: Async request that needs a human response.
|
|
52
|
+
schema:
|
|
53
|
+
dialect: json-schema-2020-12
|
|
54
|
+
value:
|
|
55
|
+
$schema: "https://json-schema.org/draft/2020-12/schema"
|
|
56
|
+
type: object
|
|
57
|
+
additionalProperties: true
|
|
58
|
+
required: [title, response_type]
|
|
59
|
+
properties:
|
|
60
|
+
type: { const: pickle_request }
|
|
61
|
+
id: { type: string }
|
|
62
|
+
title: { type: string, minLength: 1 }
|
|
63
|
+
source: { type: string }
|
|
64
|
+
message: { type: string }
|
|
65
|
+
kind: { enum: [approval, choice, input, notice, message] }
|
|
66
|
+
status:
|
|
67
|
+
enum: [pending, answered, cancelled]
|
|
68
|
+
description: Legacy lifecycle marker. Response links are authoritative for answered state.
|
|
69
|
+
priority: { enum: [low, normal, high, urgent] }
|
|
70
|
+
response_type: { type: string }
|
|
71
|
+
created_at: { type: string, format: date-time }
|
|
72
|
+
due_at: { type: string, format: date-time }
|
|
73
|
+
dedupe_key: { type: string }
|
|
74
|
+
tags:
|
|
75
|
+
type: array
|
|
76
|
+
items: { type: string }
|
|
77
|
+
links:
|
|
78
|
+
type: array
|
|
79
|
+
items:
|
|
80
|
+
type: object
|
|
81
|
+
additionalProperties: false
|
|
82
|
+
properties:
|
|
83
|
+
label: { type: string }
|
|
84
|
+
url: { type: string }
|
|
85
|
+
path: { type: string }
|
|
86
|
+
attachment_paths:
|
|
87
|
+
type: array
|
|
88
|
+
items: { type: string }
|
|
89
|
+
metadata:
|
|
90
|
+
type: object
|
|
91
|
+
additionalProperties: true
|
|
92
|
+
context:
|
|
93
|
+
type: object
|
|
94
|
+
additionalProperties: false
|
|
95
|
+
properties:
|
|
96
|
+
cwd: { type: string }
|
|
97
|
+
repo: { type: string }
|
|
98
|
+
task: { type: string }
|
|
99
|
+
collection:
|
|
100
|
+
display:
|
|
101
|
+
name_field: title
|
|
102
|
+
unique:
|
|
103
|
+
- field: id
|
|
104
|
+
scope: collection
|
|
105
|
+
lifecycle:
|
|
106
|
+
on_create:
|
|
107
|
+
set:
|
|
108
|
+
created_at: { now: true }
|
|
109
|
+
implements:
|
|
110
|
+
- contract: pickle.request
|
|
111
|
+
version: 1.0.0
|
|
112
|
+
fields:
|
|
113
|
+
id: id
|
|
114
|
+
title: title
|
|
115
|
+
source: source
|
|
116
|
+
message: message
|
|
117
|
+
kind: kind
|
|
118
|
+
status: status
|
|
119
|
+
priority: priority
|
|
120
|
+
response_type: response_type
|
|
121
|
+
created_at: created_at
|
|
122
|
+
tags: tags
|
|
123
|
+
links: links
|
|
124
|
+
attachment_paths: attachment_paths
|
|
125
|
+
metadata: metadata
|
|
126
|
+
---
|
|
127
|
+
`;
|
|
128
|
+
export const PICKLE_APPROVAL_RESPONSE_TYPE_DOCUMENT = `---
|
|
129
|
+
kind: mdbase.type
|
|
130
|
+
name: pickle_response_approval
|
|
131
|
+
version: 1
|
|
132
|
+
description: Approve, reject, or request revision for a Pickle request.
|
|
133
|
+
schema:
|
|
134
|
+
dialect: json-schema-2020-12
|
|
135
|
+
value:
|
|
136
|
+
$schema: "https://json-schema.org/draft/2020-12/schema"
|
|
137
|
+
type: object
|
|
138
|
+
additionalProperties: true
|
|
139
|
+
required: [request, decision]
|
|
140
|
+
properties:
|
|
141
|
+
type: { const: pickle_response_approval }
|
|
142
|
+
id: { type: string }
|
|
143
|
+
request: { type: string }
|
|
144
|
+
decision: { enum: [approve, reject, revise] }
|
|
145
|
+
comment: { type: string }
|
|
146
|
+
responded_at: { type: string, format: date-time }
|
|
147
|
+
responder: { type: string }
|
|
148
|
+
attachment_paths:
|
|
149
|
+
type: array
|
|
150
|
+
items: { type: string }
|
|
151
|
+
collection:
|
|
152
|
+
display:
|
|
153
|
+
name_field: decision
|
|
154
|
+
links:
|
|
155
|
+
request:
|
|
156
|
+
target_type: pickle_request
|
|
157
|
+
validate_exists: true
|
|
158
|
+
unique:
|
|
159
|
+
- field: id
|
|
160
|
+
scope: collection
|
|
161
|
+
lifecycle:
|
|
162
|
+
on_create:
|
|
163
|
+
set:
|
|
164
|
+
id: { ulid: true }
|
|
165
|
+
responded_at: { now: true }
|
|
166
|
+
---
|
|
167
|
+
`;
|
|
168
|
+
export const PICKLE_ACK_RESPONSE_TYPE_DOCUMENT = `---
|
|
169
|
+
kind: mdbase.type
|
|
170
|
+
name: pickle_response_ack
|
|
171
|
+
version: 1
|
|
172
|
+
description: Acknowledge that a Pickle message was read.
|
|
173
|
+
schema:
|
|
174
|
+
dialect: json-schema-2020-12
|
|
175
|
+
value:
|
|
176
|
+
$schema: "https://json-schema.org/draft/2020-12/schema"
|
|
177
|
+
type: object
|
|
178
|
+
additionalProperties: true
|
|
179
|
+
required: [request]
|
|
180
|
+
properties:
|
|
181
|
+
type: { const: pickle_response_ack }
|
|
182
|
+
id: { type: string }
|
|
183
|
+
request: { type: string }
|
|
184
|
+
message: { type: string }
|
|
185
|
+
responded_at: { type: string, format: date-time }
|
|
186
|
+
responder: { type: string }
|
|
187
|
+
attachment_paths:
|
|
188
|
+
type: array
|
|
189
|
+
items: { type: string }
|
|
190
|
+
collection:
|
|
191
|
+
display:
|
|
192
|
+
name_field: message
|
|
193
|
+
links:
|
|
194
|
+
request:
|
|
195
|
+
target_type: pickle_request
|
|
196
|
+
validate_exists: true
|
|
197
|
+
unique:
|
|
198
|
+
- field: id
|
|
199
|
+
scope: collection
|
|
200
|
+
lifecycle:
|
|
201
|
+
on_create:
|
|
202
|
+
set:
|
|
203
|
+
id: { ulid: true }
|
|
204
|
+
responded_at: { now: true }
|
|
205
|
+
---
|
|
206
|
+
`;
|
|
207
|
+
export const PICKLE_ATTACHMENT_TYPE_DOCUMENT = `---
|
|
208
|
+
kind: mdbase.type
|
|
209
|
+
name: pickle_attachment
|
|
210
|
+
version: 1
|
|
211
|
+
description: Markdown content attached to a Pickle request.
|
|
212
|
+
schema:
|
|
213
|
+
dialect: json-schema-2020-12
|
|
214
|
+
value:
|
|
215
|
+
$schema: "https://json-schema.org/draft/2020-12/schema"
|
|
216
|
+
type: object
|
|
217
|
+
additionalProperties: true
|
|
218
|
+
required: [request_id, filename, content_type, size_bytes, sha256]
|
|
219
|
+
properties:
|
|
220
|
+
type: { const: pickle_attachment }
|
|
221
|
+
request_id: { type: string, minLength: 1 }
|
|
222
|
+
filename: { type: string, minLength: 1 }
|
|
223
|
+
content_type: { type: string, minLength: 1 }
|
|
224
|
+
size_bytes: { type: integer, minimum: 0 }
|
|
225
|
+
sha256: { type: string, pattern: '^sha256:[0-9a-f]{64}$' }
|
|
226
|
+
created_at: { type: string, format: date-time }
|
|
227
|
+
collection:
|
|
228
|
+
display:
|
|
229
|
+
name_field: filename
|
|
230
|
+
---
|
|
231
|
+
`;
|
|
232
|
+
export const PICKLE_TYPE_PACK_PROVISION = {
|
|
233
|
+
manifest: {
|
|
234
|
+
kind: "mdbase.type-pack",
|
|
235
|
+
id: "pickle.requests",
|
|
236
|
+
version: "1.1.0",
|
|
237
|
+
name: "Pickle requests",
|
|
238
|
+
description: "Pickle request and response record types.",
|
|
239
|
+
resources: [
|
|
240
|
+
{
|
|
241
|
+
kind: "contract",
|
|
242
|
+
mode: "managed",
|
|
243
|
+
source: "contracts/pickle.request.md",
|
|
244
|
+
target: "_contracts/pickle.request.md",
|
|
245
|
+
digest: "sha256:caf9ee290e98ef51f871a1237b38531168aa1b74640241ce6edece4c2bff66f1"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
kind: "type",
|
|
249
|
+
mode: "seed",
|
|
250
|
+
source: "types/pickle_request.md",
|
|
251
|
+
target: "_types/pickle_request.md",
|
|
252
|
+
digest: "sha256:7f84e359a5a3b7d23dd3471c7557f67cb8a80eb553184db83c4750b2af5761e4"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
kind: "type",
|
|
256
|
+
mode: "seed",
|
|
257
|
+
source: "types/pickle_response_approval.md",
|
|
258
|
+
target: "_types/pickle_response_approval.md",
|
|
259
|
+
digest: "sha256:944d2b68c158297f5d2a035fa6ff3c81a7098531f77a9bc3342dc9435ffcb8b8"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
kind: "type",
|
|
263
|
+
mode: "seed",
|
|
264
|
+
source: "types/pickle_response_ack.md",
|
|
265
|
+
target: "_types/pickle_response_ack.md",
|
|
266
|
+
digest: "sha256:2530c8d21bb711889f20e0d096045cb6c9a7618583b1e2afff7ada72367de41f"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
kind: "type",
|
|
270
|
+
mode: "seed",
|
|
271
|
+
source: "types/pickle_attachment.md",
|
|
272
|
+
target: "_types/pickle_attachment.md",
|
|
273
|
+
digest: "sha256:67f14eeee2c126e99d8b37b7508fa148955b924367e2d2941b1793d1c814ec25"
|
|
274
|
+
}
|
|
275
|
+
]
|
|
276
|
+
},
|
|
277
|
+
resources: [
|
|
278
|
+
{
|
|
279
|
+
source: "contracts/pickle.request.md",
|
|
280
|
+
document: PICKLE_REQUEST_CONTRACT_DOCUMENT
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
source: "types/pickle_request.md",
|
|
284
|
+
document: PICKLE_REQUEST_TYPE_DOCUMENT
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
source: "types/pickle_response_approval.md",
|
|
288
|
+
document: PICKLE_APPROVAL_RESPONSE_TYPE_DOCUMENT
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
source: "types/pickle_response_ack.md",
|
|
292
|
+
document: PICKLE_ACK_RESPONSE_TYPE_DOCUMENT
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
source: "types/pickle_attachment.md",
|
|
296
|
+
document: PICKLE_ATTACHMENT_TYPE_DOCUMENT
|
|
297
|
+
}
|
|
298
|
+
],
|
|
299
|
+
provides: [{
|
|
300
|
+
id: "pickle.request",
|
|
301
|
+
version: "1.0.0",
|
|
302
|
+
digest: PICKLE_REQUEST_CONTRACT_DIGEST
|
|
303
|
+
}]
|
|
304
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mdbase-dev/pickle",
|
|
3
|
+
"version": "0.1.0-beta.100",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mdbase-dev/mdbase-connect.git",
|
|
10
|
+
"directory": "packages/pickle"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://mdbase.dev",
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@mdbase-dev/connect": "0.1.0-beta.100"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"typescript": "^7.0.2",
|
|
29
|
+
"vitest": "^4.1.10",
|
|
30
|
+
"@mdbase-dev/connect-dev": "0.1.0-beta.100",
|
|
31
|
+
"@mdbase-dev/connect-testing": "0.1.0-beta.100"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|