@absolutejs/sync 1.7.7 → 1.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/drizzle/index.js +15 -1
- package/dist/adapters/drizzle/index.js.map +2 -2
- package/dist/adapters/mysql/index.js +15 -1
- package/dist/adapters/mysql/index.js.map +2 -2
- package/dist/adapters/postgres/index.js +15 -1
- package/dist/adapters/postgres/index.js.map +2 -2
- package/dist/adapters/prisma/index.js +15 -1
- package/dist/adapters/prisma/index.js.map +2 -2
- package/dist/adapters/sqlite/index.js +15 -1
- package/dist/adapters/sqlite/index.js.map +2 -2
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +77 -8
- package/dist/engine/index.js.map +4 -4
- package/dist/engine/sandbox.d.ts +71 -10
- package/dist/engine/syncEngine.d.ts +16 -1
- package/dist/index.js +77 -8
- package/dist/index.js.map +4 -4
- package/dist/mcp/index.d.ts +10 -0
- package/dist/mcp/index.js +14387 -0
- package/dist/mcp/index.js.map +84 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/scheduled.js +15 -1
- package/dist/scheduled.js.map +2 -2
- package/package.json +11 -1
package/dist/engine/sandbox.d.ts
CHANGED
|
@@ -91,6 +91,66 @@ export type HandlerMetricsRecord = {
|
|
|
91
91
|
* crashes must NOT also crash the caller's mutation path).
|
|
92
92
|
*/
|
|
93
93
|
export type HandlerMetricsHook = (record: HandlerMetricsRecord) => void | Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Per-host configuration for an entry in {@link BridgeFetchConfig}.
|
|
96
|
+
* Auth is computed on the host side per call; the secret never enters
|
|
97
|
+
* the sandbox's JSC heap.
|
|
98
|
+
*/
|
|
99
|
+
export type BridgeFetchEndpoint = {
|
|
100
|
+
/**
|
|
101
|
+
* Header values to add to every request to this host. Static — read
|
|
102
|
+
* once at engine construction. Use {@link authorization} for tokens
|
|
103
|
+
* that need to be computed per call.
|
|
104
|
+
*/
|
|
105
|
+
headers?: Record<string, string>;
|
|
106
|
+
/**
|
|
107
|
+
* Compute the `Authorization` header value on each call. Synchronous
|
|
108
|
+
* or async. Throwing rejects the in-sandbox call without revealing
|
|
109
|
+
* the underlying error (the sandbox sees a generic
|
|
110
|
+
* "authorization callback failed").
|
|
111
|
+
*/
|
|
112
|
+
authorization?: () => string | Promise<string>;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* `actions.fetch(url, init)` allowlist + auth-injection config keyed by
|
|
116
|
+
* hostname. A request whose URL parses to a hostname NOT in this map is
|
|
117
|
+
* rejected before any network call. A request whose hostname IS in the
|
|
118
|
+
* map gets the configured static headers + the computed authorization
|
|
119
|
+
* stitched in on the host side. The sandbox source never sees the auth
|
|
120
|
+
* value.
|
|
121
|
+
*
|
|
122
|
+
* Hostname keys are exact (`'api.example.com'`). The special key `'*'`
|
|
123
|
+
* is a wildcard (use sparingly — it disables allowlisting).
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* createSyncEngine({
|
|
127
|
+
* bridgeFetch: {
|
|
128
|
+
* 'api.stripe.com': {
|
|
129
|
+
* authorization: () => `Bearer ${process.env.STRIPE_KEY}`,
|
|
130
|
+
* },
|
|
131
|
+
* 'api.openai.com': {
|
|
132
|
+
* authorization: () => `Bearer ${process.env.OPENAI_KEY}`,
|
|
133
|
+
* headers: { 'OpenAI-Beta': 'assistants=v2' },
|
|
134
|
+
* },
|
|
135
|
+
* },
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export type BridgeFetchConfig = Record<string, BridgeFetchEndpoint>;
|
|
140
|
+
/**
|
|
141
|
+
* Response shape `actions.fetch` resolves to inside the sandbox. The
|
|
142
|
+
* body is materialised as text on the host (so it crosses the JSC
|
|
143
|
+
* boundary as a structured-cloned string). Users parse it themselves
|
|
144
|
+
* with `JSON.parse(res.body)` for JSON responses.
|
|
145
|
+
*/
|
|
146
|
+
export type BridgeFetchResponse = {
|
|
147
|
+
ok: boolean;
|
|
148
|
+
status: number;
|
|
149
|
+
statusText: string;
|
|
150
|
+
url: string;
|
|
151
|
+
headers: Record<string, string>;
|
|
152
|
+
body: string;
|
|
153
|
+
};
|
|
94
154
|
/** Per-mutation sandbox configuration. */
|
|
95
155
|
export type SandboxConfig = {
|
|
96
156
|
/** Heap memory cap (MB). Default 32. */
|
|
@@ -129,15 +189,16 @@ export type SandboxConfig = {
|
|
|
129
189
|
*/
|
|
130
190
|
export declare const makeSandboxedHandler: (source: string, config?: SandboxConfig,
|
|
131
191
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* `mutationName` only matters when `onMetrics` is set — it's the
|
|
138
|
-
* `mutationName` field of the emitted record.
|
|
192
|
+
* Engine-level extras the per-mutation config doesn't carry:
|
|
193
|
+
* - `metricsHook` enables per-call telemetry via
|
|
194
|
+
* `callable.callWithMetrics` (small cost; off without the hook).
|
|
195
|
+
* - `bridgeFetch` enables `actions.fetch(url, init)` inside the
|
|
196
|
+
* sandbox with host-side allowlist + auth injection.
|
|
139
197
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
198
|
+
engineExtras?: {
|
|
199
|
+
metricsHook?: {
|
|
200
|
+
mutationName: string;
|
|
201
|
+
onMetrics: HandlerMetricsHook;
|
|
202
|
+
};
|
|
203
|
+
bridgeFetch?: BridgeFetchConfig;
|
|
143
204
|
}) => ((args: unknown, ctx: unknown, actions: MutationActions) => Promise<unknown>);
|
|
@@ -2,7 +2,7 @@ import type { CollectionContext, CollectionDefinition, JoinCollectionDefinition
|
|
|
2
2
|
import type { GraphCollectionDefinition } from './graph';
|
|
3
3
|
import type { MutationDefinition, TableWriter, TransactionRunner } from './mutation';
|
|
4
4
|
import type { ReactiveQueryDefinition, TableReader } from './reactive';
|
|
5
|
-
import { type HandlerMetricsHook } from './sandbox';
|
|
5
|
+
import { type BridgeFetchConfig, type HandlerMetricsHook } from './sandbox';
|
|
6
6
|
import type { PermissionsDefinition, TablePermissions } from './permissions';
|
|
7
7
|
import type { SearchCollectionDefinition } from './search';
|
|
8
8
|
import type { ScheduleDefinition } from './schedule';
|
|
@@ -326,6 +326,21 @@ export type SyncEngineOptions = {
|
|
|
326
326
|
* @see {@link HandlerMetricsRecord}
|
|
327
327
|
*/
|
|
328
328
|
handlerMetrics?: HandlerMetricsHook;
|
|
329
|
+
/**
|
|
330
|
+
* Allowlist + auth-injection map for `actions.fetch(url, init)` calls
|
|
331
|
+
* issued from inside a `sandboxedHandler`. Each entry is keyed by
|
|
332
|
+
* hostname (`'api.stripe.com'`); the value's `authorization` is a
|
|
333
|
+
* sync or async callback computed on the host so the secret never
|
|
334
|
+
* crosses into the JSC sandbox. Requests to non-allowlisted hosts
|
|
335
|
+
* are rejected before any network call.
|
|
336
|
+
*
|
|
337
|
+
* Without this set, `actions.fetch` throws "no bridgeFetch config."
|
|
338
|
+
* Plain (non-sandboxed) handlers don't use this — they can just call
|
|
339
|
+
* `fetch` directly since they run in the host process.
|
|
340
|
+
*
|
|
341
|
+
* @see {@link BridgeFetchConfig}
|
|
342
|
+
*/
|
|
343
|
+
bridgeFetch?: BridgeFetchConfig;
|
|
329
344
|
};
|
|
330
345
|
/**
|
|
331
346
|
* The Tier 3 sync engine: a registry of collections plus the view syncer. It is
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
2
16
|
var __require = import.meta.require;
|
|
3
17
|
|
|
4
18
|
// src/writeBehindCache.ts
|
|
@@ -738,12 +752,13 @@ var wrap = (source) => `
|
|
|
738
752
|
update: (table, data) => __dispatch(__callId, 'update', table, data),
|
|
739
753
|
delete: (table, row) => __dispatch(__callId, 'delete', table, row),
|
|
740
754
|
change: (collection, change) => __dispatch(__callId, 'change', collection, change),
|
|
741
|
-
now: () => __dispatch(__callId, 'now')
|
|
755
|
+
now: () => __dispatch(__callId, 'now'),
|
|
756
|
+
fetch: (url, init) => __dispatch(__callId, 'fetch', url, init)
|
|
742
757
|
};
|
|
743
758
|
return userFn(args, ctx, actions);
|
|
744
759
|
}
|
|
745
760
|
`;
|
|
746
|
-
var compile = async (source, config) => {
|
|
761
|
+
var compile = async (source, config, bridgeFetch) => {
|
|
747
762
|
const { createIsolate, Reference } = await loadIsolatedJsc();
|
|
748
763
|
const isolate = await createIsolate({
|
|
749
764
|
backend: config.backend ?? "auto",
|
|
@@ -767,6 +782,8 @@ var compile = async (source, config) => {
|
|
|
767
782
|
return a.change(rest[0], rest[1]);
|
|
768
783
|
case "now":
|
|
769
784
|
return a.now();
|
|
785
|
+
case "fetch":
|
|
786
|
+
return runBridgeFetch(bridgeFetch, rest[0], rest[1]);
|
|
770
787
|
default:
|
|
771
788
|
throw new Error(`unknown sandbox action op: ${String(op)}`);
|
|
772
789
|
}
|
|
@@ -782,8 +799,57 @@ var compile = async (source, config) => {
|
|
|
782
799
|
timeoutMs: config.timeout ?? 5000
|
|
783
800
|
};
|
|
784
801
|
};
|
|
785
|
-
var
|
|
802
|
+
var runBridgeFetch = async (config, url, init) => {
|
|
803
|
+
if (config === undefined) {
|
|
804
|
+
throw new Error("actions.fetch called but the engine has no `bridgeFetch` config \u2014 " + "pass `bridgeFetch: { ... }` to createSyncEngine.");
|
|
805
|
+
}
|
|
806
|
+
let parsed;
|
|
807
|
+
try {
|
|
808
|
+
parsed = new URL(url);
|
|
809
|
+
} catch {
|
|
810
|
+
throw new Error(`actions.fetch: invalid URL "${String(url)}"`);
|
|
811
|
+
}
|
|
812
|
+
const endpoint = config[parsed.hostname] ?? (Object.prototype.hasOwnProperty.call(config, "*") ? config["*"] : undefined);
|
|
813
|
+
if (endpoint === undefined) {
|
|
814
|
+
throw new Error(`actions.fetch: hostname "${parsed.hostname}" is not allowlisted in bridgeFetch config`);
|
|
815
|
+
}
|
|
816
|
+
const headers = { ...endpoint.headers ?? {} };
|
|
817
|
+
if (init?.headers !== undefined) {
|
|
818
|
+
const incoming = init.headers;
|
|
819
|
+
for (const [name, value] of Object.entries(incoming)) {
|
|
820
|
+
if (name.toLowerCase() === "authorization")
|
|
821
|
+
continue;
|
|
822
|
+
headers[name] = value;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (endpoint.authorization !== undefined) {
|
|
826
|
+
let auth;
|
|
827
|
+
try {
|
|
828
|
+
auth = await endpoint.authorization();
|
|
829
|
+
} catch {
|
|
830
|
+
throw new Error("actions.fetch: authorization callback failed");
|
|
831
|
+
}
|
|
832
|
+
headers.Authorization = auth;
|
|
833
|
+
}
|
|
834
|
+
const response = await fetch(url, { ...init, headers });
|
|
835
|
+
const responseHeaders = {};
|
|
836
|
+
response.headers.forEach((value, name) => {
|
|
837
|
+
responseHeaders[name] = value;
|
|
838
|
+
});
|
|
839
|
+
const body = await response.text();
|
|
840
|
+
return {
|
|
841
|
+
body,
|
|
842
|
+
headers: responseHeaders,
|
|
843
|
+
ok: response.ok,
|
|
844
|
+
status: response.status,
|
|
845
|
+
statusText: response.statusText,
|
|
846
|
+
url: response.url
|
|
847
|
+
};
|
|
848
|
+
};
|
|
849
|
+
var makeSandboxedHandler = (source, config = {}, engineExtras) => {
|
|
786
850
|
let pending;
|
|
851
|
+
const metricsHook = engineExtras?.metricsHook;
|
|
852
|
+
const bridgeFetch = engineExtras?.bridgeFetch;
|
|
787
853
|
const getCompiled = async () => {
|
|
788
854
|
if (pending !== undefined) {
|
|
789
855
|
const compiled = await pending;
|
|
@@ -791,7 +857,7 @@ var makeSandboxedHandler = (source, config = {}, metricsHook) => {
|
|
|
791
857
|
return compiled;
|
|
792
858
|
pending = undefined;
|
|
793
859
|
}
|
|
794
|
-
pending = compile(source, config);
|
|
860
|
+
pending = compile(source, config, bridgeFetch);
|
|
795
861
|
return pending;
|
|
796
862
|
};
|
|
797
863
|
return async (args, ctx, actions) => {
|
|
@@ -1847,9 +1913,12 @@ var createSyncEngine = (options = {}) => {
|
|
|
1847
1913
|
}
|
|
1848
1914
|
mutations.set(mutation.name, mutation);
|
|
1849
1915
|
if (mutation.sandboxedHandler !== undefined) {
|
|
1850
|
-
sandboxRunners.set(mutation.name, makeSandboxedHandler(mutation.sandboxedHandler, mutation.sandbox,
|
|
1851
|
-
|
|
1852
|
-
|
|
1916
|
+
sandboxRunners.set(mutation.name, makeSandboxedHandler(mutation.sandboxedHandler, mutation.sandbox, {
|
|
1917
|
+
bridgeFetch: options.bridgeFetch,
|
|
1918
|
+
metricsHook: options.handlerMetrics === undefined ? undefined : {
|
|
1919
|
+
mutationName: mutation.name,
|
|
1920
|
+
onMetrics: options.handlerMetrics
|
|
1921
|
+
}
|
|
1853
1922
|
}));
|
|
1854
1923
|
}
|
|
1855
1924
|
},
|
|
@@ -2374,5 +2443,5 @@ export {
|
|
|
2374
2443
|
createPresenceHub
|
|
2375
2444
|
};
|
|
2376
2445
|
|
|
2377
|
-
//# debugId=
|
|
2446
|
+
//# debugId=6D7E2B1F3EF1229664756E2164756E21
|
|
2378
2447
|
//# sourceMappingURL=index.js.map
|