@crafter8/sdk 0.1.0-next.1
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 +175 -0
- package/build.js +553 -0
- package/index.d.ts +473 -0
- package/index.js +1831 -0
- package/mock.d.ts +128 -0
- package/mock.js +471 -0
- package/package.json +61 -0
- package/react.d.ts +30 -0
- package/react.js +124 -0
package/mock.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ArtifactMetaBase,
|
|
3
|
+
Crafter8Client,
|
|
4
|
+
Crafter8DatapackClientOptions,
|
|
5
|
+
Crafter8HostServices,
|
|
6
|
+
Crafter8Session,
|
|
7
|
+
Crafter8Transport,
|
|
8
|
+
AppContext,
|
|
9
|
+
ModuleContext,
|
|
10
|
+
RuntimeOperationDescriptor,
|
|
11
|
+
} from "./index.js";
|
|
12
|
+
|
|
13
|
+
export type MockArtifactRecord = Record<string, unknown> & {
|
|
14
|
+
kind: string;
|
|
15
|
+
packageName?: string;
|
|
16
|
+
slug?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
summary?: string;
|
|
21
|
+
trustLevel?: string;
|
|
22
|
+
user?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type MockDatapackRecord = {
|
|
26
|
+
packageName?: string;
|
|
27
|
+
slug?: string;
|
|
28
|
+
id?: string;
|
|
29
|
+
version?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
summary?: string;
|
|
33
|
+
capability?: string;
|
|
34
|
+
profile?: string;
|
|
35
|
+
manifest?: Record<string, unknown>;
|
|
36
|
+
contents?: Array<{
|
|
37
|
+
key: string;
|
|
38
|
+
label?: string;
|
|
39
|
+
kind?: "file" | "dir";
|
|
40
|
+
path?: string;
|
|
41
|
+
role?: string | null;
|
|
42
|
+
contentType?: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
contentValues?: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type MockOperationRecord = {
|
|
48
|
+
kind?: string;
|
|
49
|
+
scope?: string;
|
|
50
|
+
selectors?: Record<string, unknown>;
|
|
51
|
+
artifactRef?: string | null;
|
|
52
|
+
artifact?: Record<string, unknown> | null;
|
|
53
|
+
operation?: Record<string, unknown> & {
|
|
54
|
+
id: string;
|
|
55
|
+
method?: string;
|
|
56
|
+
path?: string;
|
|
57
|
+
};
|
|
58
|
+
result?: unknown;
|
|
59
|
+
invoke?: (input: {
|
|
60
|
+
method: string;
|
|
61
|
+
path: string;
|
|
62
|
+
query: Record<string, unknown>;
|
|
63
|
+
body?: unknown;
|
|
64
|
+
headers?: Record<string, string>;
|
|
65
|
+
selector: Record<string, unknown>;
|
|
66
|
+
artifact: Record<string, unknown> | null;
|
|
67
|
+
operation: Record<string, unknown>;
|
|
68
|
+
}) => Promise<unknown> | unknown;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type MockCrafter8ClientOptions = {
|
|
72
|
+
session?: Partial<Crafter8Session>;
|
|
73
|
+
artifacts?: MockArtifactRecord[];
|
|
74
|
+
datapacks?: MockDatapackRecord[];
|
|
75
|
+
operations?: MockOperationRecord[];
|
|
76
|
+
datapacksOptions?: Crafter8DatapackClientOptions;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type MockCrafter8HostServices = Crafter8HostServices & {
|
|
80
|
+
events: Array<
|
|
81
|
+
| { type: "navigate"; path: string }
|
|
82
|
+
| { type: "openItemInGraph"; itemId: string }
|
|
83
|
+
>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type MockCrafter8Environment = {
|
|
87
|
+
client: Crafter8Client;
|
|
88
|
+
host: MockCrafter8HostServices;
|
|
89
|
+
appContext: AppContext;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export function createMockCrafter8Transport(options?: MockCrafter8ClientOptions): Crafter8Transport;
|
|
93
|
+
|
|
94
|
+
export function createMockCrafter8Client(options?: MockCrafter8ClientOptions): Crafter8Client;
|
|
95
|
+
|
|
96
|
+
export function createMockCrafter8HostServices(options?: {
|
|
97
|
+
navigate?: (path: string) => unknown;
|
|
98
|
+
openItemInGraph?: (itemId: string) => unknown;
|
|
99
|
+
}): MockCrafter8HostServices;
|
|
100
|
+
|
|
101
|
+
export function createMockAppContext(options?: MockCrafter8ClientOptions & {
|
|
102
|
+
client?: Crafter8Client;
|
|
103
|
+
host?: MockCrafter8HostServices;
|
|
104
|
+
hostServices?: MockCrafter8HostServices;
|
|
105
|
+
hostOptions?: {
|
|
106
|
+
navigate?: (path: string) => unknown;
|
|
107
|
+
openItemInGraph?: (itemId: string) => unknown;
|
|
108
|
+
};
|
|
109
|
+
artifact?: Partial<ArtifactMetaBase<"app">> & { id?: string; version?: string };
|
|
110
|
+
container?: unknown;
|
|
111
|
+
}): AppContext;
|
|
112
|
+
|
|
113
|
+
export function createMockModuleContext(options?: MockCrafter8ClientOptions & {
|
|
114
|
+
client?: Crafter8Client;
|
|
115
|
+
artifact?: Partial<ArtifactMetaBase<"module">> & { id?: string; version?: string };
|
|
116
|
+
}): ModuleContext;
|
|
117
|
+
|
|
118
|
+
export function createMockCrafter8Environment(options?: MockCrafter8ClientOptions & {
|
|
119
|
+
client?: Crafter8Client;
|
|
120
|
+
host?: MockCrafter8HostServices;
|
|
121
|
+
hostServices?: MockCrafter8HostServices;
|
|
122
|
+
hostOptions?: {
|
|
123
|
+
navigate?: (path: string) => unknown;
|
|
124
|
+
openItemInGraph?: (itemId: string) => unknown;
|
|
125
|
+
};
|
|
126
|
+
artifact?: Partial<ArtifactMetaBase<"app">> & { id?: string; version?: string };
|
|
127
|
+
container?: unknown;
|
|
128
|
+
}): MockCrafter8Environment;
|
package/mock.js
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOST_API_V1,
|
|
3
|
+
createAppContext,
|
|
4
|
+
createCrafter8Client,
|
|
5
|
+
createModuleContext,
|
|
6
|
+
} from "./index.js";
|
|
7
|
+
|
|
8
|
+
function isPlainObject(value) {
|
|
9
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function assertString(value, name) {
|
|
13
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
14
|
+
throw new TypeError(`${name} must be a non-empty string.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeString(value) {
|
|
19
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sortedUniqueStrings(values) {
|
|
23
|
+
return Array.from(new Set((Array.isArray(values) ? values : []).map((entry) => String(entry || "").trim()).filter(Boolean))).sort();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function asHostApiLabel(value) {
|
|
27
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
28
|
+
return { hostApi: value, hostApiLabel: `${value}.0.0` };
|
|
29
|
+
}
|
|
30
|
+
const raw = String(value || "").trim();
|
|
31
|
+
if (!raw) {
|
|
32
|
+
return { hostApi: HOST_API_V1, hostApiLabel: `${HOST_API_V1}.0.0` };
|
|
33
|
+
}
|
|
34
|
+
const match = raw.match(/^(\d+)/);
|
|
35
|
+
return {
|
|
36
|
+
hostApi: match ? Number(match[1]) : HOST_API_V1,
|
|
37
|
+
hostApiLabel: raw,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeMockSession(session = {}) {
|
|
42
|
+
const hostApi = asHostApiLabel(session.hostApi ?? session.hostApiLabel ?? HOST_API_V1);
|
|
43
|
+
return Object.freeze({
|
|
44
|
+
authenticated: Boolean(session.authenticated ?? session.userId),
|
|
45
|
+
userId: normalizeString(session.userId),
|
|
46
|
+
userDisplayName: normalizeString(session.userDisplayName ?? session.displayName),
|
|
47
|
+
activeWorkspaceId: normalizeString(session.activeWorkspaceId),
|
|
48
|
+
activeWorkspaceName: normalizeString(session.activeWorkspaceName),
|
|
49
|
+
capabilities: sortedUniqueStrings(session.capabilities),
|
|
50
|
+
hostApi: hostApi.hostApi,
|
|
51
|
+
hostApiLabel: hostApi.hostApiLabel,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function matchesQuery(value, query) {
|
|
56
|
+
if (!query) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
const haystack = [
|
|
60
|
+
value?.packageName,
|
|
61
|
+
value?.slug,
|
|
62
|
+
value?.id,
|
|
63
|
+
value?.name,
|
|
64
|
+
value?.description,
|
|
65
|
+
value?.summary,
|
|
66
|
+
]
|
|
67
|
+
.filter((entry) => typeof entry === "string")
|
|
68
|
+
.join(" ")
|
|
69
|
+
.toLowerCase();
|
|
70
|
+
return haystack.includes(query.toLowerCase());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function artifactMatchesSelector(artifact, selector = {}) {
|
|
74
|
+
if (selector.kind && artifact.kind !== selector.kind) return false;
|
|
75
|
+
if (selector.trustLevel && String(artifact.trustLevel || "") !== String(selector.trustLevel)) return false;
|
|
76
|
+
if (selector.packageName && String(artifact.packageName || "") !== String(selector.packageName)) return false;
|
|
77
|
+
if (selector.slug && String(artifact.slug || "") !== String(selector.slug)) return false;
|
|
78
|
+
if (selector.id && String(artifact.id || "") !== String(selector.id)) return false;
|
|
79
|
+
if (selector.user && String(artifact.user || "") !== String(selector.user)) return false;
|
|
80
|
+
if (selector.query && !matchesQuery(artifact, String(selector.query))) return false;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function guessContentType(value) {
|
|
85
|
+
if (typeof value === "string") {
|
|
86
|
+
return "text/plain";
|
|
87
|
+
}
|
|
88
|
+
return "application/json";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function deriveDatapackContents(contentValues = {}) {
|
|
92
|
+
return Object.entries(contentValues)
|
|
93
|
+
.map(([key, value]) => {
|
|
94
|
+
const normalizedKey = String(key || "").trim();
|
|
95
|
+
if (!normalizedKey) return null;
|
|
96
|
+
return {
|
|
97
|
+
key: normalizedKey,
|
|
98
|
+
label: normalizedKey,
|
|
99
|
+
kind: "file",
|
|
100
|
+
path: `${normalizedKey}.json`,
|
|
101
|
+
contentType: guessContentType(value),
|
|
102
|
+
role: normalizedKey === "manifest" ? "dataset-manifest" : "content",
|
|
103
|
+
};
|
|
104
|
+
})
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildInlineDatapackContent(slug, definition, value) {
|
|
109
|
+
const rawText = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
110
|
+
const parsed = typeof value === "string" ? null : value;
|
|
111
|
+
return {
|
|
112
|
+
...definition,
|
|
113
|
+
sizeBytes: Buffer.byteLength(rawText, "utf8"),
|
|
114
|
+
readPolicy: {
|
|
115
|
+
maxInlineBytes: 256 * 1024,
|
|
116
|
+
textLike: true,
|
|
117
|
+
inlineAllowed: true,
|
|
118
|
+
tooLarge: false,
|
|
119
|
+
},
|
|
120
|
+
encoding: "utf8",
|
|
121
|
+
parsed,
|
|
122
|
+
text: parsed === null ? rawText : null,
|
|
123
|
+
sourceKind: "published",
|
|
124
|
+
deliveryKind: "inline",
|
|
125
|
+
url: `/api/datapacks/${encodeURIComponent(slug)}/content?key=${encodeURIComponent(definition.key)}`,
|
|
126
|
+
cacheKey: `${slug}:${definition.key}:mock`,
|
|
127
|
+
requiresAuth: false,
|
|
128
|
+
warnings: [],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeMockDatapack(record) {
|
|
133
|
+
if (!isPlainObject(record)) {
|
|
134
|
+
throw new TypeError("mock datapack record must be an object.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const packageName = normalizeString(record.packageName);
|
|
138
|
+
const slug = normalizeString(record.slug) ?? (packageName ? packageName.split("/").pop() : null);
|
|
139
|
+
const id = normalizeString(record.id) ?? packageName ?? slug;
|
|
140
|
+
assertString(id, "mock datapack id");
|
|
141
|
+
assertString(slug, "mock datapack slug");
|
|
142
|
+
const version = normalizeString(record.version) ?? "0.1.0";
|
|
143
|
+
const contentValues = isPlainObject(record.contentValues) ? { ...record.contentValues } : {};
|
|
144
|
+
const contents = Array.isArray(record.contents) && record.contents.length > 0 ? record.contents.map((entry) => ({ ...entry })) : deriveDatapackContents(contentValues);
|
|
145
|
+
const manifest =
|
|
146
|
+
isPlainObject(record.manifest)
|
|
147
|
+
? { ...record.manifest }
|
|
148
|
+
: {
|
|
149
|
+
name: normalizeString(record.name) ?? slug,
|
|
150
|
+
version,
|
|
151
|
+
profile: normalizeString(record.profile) ?? null,
|
|
152
|
+
contentKeys: contents.map((entry) => entry.key),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const publicEntry = Object.freeze({
|
|
156
|
+
kind: "datapack",
|
|
157
|
+
packageName,
|
|
158
|
+
id,
|
|
159
|
+
slug,
|
|
160
|
+
version,
|
|
161
|
+
name: normalizeString(record.name) ?? slug,
|
|
162
|
+
description: normalizeString(record.description ?? record.summary) ?? "",
|
|
163
|
+
capability: normalizeString(record.capability) ?? "datapacks.read",
|
|
164
|
+
contentKeys: contents.map((entry) => entry.key),
|
|
165
|
+
artifactRef: packageName ? `datapack:${packageName}` : `datapack:${slug}`,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const contentsByKey = new Map();
|
|
169
|
+
for (const entry of contents) {
|
|
170
|
+
const key = String(entry.key || "").trim();
|
|
171
|
+
if (!key) continue;
|
|
172
|
+
const value = Object.prototype.hasOwnProperty.call(contentValues, key) ? contentValues[key] : null;
|
|
173
|
+
contentsByKey.set(
|
|
174
|
+
key,
|
|
175
|
+
Object.freeze({
|
|
176
|
+
definition: Object.freeze({
|
|
177
|
+
key,
|
|
178
|
+
label: normalizeString(entry.label) ?? key,
|
|
179
|
+
kind: entry.kind === "dir" ? "dir" : "file",
|
|
180
|
+
path: normalizeString(entry.path) ?? `${key}.json`,
|
|
181
|
+
role: normalizeString(entry.role),
|
|
182
|
+
contentType: normalizeString(entry.contentType) ?? guessContentType(value),
|
|
183
|
+
}),
|
|
184
|
+
value,
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return Object.freeze({
|
|
190
|
+
publicEntry,
|
|
191
|
+
manifest: Object.freeze(manifest),
|
|
192
|
+
contents: Object.freeze(contents.map((entry) => Object.freeze({ ...entry }))),
|
|
193
|
+
contentValues: Object.freeze({ ...contentValues }),
|
|
194
|
+
contentsByKey,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeOperationRecord(record) {
|
|
199
|
+
if (!isPlainObject(record)) {
|
|
200
|
+
throw new TypeError("mock operation record must be an object.");
|
|
201
|
+
}
|
|
202
|
+
const operation = isPlainObject(record.operation) ? { ...record.operation } : { ...record };
|
|
203
|
+
const operationId = normalizeString(operation.id);
|
|
204
|
+
const path = normalizeString(operation.path) ?? `/api/mock/operations/${encodeURIComponent(operationId || "operation")}`;
|
|
205
|
+
return Object.freeze({
|
|
206
|
+
kind: normalizeString(record.kind ?? operation.kind) ?? "module",
|
|
207
|
+
scope: normalizeString(record.scope ?? operation.scope) ?? "public",
|
|
208
|
+
selectors: isPlainObject(record.selectors) ? { ...record.selectors } : {},
|
|
209
|
+
artifactRef: normalizeString(record.artifactRef),
|
|
210
|
+
artifact: isPlainObject(record.artifact) ? { ...record.artifact } : null,
|
|
211
|
+
operation: Object.freeze({
|
|
212
|
+
id: operationId ?? "operation",
|
|
213
|
+
method: normalizeString(operation.method)?.toUpperCase() ?? "POST",
|
|
214
|
+
surface: normalizeString(operation.surface) ?? "mock",
|
|
215
|
+
scope: normalizeString(operation.scope ?? record.scope) ?? "public",
|
|
216
|
+
capability: normalizeString(operation.capability),
|
|
217
|
+
description: normalizeString(operation.description) ?? "",
|
|
218
|
+
path,
|
|
219
|
+
pathTemplate: normalizeString(operation.pathTemplate),
|
|
220
|
+
pathParams: Array.isArray(operation.pathParams) ? operation.pathParams.slice() : [],
|
|
221
|
+
query: Array.isArray(operation.query) ? operation.query.slice() : [],
|
|
222
|
+
body: operation.body ?? null,
|
|
223
|
+
result: operation.result ?? null,
|
|
224
|
+
execution: operation.execution ?? null,
|
|
225
|
+
examples: operation.examples ?? null,
|
|
226
|
+
}),
|
|
227
|
+
invoke: typeof record.invoke === "function" ? record.invoke : async () => record.result ?? operation.result ?? null,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function parseMockRoutePath(inputPath) {
|
|
232
|
+
const url = new URL(inputPath, "https://mock.crafter8.local");
|
|
233
|
+
return {
|
|
234
|
+
path: url.pathname,
|
|
235
|
+
query: Object.fromEntries(url.searchParams.entries()),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildArtifactsOperationsResponse(records, selector = {}) {
|
|
240
|
+
const filtered = records.filter((entry) => artifactMatchesSelector({ ...entry.selectors, kind: entry.kind, ...entry.artifact }, selector));
|
|
241
|
+
if (selector.packageName || selector.slug || selector.id || selector.user) {
|
|
242
|
+
const first = filtered[0] || null;
|
|
243
|
+
if (!first) {
|
|
244
|
+
return { data: { resolved: null, catalog: [] } };
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
data: {
|
|
248
|
+
resolved: {
|
|
249
|
+
kind: first.kind,
|
|
250
|
+
scope: first.scope,
|
|
251
|
+
selectors: first.selectors,
|
|
252
|
+
resolvedUser: normalizeString(selector.user),
|
|
253
|
+
artifactRef: first.artifactRef,
|
|
254
|
+
artifact: first.artifact,
|
|
255
|
+
operations: filtered
|
|
256
|
+
.filter((entry) => !selector.operationId || String(entry.operation.id) === String(selector.operationId))
|
|
257
|
+
.map((entry) => entry.operation),
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const groups = new Map();
|
|
264
|
+
for (const record of filtered) {
|
|
265
|
+
const groupKey = `${record.kind}:${record.scope}`;
|
|
266
|
+
const group = groups.get(groupKey) || {
|
|
267
|
+
kind: record.kind,
|
|
268
|
+
scope: record.scope,
|
|
269
|
+
selectors: record.selectors,
|
|
270
|
+
operations: [],
|
|
271
|
+
};
|
|
272
|
+
if (!selector.operationId || String(record.operation.id) === String(selector.operationId)) {
|
|
273
|
+
group.operations.push(record.operation);
|
|
274
|
+
}
|
|
275
|
+
groups.set(groupKey, group);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
data: {
|
|
279
|
+
catalog: Array.from(groups.values()),
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function createMockCrafter8Transport(options = {}) {
|
|
285
|
+
const session = normalizeMockSession(options.session);
|
|
286
|
+
const explicitArtifacts = Object.freeze(
|
|
287
|
+
(Array.isArray(options.artifacts) ? options.artifacts : []).map((entry) => Object.freeze({ ...entry })),
|
|
288
|
+
);
|
|
289
|
+
const datapacks = Object.freeze(
|
|
290
|
+
(Array.isArray(options.datapacks) ? options.datapacks : []).map((entry) => normalizeMockDatapack(entry)),
|
|
291
|
+
);
|
|
292
|
+
const artifacts = Object.freeze([...explicitArtifacts, ...datapacks.map((entry) => entry.publicEntry)]);
|
|
293
|
+
const operations = Object.freeze(
|
|
294
|
+
(Array.isArray(options.operations) ? options.operations : []).map((entry) => normalizeOperationRecord(entry)),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const datapackBySlug = new Map(datapacks.map((entry) => [entry.publicEntry.slug, entry]));
|
|
298
|
+
const invokeByRoute = new Map(
|
|
299
|
+
operations.map((entry) => [`${entry.operation.method} ${entry.operation.path}`, entry]),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
return Object.freeze({
|
|
303
|
+
async request(request) {
|
|
304
|
+
const method = String(request?.method || "GET").toUpperCase();
|
|
305
|
+
const parsed = parseMockRoutePath(request?.path || "/");
|
|
306
|
+
const query = { ...parsed.query, ...(isPlainObject(request?.query) ? request.query : {}) };
|
|
307
|
+
|
|
308
|
+
if (method === "GET" && parsed.path === "/api/session/v1") {
|
|
309
|
+
return { data: { session } };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (method === "GET" && parsed.path === "/api/artifacts") {
|
|
313
|
+
const rows = artifacts.filter((entry) => artifactMatchesSelector(entry, query));
|
|
314
|
+
return { data: { artifacts: rows } };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (method === "GET" && parsed.path === "/api/artifacts/operations") {
|
|
318
|
+
return buildArtifactsOperationsResponse(operations, query);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (method === "GET" && parsed.path === "/api/datapacks") {
|
|
322
|
+
return { data: { datapacks: datapacks.map((entry) => entry.publicEntry) } };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const datapackMatch = parsed.path.match(/^\/api\/datapacks\/([^/]+)\/([^/]+)$/);
|
|
326
|
+
if (method === "GET" && datapackMatch) {
|
|
327
|
+
const slug = decodeURIComponent(datapackMatch[1]);
|
|
328
|
+
const action = decodeURIComponent(datapackMatch[2]);
|
|
329
|
+
const datapack = datapackBySlug.get(slug);
|
|
330
|
+
if (!datapack) {
|
|
331
|
+
throw new Error(`Mock datapack not found: ${slug}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (action === "manifest") {
|
|
335
|
+
return { data: { datapack: datapack.publicEntry, manifest: datapack.manifest } };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (action === "contents") {
|
|
339
|
+
return { data: { datapack: datapack.publicEntry, dataset: null, contents: datapack.contents } };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (action === "resolve-content" || action === "content") {
|
|
343
|
+
const key = normalizeString(query.key);
|
|
344
|
+
if (!key) {
|
|
345
|
+
throw new Error("Mock datapack content key is required.");
|
|
346
|
+
}
|
|
347
|
+
const content = datapack.contentsByKey.get(key);
|
|
348
|
+
if (!content) {
|
|
349
|
+
throw new Error(`Mock datapack content not found: ${slug}/${key}`);
|
|
350
|
+
}
|
|
351
|
+
const resolved = buildInlineDatapackContent(slug, content.definition, content.value);
|
|
352
|
+
return { data: { datapack: datapack.publicEntry, content: resolved } };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const invokeEntry = invokeByRoute.get(`${method} ${parsed.path}`);
|
|
357
|
+
if (invokeEntry) {
|
|
358
|
+
const result = await invokeEntry.invoke({
|
|
359
|
+
method,
|
|
360
|
+
path: parsed.path,
|
|
361
|
+
query,
|
|
362
|
+
body: request?.body,
|
|
363
|
+
headers: request?.headers ?? {},
|
|
364
|
+
selector: invokeEntry.selectors,
|
|
365
|
+
artifact: invokeEntry.artifact,
|
|
366
|
+
operation: invokeEntry.operation,
|
|
367
|
+
});
|
|
368
|
+
return { data: result };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
throw new Error(`Unsupported mock Crafter8 route: ${method} ${parsed.path}`);
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function createMockCrafter8Client(options = {}) {
|
|
377
|
+
return createCrafter8Client({
|
|
378
|
+
transport: createMockCrafter8Transport(options),
|
|
379
|
+
datapacks: options.datapacksOptions ?? { mode: "local-first" },
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function createMockCrafter8HostServices(options = {}) {
|
|
384
|
+
const events = [];
|
|
385
|
+
const navigate = typeof options.navigate === "function" ? options.navigate : (path) => path;
|
|
386
|
+
const openItemInGraph =
|
|
387
|
+
typeof options.openItemInGraph === "function" ? options.openItemInGraph : (itemId) => itemId;
|
|
388
|
+
|
|
389
|
+
const host = {
|
|
390
|
+
navigation: {
|
|
391
|
+
navigate(path) {
|
|
392
|
+
assertString(path, "navigation path");
|
|
393
|
+
events.push({ type: "navigate", path: path.trim() });
|
|
394
|
+
return navigate(path.trim());
|
|
395
|
+
},
|
|
396
|
+
openItemInGraph(itemId) {
|
|
397
|
+
assertString(itemId, "itemId");
|
|
398
|
+
events.push({ type: "openItemInGraph", itemId: itemId.trim() });
|
|
399
|
+
return openItemInGraph(itemId.trim());
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
events,
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
return Object.freeze(host);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function createDefaultContainer() {
|
|
409
|
+
if (typeof document !== "undefined" && typeof document.createElement === "function") {
|
|
410
|
+
return document.createElement("div");
|
|
411
|
+
}
|
|
412
|
+
return { nodeType: 1 };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function createMockAppContext(options = {}) {
|
|
416
|
+
const client = options.client ?? createMockCrafter8Client(options);
|
|
417
|
+
const hostServices = options.hostServices ?? options.host ?? createMockCrafter8HostServices(options.hostOptions);
|
|
418
|
+
const artifact = {
|
|
419
|
+
kind: "app",
|
|
420
|
+
id: options.artifact?.id ?? "mock.app",
|
|
421
|
+
version: options.artifact?.version ?? "0.1.0",
|
|
422
|
+
name: options.artifact?.name,
|
|
423
|
+
summary: options.artifact?.summary,
|
|
424
|
+
capabilities: options.artifact?.capabilities,
|
|
425
|
+
dependencies: options.artifact?.dependencies,
|
|
426
|
+
uses: options.artifact?.uses,
|
|
427
|
+
provides: options.artifact?.provides,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
return createAppContext({
|
|
431
|
+
container: options.container ?? createDefaultContainer(),
|
|
432
|
+
client,
|
|
433
|
+
hostServices,
|
|
434
|
+
artifact,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function createMockModuleContext(options = {}) {
|
|
439
|
+
const client = options.client ?? createMockCrafter8Client(options);
|
|
440
|
+
const artifact = {
|
|
441
|
+
kind: "module",
|
|
442
|
+
id: options.artifact?.id ?? "mock.module",
|
|
443
|
+
version: options.artifact?.version ?? "0.1.0",
|
|
444
|
+
name: options.artifact?.name,
|
|
445
|
+
summary: options.artifact?.summary,
|
|
446
|
+
capabilities: options.artifact?.capabilities,
|
|
447
|
+
dependencies: options.artifact?.dependencies,
|
|
448
|
+
uses: options.artifact?.uses,
|
|
449
|
+
provides: options.artifact?.provides,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
return createModuleContext({
|
|
453
|
+
client,
|
|
454
|
+
artifact,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function createMockCrafter8Environment(options = {}) {
|
|
459
|
+
const client = options.client ?? createMockCrafter8Client(options);
|
|
460
|
+
const host = options.hostServices ?? options.host ?? createMockCrafter8HostServices(options.hostOptions);
|
|
461
|
+
const appContext = createMockAppContext({
|
|
462
|
+
...options,
|
|
463
|
+
client,
|
|
464
|
+
hostServices: host,
|
|
465
|
+
});
|
|
466
|
+
return Object.freeze({
|
|
467
|
+
client,
|
|
468
|
+
host,
|
|
469
|
+
appContext,
|
|
470
|
+
});
|
|
471
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crafter8/sdk",
|
|
3
|
+
"version": "0.1.0-next.1",
|
|
4
|
+
"description": "Crafter8 artifact SDK for code-first app, module, and datapack declarations.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./index.js",
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/crafter8/sdk.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/crafter8/sdk",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/crafter8/sdk/issues"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"crafter8",
|
|
22
|
+
"sdk",
|
|
23
|
+
"artifacts",
|
|
24
|
+
"datapacks",
|
|
25
|
+
"modules",
|
|
26
|
+
"apps"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"crafter8-build": "./build.js"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./index.d.ts",
|
|
34
|
+
"default": "./index.js"
|
|
35
|
+
},
|
|
36
|
+
"./mock": {
|
|
37
|
+
"types": "./mock.d.ts",
|
|
38
|
+
"default": "./mock.js"
|
|
39
|
+
},
|
|
40
|
+
"./react": {
|
|
41
|
+
"types": "./react.d.ts",
|
|
42
|
+
"default": "./react.js"
|
|
43
|
+
},
|
|
44
|
+
"./build": "./build.js",
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"index.js",
|
|
49
|
+
"index.d.ts",
|
|
50
|
+
"mock.js",
|
|
51
|
+
"mock.d.ts",
|
|
52
|
+
"react.js",
|
|
53
|
+
"react.d.ts",
|
|
54
|
+
"build.js",
|
|
55
|
+
"LICENSE",
|
|
56
|
+
"README.md"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"test": "node --test ./test/**/*.test.mjs"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/react.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AppContext, Crafter8Client, Crafter8HostServices, HostRuntimeApiV1 } from "./index.js";
|
|
2
|
+
|
|
3
|
+
export type Crafter8ContextValue = {
|
|
4
|
+
appContext: AppContext;
|
|
5
|
+
client: Crafter8Client;
|
|
6
|
+
host?: Crafter8HostServices;
|
|
7
|
+
runtime?: HostRuntimeApiV1;
|
|
8
|
+
artifact: AppContext["artifact"];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type Crafter8ReactLike = {
|
|
12
|
+
createContext(defaultValue: null): unknown;
|
|
13
|
+
createElement(type: unknown, props: Record<string, unknown> | null, ...children: unknown[]): unknown;
|
|
14
|
+
useContext(context: unknown): unknown;
|
|
15
|
+
useMemo<T>(factory: () => T, deps: readonly unknown[]): T;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export declare function createCrafter8ReactBindings(React: Crafter8ReactLike): {
|
|
19
|
+
Crafter8Provider(props: {
|
|
20
|
+
appContext?: AppContext;
|
|
21
|
+
value?: Crafter8ContextValue | AppContext;
|
|
22
|
+
children?: unknown;
|
|
23
|
+
}): unknown;
|
|
24
|
+
useCrafter8(): Crafter8ContextValue;
|
|
25
|
+
useCrafter8AppContext(): AppContext;
|
|
26
|
+
useCrafter8Client(): Crafter8Client;
|
|
27
|
+
useCrafter8Host(): Crafter8HostServices | undefined;
|
|
28
|
+
useCrafter8Artifact(): AppContext["artifact"];
|
|
29
|
+
useCrafter8Runtime(): HostRuntimeApiV1 | undefined;
|
|
30
|
+
};
|