@absolutejs/sync 1.9.2 → 1.10.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/dist/codeMode.d.ts +130 -0
- package/dist/codeMode.js +52 -0
- package/dist/codeMode.js.map +10 -0
- package/package.json +6 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@absolutejs/sync/code-mode` — wraps engine mutations as Code Mode host
|
|
3
|
+
* tools.
|
|
4
|
+
*
|
|
5
|
+
* The Code Mode pattern (Cloudflare Dynamic Workers / Anthropic
|
|
6
|
+
* programmatic tool calling, both ~2026) replaces "N tool calls per
|
|
7
|
+
* turn" with "1 tool call whose body is JS that chains the underlying
|
|
8
|
+
* fns." Sync's contribution: the engine's mutation surface is the
|
|
9
|
+
* underlying fns. The model writes
|
|
10
|
+
*
|
|
11
|
+
* ```js
|
|
12
|
+
* const c = await runMutation('comments:create', { body: '@bob …', resourceId });
|
|
13
|
+
* await runMutation('comments:toggleReaction', { commentId: c.id, emoji: '👍' });
|
|
14
|
+
* return c.id;
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* and three would-be tool turns collapse into one — only the final
|
|
18
|
+
* `return` enters the conversation context.
|
|
19
|
+
*
|
|
20
|
+
* The factory here returns a **host-tool map** that's shape-compatible
|
|
21
|
+
* with `@absolutejs/ai`'s `codeModeTool({ tools })` option. We don't
|
|
22
|
+
* import `@absolutejs/ai` because the consumer is responsible for
|
|
23
|
+
* wiring the two; sync stays decoupled from the AI SDK.
|
|
24
|
+
*
|
|
25
|
+
* ## v0.1 semantics (read carefully)
|
|
26
|
+
*
|
|
27
|
+
* - Each `runMutation` call runs in its own DB transaction (the
|
|
28
|
+
* engine's per-call retry/transaction wrapper). If mutation 3/5
|
|
29
|
+
* fails, mutations 1–2 are already committed; the model receives
|
|
30
|
+
* the error and decides whether to compensate (e.g. by calling a
|
|
31
|
+
* delete mutation).
|
|
32
|
+
* - Cross-mutation atomicity (all-or-nothing across N runMutations) is
|
|
33
|
+
* NOT provided here — it would need a new engine batch primitive.
|
|
34
|
+
* That's a deliberate v0.2 followup; this v0.1 ships the integration
|
|
35
|
+
* without lying about transactional semantics.
|
|
36
|
+
* - The host-side `ctx` factory runs ONCE per `run_mutations` tool
|
|
37
|
+
* call; the same ctx is threaded through every mutation in that
|
|
38
|
+
* script. This matches the "one model turn ≈ one user request"
|
|
39
|
+
* shape, where the user identity doesn't change mid-script.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { engineMutationsAsHostTools } from '@absolutejs/sync/code-mode';
|
|
44
|
+
* import { codeModeTool } from '@absolutejs/ai/tools';
|
|
45
|
+
*
|
|
46
|
+
* const sessionCtx = () => ({ userId: currentSessionUserId() });
|
|
47
|
+
* const hostTools = engineMutationsAsHostTools({
|
|
48
|
+
* ctx: sessionCtx,
|
|
49
|
+
* engine,
|
|
50
|
+
* mutations: [
|
|
51
|
+
* {
|
|
52
|
+
* description: 'Post a comment on a resource.',
|
|
53
|
+
* name: 'comments:create',
|
|
54
|
+
* tsSignature:
|
|
55
|
+
* '(args: { resourceId: string; body: string }) => ' +
|
|
56
|
+
* 'Promise<{ id: string; authorId: string; body: string }>',
|
|
57
|
+
* },
|
|
58
|
+
* {
|
|
59
|
+
* description: 'Toggle a reaction emoji on a comment.',
|
|
60
|
+
* name: 'comments:toggleReaction',
|
|
61
|
+
* tsSignature:
|
|
62
|
+
* '(args: { commentId: string; emoji: string }) => ' +
|
|
63
|
+
* 'Promise<{ added: boolean }>',
|
|
64
|
+
* },
|
|
65
|
+
* ],
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* const aiTool = codeModeTool({ tools: hostTools });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
import type { SyncEngine } from './engine/syncEngine';
|
|
72
|
+
/** One engine mutation surfaced to the model as a host function. */
|
|
73
|
+
export type MutationToolDescriptor = {
|
|
74
|
+
/** Engine-registered mutation name (e.g. `'comments:create'`). */
|
|
75
|
+
name: string;
|
|
76
|
+
/**
|
|
77
|
+
* One-line description shown to the model in the Code Mode prompt.
|
|
78
|
+
* Should describe *what the mutation does*, not just its inputs.
|
|
79
|
+
*/
|
|
80
|
+
description: string;
|
|
81
|
+
/**
|
|
82
|
+
* TypeScript signature shown to the model. Default
|
|
83
|
+
* `'(args: any) => Promise<any>'` — provide a real signature so the
|
|
84
|
+
* model knows the input shape.
|
|
85
|
+
*/
|
|
86
|
+
tsSignature?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Override the host-fn name surfaced to the model. Default: the
|
|
89
|
+
* mutation `name` with `:` replaced by `_` (engine names are not
|
|
90
|
+
* valid JS identifiers). For `'comments:create'` the model sees
|
|
91
|
+
* `comments_create` by default.
|
|
92
|
+
*/
|
|
93
|
+
hostFnName?: string;
|
|
94
|
+
};
|
|
95
|
+
/** Options for {@link engineMutationsAsHostTools}. */
|
|
96
|
+
export type EngineMutationsAsHostToolsOptions<Ctx> = {
|
|
97
|
+
/** The engine whose mutations should be exposed. */
|
|
98
|
+
engine: SyncEngine;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the per-call ctx — invoked ONCE at the start of each
|
|
101
|
+
* Code Mode `run_mutations` call and threaded through every
|
|
102
|
+
* mutation in that script. Typically returns the current
|
|
103
|
+
* session/user identity. May be sync or async.
|
|
104
|
+
*/
|
|
105
|
+
ctx: () => Ctx | Promise<Ctx>;
|
|
106
|
+
/** The mutation set to expose. */
|
|
107
|
+
mutations: MutationToolDescriptor[];
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Shape of one entry in the host-tool map. Mirrors
|
|
111
|
+
* `@absolutejs/ai/tools`'s `CodeModeHostTool` exactly — no import
|
|
112
|
+
* needed; the AI SDK consumes any record with this shape.
|
|
113
|
+
*/
|
|
114
|
+
export type CodeModeHostTool = {
|
|
115
|
+
description: string;
|
|
116
|
+
tsSignature: string;
|
|
117
|
+
handler: (...args: unknown[]) => unknown;
|
|
118
|
+
};
|
|
119
|
+
/** Map of model-visible host-fn name → host tool. */
|
|
120
|
+
export type CodeModeHostToolMap = Record<string, CodeModeHostTool>;
|
|
121
|
+
/**
|
|
122
|
+
* Build a host-tool map that exposes the engine's mutation surface to a
|
|
123
|
+
* Code Mode tool. Pass the result to `codeModeTool({ tools })` from
|
|
124
|
+
* `@absolutejs/ai/tools`.
|
|
125
|
+
*
|
|
126
|
+
* The function throws synchronously at build time if any descriptor
|
|
127
|
+
* names a mutation that isn't registered on the engine, so a typo'd
|
|
128
|
+
* allowlist surfaces at boot, not at the first model call.
|
|
129
|
+
*/
|
|
130
|
+
export declare const engineMutationsAsHostTools: <Ctx>(options: EngineMutationsAsHostToolsOptions<Ctx>) => CodeModeHostToolMap;
|
package/dist/codeMode.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
};
|
|
16
|
+
var __require = import.meta.require;
|
|
17
|
+
|
|
18
|
+
// src/codeMode.ts
|
|
19
|
+
var DEFAULT_TS_SIGNATURE = "(args: any) => Promise<any>";
|
|
20
|
+
var defaultHostFnName = (mutationName) => mutationName.replace(/[^A-Za-z0-9_]/g, "_");
|
|
21
|
+
var engineMutationsAsHostTools = (options) => {
|
|
22
|
+
const { engine, ctx, mutations } = options;
|
|
23
|
+
const registered = new Set(engine.inspect().mutations);
|
|
24
|
+
const map = {};
|
|
25
|
+
const seenHostFnNames = new Set;
|
|
26
|
+
for (const descriptor of mutations) {
|
|
27
|
+
if (!registered.has(descriptor.name)) {
|
|
28
|
+
throw new Error(`engineMutationsAsHostTools: mutation "${descriptor.name}" is not registered on the engine. ` + `Register it first, or remove it from the mutations list.`);
|
|
29
|
+
}
|
|
30
|
+
const hostFnName = descriptor.hostFnName ?? defaultHostFnName(descriptor.name);
|
|
31
|
+
if (seenHostFnNames.has(hostFnName)) {
|
|
32
|
+
throw new Error(`engineMutationsAsHostTools: duplicate host-fn name "${hostFnName}". ` + `Two mutations map to the same identifier \u2014 set hostFnName explicitly on one of them.`);
|
|
33
|
+
}
|
|
34
|
+
seenHostFnNames.add(hostFnName);
|
|
35
|
+
map[hostFnName] = {
|
|
36
|
+
description: descriptor.description,
|
|
37
|
+
handler: async (...args) => {
|
|
38
|
+
const payload = args[0];
|
|
39
|
+
const resolvedCtx = await ctx();
|
|
40
|
+
return engine.runMutation(descriptor.name, payload, resolvedCtx);
|
|
41
|
+
},
|
|
42
|
+
tsSignature: descriptor.tsSignature ?? DEFAULT_TS_SIGNATURE
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return map;
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
engineMutationsAsHostTools
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//# debugId=F24C2874EAF571E664756E2164756E21
|
|
52
|
+
//# sourceMappingURL=codeMode.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/codeMode.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `@absolutejs/sync/code-mode` — wraps engine mutations as Code Mode host\n * tools.\n *\n * The Code Mode pattern (Cloudflare Dynamic Workers / Anthropic\n * programmatic tool calling, both ~2026) replaces \"N tool calls per\n * turn\" with \"1 tool call whose body is JS that chains the underlying\n * fns.\" Sync's contribution: the engine's mutation surface is the\n * underlying fns. The model writes\n *\n * ```js\n * const c = await runMutation('comments:create', { body: '@bob …', resourceId });\n * await runMutation('comments:toggleReaction', { commentId: c.id, emoji: '👍' });\n * return c.id;\n * ```\n *\n * and three would-be tool turns collapse into one — only the final\n * `return` enters the conversation context.\n *\n * The factory here returns a **host-tool map** that's shape-compatible\n * with `@absolutejs/ai`'s `codeModeTool({ tools })` option. We don't\n * import `@absolutejs/ai` because the consumer is responsible for\n * wiring the two; sync stays decoupled from the AI SDK.\n *\n * ## v0.1 semantics (read carefully)\n *\n * - Each `runMutation` call runs in its own DB transaction (the\n * engine's per-call retry/transaction wrapper). If mutation 3/5\n * fails, mutations 1–2 are already committed; the model receives\n * the error and decides whether to compensate (e.g. by calling a\n * delete mutation).\n * - Cross-mutation atomicity (all-or-nothing across N runMutations) is\n * NOT provided here — it would need a new engine batch primitive.\n * That's a deliberate v0.2 followup; this v0.1 ships the integration\n * without lying about transactional semantics.\n * - The host-side `ctx` factory runs ONCE per `run_mutations` tool\n * call; the same ctx is threaded through every mutation in that\n * script. This matches the \"one model turn ≈ one user request\"\n * shape, where the user identity doesn't change mid-script.\n *\n * @example\n * ```ts\n * import { engineMutationsAsHostTools } from '@absolutejs/sync/code-mode';\n * import { codeModeTool } from '@absolutejs/ai/tools';\n *\n * const sessionCtx = () => ({ userId: currentSessionUserId() });\n * const hostTools = engineMutationsAsHostTools({\n * ctx: sessionCtx,\n * engine,\n * mutations: [\n * {\n * description: 'Post a comment on a resource.',\n * name: 'comments:create',\n * tsSignature:\n * '(args: { resourceId: string; body: string }) => ' +\n * 'Promise<{ id: string; authorId: string; body: string }>',\n * },\n * {\n * description: 'Toggle a reaction emoji on a comment.',\n * name: 'comments:toggleReaction',\n * tsSignature:\n * '(args: { commentId: string; emoji: string }) => ' +\n * 'Promise<{ added: boolean }>',\n * },\n * ],\n * });\n *\n * const aiTool = codeModeTool({ tools: hostTools });\n * ```\n */\n\nimport type { SyncEngine } from './engine/syncEngine';\n\n/** One engine mutation surfaced to the model as a host function. */\nexport type MutationToolDescriptor = {\n\t/** Engine-registered mutation name (e.g. `'comments:create'`). */\n\tname: string;\n\t/**\n\t * One-line description shown to the model in the Code Mode prompt.\n\t * Should describe *what the mutation does*, not just its inputs.\n\t */\n\tdescription: string;\n\t/**\n\t * TypeScript signature shown to the model. Default\n\t * `'(args: any) => Promise<any>'` — provide a real signature so the\n\t * model knows the input shape.\n\t */\n\ttsSignature?: string;\n\t/**\n\t * Override the host-fn name surfaced to the model. Default: the\n\t * mutation `name` with `:` replaced by `_` (engine names are not\n\t * valid JS identifiers). For `'comments:create'` the model sees\n\t * `comments_create` by default.\n\t */\n\thostFnName?: string;\n};\n\n/** Options for {@link engineMutationsAsHostTools}. */\nexport type EngineMutationsAsHostToolsOptions<Ctx> = {\n\t/** The engine whose mutations should be exposed. */\n\tengine: SyncEngine;\n\t/**\n\t * Resolve the per-call ctx — invoked ONCE at the start of each\n\t * Code Mode `run_mutations` call and threaded through every\n\t * mutation in that script. Typically returns the current\n\t * session/user identity. May be sync or async.\n\t */\n\tctx: () => Ctx | Promise<Ctx>;\n\t/** The mutation set to expose. */\n\tmutations: MutationToolDescriptor[];\n};\n\n/**\n * Shape of one entry in the host-tool map. Mirrors\n * `@absolutejs/ai/tools`'s `CodeModeHostTool` exactly — no import\n * needed; the AI SDK consumes any record with this shape.\n */\nexport type CodeModeHostTool = {\n\tdescription: string;\n\ttsSignature: string;\n\thandler: (...args: unknown[]) => unknown;\n};\n\n/** Map of model-visible host-fn name → host tool. */\nexport type CodeModeHostToolMap = Record<string, CodeModeHostTool>;\n\nconst DEFAULT_TS_SIGNATURE = '(args: any) => Promise<any>';\n\nconst defaultHostFnName = (mutationName: string): string =>\n\tmutationName.replace(/[^A-Za-z0-9_]/g, '_');\n\n/**\n * Build a host-tool map that exposes the engine's mutation surface to a\n * Code Mode tool. Pass the result to `codeModeTool({ tools })` from\n * `@absolutejs/ai/tools`.\n *\n * The function throws synchronously at build time if any descriptor\n * names a mutation that isn't registered on the engine, so a typo'd\n * allowlist surfaces at boot, not at the first model call.\n */\nexport const engineMutationsAsHostTools = <Ctx>(\n\toptions: EngineMutationsAsHostToolsOptions<Ctx>\n): CodeModeHostToolMap => {\n\tconst { engine, ctx, mutations } = options;\n\tconst registered = new Set(engine.inspect().mutations);\n\tconst map: CodeModeHostToolMap = {};\n\tconst seenHostFnNames = new Set<string>();\n\n\tfor (const descriptor of mutations) {\n\t\tif (!registered.has(descriptor.name)) {\n\t\t\tthrow new Error(\n\t\t\t\t`engineMutationsAsHostTools: mutation \"${descriptor.name}\" is not registered on the engine. ` +\n\t\t\t\t\t`Register it first, or remove it from the mutations list.`\n\t\t\t);\n\t\t}\n\t\tconst hostFnName =\n\t\t\tdescriptor.hostFnName ?? defaultHostFnName(descriptor.name);\n\t\tif (seenHostFnNames.has(hostFnName)) {\n\t\t\tthrow new Error(\n\t\t\t\t`engineMutationsAsHostTools: duplicate host-fn name \"${hostFnName}\". ` +\n\t\t\t\t\t`Two mutations map to the same identifier — set hostFnName explicitly on one of them.`\n\t\t\t);\n\t\t}\n\t\tseenHostFnNames.add(hostFnName);\n\n\t\tmap[hostFnName] = {\n\t\t\tdescription: descriptor.description,\n\t\t\thandler: async (...args: unknown[]): Promise<unknown> => {\n\t\t\t\t// Code Mode passes positional args from the model's JS call.\n\t\t\t\t// Engine mutations take a single `args` value — first\n\t\t\t\t// positional arg is the mutation payload.\n\t\t\t\tconst payload = args[0];\n\t\t\t\tconst resolvedCtx = await ctx();\n\t\t\t\treturn engine.runMutation(\n\t\t\t\t\tdescriptor.name,\n\t\t\t\t\tpayload,\n\t\t\t\t\tresolvedCtx\n\t\t\t\t);\n\t\t\t},\n\t\t\ttsSignature: descriptor.tsSignature ?? DEFAULT_TS_SIGNATURE\n\t\t};\n\t}\n\n\treturn map;\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AA8HA,IAAM,uBAAuB;AAE7B,IAAM,oBAAoB,CAAC,iBAC1B,aAAa,QAAQ,kBAAkB,GAAG;AAWpC,IAAM,6BAA6B,CACzC,YACyB;AAAA,EACzB,QAAQ,QAAQ,KAAK,cAAc;AAAA,EACnC,MAAM,aAAa,IAAI,IAAI,OAAO,QAAQ,EAAE,SAAS;AAAA,EACrD,MAAM,MAA2B,CAAC;AAAA,EAClC,MAAM,kBAAkB,IAAI;AAAA,EAE5B,WAAW,cAAc,WAAW;AAAA,IACnC,IAAI,CAAC,WAAW,IAAI,WAAW,IAAI,GAAG;AAAA,MACrC,MAAM,IAAI,MACT,yCAAyC,WAAW,4CACnD,0DACF;AAAA,IACD;AAAA,IACA,MAAM,aACL,WAAW,cAAc,kBAAkB,WAAW,IAAI;AAAA,IAC3D,IAAI,gBAAgB,IAAI,UAAU,GAAG;AAAA,MACpC,MAAM,IAAI,MACT,uDAAuD,kBACtD,2FACF;AAAA,IACD;AAAA,IACA,gBAAgB,IAAI,UAAU;AAAA,IAE9B,IAAI,cAAc;AAAA,MACjB,aAAa,WAAW;AAAA,MACxB,SAAS,UAAU,SAAsC;AAAA,QAIxD,MAAM,UAAU,KAAK;AAAA,QACrB,MAAM,cAAc,MAAM,IAAI;AAAA,QAC9B,OAAO,OAAO,YACb,WAAW,MACX,SACA,WACD;AAAA;AAAA,MAED,aAAa,WAAW,eAAe;AAAA,IACxC;AAAA,EACD;AAAA,EAEA,OAAO;AAAA;",
|
|
8
|
+
"debugId": "F24C2874EAF571E664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/sync",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Lightweight reactive-push and write-behind-cache primitives for Elysia and the AbsoluteJS ecosystem — kill polling and keep a remote store off your hot path, without adopting a whole sync-engine backend.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"import": "./dist/testing.js",
|
|
29
29
|
"default": "./dist/testing.js"
|
|
30
30
|
},
|
|
31
|
+
"./code-mode": {
|
|
32
|
+
"types": "./dist/codeMode.d.ts",
|
|
33
|
+
"import": "./dist/codeMode.js",
|
|
34
|
+
"default": "./dist/codeMode.js"
|
|
35
|
+
},
|
|
31
36
|
"./mcp": {
|
|
32
37
|
"types": "./dist/mcp/index.d.ts",
|
|
33
38
|
"import": "./dist/mcp/index.js",
|