@peers-app/peers-sdk 0.26.5 → 0.27.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 +3 -1
- package/bin/peers-package.js +13 -0
- package/define/package.json +4 -0
- package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
- package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
- package/dist/contracts/__tests__/also-implements.test.js +110 -0
- package/dist/contracts/__tests__/builder.test.js +45 -0
- package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
- package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
- package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
- package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
- package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
- package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
- package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
- package/dist/contracts/__tests__/registry.test.js +68 -0
- package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
- package/dist/contracts/__tests__/target-dispatch.js +24 -0
- package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
- package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
- package/dist/contracts/__tests__/validate.test.js +177 -7
- package/dist/contracts/__tests__/workflow-context.test.js +0 -11
- package/dist/contracts/builder.d.ts +16 -2
- package/dist/contracts/builder.js +44 -3
- package/dist/contracts/contract-definition-schema.d.ts +251 -0
- package/dist/contracts/contract-definition-schema.js +48 -0
- package/dist/contracts/contract-provider-router.d.ts +2 -0
- package/dist/contracts/contract-provider-router.js +5 -2
- package/dist/contracts/contract-proxy.d.ts +52 -18
- package/dist/contracts/contract-proxy.js +144 -203
- package/dist/contracts/contract-resolvers.d.ts +6 -4
- package/dist/contracts/contract-resolvers.js +32 -9
- package/dist/contracts/frozen-contracts.d.ts +59 -0
- package/dist/contracts/frozen-contracts.js +86 -0
- package/dist/contracts/host-contract-client.d.ts +134 -0
- package/dist/contracts/host-contract-client.js +396 -0
- package/dist/contracts/host-contract-transport.d.ts +2 -0
- package/dist/contracts/host-contract-transport.js +8 -0
- package/dist/contracts/index.d.ts +6 -4
- package/dist/contracts/index.js +11 -5
- package/dist/contracts/registry.d.ts +31 -1
- package/dist/contracts/registry.js +100 -6
- package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
- package/dist/contracts/system/contract-catalog-contract.js +17 -5
- package/dist/contracts/tool-catalog-service.js +26 -6
- package/dist/contracts/tool-catalog.d.ts +1 -1
- package/dist/contracts/tool-catalog.js +4 -1
- package/dist/contracts/types.d.ts +32 -2
- package/dist/contracts/validate.d.ts +16 -1
- package/dist/contracts/validate.js +139 -25
- package/dist/data/change-tracking.d.ts +17 -5
- package/dist/data/change-tracking.js +22 -6
- package/dist/data/change-tracking.test.d.ts +1 -0
- package/dist/data/change-tracking.test.js +62 -0
- package/dist/data/group-members.d.ts +6 -1
- package/dist/data/group-members.js +12 -4
- package/dist/data/groups.d.ts +6 -1
- package/dist/data/groups.js +12 -4
- package/dist/data/orm/sql.data-source.d.ts +9 -0
- package/dist/data/orm/sql.data-source.js +47 -2
- package/dist/data/orm/sql.data-source.test.js +142 -0
- package/dist/data/orm/table-container.js +9 -9
- package/dist/data/orm/table-container.test.js +44 -0
- package/dist/data/orm/types.d.ts +41 -1
- package/dist/data/orm/types.js +159 -28
- package/dist/data/orm/types.test.js +129 -0
- package/dist/data/package-versions.d.ts +5 -1
- package/dist/data/package-versions.js +11 -4
- package/dist/data/packages.d.ts +7 -1
- package/dist/data/packages.js +13 -4
- package/dist/data/peer-types.d.ts +8 -62
- package/dist/data/persistent-vars.d.ts +4 -1
- package/dist/data/persistent-vars.js +7 -2
- package/dist/data/table-signing.test.d.ts +1 -0
- package/dist/data/table-signing.test.js +118 -0
- package/dist/data/tools.d.ts +29 -221
- package/dist/data/users.d.ts +1 -2
- package/dist/data/users.js +8 -5
- package/dist/define/also-implements.test.d.ts +1 -0
- package/dist/define/also-implements.test.js +123 -0
- package/dist/define/context.d.ts +77 -0
- package/dist/define/context.js +2 -0
- package/dist/define/contract-ref.d.ts +177 -0
- package/dist/define/contract-ref.js +67 -0
- package/dist/define/declare-contract.d.ts +167 -0
- package/dist/define/declare-contract.js +220 -0
- package/dist/define/declare-contract.test.d.ts +1 -0
- package/dist/define/declare-contract.test.js +573 -0
- package/dist/define/define-package.d.ts +203 -0
- package/dist/define/define-package.js +289 -0
- package/dist/define/define.test.d.ts +1 -0
- package/dist/define/define.test.js +1010 -0
- package/dist/define/empty-fields.d.ts +15 -0
- package/dist/define/empty-fields.js +134 -0
- package/dist/define/empty-fields.test.d.ts +1 -0
- package/dist/define/empty-fields.test.js +55 -0
- package/dist/define/events-observables.test.d.ts +1 -0
- package/dist/define/events-observables.test.js +1013 -0
- package/dist/define/events.d.ts +81 -0
- package/dist/define/events.js +81 -0
- package/dist/define/generated-field.d.ts +20 -0
- package/dist/define/generated-field.js +25 -0
- package/dist/define/index.d.ts +28 -0
- package/dist/define/index.js +62 -0
- package/dist/define/lazy-members.d.ts +8 -0
- package/dist/define/lazy-members.js +38 -0
- package/dist/define/manifest.d.ts +63 -0
- package/dist/define/manifest.js +653 -0
- package/dist/define/observables.d.ts +146 -0
- package/dist/define/observables.js +187 -0
- package/dist/define/serve-members.d.ts +21 -0
- package/dist/define/serve-members.js +152 -0
- package/dist/define/serve-members.test.d.ts +1 -0
- package/dist/define/serve-members.test.js +630 -0
- package/dist/define/serve.d.ts +22 -0
- package/dist/define/serve.js +58 -0
- package/dist/define/system-contracts.d.ts +41 -0
- package/dist/define/system-contracts.js +21 -0
- package/dist/define/table-handle.d.ts +13 -0
- package/dist/define/table-handle.js +36 -0
- package/dist/define/table.d.ts +128 -0
- package/dist/define/table.js +122 -0
- package/dist/define/tool.d.ts +129 -0
- package/dist/define/tool.js +91 -0
- package/dist/define/vars.d.ts +82 -0
- package/dist/define/vars.js +256 -0
- package/dist/define/vars.test.d.ts +1 -0
- package/dist/define/vars.test.js +39 -0
- package/dist/define/wire-value.d.ts +12 -0
- package/dist/define/wire-value.js +76 -0
- package/dist/define/worker-state.d.ts +15 -0
- package/dist/define/worker-state.js +45 -0
- package/dist/device/tx-encoding.test.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/observable.d.ts +42 -2
- package/dist/observable.js +25 -7
- package/dist/package-build/build-package.d.ts +60 -0
- package/dist/package-build/build-package.js +312 -0
- package/dist/package-build/build-package.test.d.ts +1 -0
- package/dist/package-build/build-package.test.js +204 -0
- package/dist/package-build/cli.d.ts +7 -0
- package/dist/package-build/cli.js +218 -0
- package/dist/package-build/contract-add.d.ts +35 -0
- package/dist/package-build/contract-add.js +97 -0
- package/dist/package-build/contract-add.test.d.ts +1 -0
- package/dist/package-build/contract-add.test.js +467 -0
- package/dist/package-build/contract-source.d.ts +59 -0
- package/dist/package-build/contract-source.js +471 -0
- package/dist/package-build/freeze-contracts.d.ts +56 -0
- package/dist/package-build/freeze-contracts.js +198 -0
- package/dist/package-build/freeze-contracts.test.d.ts +1 -0
- package/dist/package-build/freeze-contracts.test.js +355 -0
- package/dist/package-installer/package-creator.d.ts +4 -0
- package/dist/package-installer/package-creator.js +57 -16
- package/dist/package-installer/package-creator.test.js +86 -0
- package/dist/package-loader/contract-members.d.ts +53 -0
- package/dist/package-loader/contract-members.js +31 -0
- package/dist/package-loader/contract-package-loader.js +5 -1
- package/dist/package-loader/contract-package-loader.test.js +11 -0
- package/dist/package-loader/host-authority-loading.test.js +9 -5
- package/dist/package-loader/index.d.ts +1 -0
- package/dist/package-loader/index.js +1 -0
- package/dist/package-loader/isolated-artifact.d.ts +80 -17
- package/dist/package-loader/isolated-artifact.js +257 -51
- package/dist/package-loader/isolated-artifact.test.js +294 -21
- package/dist/package-loader/isolated-runtime.d.ts +22 -7
- package/dist/package-loader/isolated-table-gateway.js +30 -11
- package/dist/package-loader/isolated-table-gateway.test.js +243 -0
- package/dist/package-loader/package-loader.d.ts +8 -1
- package/dist/package-loader/package-loader.js +39 -20
- package/dist/package-loader/package-loader.test.js +47 -0
- package/dist/peer-id.d.ts +25 -0
- package/dist/peer-id.js +99 -0
- package/dist/serial-json.compat.test.d.ts +1 -0
- package/dist/serial-json.compat.test.js +369 -0
- package/dist/serial-json.d.ts +41 -5
- package/dist/serial-json.js +216 -157
- package/dist/serial-json.oracle.d.ts +21 -0
- package/dist/serial-json.oracle.js +222 -0
- package/dist/serial-json.test.js +315 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/mock-host-contract.d.ts +123 -0
- package/dist/testing/mock-host-contract.js +434 -0
- package/dist/testing/mock-host-contract.test.d.ts +1 -0
- package/dist/testing/mock-host-contract.test.js +782 -0
- package/dist/types/field-type.d.ts +35 -18
- package/dist/types/field-type.js +4 -1
- package/dist/types/zod-types.d.ts +9 -0
- package/dist/types/zod-types.js +8 -2
- package/dist/utils.d.ts +1 -25
- package/dist/utils.js +10 -90
- package/package.json +9 -2
- package/testing/package.json +4 -0
|
@@ -16,21 +16,66 @@ const package_dev_tools_1 = require("./package-dev-tools");
|
|
|
16
16
|
const package_installer_1 = require("./package-installer");
|
|
17
17
|
const DEFAULT_TEMPLATE_REPO = "https://github.com/peers-app/peers-package-template.git";
|
|
18
18
|
const TEMPLATE_CACHE_DIR = ".peers-package-template";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
/** Template files the creator fills in: the ids (in `src/ids.json` in older templates) and README. */
|
|
20
|
+
const TEMPLATE_TOKEN_FILES = [["src", "ids.json"], ["src", "consts.ts"], ["README.md"]];
|
|
21
|
+
/**
|
|
22
|
+
* A TypeScript string expression for `value`, usually quoted as Biome quotes a literal.
|
|
23
|
+
* Encode names containing text SES rejects even inside strings, so the bundled declaration
|
|
24
|
+
* can use the same display name without making a newly created package fail to build.
|
|
25
|
+
*/
|
|
26
|
+
function sourceStringLiteral(value) {
|
|
27
|
+
if (/(?:<!--|-->|\b(?:import|eval)\s*(?:\(|\/[/*]))/.test(value)) {
|
|
28
|
+
const encoded = JSON.stringify(value).replace(/[<>()/]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
29
|
+
return `String(JSON.parse(${sourceStringLiteral(encoded)}))`;
|
|
30
|
+
}
|
|
31
|
+
// JSON leaves U+2028 and U+2029 unescaped, and Biome's formatter reads them as line breaks.
|
|
32
|
+
const json = JSON.stringify(value).replace(/[\u2028\u2029]/g, (separator) => `\\u${separator.charCodeAt(0).toString(16)}`);
|
|
33
|
+
if (value.split('"').length <= value.split("'").length) {
|
|
34
|
+
return json;
|
|
35
|
+
}
|
|
36
|
+
// JSON escapes every backslash, so each `\"` left in its body is an escaped quote.
|
|
37
|
+
return `'${json.slice(1, -1).replaceAll('\\"', '"').replaceAll("'", "\\'")}'`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Fill in a template file's tokens. A quoted `"<package-name>"` becomes a string expression in the
|
|
41
|
+
* file's own syntax, so a name with quotes or backslashes still makes valid source.
|
|
42
|
+
*/
|
|
43
|
+
function patchTemplateTokens(contents, fileName, values) {
|
|
44
|
+
const quotedName = fileName.endsWith(".json")
|
|
45
|
+
? JSON.stringify(values.packageName)
|
|
46
|
+
: fileName.endsWith(".ts")
|
|
47
|
+
? sourceStringLiteral(values.packageName)
|
|
48
|
+
: `"${values.packageName}"`;
|
|
49
|
+
// Replace once: names may themselves contain tokens or replacement patterns such as `$&`.
|
|
50
|
+
const replacements = {
|
|
51
|
+
'"<package-name>"': quotedName,
|
|
52
|
+
"<package-name>": values.packageName,
|
|
53
|
+
"<package-id>": values.packageId,
|
|
54
|
+
"<app-screen-id>": values.appScreenId,
|
|
55
|
+
"<contract-id>": values.contractId,
|
|
56
|
+
};
|
|
57
|
+
return contents.replace(/"<package-name>"|<package-name>|<package-id>|<app-screen-id>|<contract-id>/g, (token) => replacements[token]);
|
|
25
58
|
}
|
|
26
59
|
/**
|
|
27
60
|
* Convert a human-readable package name to a filesystem-safe folder slug.
|
|
61
|
+
* Keeps existing ASCII slugs so creation still imports folders made by older creators.
|
|
62
|
+
* Uses Unicode letters/numbers when the ASCII slug is empty, or a fresh package folder for
|
|
63
|
+
* names with no letters or numbers, rather than resolving to the packages root.
|
|
64
|
+
* Shortens names exceeding a filesystem component's byte limit with a stable hash suffix.
|
|
28
65
|
* "My Widget" → "my-widget", "camelCase Thing" → "camel-case-thing"
|
|
29
66
|
*/
|
|
30
67
|
function nameToFolderSlug(name) {
|
|
31
|
-
let slug = name.replace(/[^a-zA-Z0-9]/g, " ");
|
|
68
|
+
let slug = name.replace(/[^a-zA-Z0-9]/g, " ").trim();
|
|
69
|
+
if (!slug)
|
|
70
|
+
slug = name.replace(/[^\p{L}\p{N}]/gu, " ");
|
|
32
71
|
slug = slug.replace(/\s+/g, " ").trim();
|
|
33
|
-
|
|
72
|
+
if (!slug)
|
|
73
|
+
return `package-${(0, utils_1.newid)()}`;
|
|
74
|
+
const folder = (0, utils_1.camelCaseToHyphens)(slug);
|
|
75
|
+
if (new TextEncoder().encode(folder).length <= 240)
|
|
76
|
+
return folder;
|
|
77
|
+
// Fifty code points use at most 200 UTF-8 bytes, leaving room for the suffix under 255.
|
|
78
|
+
return `${Array.from(folder).slice(0, 50).join("")}-${(0, utils_1.simpleHash)(name).toString(36)}`;
|
|
34
79
|
}
|
|
35
80
|
const OFFLINE_PULL_PATTERN = /unable to access|could not resolve host|failed to connect|connection (refused|timed out|reset)|network is unreachable|enotfound|etimedout|eai_again|offline|ssl connect error|could not fetch/i;
|
|
36
81
|
/**
|
|
@@ -320,23 +365,19 @@ async function createPackage(dataContext, deps, opts) {
|
|
|
320
365
|
packageObj.author = opts.author;
|
|
321
366
|
}
|
|
322
367
|
delete packageObj.repository;
|
|
323
|
-
await deps.fs.writeFile(packageJsonPath, JSON.stringify(packageObj, null, 2));
|
|
324
|
-
//
|
|
325
|
-
// legacy templates created before isolated package artifacts became the default.
|
|
368
|
+
await deps.fs.writeFile(packageJsonPath, `${JSON.stringify(packageObj, null, 2)}\n`);
|
|
369
|
+
// Fill in the template's ids and name.
|
|
326
370
|
const templateValues = {
|
|
327
371
|
packageId,
|
|
328
372
|
packageName: opts.name,
|
|
329
373
|
appScreenId,
|
|
330
374
|
contractId,
|
|
331
375
|
};
|
|
332
|
-
for (const relativePath of
|
|
333
|
-
["src", "ids.json"],
|
|
334
|
-
["src", "consts.ts"],
|
|
335
|
-
]) {
|
|
376
|
+
for (const relativePath of TEMPLATE_TOKEN_FILES) {
|
|
336
377
|
const templatePath = deps.resolvePath(location, ...relativePath);
|
|
337
378
|
if (await deps.fs.exists(templatePath)) {
|
|
338
379
|
const contents = await deps.fs.readFile(templatePath);
|
|
339
|
-
await deps.fs.writeFile(templatePath, patchTemplateTokens(contents, templateValues));
|
|
380
|
+
await deps.fs.writeFile(templatePath, patchTemplateTokens(contents, relativePath[relativePath.length - 1], templateValues));
|
|
340
381
|
}
|
|
341
382
|
}
|
|
342
383
|
// Initialize as a new project: remove template git history, install, build, fresh git
|
|
@@ -100,6 +100,21 @@ describe("nameToFolderSlug", () => {
|
|
|
100
100
|
it("collapses multiple spaces", () => {
|
|
101
101
|
expect((0, package_creator_1.nameToFolderSlug)("too many spaces")).toBe("too-many-spaces");
|
|
102
102
|
});
|
|
103
|
+
it("preserves non-ASCII names and allocates a folder for symbol-only names", () => {
|
|
104
|
+
expect((0, package_creator_1.nameToFolderSlug)("買い物")).toBe("買い物");
|
|
105
|
+
expect((0, package_creator_1.nameToFolderSlug)("Café 漢字")).toBe("caf"); // Keep older creators' folder names.
|
|
106
|
+
const first = (0, package_creator_1.nameToFolderSlug)("🛒");
|
|
107
|
+
expect(first).toMatch(/^package-[a-z0-9]{25}$/);
|
|
108
|
+
expect((0, package_creator_1.nameToFolderSlug)("🛒")).not.toBe(first);
|
|
109
|
+
});
|
|
110
|
+
it("keeps long ASCII and Unicode names within a filesystem component's byte limit", () => {
|
|
111
|
+
for (const name of ["Long Name ".repeat(40), "漢".repeat(100)]) {
|
|
112
|
+
const folder = (0, package_creator_1.nameToFolderSlug)(name);
|
|
113
|
+
expect(Buffer.byteLength(folder, "utf8")).toBeLessThanOrEqual(255);
|
|
114
|
+
expect((0, package_creator_1.nameToFolderSlug)(name)).toBe(folder);
|
|
115
|
+
expect((0, package_creator_1.nameToFolderSlug)(`${name}more`)).not.toBe(folder);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
103
118
|
});
|
|
104
119
|
describe("createPackage", () => {
|
|
105
120
|
it("fails before scaffolding when Node.js or npm is missing", async () => {
|
|
@@ -458,6 +473,77 @@ describe("createPackage", () => {
|
|
|
458
473
|
mockGetPackageInfo.mockResolvedValue({ name: "no-id-pkg" });
|
|
459
474
|
await expect((0, package_creator_1.createPackage)(dc, deps, { name: "bad-pkg", location: "/some/path" })).rejects.toThrow("does not contain a valid Peers package");
|
|
460
475
|
});
|
|
476
|
+
it("fills in the template's consts.ts and README, with the name as one string expression", async () => {
|
|
477
|
+
const template = {
|
|
478
|
+
"/packages/x/src/consts.ts": [
|
|
479
|
+
'export const packageId = "<package-id>";',
|
|
480
|
+
'export const packageName = "<package-name>";',
|
|
481
|
+
'export const contractId = "<contract-id>";',
|
|
482
|
+
'export const appScreenId = "<app-screen-id>";',
|
|
483
|
+
].join("\n"),
|
|
484
|
+
"/packages/x/README.md": "# <package-name>\n",
|
|
485
|
+
"/packages/x/src/ids.json": '{"packageName":"<package-name>","packageId":"<package-id>"}',
|
|
486
|
+
};
|
|
487
|
+
async function create(name) {
|
|
488
|
+
const deps = makeMockDeps({});
|
|
489
|
+
deps.fs.exists.mockImplementation(async (path) => path in template);
|
|
490
|
+
deps.fs.readFile.mockImplementation(async (path) => template[path]);
|
|
491
|
+
deps.fs.readJson.mockResolvedValue({ name: "template", peers: {} });
|
|
492
|
+
mockGroupDeviceVar.mockReturnValue(jest.fn());
|
|
493
|
+
mockInstallPackageFromBundles.mockResolvedValue({
|
|
494
|
+
packageVersion: { packageVersionId: "pv" },
|
|
495
|
+
loaded: undefined,
|
|
496
|
+
unchanged: false,
|
|
497
|
+
});
|
|
498
|
+
await (0, package_creator_1.createPackage)(mockDataContext(), deps, { name, location: "/packages/x" });
|
|
499
|
+
const written = (path) => deps.fs.writeFile.mock.calls.find((call) => call[0] === path)[1];
|
|
500
|
+
const consts = written("/packages/x/src/consts.ts");
|
|
501
|
+
const literal = /packageName = (.*);/.exec(consts)?.[1] ?? "";
|
|
502
|
+
return {
|
|
503
|
+
consts,
|
|
504
|
+
literal,
|
|
505
|
+
readme: written("/packages/x/README.md"),
|
|
506
|
+
ids: JSON.parse(written("/packages/x/src/ids.json")),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
const plain = await create("My Widget");
|
|
510
|
+
expect(plain.literal).toBe('"My Widget"');
|
|
511
|
+
expect(plain.consts).toMatch(/packageId = "[a-z0-9]{25}";/);
|
|
512
|
+
expect(plain.consts).not.toContain("<");
|
|
513
|
+
expect(plain.readme).toBe("# My Widget\n");
|
|
514
|
+
expect((await create("Cash $& Carry")).readme).toBe("# Cash $& Carry\n");
|
|
515
|
+
// Quoted as Biome's formatter would, so a new package passes `npm run lint`.
|
|
516
|
+
for (const [name, literal] of [
|
|
517
|
+
["Mark's List", `"Mark's List"`],
|
|
518
|
+
['The "Best" List', `'The "Best" List'`],
|
|
519
|
+
[`Mark's "Best" \\ List`, `'Mark\\'s "Best" \\\\ List'`],
|
|
520
|
+
["Cash $& Carry", `"Cash $& Carry"`],
|
|
521
|
+
["Café 漢字 🥛", `"Café 漢字 🥛"`],
|
|
522
|
+
["Line\u2028and\u2029paragraph", `"Line\\u2028and\\u2029paragraph"`],
|
|
523
|
+
['Say "hi"\u2028twice', `'Say "hi"\\u2028twice'`],
|
|
524
|
+
["A <package-name> <package-id>", `"A <package-name> <package-id>"`],
|
|
525
|
+
]) {
|
|
526
|
+
const created = await create(name);
|
|
527
|
+
expect(created.literal).toBe(literal);
|
|
528
|
+
expect(new Function(`return ${created.literal};`)()).toBe(name);
|
|
529
|
+
expect(created.readme).toBe(`# ${name}\n`);
|
|
530
|
+
expect(created.ids.packageName).toBe(name);
|
|
531
|
+
expect(created.ids.packageId).toMatch(/^[a-z0-9]{25}$/);
|
|
532
|
+
}
|
|
533
|
+
for (const name of [
|
|
534
|
+
"List <!-- comments -->",
|
|
535
|
+
"List import( jokes",
|
|
536
|
+
"List import /* notes",
|
|
537
|
+
'List eval( "quoted" \\ 漢字\u2028',
|
|
538
|
+
]) {
|
|
539
|
+
const created = await create(name);
|
|
540
|
+
expect(created.literal).toMatch(/^String\(JSON.parse\(/);
|
|
541
|
+
expect(created.literal).not.toMatch(/<!--|-->|\b(?:import|eval)\s*(?:\(|\/[/*])/);
|
|
542
|
+
expect(new Function(`return ${created.literal};`)()).toBe(name);
|
|
543
|
+
expect(created.ids.packageName).toBe(name);
|
|
544
|
+
expect(created.readme).toBe(`# ${name}\n`);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
461
547
|
it("uses direct clone (no caching) when custom templateRepo is provided", async () => {
|
|
462
548
|
const deps = makeMockDeps({});
|
|
463
549
|
const dc = mockDataContext();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the host and a guest API 3 provider talk about contract members: the guest handler names
|
|
3
|
+
* the host invokes and what they carry. Kept apart from the manifest code so a provider bundle
|
|
4
|
+
* can import it cheaply.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Guest handler name the host invokes with a consumer's call on one of a contract's tables,
|
|
8
|
+
* observables, or events (guest API 3). It takes an {@link IIsolatedContractMemberCall} under
|
|
9
|
+
* the caller's authority.
|
|
10
|
+
*
|
|
11
|
+
* @param contractId - The provided contract's id.
|
|
12
|
+
* @param version - The provided contract's version.
|
|
13
|
+
*/
|
|
14
|
+
export declare function contractMemberHandlerName(contractId: string, version: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Guest handler name the host invokes with each table change, event, or observable value bound
|
|
17
|
+
* for one subscriber (guest API 3), under that subscriber's authority. It takes an
|
|
18
|
+
* {@link IIsolatedContractDelivery} and returns an {@link IIsolatedContractDeliveryResult}.
|
|
19
|
+
*
|
|
20
|
+
* @param contractId - The provided contract's id.
|
|
21
|
+
* @param version - The provided contract's version.
|
|
22
|
+
*/
|
|
23
|
+
export declare function contractDeliveryHandlerName(contractId: string, version: number): string;
|
|
24
|
+
/** A consumer's call on a contract member, as the provider's worker receives it. */
|
|
25
|
+
export interface IIsolatedContractMemberCall {
|
|
26
|
+
/** Which kind of member. Tools have their own handlers. */
|
|
27
|
+
member: "table" | "observable" | "event";
|
|
28
|
+
/** The member's name in the contract. */
|
|
29
|
+
name: string;
|
|
30
|
+
/**
|
|
31
|
+
* A table method (`get`, `list`, `count`, `findOne`, `save`, `delete`), an observable's `get`
|
|
32
|
+
* or `set`, or `subscribe` / `unsubscribe` (table `dataChanged`, events, observables).
|
|
33
|
+
*/
|
|
34
|
+
operation: string;
|
|
35
|
+
/** The call's arguments. */
|
|
36
|
+
args: unknown[];
|
|
37
|
+
}
|
|
38
|
+
/** One outgoing message for one subscriber, as the provider's worker receives it. */
|
|
39
|
+
export interface IIsolatedContractDelivery {
|
|
40
|
+
/** A table's `dataChanged`, an event, or an observable's value. */
|
|
41
|
+
member: "table" | "observable" | "event";
|
|
42
|
+
/** The member's name in the contract. */
|
|
43
|
+
name: string;
|
|
44
|
+
/** The change event, event payload, or observable value. */
|
|
45
|
+
value: unknown;
|
|
46
|
+
}
|
|
47
|
+
/** What the provider does with an outgoing message: send it (possibly changed), or drop it. */
|
|
48
|
+
export interface IIsolatedContractDeliveryResult {
|
|
49
|
+
/** False drops the message for this subscriber. */
|
|
50
|
+
send: boolean;
|
|
51
|
+
/** What to send when `send` is true. */
|
|
52
|
+
value?: unknown;
|
|
53
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* How the host and a guest API 3 provider talk about contract members: the guest handler names
|
|
4
|
+
* the host invokes and what they carry. Kept apart from the manifest code so a provider bundle
|
|
5
|
+
* can import it cheaply.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.contractMemberHandlerName = contractMemberHandlerName;
|
|
9
|
+
exports.contractDeliveryHandlerName = contractDeliveryHandlerName;
|
|
10
|
+
/**
|
|
11
|
+
* Guest handler name the host invokes with a consumer's call on one of a contract's tables,
|
|
12
|
+
* observables, or events (guest API 3). It takes an {@link IIsolatedContractMemberCall} under
|
|
13
|
+
* the caller's authority.
|
|
14
|
+
*
|
|
15
|
+
* @param contractId - The provided contract's id.
|
|
16
|
+
* @param version - The provided contract's version.
|
|
17
|
+
*/
|
|
18
|
+
function contractMemberHandlerName(contractId, version) {
|
|
19
|
+
return `contract:${JSON.stringify([contractId, version])}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Guest handler name the host invokes with each table change, event, or observable value bound
|
|
23
|
+
* for one subscriber (guest API 3), under that subscriber's authority. It takes an
|
|
24
|
+
* {@link IIsolatedContractDelivery} and returns an {@link IIsolatedContractDeliveryResult}.
|
|
25
|
+
*
|
|
26
|
+
* @param contractId - The provided contract's id.
|
|
27
|
+
* @param version - The provided contract's version.
|
|
28
|
+
*/
|
|
29
|
+
function contractDeliveryHandlerName(contractId, version) {
|
|
30
|
+
return `deliver:${JSON.stringify([contractId, version])}`;
|
|
31
|
+
}
|
|
@@ -30,7 +30,11 @@ async function installContractPackage(dataContext, packageDefinition, opts) {
|
|
|
30
30
|
const definition = existing && !existing.devTag && contract.devTag === "dev"
|
|
31
31
|
? { ...contract, devTag: undefined }
|
|
32
32
|
: contract;
|
|
33
|
-
return {
|
|
33
|
+
return {
|
|
34
|
+
definition,
|
|
35
|
+
alsoImplements: packageDefinition.alsoImplements.get(key) ?? [],
|
|
36
|
+
exposes: packageDefinition.exposes?.get(key) ?? [],
|
|
37
|
+
};
|
|
34
38
|
});
|
|
35
39
|
const catalog = registry.toolCatalog ?? new tool_catalog_1.ToolCatalog(dataContext, registry);
|
|
36
40
|
const prepare = async () => {
|
|
@@ -91,6 +91,17 @@ describe("installContractPackage registry wiring", () => {
|
|
|
91
91
|
expect(registry.getProviderPackageId(contractId, 1)).toBe(packageId);
|
|
92
92
|
expect(registry.getContractKeysForPackage(packageId)).toEqual([`${contractId}@1`]);
|
|
93
93
|
});
|
|
94
|
+
it("registers what a hub implementation exposes", async () => {
|
|
95
|
+
const registry = new registry_1.ContractRegistry();
|
|
96
|
+
const hubId = (0, utils_1.newid)();
|
|
97
|
+
const definition = makeDefinition({
|
|
98
|
+
exposes: new Map([[`${hubId}@1`, [{ contractId, version: 1 }]]]),
|
|
99
|
+
});
|
|
100
|
+
definition.contracts.push({ ...definition.contracts[0], contractId: hubId, name: "Hub" });
|
|
101
|
+
await (0, contract_package_loader_1.installContractPackage)(mockDataContext(), definition, { registry });
|
|
102
|
+
expect(registry.getExposedContracts(hubId, 1, packageId)).toEqual([{ contractId, version: 1 }]);
|
|
103
|
+
expect(registry.getExposedContracts(contractId, 1, packageId)).toEqual([]);
|
|
104
|
+
});
|
|
94
105
|
it("persists CLI runner metadata without executable registration", async () => {
|
|
95
106
|
const registry = new registry_1.ContractRegistry();
|
|
96
107
|
const context = mockDataContext();
|
|
@@ -50,22 +50,26 @@ test("host-backed package endpoints honor declared propagation and reread live m
|
|
|
50
50
|
},
|
|
51
51
|
toolFn: fn,
|
|
52
52
|
});
|
|
53
|
-
const route = make("route", 20, (_, ctx) => ctx.
|
|
53
|
+
const route = make("route", 20, (_, ctx) => ctx.dispatchTool?.({ contractId, version: 1, toolName: "target", providerPackageId: packageId }, {}));
|
|
54
54
|
route.tool.authorityPropagation = "preserve";
|
|
55
55
|
const effect = jest.fn(() => "admin target");
|
|
56
56
|
(0, index_1.registerContractResolver)(contractId, 1, () => ({
|
|
57
57
|
contractId,
|
|
58
58
|
version: 1,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
actionTools: { target: make("target", 60, effect) },
|
|
59
|
+
forwardCallContext: true,
|
|
60
|
+
tools: { route, target: make("target", 60, effect) },
|
|
62
61
|
}), { providerPackageId: packageId });
|
|
63
62
|
loader.contractRegistry.register(packageId, {
|
|
64
63
|
contractId,
|
|
65
64
|
version: 1,
|
|
66
65
|
name: "Arbitrary",
|
|
67
66
|
description: "",
|
|
68
|
-
tools: [
|
|
67
|
+
tools: ["route", "target"].map((name) => ({
|
|
68
|
+
name,
|
|
69
|
+
usageDescription: name,
|
|
70
|
+
inputFields: [],
|
|
71
|
+
outputFields: [],
|
|
72
|
+
})),
|
|
69
73
|
tables: [],
|
|
70
74
|
observables: [],
|
|
71
75
|
events: [],
|
|
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./contract-members"), exports);
|
|
17
18
|
__exportStar(require("./contract-package-loader"), exports);
|
|
18
19
|
__exportStar(require("./isolated-artifact"), exports);
|
|
19
20
|
__exportStar(require("./isolated-persistent-var-gateway"), exports);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IAlsoImplementsDeclaration, IConsumedContract, IContractDefinition, IContractEvent, IContractObservable, IContractTool } from "../contracts/types";
|
|
1
|
+
import type { IAlsoImplementsDeclaration, IConsumedContract, IContractDefinition, IContractEvent, IContractObservable, IContractTool, IExposedContract } from "../contracts/types";
|
|
2
2
|
import { type PersistentVarScope } from "../data/persistent-var-identity";
|
|
3
3
|
import type { IAppNav } from "../types/app-nav";
|
|
4
4
|
import { type IField } from "../types/field-type";
|
|
@@ -46,10 +46,36 @@ export interface IIsolatedContractTool extends Omit<IContractTool, "inputFields"
|
|
|
46
46
|
*
|
|
47
47
|
* When omitted, the runtime's `invokeTimeoutMs` applies. Capped at
|
|
48
48
|
* {@link ISOLATED_TOOL_TIMEOUT_MAX_MS}. Host calls made during the invoke,
|
|
49
|
-
* including System HTTP, count against this deadline
|
|
49
|
+
* including System HTTP, count against this deadline, as does time a handler spends
|
|
50
|
+
* waiting on the package's own locks. A call that times out is rejected; its handler keeps
|
|
51
|
+
* running unless the worker is stuck, in either guest API.
|
|
50
52
|
*/
|
|
51
53
|
timeoutMs?: number;
|
|
52
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The latest guest API version an isolated manifest targets.
|
|
57
|
+
*
|
|
58
|
+
* - `3`: as `2`, and the package serves its own contract members. The host sends consumers'
|
|
59
|
+
* table calls, observable reads and writes, and subscriptions to the provider's worker
|
|
60
|
+
* (`contractMemberHandlerName`), and passes every table change, event, and observable
|
|
61
|
+
* value bound for a subscriber through it first (`contractDeliveryHandlerName`).
|
|
62
|
+
* - `2`: handlers receive `(args, ctx)` and calls run concurrently; the package orders any
|
|
63
|
+
* state it shares between calls itself. The host serves contract tables and stored
|
|
64
|
+
* observables from storage, without the worker.
|
|
65
|
+
* - Omitted: the legacy global helpers, with one call at a time.
|
|
66
|
+
*
|
|
67
|
+
* Hosts that predate a version reject it.
|
|
68
|
+
*/
|
|
69
|
+
export declare const ISOLATED_GUEST_API_VERSION = 3;
|
|
70
|
+
/** Guest API versions a manifest may declare. */
|
|
71
|
+
export type IsolatedGuestApi = 2 | 3;
|
|
72
|
+
/**
|
|
73
|
+
* True when the package serves its contract members itself (guest API 3): every consumer call
|
|
74
|
+
* and outgoing message goes through its worker.
|
|
75
|
+
*
|
|
76
|
+
* @param manifest - The package's manifest.
|
|
77
|
+
*/
|
|
78
|
+
export declare function servesContractMembers(manifest: Pick<IIsolatedPackageManifest, "guestApi">): boolean;
|
|
53
79
|
/** Longest per-tool invoke deadline an isolated manifest may declare. */
|
|
54
80
|
export declare const ISOLATED_TOOL_TIMEOUT_MAX_MS = 120000;
|
|
55
81
|
/**
|
|
@@ -92,6 +118,13 @@ export interface IIsolatedPackageTable {
|
|
|
92
118
|
fields: IField[];
|
|
93
119
|
/** Schema version for host table-definition updates. Defaults to `1`. */
|
|
94
120
|
versionNumber?: number;
|
|
121
|
+
/**
|
|
122
|
+
* Keep rows on the device that writes them: no change rows are recorded, so no row syncs.
|
|
123
|
+
* The schema still syncs, so every device in the group has its own copy of the table. Changing
|
|
124
|
+
* it is a schema change, so it needs a higher `versionNumber`. It is storage policy, never part
|
|
125
|
+
* of a contract's table shape.
|
|
126
|
+
*/
|
|
127
|
+
localOnly?: boolean;
|
|
95
128
|
}
|
|
96
129
|
/** Name-only reference that exposes one package table through a contract. */
|
|
97
130
|
export interface IIsolatedContractTableRef {
|
|
@@ -103,12 +136,17 @@ export interface IIsolatedContractPersistentVarBinding {
|
|
|
103
136
|
name: string;
|
|
104
137
|
defaultValue: unknown;
|
|
105
138
|
}
|
|
106
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Observable contract member. With `persistentVar`, a package-owned persistent variable holds
|
|
141
|
+
* the value. Without it (guest API 2 or later), the worker holds the value in memory and
|
|
142
|
+
* publishes it with `publishOwnedObservable`. Guest API 3 serves all consumer calls and
|
|
143
|
+
* deliveries through the worker; older packages serve stored values directly from the host.
|
|
144
|
+
*/
|
|
107
145
|
export interface IIsolatedContractObservable extends IContractObservable {
|
|
108
|
-
persistentVar
|
|
146
|
+
persistentVar?: IIsolatedContractPersistentVarBinding;
|
|
109
147
|
}
|
|
110
148
|
/**
|
|
111
|
-
* Event an isolated provider may emit
|
|
149
|
+
* Event an isolated provider may emit at any time while its current worker runs.
|
|
112
150
|
*
|
|
113
151
|
* Shape matches {@link IContractEvent}. `payloadFields` defaults to `[]`
|
|
114
152
|
* during validation when omitted. Events are host-owned: the worker emits via
|
|
@@ -128,12 +166,17 @@ export interface IIsolatedProvidedContract {
|
|
|
128
166
|
tools: IIsolatedContractTool[];
|
|
129
167
|
/** Name-only references to package tables exposed to consumers. */
|
|
130
168
|
tables?: IIsolatedContractTableRef[];
|
|
131
|
-
/**
|
|
169
|
+
/** Observable values exposed to consumers, stored in a pvar or held by the worker. */
|
|
132
170
|
observables?: IIsolatedContractObservable[];
|
|
133
|
-
/** Events the worker may emit
|
|
171
|
+
/** Events the worker may emit with `emitContractEvent`, at any time while it runs. */
|
|
134
172
|
events?: IIsolatedContractEvent[];
|
|
135
173
|
/** Older contract versions this provider shape also satisfies. */
|
|
136
174
|
alsoImplements?: IAlsoImplementsDeclaration[];
|
|
175
|
+
/**
|
|
176
|
+
* Other contracts this package provides whose tools this contract's consumers may call on
|
|
177
|
+
* it: set when this contract is a hub's, such as Voice Skill. Not part of the contract's shape.
|
|
178
|
+
*/
|
|
179
|
+
exposes?: IExposedContract[];
|
|
137
180
|
}
|
|
138
181
|
/** Contract this package is allowed to call through the trusted host. */
|
|
139
182
|
export interface IIsolatedConsumedContract {
|
|
@@ -143,11 +186,6 @@ export interface IIsolatedConsumedContract {
|
|
|
143
186
|
providerPackageIds?: string[];
|
|
144
187
|
fallbackToDefaultProvider?: boolean;
|
|
145
188
|
}
|
|
146
|
-
/** Package-owned catalog selector bound to a registered canonical action. */
|
|
147
|
-
export interface IIsolatedActionAlias {
|
|
148
|
-
name: string;
|
|
149
|
-
target: IIsolatedContractToolRef;
|
|
150
|
-
}
|
|
151
189
|
/**
|
|
152
190
|
* Pure-data package description. Validated on the host; never executed there.
|
|
153
191
|
*/
|
|
@@ -171,8 +209,12 @@ export interface IIsolatedPackageManifest {
|
|
|
171
209
|
*/
|
|
172
210
|
network?: IIsolatedPackageNetwork;
|
|
173
211
|
appNavs?: IAppNav[];
|
|
174
|
-
/**
|
|
175
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Guest API version ({@link ISOLATED_GUEST_API_VERSION}). `2` for handlers that take
|
|
214
|
+
* `(args, ctx)`; `3` when the package also serves its own contract members; omit for the
|
|
215
|
+
* legacy global helpers, which run one call at a time.
|
|
216
|
+
*/
|
|
217
|
+
guestApi?: IsolatedGuestApi;
|
|
176
218
|
}
|
|
177
219
|
/**
|
|
178
220
|
* Installable isolated package: declarative manifest plus provider source
|
|
@@ -205,8 +247,6 @@ export interface IIsolatedInstalledPackage {
|
|
|
205
247
|
tables: IIsolatedPackageTable[];
|
|
206
248
|
definitions: IContractDefinition[];
|
|
207
249
|
appNavs?: IAppNav[];
|
|
208
|
-
/** Private checked targets, independent of the frozen Voice Skill catalog. */
|
|
209
|
-
actionAliases?: IIsolatedActionAlias[];
|
|
210
250
|
}
|
|
211
251
|
/** Host-contract member guests use to request a consumed-contract tool call. */
|
|
212
252
|
export declare const ISOLATED_CONSUME_HOST_MEMBER = "contract.consume";
|
|
@@ -216,6 +256,22 @@ export declare const ISOLATED_TABLE_HOST_MEMBER = "package.table";
|
|
|
216
256
|
export declare const ISOLATED_PERSISTENT_VAR_HOST_MEMBER = "package.pvar";
|
|
217
257
|
/** Host-contract member guests use to emit a provided-contract event. */
|
|
218
258
|
export declare const ISOLATED_EVENT_HOST_MEMBER = "contract.emit";
|
|
259
|
+
/** Host-contract member guests use to publish an in-memory provided-contract observable. */
|
|
260
|
+
export declare const ISOLATED_OBSERVABLE_HOST_MEMBER = "package.observable";
|
|
261
|
+
/**
|
|
262
|
+
* How deep field lists may nest through object `fields`: a tool's inputs, say, are level 1.
|
|
263
|
+
* Deeper nesting is refused, so a hostile manifest can't exhaust the validator's stack.
|
|
264
|
+
*/
|
|
265
|
+
export declare const ISOLATED_MAX_FIELD_DEPTH = 16;
|
|
266
|
+
/**
|
|
267
|
+
* True when `value` may appear in `enumValues` on a field of `type`: a string, number, or boolean
|
|
268
|
+
* of that type, or any of them on `any`. Shared with the manifest builder; not a public SDK export.
|
|
269
|
+
*
|
|
270
|
+
* @internal
|
|
271
|
+
* @param value - A candidate enum value.
|
|
272
|
+
* @param type - The field's type.
|
|
273
|
+
*/
|
|
274
|
+
export declare function enumValueFits(value: unknown, type: string): boolean;
|
|
219
275
|
/**
|
|
220
276
|
* True when `bundle` starts with {@link ISOLATED_PACKAGE_MAGIC}.
|
|
221
277
|
*
|
|
@@ -229,11 +285,18 @@ export declare function looksLikeIsolatedPackageArtifact(bundle: string): boolea
|
|
|
229
285
|
* Validate a wire-safe isolated package artifact. Does not evaluate
|
|
230
286
|
* `providerSource`.
|
|
231
287
|
*
|
|
288
|
+
* Fields keep only the keys this SDK knows: a host drops any other field key, since a newer
|
|
289
|
+
* SDK's keys only describe values further.
|
|
290
|
+
*
|
|
232
291
|
* @param artifact - Parsed artifact object.
|
|
292
|
+
* @param options - `strict` refuses field keys this SDK doesn't know instead; the package build
|
|
293
|
+
* uses it, where an unknown key is a mistake.
|
|
233
294
|
* @returns The same manifest after in-place field normalization.
|
|
234
295
|
* @throws If identity, contracts, tools, or source are invalid.
|
|
235
296
|
*/
|
|
236
|
-
export declare function validateIsolatedPackageArtifact(artifact: IIsolatedPackageArtifact
|
|
297
|
+
export declare function validateIsolatedPackageArtifact(artifact: IIsolatedPackageArtifact, options?: {
|
|
298
|
+
strict?: boolean;
|
|
299
|
+
}): IIsolatedPackageManifest;
|
|
237
300
|
/**
|
|
238
301
|
* Parse a magic-header + JSON isolated package bundle.
|
|
239
302
|
*
|