@relayfile/adapter-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +430 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/docs/change-detector.d.ts +23 -0
- package/dist/src/docs/change-detector.d.ts.map +1 -0
- package/dist/src/docs/change-detector.js +126 -0
- package/dist/src/docs/change-detector.js.map +1 -0
- package/dist/src/docs/crawler.d.ts +22 -0
- package/dist/src/docs/crawler.d.ts.map +1 -0
- package/dist/src/docs/crawler.js +296 -0
- package/dist/src/docs/crawler.js.map +1 -0
- package/dist/src/docs/extractor.d.ts +12 -0
- package/dist/src/docs/extractor.d.ts.map +1 -0
- package/dist/src/docs/extractor.js +549 -0
- package/dist/src/docs/extractor.js.map +1 -0
- package/dist/src/docs/generator.d.ts +14 -0
- package/dist/src/docs/generator.d.ts.map +1 -0
- package/dist/src/docs/generator.js +368 -0
- package/dist/src/docs/generator.js.map +1 -0
- package/dist/src/docs/mapping-generator.d.ts +13 -0
- package/dist/src/docs/mapping-generator.d.ts.map +1 -0
- package/dist/src/docs/mapping-generator.js +104 -0
- package/dist/src/docs/mapping-generator.js.map +1 -0
- package/dist/src/docs/types.d.ts +110 -0
- package/dist/src/docs/types.d.ts.map +1 -0
- package/dist/src/docs/types.js +2 -0
- package/dist/src/docs/types.js.map +1 -0
- package/dist/src/docs/updater.d.ts +11 -0
- package/dist/src/docs/updater.d.ts.map +1 -0
- package/dist/src/docs/updater.js +133 -0
- package/dist/src/docs/updater.js.map +1 -0
- package/dist/src/drift/drift-checker.d.ts +13 -0
- package/dist/src/drift/drift-checker.d.ts.map +1 -0
- package/dist/src/drift/drift-checker.js +200 -0
- package/dist/src/drift/drift-checker.js.map +1 -0
- package/dist/src/generate/adapter-generator.d.ts +4 -0
- package/dist/src/generate/adapter-generator.d.ts.map +1 -0
- package/dist/src/generate/adapter-generator.js +115 -0
- package/dist/src/generate/adapter-generator.js.map +1 -0
- package/dist/src/generate/types-generator.d.ts +3 -0
- package/dist/src/generate/types-generator.d.ts.map +1 -0
- package/dist/src/generate/types-generator.js +105 -0
- package/dist/src/generate/types-generator.js.map +1 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/ingest/index.d.ts +4 -0
- package/dist/src/ingest/index.d.ts.map +1 -0
- package/dist/src/ingest/index.js +34 -0
- package/dist/src/ingest/index.js.map +1 -0
- package/dist/src/ingest/openapi.d.ts +8 -0
- package/dist/src/ingest/openapi.d.ts.map +1 -0
- package/dist/src/ingest/openapi.js +187 -0
- package/dist/src/ingest/openapi.js.map +1 -0
- package/dist/src/ingest/postman.d.ts +3 -0
- package/dist/src/ingest/postman.d.ts.map +1 -0
- package/dist/src/ingest/postman.js +14 -0
- package/dist/src/ingest/postman.js.map +1 -0
- package/dist/src/ingest/sample.d.ts +4 -0
- package/dist/src/ingest/sample.d.ts.map +1 -0
- package/dist/src/ingest/sample.js +72 -0
- package/dist/src/ingest/sample.js.map +1 -0
- package/dist/src/ingest/shared.d.ts +6 -0
- package/dist/src/ingest/shared.d.ts.map +1 -0
- package/dist/src/ingest/shared.js +52 -0
- package/dist/src/ingest/shared.js.map +1 -0
- package/dist/src/ingest/types.d.ts +44 -0
- package/dist/src/ingest/types.d.ts.map +1 -0
- package/dist/src/ingest/types.js +2 -0
- package/dist/src/ingest/types.js.map +1 -0
- package/dist/src/runtime/schema-adapter.d.ts +71 -0
- package/dist/src/runtime/schema-adapter.d.ts.map +1 -0
- package/dist/src/runtime/schema-adapter.js +887 -0
- package/dist/src/runtime/schema-adapter.js.map +1 -0
- package/dist/src/spec/parser.d.ts +9 -0
- package/dist/src/spec/parser.d.ts.map +1 -0
- package/dist/src/spec/parser.js +371 -0
- package/dist/src/spec/parser.js.map +1 -0
- package/dist/src/spec/template.d.ts +8 -0
- package/dist/src/spec/template.d.ts.map +1 -0
- package/dist/src/spec/template.js +75 -0
- package/dist/src/spec/template.js.map +1 -0
- package/dist/src/spec/types.d.ts +88 -0
- package/dist/src/spec/types.d.ts.map +1 -0
- package/dist/src/spec/types.js +2 -0
- package/dist/src/spec/types.js.map +1 -0
- package/dist/tests/docs/change-detector.test.d.ts +2 -0
- package/dist/tests/docs/change-detector.test.d.ts.map +1 -0
- package/dist/tests/docs/change-detector.test.js +24 -0
- package/dist/tests/docs/change-detector.test.js.map +1 -0
- package/dist/tests/docs/crawler.test.d.ts +2 -0
- package/dist/tests/docs/crawler.test.d.ts.map +1 -0
- package/dist/tests/docs/crawler.test.js +55 -0
- package/dist/tests/docs/crawler.test.js.map +1 -0
- package/dist/tests/docs/extractor.test.d.ts +2 -0
- package/dist/tests/docs/extractor.test.d.ts.map +1 -0
- package/dist/tests/docs/extractor.test.js +63 -0
- package/dist/tests/docs/extractor.test.js.map +1 -0
- package/dist/tests/docs/generator.test.d.ts +2 -0
- package/dist/tests/docs/generator.test.d.ts.map +1 -0
- package/dist/tests/docs/generator.test.js +46 -0
- package/dist/tests/docs/generator.test.js.map +1 -0
- package/dist/tests/drift/drift-checker.test.d.ts +2 -0
- package/dist/tests/drift/drift-checker.test.d.ts.map +1 -0
- package/dist/tests/drift/drift-checker.test.js +59 -0
- package/dist/tests/drift/drift-checker.test.js.map +1 -0
- package/dist/tests/round-trip/fake-connection.d.ts +29 -0
- package/dist/tests/round-trip/fake-connection.d.ts.map +1 -0
- package/dist/tests/round-trip/fake-connection.js +174 -0
- package/dist/tests/round-trip/fake-connection.js.map +1 -0
- package/dist/tests/round-trip/github-pulls.test.d.ts +2 -0
- package/dist/tests/round-trip/github-pulls.test.d.ts.map +1 -0
- package/dist/tests/round-trip/github-pulls.test.js +12 -0
- package/dist/tests/round-trip/github-pulls.test.js.map +1 -0
- package/dist/tests/round-trip/harness.d.ts +74 -0
- package/dist/tests/round-trip/harness.d.ts.map +1 -0
- package/dist/tests/round-trip/harness.js +323 -0
- package/dist/tests/round-trip/harness.js.map +1 -0
- package/dist/tests/round-trip/vfs-snapshot.d.ts +45 -0
- package/dist/tests/round-trip/vfs-snapshot.d.ts.map +1 -0
- package/dist/tests/round-trip/vfs-snapshot.js +218 -0
- package/dist/tests/round-trip/vfs-snapshot.js.map +1 -0
- package/dist/tests/runtime/schema-adapter.sync.test.d.ts +2 -0
- package/dist/tests/runtime/schema-adapter.sync.test.d.ts.map +1 -0
- package/dist/tests/runtime/schema-adapter.sync.test.js +962 -0
- package/dist/tests/runtime/schema-adapter.sync.test.js.map +1 -0
- package/dist/tests/runtime/schema-adapter.test.d.ts +2 -0
- package/dist/tests/runtime/schema-adapter.test.d.ts.map +1 -0
- package/dist/tests/runtime/schema-adapter.test.js +100 -0
- package/dist/tests/runtime/schema-adapter.test.js.map +1 -0
- package/dist/tests/spec/parser.test.d.ts +2 -0
- package/dist/tests/spec/parser.test.d.ts.map +1 -0
- package/dist/tests/spec/parser.test.js +248 -0
- package/dist/tests/spec/parser.test.js.map +1 -0
- package/mappings/github.mapping.yaml +35 -0
- package/mappings/slack.mapping.yaml +18 -0
- package/package.json +52 -0
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
import { computeCanonicalPath, IntegrationAdapter, } from "@relayfile/sdk";
|
|
2
|
+
import { minimatch } from "minimatch";
|
|
3
|
+
import { interpolateTemplate, pickFields, readTemplateValue, } from "../spec/template.js";
|
|
4
|
+
export { IntegrationAdapter } from "@relayfile/sdk";
|
|
5
|
+
export class SchemaAdapter extends IntegrationAdapter {
|
|
6
|
+
name;
|
|
7
|
+
version;
|
|
8
|
+
spec;
|
|
9
|
+
defaultConnectionId;
|
|
10
|
+
resolveConnectionIdFn;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(options.client, options.provider);
|
|
13
|
+
this.spec = options.spec;
|
|
14
|
+
this.name = options.spec.adapter.name;
|
|
15
|
+
this.version = options.spec.adapter.version;
|
|
16
|
+
this.defaultConnectionId = options.defaultConnectionId;
|
|
17
|
+
this.resolveConnectionIdFn = options.resolveConnectionId;
|
|
18
|
+
}
|
|
19
|
+
computePath(objectType, objectId) {
|
|
20
|
+
return computeCanonicalPath(this.name, objectType, objectId);
|
|
21
|
+
}
|
|
22
|
+
computeWebhookPath(event) {
|
|
23
|
+
const mapping = this.resolveWebhookMapping(event);
|
|
24
|
+
return interpolateTemplate(mapping.path, event.payload, { strict: true });
|
|
25
|
+
}
|
|
26
|
+
computeResourcePath(resourceName, input) {
|
|
27
|
+
const mapping = this.spec.resources?.[resourceName];
|
|
28
|
+
if (!mapping) {
|
|
29
|
+
throw new Error(`Unknown resource mapping "${resourceName}"`);
|
|
30
|
+
}
|
|
31
|
+
return interpolateTemplate(mapping.path, input, { strict: true });
|
|
32
|
+
}
|
|
33
|
+
normalizePayload(event, mapping) {
|
|
34
|
+
const payload = event.payload;
|
|
35
|
+
return pickFields(payload, mapping?.extract);
|
|
36
|
+
}
|
|
37
|
+
computeSemantics(objectType, objectId, payload) {
|
|
38
|
+
const properties = {
|
|
39
|
+
provider: this.name,
|
|
40
|
+
"provider.object_type": objectType,
|
|
41
|
+
"provider.object_id": objectId,
|
|
42
|
+
};
|
|
43
|
+
if (typeof payload.status === "string") {
|
|
44
|
+
properties["provider.status"] = payload.status;
|
|
45
|
+
}
|
|
46
|
+
return { properties };
|
|
47
|
+
}
|
|
48
|
+
supportedEvents() {
|
|
49
|
+
return Object.keys(this.spec.webhooks);
|
|
50
|
+
}
|
|
51
|
+
async sync(first, second = {}, third = {}) {
|
|
52
|
+
const { workspaceId, resourceName, options } = this.resolveSyncInvocation(first, second, third);
|
|
53
|
+
const mapping = this.spec.resources?.[resourceName];
|
|
54
|
+
if (!mapping) {
|
|
55
|
+
throw new Error(`Unknown resource mapping "${resourceName}"`);
|
|
56
|
+
}
|
|
57
|
+
const signal = options.signal;
|
|
58
|
+
throwIfAborted(signal);
|
|
59
|
+
const parsedEndpoint = parseEndpointDescriptor(mapping.endpoint);
|
|
60
|
+
const input = buildSyncInput(options);
|
|
61
|
+
const endpoint = interpolateBracedTemplate(parsedEndpoint.path, input, "resource endpoint");
|
|
62
|
+
const syncMetadata = mapping.sync;
|
|
63
|
+
const objectType = syncMetadata?.modelName ?? resourceName;
|
|
64
|
+
const checkpointKey = syncMetadata?.checkpointKey ?? resourceName;
|
|
65
|
+
const inputScope = checkpointScope(input);
|
|
66
|
+
const checkpointPath = syncCheckpointPath(this.name, resourceName, checkpointKey, inputScope);
|
|
67
|
+
const connectionId = this.resolveSyncConnectionId(resourceName, options);
|
|
68
|
+
const maxPages = readNonNegativeInteger(options.maxPages) ?? DEFAULT_SYNC_MAX_PAGES;
|
|
69
|
+
const checkpoint = options.resume === false
|
|
70
|
+
? undefined
|
|
71
|
+
: await this.readSyncCheckpoint(workspaceId, checkpointPath, signal);
|
|
72
|
+
const initialCursor = readString(options.cursor) ?? checkpointCursor(checkpoint);
|
|
73
|
+
let cursor = initialCursor;
|
|
74
|
+
let watermark = readString(options.watermark) ??
|
|
75
|
+
readString(options.since) ??
|
|
76
|
+
readString(checkpoint?.watermark);
|
|
77
|
+
const requestWatermark = watermark;
|
|
78
|
+
let page = readPositiveInteger(initialCursor) ?? 1;
|
|
79
|
+
let offset = readNonNegativeInteger(initialCursor) ?? 0;
|
|
80
|
+
let linkTarget = mapping.pagination?.strategy === "link-header" && initialCursor
|
|
81
|
+
? parseLinkTarget(initialCursor)
|
|
82
|
+
: undefined;
|
|
83
|
+
const result = {
|
|
84
|
+
filesWritten: 0,
|
|
85
|
+
filesUpdated: 0,
|
|
86
|
+
filesDeleted: 0,
|
|
87
|
+
paths: [],
|
|
88
|
+
cursor: initialCursor,
|
|
89
|
+
nextCursor: initialCursor ?? null,
|
|
90
|
+
syncedObjectTypes: [objectType],
|
|
91
|
+
errors: [],
|
|
92
|
+
};
|
|
93
|
+
let runPagesSynced = 0;
|
|
94
|
+
let pagesSynced = readNonNegativeInteger(checkpoint?.pagesSynced) ?? 0;
|
|
95
|
+
let recordsSynced = readNonNegativeInteger(checkpoint?.recordsSynced) ?? 0;
|
|
96
|
+
let nextCursor = initialCursor ?? null;
|
|
97
|
+
let hasNextPage = true;
|
|
98
|
+
const seenPageSignatures = new Set();
|
|
99
|
+
while (hasNextPage) {
|
|
100
|
+
throwIfAborted(signal);
|
|
101
|
+
if (runPagesSynced >= maxPages) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
const pageRequest = buildPageRequest({
|
|
105
|
+
baseQuery: options.query,
|
|
106
|
+
pagination: mapping.pagination,
|
|
107
|
+
cursor,
|
|
108
|
+
page,
|
|
109
|
+
offset,
|
|
110
|
+
limit: options.limit,
|
|
111
|
+
watermark: requestWatermark,
|
|
112
|
+
watermarkParamName: readString(options.watermarkParamName) ??
|
|
113
|
+
readString(options.sinceParamName),
|
|
114
|
+
linkTarget,
|
|
115
|
+
});
|
|
116
|
+
let response;
|
|
117
|
+
try {
|
|
118
|
+
const proxyRequest = {
|
|
119
|
+
method: parsedEndpoint.method,
|
|
120
|
+
baseUrl: linkTarget?.baseUrl ?? this.spec.adapter.baseUrl ?? "",
|
|
121
|
+
endpoint: linkTarget?.endpoint ?? endpoint,
|
|
122
|
+
connectionId,
|
|
123
|
+
query: pageRequest.query,
|
|
124
|
+
signal,
|
|
125
|
+
};
|
|
126
|
+
response = await this.provider.proxy(proxyRequest);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (isAbortError(error)) {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
result.errors.push({
|
|
133
|
+
objectType,
|
|
134
|
+
error: error instanceof Error ? error.message : String(error),
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
throwIfAborted(signal);
|
|
139
|
+
if (response.status >= 400) {
|
|
140
|
+
result.errors.push({
|
|
141
|
+
objectType,
|
|
142
|
+
error: `Sync failed with ${response.status}: ${JSON.stringify(response.data)}`,
|
|
143
|
+
});
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
const nextLinkTarget = mapping.pagination?.strategy === "link-header"
|
|
147
|
+
? parseNextLink(response.headers)
|
|
148
|
+
: undefined;
|
|
149
|
+
const repeatedLinkTarget = repeatedLinkHeaderTarget(mapping.pagination, nextLinkTarget, cursor);
|
|
150
|
+
if (repeatedLinkTarget) {
|
|
151
|
+
result.errors.push({
|
|
152
|
+
objectType,
|
|
153
|
+
error: `Pagination stalled for ${resourceName}: repeated link-header target ${repeatedLinkTarget}.`,
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
const records = extractSyncRecords(response.data, mapping);
|
|
158
|
+
const pageSignature = syncPageSignature(mapping.pagination, records);
|
|
159
|
+
if (pageSignature) {
|
|
160
|
+
if (seenPageSignatures.has(pageSignature)) {
|
|
161
|
+
result.errors.push({
|
|
162
|
+
objectType,
|
|
163
|
+
error: `Pagination stalled for ${resourceName}: repeated page data.`,
|
|
164
|
+
});
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
seenPageSignatures.add(pageSignature);
|
|
168
|
+
}
|
|
169
|
+
let pageHadWriteFailure = false;
|
|
170
|
+
for (const rawRecord of records) {
|
|
171
|
+
let path;
|
|
172
|
+
try {
|
|
173
|
+
throwIfAborted(signal);
|
|
174
|
+
const record = normalizeSyncRecord(rawRecord);
|
|
175
|
+
const pathInput = { ...input, ...record };
|
|
176
|
+
path = interpolateResourcePath(mapping.path, pathInput);
|
|
177
|
+
const payload = pickFields(record, mapping.extract);
|
|
178
|
+
const objectId = resolveObjectId(record, syncMetadata?.cursorField, path);
|
|
179
|
+
const baseRevision = await this.resolveBaseRevision(workspaceId, path, signal);
|
|
180
|
+
throwIfAborted(signal);
|
|
181
|
+
await this.client.writeFile({
|
|
182
|
+
workspaceId,
|
|
183
|
+
path,
|
|
184
|
+
baseRevision,
|
|
185
|
+
content: `${JSON.stringify(payload, null, 2)}\n`,
|
|
186
|
+
contentType: "application/json",
|
|
187
|
+
encoding: "utf-8",
|
|
188
|
+
semantics: this.computeSemantics(objectType, objectId, record),
|
|
189
|
+
signal,
|
|
190
|
+
});
|
|
191
|
+
if (baseRevision === "0") {
|
|
192
|
+
result.filesWritten += 1;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
result.filesUpdated += 1;
|
|
196
|
+
}
|
|
197
|
+
result.paths?.push(path);
|
|
198
|
+
recordsSynced += 1;
|
|
199
|
+
watermark = advanceWatermark(watermark, syncMetadata?.cursorField
|
|
200
|
+
? readTemplateValue(record, syncMetadata.cursorField)
|
|
201
|
+
: undefined);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
if (isAbortError(error)) {
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
const syncError = {
|
|
208
|
+
objectType,
|
|
209
|
+
error: error instanceof Error ? error.message : String(error),
|
|
210
|
+
};
|
|
211
|
+
if (path) {
|
|
212
|
+
syncError.path = path;
|
|
213
|
+
}
|
|
214
|
+
result.errors.push(syncError);
|
|
215
|
+
pageHadWriteFailure = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (pageHadWriteFailure) {
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
runPagesSynced += 1;
|
|
222
|
+
pagesSynced += 1;
|
|
223
|
+
const nextPage = resolveNextPage({
|
|
224
|
+
pagination: mapping.pagination,
|
|
225
|
+
response,
|
|
226
|
+
recordsRead: records.length,
|
|
227
|
+
page,
|
|
228
|
+
offset,
|
|
229
|
+
limit: pageRequest.limit,
|
|
230
|
+
cursor,
|
|
231
|
+
maxPages,
|
|
232
|
+
pagesSynced: runPagesSynced,
|
|
233
|
+
nextLinkTarget,
|
|
234
|
+
});
|
|
235
|
+
nextCursor = nextPage.cursor;
|
|
236
|
+
result.nextCursor = nextCursor;
|
|
237
|
+
try {
|
|
238
|
+
throwIfAborted(signal);
|
|
239
|
+
await this.writeSyncCheckpoint(workspaceId, checkpointPath, {
|
|
240
|
+
adapter: this.name,
|
|
241
|
+
resourceName,
|
|
242
|
+
checkpointKey,
|
|
243
|
+
inputScope,
|
|
244
|
+
cursor: nextCursor ?? undefined,
|
|
245
|
+
nextCursor,
|
|
246
|
+
watermark,
|
|
247
|
+
updatedAt: new Date().toISOString(),
|
|
248
|
+
pagesSynced,
|
|
249
|
+
recordsSynced,
|
|
250
|
+
}, signal);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (isAbortError(error)) {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
result.errors.push({
|
|
257
|
+
path: checkpointPath,
|
|
258
|
+
objectType,
|
|
259
|
+
error: error instanceof Error ? error.message : String(error),
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
hasNextPage = nextPage.hasNext;
|
|
264
|
+
cursor = nextPage.cursor ?? undefined;
|
|
265
|
+
page = nextPage.page ?? page + 1;
|
|
266
|
+
offset = nextPage.offset ?? offset + records.length;
|
|
267
|
+
linkTarget = nextPage.linkTarget;
|
|
268
|
+
}
|
|
269
|
+
result.nextCursor = nextCursor;
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
async ingestWebhook(workspaceId, event) {
|
|
273
|
+
const mapping = this.resolveWebhookMapping(event);
|
|
274
|
+
const path = interpolateTemplate(mapping.path, event.payload, { strict: true });
|
|
275
|
+
const data = this.normalizePayload(event, mapping);
|
|
276
|
+
await this.client.ingestWebhook({
|
|
277
|
+
workspaceId,
|
|
278
|
+
provider: this.name,
|
|
279
|
+
event_type: event.eventType,
|
|
280
|
+
path,
|
|
281
|
+
data,
|
|
282
|
+
delivery_id: event.metadata?.delivery_id ?? event.metadata?.deliveryId,
|
|
283
|
+
timestamp: event.metadata?.timestamp ?? new Date().toISOString(),
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
filesWritten: 1,
|
|
287
|
+
filesUpdated: 0,
|
|
288
|
+
filesDeleted: 0,
|
|
289
|
+
paths: [path],
|
|
290
|
+
errors: [],
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
matchWriteback(path) {
|
|
294
|
+
for (const [name, mapping] of Object.entries(this.spec.writebacks ?? {})) {
|
|
295
|
+
if (!minimatch(path, mapping.match, { dot: true })) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const wildcardValues = extractWildcardValues(mapping.match, path);
|
|
299
|
+
const parsed = parseEndpointDescriptor(mapping.endpoint);
|
|
300
|
+
const placeholders = extractEndpointParams(parsed.path);
|
|
301
|
+
const params = Object.fromEntries(placeholders.map((placeholder, index) => [placeholder, wildcardValues[index] ?? ""]));
|
|
302
|
+
return {
|
|
303
|
+
name,
|
|
304
|
+
mapping,
|
|
305
|
+
method: parsed.method,
|
|
306
|
+
endpointPath: interpolateEndpointParams(parsed.path, params),
|
|
307
|
+
params,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
async handleWriteback(workspaceId, path, content) {
|
|
313
|
+
const match = this.matchWriteback(path);
|
|
314
|
+
if (!match) {
|
|
315
|
+
throw new Error(`No writeback mapping matched ${path}`);
|
|
316
|
+
}
|
|
317
|
+
const parsedContent = safeJsonParse(content);
|
|
318
|
+
const connectionId = await this.resolveConnectionId({
|
|
319
|
+
workspaceId,
|
|
320
|
+
path,
|
|
321
|
+
content,
|
|
322
|
+
parsedContent,
|
|
323
|
+
match,
|
|
324
|
+
});
|
|
325
|
+
return this.provider.proxy({
|
|
326
|
+
method: match.method,
|
|
327
|
+
baseUrl: match.mapping.baseUrl ?? this.spec.adapter.baseUrl ?? "",
|
|
328
|
+
endpoint: match.endpointPath,
|
|
329
|
+
connectionId,
|
|
330
|
+
body: parsedContent ?? content,
|
|
331
|
+
headers: {
|
|
332
|
+
"Content-Type": parsedContent === undefined ? "text/plain" : "application/json",
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async writeBack(workspaceId, path, content) {
|
|
337
|
+
const response = await this.handleWriteback(workspaceId, path, content);
|
|
338
|
+
if (response.status >= 400) {
|
|
339
|
+
throw new Error(`Writeback failed with ${response.status}: ${JSON.stringify(response.data)}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
resolveWebhookMapping(event) {
|
|
343
|
+
for (const key of webhookLookupKeys(event)) {
|
|
344
|
+
const mapping = this.spec.webhooks[key];
|
|
345
|
+
if (mapping) {
|
|
346
|
+
return mapping;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
throw new Error(`No webhook mapping for event ${event.eventType} / objectType ${event.objectType}`);
|
|
350
|
+
}
|
|
351
|
+
async resolveConnectionId(context) {
|
|
352
|
+
const parsedContent = context.parsedContent;
|
|
353
|
+
if (typeof parsedContent === "object" && parsedContent !== null) {
|
|
354
|
+
const record = parsedContent;
|
|
355
|
+
const direct = readString(record.connectionId);
|
|
356
|
+
const metadata = typeof record.metadata === "object" && record.metadata !== null
|
|
357
|
+
? readString(record.metadata.connectionId)
|
|
358
|
+
: undefined;
|
|
359
|
+
if (direct || metadata) {
|
|
360
|
+
return direct ?? metadata ?? "";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (this.resolveConnectionIdFn) {
|
|
364
|
+
const resolved = await this.resolveConnectionIdFn(context);
|
|
365
|
+
if (resolved.trim()) {
|
|
366
|
+
return resolved.trim();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (this.defaultConnectionId?.trim()) {
|
|
370
|
+
return this.defaultConnectionId.trim();
|
|
371
|
+
}
|
|
372
|
+
throw new Error(`Missing connection id for writeback ${context.path}. Configure defaultConnectionId or resolveConnectionId.`);
|
|
373
|
+
}
|
|
374
|
+
resolveSyncInvocation(first, second, third) {
|
|
375
|
+
const resourceNames = Object.keys(this.spec.resources ?? {});
|
|
376
|
+
if (typeof second === "string") {
|
|
377
|
+
return { workspaceId: first, resourceName: second, options: third };
|
|
378
|
+
}
|
|
379
|
+
const options = second;
|
|
380
|
+
const optionWorkspaceId = readString(options.workspaceId);
|
|
381
|
+
const optionResourceName = readString(options.resourceName);
|
|
382
|
+
if (resourceNames.includes(first) && optionWorkspaceId) {
|
|
383
|
+
return {
|
|
384
|
+
workspaceId: optionWorkspaceId,
|
|
385
|
+
resourceName: first,
|
|
386
|
+
options,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
const resourceName = optionResourceName ??
|
|
390
|
+
(resourceNames.length === 1 ? resourceNames[0] : undefined);
|
|
391
|
+
if (!resourceName) {
|
|
392
|
+
throw new Error("Missing resourceName for sync. Pass sync(workspaceId, resourceName, options) or options.resourceName.");
|
|
393
|
+
}
|
|
394
|
+
return { workspaceId: first, resourceName, options };
|
|
395
|
+
}
|
|
396
|
+
resolveSyncConnectionId(resourceName, options) {
|
|
397
|
+
const direct = readString(options.connectionId);
|
|
398
|
+
if (direct?.trim()) {
|
|
399
|
+
return direct.trim();
|
|
400
|
+
}
|
|
401
|
+
if (this.defaultConnectionId?.trim()) {
|
|
402
|
+
return this.defaultConnectionId.trim();
|
|
403
|
+
}
|
|
404
|
+
throw new Error(`Missing connection id for sync ${resourceName}. Configure defaultConnectionId or pass options.connectionId.`);
|
|
405
|
+
}
|
|
406
|
+
async readSyncCheckpoint(workspaceId, path, signal) {
|
|
407
|
+
const reader = this.client;
|
|
408
|
+
if (!reader.readFile) {
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const file = await reader.readFile(workspaceId, path, undefined, signal);
|
|
413
|
+
const parsed = safeJsonParse(file.content);
|
|
414
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
if (isAbortError(error)) {
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async writeSyncCheckpoint(workspaceId, path, checkpoint, signal) {
|
|
424
|
+
const baseRevision = await this.resolveBaseRevision(workspaceId, path, signal);
|
|
425
|
+
await this.client.writeFile({
|
|
426
|
+
workspaceId,
|
|
427
|
+
path,
|
|
428
|
+
baseRevision,
|
|
429
|
+
content: `${JSON.stringify(checkpoint, null, 2)}\n`,
|
|
430
|
+
contentType: "application/json",
|
|
431
|
+
encoding: "utf-8",
|
|
432
|
+
signal,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
async resolveBaseRevision(workspaceId, path, signal) {
|
|
436
|
+
const reader = this.client;
|
|
437
|
+
if (!reader.readFile) {
|
|
438
|
+
return "0";
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const file = await reader.readFile(workspaceId, path, undefined, signal);
|
|
442
|
+
return readString(file.revision) ?? "0";
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
if (isAbortError(error)) {
|
|
446
|
+
throw error;
|
|
447
|
+
}
|
|
448
|
+
return "0";
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const DEFAULT_SYNC_MAX_PAGES = 1000;
|
|
453
|
+
function webhookLookupKeys(event) {
|
|
454
|
+
const eventRoot = event.eventType.split(".")[0] ?? event.eventType;
|
|
455
|
+
return [...new Set([event.eventType, event.objectType, eventRoot])];
|
|
456
|
+
}
|
|
457
|
+
function parseEndpointDescriptor(value) {
|
|
458
|
+
const match = value.match(/^(DELETE|GET|PATCH|POST|PUT)\s+(\/.+)$/);
|
|
459
|
+
if (!match) {
|
|
460
|
+
throw new Error(`Invalid endpoint descriptor "${value}"`);
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
method: match[1],
|
|
464
|
+
path: match[2],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function extractEndpointParams(path) {
|
|
468
|
+
return [...path.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]);
|
|
469
|
+
}
|
|
470
|
+
function extractWildcardValues(pattern, path) {
|
|
471
|
+
const wildcardRegex = pattern
|
|
472
|
+
.split("*")
|
|
473
|
+
.map(escapeRegex)
|
|
474
|
+
.join("(.+?)");
|
|
475
|
+
const regex = new RegExp(`^${wildcardRegex}$`);
|
|
476
|
+
const match = path.match(regex);
|
|
477
|
+
return match ? match.slice(1).map(decodeURIComponent) : [];
|
|
478
|
+
}
|
|
479
|
+
function interpolateEndpointParams(template, params) {
|
|
480
|
+
return template.replace(/\{([^}]+)\}/g, (_match, name) => {
|
|
481
|
+
const value = params[name];
|
|
482
|
+
if (!value) {
|
|
483
|
+
throw new Error(`Missing writeback parameter "${name}"`);
|
|
484
|
+
}
|
|
485
|
+
return encodeURIComponent(value);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
function buildSyncInput(options) {
|
|
489
|
+
const input = {};
|
|
490
|
+
if (isRecord(options.input)) {
|
|
491
|
+
Object.assign(input, options.input);
|
|
492
|
+
}
|
|
493
|
+
if (isRecord(options.params)) {
|
|
494
|
+
Object.assign(input, options.params);
|
|
495
|
+
}
|
|
496
|
+
for (const [key, value] of Object.entries(options)) {
|
|
497
|
+
if (!SYNC_OPTION_KEYS.has(key) && value !== undefined) {
|
|
498
|
+
input[key] = value;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return input;
|
|
502
|
+
}
|
|
503
|
+
function buildPageRequest(input) {
|
|
504
|
+
const query = {
|
|
505
|
+
...stringifyQuery(input.baseQuery),
|
|
506
|
+
...(input.linkTarget?.query ?? {}),
|
|
507
|
+
};
|
|
508
|
+
const limit = resolvePageSize(input.pagination, input.limit);
|
|
509
|
+
if (input.watermark) {
|
|
510
|
+
query[input.watermarkParamName ?? "since"] = input.watermark;
|
|
511
|
+
}
|
|
512
|
+
switch (input.pagination?.strategy) {
|
|
513
|
+
case "cursor":
|
|
514
|
+
if (input.cursor) {
|
|
515
|
+
query[input.pagination.paramName ?? "cursor"] = input.cursor;
|
|
516
|
+
}
|
|
517
|
+
if (limit !== undefined) {
|
|
518
|
+
query.limit = String(limit);
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
case "next-token":
|
|
522
|
+
if (input.cursor) {
|
|
523
|
+
query[input.pagination.paramName ?? "page_token"] = input.cursor;
|
|
524
|
+
}
|
|
525
|
+
if (limit !== undefined) {
|
|
526
|
+
query.limit = String(limit);
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
case "offset":
|
|
530
|
+
query[input.pagination.paramName ?? "offset"] = String(input.offset);
|
|
531
|
+
if (limit !== undefined) {
|
|
532
|
+
query[input.pagination.limitParamName ?? "limit"] = String(limit);
|
|
533
|
+
}
|
|
534
|
+
break;
|
|
535
|
+
case "page":
|
|
536
|
+
query[input.pagination.paramName ?? "page"] = String(input.page);
|
|
537
|
+
if (limit !== undefined) {
|
|
538
|
+
query[input.pagination.limitParamName ?? "limit"] = String(limit);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case "link-header":
|
|
542
|
+
case undefined:
|
|
543
|
+
if (limit !== undefined) {
|
|
544
|
+
query.limit = String(limit);
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
query: Object.keys(query).length > 0 ? query : undefined,
|
|
550
|
+
limit,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function resolveNextPage(input) {
|
|
554
|
+
const stopForMaxPages = input.maxPages !== undefined &&
|
|
555
|
+
input.pagesSynced >= input.maxPages;
|
|
556
|
+
switch (input.pagination?.strategy) {
|
|
557
|
+
case "cursor": {
|
|
558
|
+
const nextCursor = stringifyScalar(readTemplateValue(input.response.data, input.pagination.cursorPath));
|
|
559
|
+
return {
|
|
560
|
+
hasNext: Boolean(!stopForMaxPages && nextCursor && nextCursor !== input.cursor),
|
|
561
|
+
cursor: nextCursor ?? null,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
case "next-token": {
|
|
565
|
+
const nextCursor = stringifyScalar(readTemplateValue(input.response.data, input.pagination.tokenPath));
|
|
566
|
+
return {
|
|
567
|
+
hasNext: Boolean(!stopForMaxPages && nextCursor && nextCursor !== input.cursor),
|
|
568
|
+
cursor: nextCursor ?? null,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
case "offset": {
|
|
572
|
+
const nextOffset = input.offset + input.recordsRead;
|
|
573
|
+
const hasMore = input.recordsRead > 0 &&
|
|
574
|
+
input.limit !== undefined &&
|
|
575
|
+
input.limit > 0 &&
|
|
576
|
+
input.recordsRead >= input.limit &&
|
|
577
|
+
nextOffset > input.offset;
|
|
578
|
+
const hasNext = !stopForMaxPages && hasMore;
|
|
579
|
+
return {
|
|
580
|
+
hasNext,
|
|
581
|
+
cursor: hasMore ? String(nextOffset) : null,
|
|
582
|
+
offset: nextOffset,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
case "page": {
|
|
586
|
+
const nextPage = input.page + 1;
|
|
587
|
+
const hasMore = input.recordsRead > 0 &&
|
|
588
|
+
input.limit !== undefined &&
|
|
589
|
+
input.limit > 0 &&
|
|
590
|
+
input.recordsRead >= input.limit;
|
|
591
|
+
const hasNext = !stopForMaxPages && hasMore;
|
|
592
|
+
return {
|
|
593
|
+
hasNext,
|
|
594
|
+
cursor: hasMore ? String(nextPage) : null,
|
|
595
|
+
page: nextPage,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
case "link-header": {
|
|
599
|
+
const target = input.nextLinkTarget;
|
|
600
|
+
return {
|
|
601
|
+
hasNext: !stopForMaxPages &&
|
|
602
|
+
target !== undefined &&
|
|
603
|
+
target.raw !== input.cursor,
|
|
604
|
+
cursor: target?.raw ?? null,
|
|
605
|
+
linkTarget: target,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
case undefined:
|
|
609
|
+
return { hasNext: false, cursor: null };
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function resolvePageSize(pagination, limit) {
|
|
613
|
+
if (pagination?.strategy === "offset" || pagination?.strategy === "page") {
|
|
614
|
+
return pagination.pageSize ?? limit;
|
|
615
|
+
}
|
|
616
|
+
return limit;
|
|
617
|
+
}
|
|
618
|
+
function syncPageSignature(pagination, records) {
|
|
619
|
+
if (records.length === 0 ||
|
|
620
|
+
(pagination?.strategy !== "offset" &&
|
|
621
|
+
pagination?.strategy !== "page" &&
|
|
622
|
+
pagination?.strategy !== "link-header")) {
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
return stableStringify(records);
|
|
626
|
+
}
|
|
627
|
+
function repeatedLinkHeaderTarget(pagination, target, cursor) {
|
|
628
|
+
if (pagination?.strategy !== "link-header" || !cursor) {
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
return target?.raw === cursor ? cursor : undefined;
|
|
632
|
+
}
|
|
633
|
+
function extractSyncRecords(data, mapping) {
|
|
634
|
+
if (!mapping.iterate) {
|
|
635
|
+
return data === undefined || data === null ? [] : [data];
|
|
636
|
+
}
|
|
637
|
+
if (Array.isArray(data)) {
|
|
638
|
+
return data;
|
|
639
|
+
}
|
|
640
|
+
if (!isRecord(data)) {
|
|
641
|
+
return [];
|
|
642
|
+
}
|
|
643
|
+
for (const key of ["items", "data", "results", "records"]) {
|
|
644
|
+
const value = data[key];
|
|
645
|
+
if (Array.isArray(value)) {
|
|
646
|
+
return value;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
function normalizeSyncRecord(value) {
|
|
652
|
+
return isRecord(value) ? value : { value };
|
|
653
|
+
}
|
|
654
|
+
function interpolateResourcePath(template, input) {
|
|
655
|
+
return interpolateBracedTemplate(interpolateTemplate(template, input, { strict: true }), input, "resource path");
|
|
656
|
+
}
|
|
657
|
+
function interpolateBracedTemplate(template, input, label) {
|
|
658
|
+
return template.replace(/\{([^}]+)\}/g, (_match, rawName) => {
|
|
659
|
+
const name = rawName.trim();
|
|
660
|
+
const value = readTemplateValue(input, name);
|
|
661
|
+
if (value === undefined || value === null || value === "") {
|
|
662
|
+
throw new Error(`Missing ${label} parameter "${name}"`);
|
|
663
|
+
}
|
|
664
|
+
return encodePathValue(value);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function resolveObjectId(record, cursorField, path) {
|
|
668
|
+
for (const field of ["objectId", "object_id", "id", "uuid", "key"]) {
|
|
669
|
+
const value = stringifyScalar(readTemplateValue(record, field));
|
|
670
|
+
if (value) {
|
|
671
|
+
return value;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (cursorField) {
|
|
675
|
+
const value = stringifyScalar(readTemplateValue(record, cursorField));
|
|
676
|
+
if (value) {
|
|
677
|
+
return value;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return path;
|
|
681
|
+
}
|
|
682
|
+
function checkpointCursor(checkpoint) {
|
|
683
|
+
return (stringifyScalar(checkpoint?.cursor) ??
|
|
684
|
+
stringifyScalar(checkpoint?.nextCursor));
|
|
685
|
+
}
|
|
686
|
+
function advanceWatermark(current, candidate) {
|
|
687
|
+
const value = stringifyScalar(candidate);
|
|
688
|
+
if (!value) {
|
|
689
|
+
return current;
|
|
690
|
+
}
|
|
691
|
+
if (!current) {
|
|
692
|
+
return value;
|
|
693
|
+
}
|
|
694
|
+
const currentNumber = Number(current);
|
|
695
|
+
const nextNumber = Number(value);
|
|
696
|
+
if (Number.isFinite(currentNumber) && Number.isFinite(nextNumber)) {
|
|
697
|
+
return nextNumber > currentNumber ? value : current;
|
|
698
|
+
}
|
|
699
|
+
return value > current ? value : current;
|
|
700
|
+
}
|
|
701
|
+
function parseNextLink(headers) {
|
|
702
|
+
const link = readHeader(headers, "link");
|
|
703
|
+
if (!link) {
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
for (const part of link.split(",")) {
|
|
707
|
+
const target = part.match(/<([^>]+)>/);
|
|
708
|
+
if (!target) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const params = part.slice((target.index ?? 0) + target[0].length).split(";");
|
|
712
|
+
if (params.some((param) => isNextLinkRelation(param))) {
|
|
713
|
+
return parseLinkTarget(target[1]);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
718
|
+
function isNextLinkRelation(value) {
|
|
719
|
+
const [rawName, rawValue] = value.split("=", 2);
|
|
720
|
+
if (rawName?.trim().toLowerCase() !== "rel") {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
return rawValue?.trim().replace(/^"|"$/g, "").toLowerCase() === "next";
|
|
724
|
+
}
|
|
725
|
+
function parseLinkTarget(value) {
|
|
726
|
+
try {
|
|
727
|
+
const url = new URL(value, "https://relayfile.local");
|
|
728
|
+
const query = {};
|
|
729
|
+
url.searchParams.forEach((item, key) => {
|
|
730
|
+
query[key] = item;
|
|
731
|
+
});
|
|
732
|
+
return {
|
|
733
|
+
raw: value,
|
|
734
|
+
baseUrl: value.startsWith("http") ? url.origin : undefined,
|
|
735
|
+
endpoint: url.pathname,
|
|
736
|
+
query,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
return { raw: value, endpoint: value, query: {} };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function stringifyQuery(input) {
|
|
744
|
+
const query = {};
|
|
745
|
+
if (!input) {
|
|
746
|
+
return query;
|
|
747
|
+
}
|
|
748
|
+
for (const [key, value] of Object.entries(input)) {
|
|
749
|
+
const scalar = stringifyScalar(value);
|
|
750
|
+
if (scalar !== undefined) {
|
|
751
|
+
query[key] = scalar;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return query;
|
|
755
|
+
}
|
|
756
|
+
function stringifyScalar(value) {
|
|
757
|
+
if (value === undefined || value === null) {
|
|
758
|
+
return undefined;
|
|
759
|
+
}
|
|
760
|
+
if (typeof value === "string") {
|
|
761
|
+
return value;
|
|
762
|
+
}
|
|
763
|
+
if (typeof value === "number" ||
|
|
764
|
+
typeof value === "boolean" ||
|
|
765
|
+
typeof value === "bigint") {
|
|
766
|
+
return String(value);
|
|
767
|
+
}
|
|
768
|
+
if (value instanceof Date) {
|
|
769
|
+
return value.toISOString();
|
|
770
|
+
}
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
function encodePathValue(value) {
|
|
774
|
+
if (Array.isArray(value)) {
|
|
775
|
+
return value.map((item) => encodePathValue(item)).join("/");
|
|
776
|
+
}
|
|
777
|
+
return encodeURIComponent(stringifyScalar(value) ?? JSON.stringify(value));
|
|
778
|
+
}
|
|
779
|
+
function syncCheckpointPath(adapterName, resourceName, checkpointKey, inputScope) {
|
|
780
|
+
return [
|
|
781
|
+
".sync-state",
|
|
782
|
+
encodeCheckpointSegment(adapterName),
|
|
783
|
+
encodeCheckpointSegment(resourceName),
|
|
784
|
+
`${encodeCheckpointSegment(checkpointKey)}-${inputScope}.json`,
|
|
785
|
+
].join("/");
|
|
786
|
+
}
|
|
787
|
+
function checkpointScope(input) {
|
|
788
|
+
return hashString(stableStringify(input));
|
|
789
|
+
}
|
|
790
|
+
function encodeCheckpointSegment(value) {
|
|
791
|
+
return encodeURIComponent(value);
|
|
792
|
+
}
|
|
793
|
+
function stableStringify(value) {
|
|
794
|
+
if (Array.isArray(value)) {
|
|
795
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
796
|
+
}
|
|
797
|
+
if (isRecord(value)) {
|
|
798
|
+
return `{${Object.keys(value)
|
|
799
|
+
.sort()
|
|
800
|
+
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
|
|
801
|
+
.join(",")}}`;
|
|
802
|
+
}
|
|
803
|
+
return JSON.stringify(value) ?? "undefined";
|
|
804
|
+
}
|
|
805
|
+
function hashString(value) {
|
|
806
|
+
let hash = 0x811c9dc5;
|
|
807
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
808
|
+
hash ^= value.charCodeAt(index);
|
|
809
|
+
hash = Math.imul(hash, 0x01000193);
|
|
810
|
+
}
|
|
811
|
+
return (hash >>> 0).toString(36);
|
|
812
|
+
}
|
|
813
|
+
function readHeader(headers, name) {
|
|
814
|
+
if (!headers) {
|
|
815
|
+
return undefined;
|
|
816
|
+
}
|
|
817
|
+
const target = name.toLowerCase();
|
|
818
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
819
|
+
if (key.toLowerCase() === target) {
|
|
820
|
+
return value;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
function readPositiveInteger(value) {
|
|
826
|
+
const number = Number(value);
|
|
827
|
+
if (!Number.isInteger(number) || number < 1) {
|
|
828
|
+
return undefined;
|
|
829
|
+
}
|
|
830
|
+
return number;
|
|
831
|
+
}
|
|
832
|
+
function readNonNegativeInteger(value) {
|
|
833
|
+
const number = Number(value);
|
|
834
|
+
if (!Number.isInteger(number) || number < 0) {
|
|
835
|
+
return undefined;
|
|
836
|
+
}
|
|
837
|
+
return number;
|
|
838
|
+
}
|
|
839
|
+
function throwIfAborted(signal) {
|
|
840
|
+
if (!signal?.aborted) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const error = new Error("The operation was aborted.");
|
|
844
|
+
error.name = "AbortError";
|
|
845
|
+
throw error;
|
|
846
|
+
}
|
|
847
|
+
function isAbortError(error) {
|
|
848
|
+
return (typeof error === "object" &&
|
|
849
|
+
error !== null &&
|
|
850
|
+
"name" in error &&
|
|
851
|
+
error.name === "AbortError");
|
|
852
|
+
}
|
|
853
|
+
function isRecord(value) {
|
|
854
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
855
|
+
}
|
|
856
|
+
function escapeRegex(value) {
|
|
857
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
858
|
+
}
|
|
859
|
+
function safeJsonParse(value) {
|
|
860
|
+
try {
|
|
861
|
+
return JSON.parse(value);
|
|
862
|
+
}
|
|
863
|
+
catch {
|
|
864
|
+
return undefined;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function readString(value) {
|
|
868
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
869
|
+
}
|
|
870
|
+
const SYNC_OPTION_KEYS = new Set([
|
|
871
|
+
"connectionId",
|
|
872
|
+
"cursor",
|
|
873
|
+
"input",
|
|
874
|
+
"limit",
|
|
875
|
+
"maxPages",
|
|
876
|
+
"params",
|
|
877
|
+
"query",
|
|
878
|
+
"resourceName",
|
|
879
|
+
"resume",
|
|
880
|
+
"signal",
|
|
881
|
+
"since",
|
|
882
|
+
"sinceParamName",
|
|
883
|
+
"watermark",
|
|
884
|
+
"watermarkParamName",
|
|
885
|
+
"workspaceId",
|
|
886
|
+
]);
|
|
887
|
+
//# sourceMappingURL=schema-adapter.js.map
|