@peers-app/peers-sdk 0.26.6 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/create-group.js +1 -1
- package/dist/data/group-members.d.ts +14 -0
- package/dist/data/group-members.js +36 -0
- package/dist/data/group-members.test.js +49 -0
- 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/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/tools.d.ts +29 -221
- 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/invites/group-approval.js +4 -2
- package/dist/invites/group-approval.test.js +2 -0
- 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 +22 -25
- package/dist/utils.js +45 -92
- package/dist/utils.test.js +36 -0
- package/package.json +9 -2
- package/testing/package.json +4 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.contractSourceFromManifest = contractSourceFromManifest;
|
|
4
|
+
exports.contractSourceFromDefinition = contractSourceFromDefinition;
|
|
5
|
+
exports.parseContractSelector = parseContractSelector;
|
|
6
|
+
exports.generateContractDeclaration = generateContractDeclaration;
|
|
7
|
+
const access_level_1 = require("../data/access-level");
|
|
8
|
+
const field_type_1 = require("../types/field-type");
|
|
9
|
+
const RESERVED_NAMES = new Set([
|
|
10
|
+
..."await break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import in instanceof interface let new null package private protected public return static super switch this throw true try typeof var void while with yield".split(" "),
|
|
11
|
+
"AccessLevel",
|
|
12
|
+
"arguments",
|
|
13
|
+
"ContractEventPayloadsOf",
|
|
14
|
+
"ContractObservableValuesOf",
|
|
15
|
+
"ContractTableRowsOf",
|
|
16
|
+
"FieldType",
|
|
17
|
+
"Uint8Array",
|
|
18
|
+
"declareContract",
|
|
19
|
+
"declareObservable",
|
|
20
|
+
"declareTool",
|
|
21
|
+
"defineEvent",
|
|
22
|
+
"defineTable",
|
|
23
|
+
"eval",
|
|
24
|
+
"generatedField",
|
|
25
|
+
"z",
|
|
26
|
+
"zodPeerId",
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* The contract in a provider's manifest, by `contractId@version` (or the only one).
|
|
30
|
+
*
|
|
31
|
+
* @param manifest - A package's manifest.
|
|
32
|
+
* @param selector - `contractId@version`; optional when the package provides one contract.
|
|
33
|
+
*/
|
|
34
|
+
function contractSourceFromManifest(manifest, selector) {
|
|
35
|
+
const provided = manifest.provides ?? [];
|
|
36
|
+
const wanted = selector ? parseContractSelector(selector) : undefined;
|
|
37
|
+
const matches = wanted
|
|
38
|
+
? provided.filter((contract) => contract.contractId === wanted.contractId && contract.version === wanted.version)
|
|
39
|
+
: provided;
|
|
40
|
+
if (matches.length !== 1) {
|
|
41
|
+
const listed = provided.map((contract) => `${contract.contractId}@${contract.version}`);
|
|
42
|
+
throw new Error(wanted
|
|
43
|
+
? `The manifest doesn't provide ${selector}; it provides ${listed.join(", ") || "nothing"}`
|
|
44
|
+
: `The manifest provides ${listed.length} contracts; choose one with --contract ${listed[0] ?? "<id>@<version>"}`);
|
|
45
|
+
}
|
|
46
|
+
const [contract] = matches;
|
|
47
|
+
const tables = (contract.tables ?? []).map((ref) => {
|
|
48
|
+
const table = (manifest.tables ?? []).find((candidate) => candidate.name === ref.name);
|
|
49
|
+
if (!table) {
|
|
50
|
+
throw new Error(`The manifest's contract lists table "${ref.name}" but doesn't define it`);
|
|
51
|
+
}
|
|
52
|
+
return table;
|
|
53
|
+
});
|
|
54
|
+
return { contract, tables };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A registered contract definition, with the active provider's tool policy, as the generator
|
|
58
|
+
* reads it.
|
|
59
|
+
*
|
|
60
|
+
* @param input - `getContractDefinition`'s result.
|
|
61
|
+
*/
|
|
62
|
+
function contractSourceFromDefinition(input) {
|
|
63
|
+
const { definition } = input ?? {};
|
|
64
|
+
if (!definition ||
|
|
65
|
+
typeof definition.contractId !== "string" ||
|
|
66
|
+
!Array.isArray(definition.tools)) {
|
|
67
|
+
throw new Error("Expected getContractDefinition's result: { definition, tools }");
|
|
68
|
+
}
|
|
69
|
+
const policy = new Map((input.tools ?? []).map((tool) => [tool.name, tool]));
|
|
70
|
+
const tools = definition.tools.map((tool) => {
|
|
71
|
+
const toolPolicy = policy.get(tool.name);
|
|
72
|
+
if (toolPolicy?.accessLevel === undefined) {
|
|
73
|
+
throw new Error(input.tools
|
|
74
|
+
? `The Runtime reported no access level for tool "${tool.name}" (is its active provider loaded?); generate from that provider's manifest or artifact instead`
|
|
75
|
+
: `Tool "${tool.name}" has no access level in the definition; generate from getContractDefinition's result, which carries the active provider's`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
name: tool.name,
|
|
79
|
+
usageDescription: tool.usageDescription,
|
|
80
|
+
inputFields: tool.inputFields,
|
|
81
|
+
outputFields: tool.outputFields,
|
|
82
|
+
suggestedAccessLevel: toolPolicy.accessLevel,
|
|
83
|
+
...(toolPolicy.authorityPropagation
|
|
84
|
+
? { authorityPropagation: toolPolicy.authorityPropagation }
|
|
85
|
+
: {}),
|
|
86
|
+
...(tool.remoteAccessLevel === undefined
|
|
87
|
+
? {}
|
|
88
|
+
: { remoteAccessLevel: tool.remoteAccessLevel }),
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
contract: {
|
|
93
|
+
contractId: definition.contractId,
|
|
94
|
+
version: definition.version,
|
|
95
|
+
name: definition.name,
|
|
96
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
97
|
+
tools,
|
|
98
|
+
...(definition.tables?.length
|
|
99
|
+
? { tables: definition.tables.map((table) => ({ name: table.name })) }
|
|
100
|
+
: {}),
|
|
101
|
+
...(definition.observables?.length ? { observables: definition.observables } : {}),
|
|
102
|
+
...(definition.events?.length ? { events: definition.events } : {}),
|
|
103
|
+
},
|
|
104
|
+
tables: (definition.tables ?? []).map((table) => ({
|
|
105
|
+
name: table.name,
|
|
106
|
+
description: table.description,
|
|
107
|
+
primaryKeyName: table.primaryKeyName,
|
|
108
|
+
fields: table.fields,
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse `contractId@version`.
|
|
114
|
+
*
|
|
115
|
+
* @param selector - The contract's id and version.
|
|
116
|
+
*/
|
|
117
|
+
function parseContractSelector(selector) {
|
|
118
|
+
const match = /^([0-9a-z]{25})@([1-9][0-9]*)$/i.exec(selector);
|
|
119
|
+
if (!match || !Number.isSafeInteger(Number(match[2]))) {
|
|
120
|
+
throw new Error(`Expected <contractId>@<version>, got "${selector}"`);
|
|
121
|
+
}
|
|
122
|
+
return { contractId: match[1], version: Number(match[2]) };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Write a contract's declaration as TypeScript: a `declareContract(...)` call whose zod schemas
|
|
126
|
+
* give the same IR, for a package that implements or calls a contract it doesn't own. The file
|
|
127
|
+
* is meant to be checked in and regenerated, not edited.
|
|
128
|
+
*
|
|
129
|
+
* What the IR carries comes through: names, types, optional fields, arrays, nested object
|
|
130
|
+
* fields, enum and literal values, descriptions, and each tool's access level and authority
|
|
131
|
+
* propagation. Refinements (a regex, a minimum) and defaults made by code aren't in the IR, so
|
|
132
|
+
* generated schemas check only what the IR describes. Objects keep keys their fields don't list.
|
|
133
|
+
*
|
|
134
|
+
* @param input - The contract and its tables.
|
|
135
|
+
* @param options - Where it came from (for the file's header), and an export name to use
|
|
136
|
+
* instead of the one made from the contract's name.
|
|
137
|
+
*/
|
|
138
|
+
function generateContractDeclaration(input, options) {
|
|
139
|
+
const { contract, tables } = input;
|
|
140
|
+
assertVersion(contract.version, "Contract version");
|
|
141
|
+
const exportName = options.exportName ?? pascalCase(contract.name);
|
|
142
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(exportName) || RESERVED_NAMES.has(exportName)) {
|
|
143
|
+
throw new Error(`"${exportName}" isn't a usable export name; pass --name`);
|
|
144
|
+
}
|
|
145
|
+
const imports = new Set(["declareContract"]);
|
|
146
|
+
const code = new Code(imports);
|
|
147
|
+
const lines = [];
|
|
148
|
+
lines.push(`// Generated by \`peers-package contract add\` from ${docText(options.from)}.`, "// Regenerate it rather than edit it; a stable package release freezes the contract's shape.", "");
|
|
149
|
+
const eventNames = new Map();
|
|
150
|
+
for (const event of contract.events ?? []) {
|
|
151
|
+
const name = uniqueName(`${camelCase(exportName)}${pascalCase(event.name)}`, new Set([...RESERVED_NAMES, exportName, ...eventNames.values()]));
|
|
152
|
+
eventNames.set(event.name, name);
|
|
153
|
+
imports.add("defineEvent");
|
|
154
|
+
const fields = event.payloadFields ?? [];
|
|
155
|
+
lines.push(`/** ${docText(event.description)} */`, `export const ${name} = defineEvent({`, ` name: ${JSON.stringify(event.name)},`, ` description: ${JSON.stringify(event.description)},`, ...(fields.length ? [` payloadSchema: ${code.objectSchema(fields, 1)},`] : []), "});", "");
|
|
156
|
+
}
|
|
157
|
+
const members = [];
|
|
158
|
+
if (tables.length) {
|
|
159
|
+
imports.add("defineTable");
|
|
160
|
+
// Local constants preserve defineTable's inferred row type in declareContract's list.
|
|
161
|
+
const taken = new Set([...RESERVED_NAMES, exportName, ...eventNames.values()]);
|
|
162
|
+
const names = tables.map((table) => {
|
|
163
|
+
const name = uniqueName(`${camelCase(exportName)}${pascalCase(table.name)}`, taken);
|
|
164
|
+
taken.add(name);
|
|
165
|
+
lines.push(`const ${name} = ${code.table(table, 0)};`, "");
|
|
166
|
+
return name;
|
|
167
|
+
});
|
|
168
|
+
members.push(` tables: [${names.join(", ")}],`);
|
|
169
|
+
}
|
|
170
|
+
const publicTools = contract.tools.filter((tool) => !tool.private);
|
|
171
|
+
if (publicTools.length) {
|
|
172
|
+
imports.add("declareTool");
|
|
173
|
+
members.push(" tools: [", ...publicTools.map((tool) => ` ${code.tool(tool, 2)},`), " ],");
|
|
174
|
+
}
|
|
175
|
+
if (contract.observables?.length) {
|
|
176
|
+
imports.add("declareObservable");
|
|
177
|
+
members.push(" observables: [", ...contract.observables.map((observable) => ` ${code.observable(observable, 2)},`), " ],");
|
|
178
|
+
}
|
|
179
|
+
if (contract.events?.length) {
|
|
180
|
+
members.push(` events: [${[...eventNames.values()].join(", ")}],`);
|
|
181
|
+
}
|
|
182
|
+
lines.push(`/** ${docText(contract.description || `${contract.name} v${contract.version}.`)} */`, `export const ${exportName} = declareContract({`, ` contractId: ${JSON.stringify(contract.contractId)},`, ` version: ${contract.version},`, ` name: ${JSON.stringify(contract.name)},`, ...(contract.description ? [` description: ${JSON.stringify(contract.description)},`] : []), ...members, "});");
|
|
183
|
+
const types = [];
|
|
184
|
+
if (contract.observables?.length) {
|
|
185
|
+
imports.add("type ContractObservableValuesOf");
|
|
186
|
+
types.push("", `/** ${docText(`Each observable's value, by name: \`${exportName}Observables[${JSON.stringify(contract.observables[0].name)}]\`.`)} */`, `export type ${exportName}Observables = ContractObservableValuesOf<typeof ${exportName}>;`);
|
|
187
|
+
}
|
|
188
|
+
if (contract.events?.length) {
|
|
189
|
+
imports.add("type ContractEventPayloadsOf");
|
|
190
|
+
types.push("", "/** Each event's payload, by name. */", `export type ${exportName}Events = ContractEventPayloadsOf<typeof ${exportName}>;`);
|
|
191
|
+
}
|
|
192
|
+
if (tables.length) {
|
|
193
|
+
imports.add("type ContractTableRowsOf");
|
|
194
|
+
types.push("", "/** Each table's rows, by name. */", `export type ${exportName}Rows = ContractTableRowsOf<typeof ${exportName}>;`);
|
|
195
|
+
}
|
|
196
|
+
lines.push(...types, "");
|
|
197
|
+
const sorted = [...imports].sort((a, b) => a.replace(/^type /, "").localeCompare(b.replace(/^type /, ""), "en", { sensitivity: "base" }));
|
|
198
|
+
const header = lines.slice(0, 3);
|
|
199
|
+
const body = lines.slice(3);
|
|
200
|
+
const source = [
|
|
201
|
+
...header,
|
|
202
|
+
`import { ${sorted.join(", ")} } from "@peers-app/peers-sdk/define";`,
|
|
203
|
+
"",
|
|
204
|
+
...body,
|
|
205
|
+
].join("\n");
|
|
206
|
+
return {
|
|
207
|
+
fileName: `${kebabCase(contract.name) || kebabCase(exportName) || "contract"}.v${contract.version}.ts`,
|
|
208
|
+
exportName,
|
|
209
|
+
source,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/** Builds the zod source for IR, noting the SDK names it uses. */
|
|
213
|
+
class Code {
|
|
214
|
+
imports;
|
|
215
|
+
constructor(imports) {
|
|
216
|
+
this.imports = imports;
|
|
217
|
+
}
|
|
218
|
+
table(table, depth) {
|
|
219
|
+
if (table.versionNumber !== undefined)
|
|
220
|
+
assertVersion(table.versionNumber, "Table version");
|
|
221
|
+
return this.call("defineTable", depth, [
|
|
222
|
+
["name", JSON.stringify(table.name)],
|
|
223
|
+
["description", JSON.stringify(table.description ?? "")],
|
|
224
|
+
["primaryKeyName", JSON.stringify(table.primaryKeyName)],
|
|
225
|
+
["schema", this.objectSchema(table.fields, depth + 1)],
|
|
226
|
+
...(table.versionNumber === undefined
|
|
227
|
+
? []
|
|
228
|
+
: [["versionNumber", String(table.versionNumber)]]),
|
|
229
|
+
...(table.localOnly ? [["localOnly", "true"]] : []),
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
tool(tool, depth) {
|
|
233
|
+
// An omitted level is the Runtime's default: None for a preserving tool, else Self.
|
|
234
|
+
const level = tool.suggestedAccessLevel ??
|
|
235
|
+
(tool.authorityPropagation === "preserve" ? access_level_1.AccessLevel.None : access_level_1.AccessLevel.Self);
|
|
236
|
+
return this.call("declareTool", depth, [
|
|
237
|
+
["name", JSON.stringify(tool.name)],
|
|
238
|
+
["usageDescription", JSON.stringify(tool.usageDescription)],
|
|
239
|
+
["accessLevel", this.accessLevel(level)],
|
|
240
|
+
...(tool.authorityPropagation
|
|
241
|
+
? [["authorityPropagation", JSON.stringify(tool.authorityPropagation)]]
|
|
242
|
+
: []),
|
|
243
|
+
...(tool.remoteAccessLevel === undefined
|
|
244
|
+
? []
|
|
245
|
+
: [["remoteAccessLevel", this.accessLevel(tool.remoteAccessLevel)]]),
|
|
246
|
+
["inputSchema", this.objectSchema(tool.inputFields ?? [], depth + 1)],
|
|
247
|
+
["outputSchema", this.objectSchema(tool.outputFields ?? [], depth + 1)],
|
|
248
|
+
]);
|
|
249
|
+
}
|
|
250
|
+
observable(observable, depth) {
|
|
251
|
+
const shape = observable.value
|
|
252
|
+
? [["schema", this.valueSchema(observable.value, observable.valueType, depth + 1)]]
|
|
253
|
+
: [["valueType", this.fieldType(observable.valueType)]];
|
|
254
|
+
return this.call("declareObservable", depth, [
|
|
255
|
+
["name", JSON.stringify(observable.name)],
|
|
256
|
+
["description", JSON.stringify(observable.description)],
|
|
257
|
+
...shape,
|
|
258
|
+
...(observable.writable ? [["writable", "true"]] : []),
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
objectSchema(fields, depth) {
|
|
262
|
+
this.imports.add("z");
|
|
263
|
+
if (!fields.length) {
|
|
264
|
+
return "z.object({}).passthrough()";
|
|
265
|
+
}
|
|
266
|
+
const pad = " ".repeat(depth + 1);
|
|
267
|
+
const entries = fields.map((field) => `${pad}${propertyKey(field.name)}: ${this.field(field, depth + 1)},`);
|
|
268
|
+
return `z.object({\n${entries.join("\n")}\n${" ".repeat(depth)}}).passthrough()`;
|
|
269
|
+
}
|
|
270
|
+
/** An observable's whole value: an empty one is `null` on the wire. */
|
|
271
|
+
valueSchema(value, valueType, depth) {
|
|
272
|
+
const { optional, ...rest } = value;
|
|
273
|
+
const schema = this.field(rest, depth);
|
|
274
|
+
const result = optional ? `${schema}.nullable()` : schema;
|
|
275
|
+
// valueType describes what the host admits, independently of the detailed value shape.
|
|
276
|
+
// Transforms and numeric native enums can make it wider than the schema's scalar type.
|
|
277
|
+
const inferred = optional
|
|
278
|
+
? field_type_1.FieldType.any
|
|
279
|
+
: value.isArray
|
|
280
|
+
? field_type_1.FieldType.object
|
|
281
|
+
: value.type === field_type_1.FieldType.number && (value.enumValues?.length ?? 0) > 1
|
|
282
|
+
? field_type_1.FieldType.any
|
|
283
|
+
: value.type;
|
|
284
|
+
return inferred === valueType && optional !== false
|
|
285
|
+
? result
|
|
286
|
+
: this.annotate(result, value, valueType);
|
|
287
|
+
}
|
|
288
|
+
field(field, depth) {
|
|
289
|
+
let schema = this.base(field, depth);
|
|
290
|
+
if (field.isArray)
|
|
291
|
+
schema = `z.array(${schema})`;
|
|
292
|
+
if (field.defaultValue !== undefined && isPlainData(field.defaultValue)) {
|
|
293
|
+
schema = `${schema}.default(${dataSource(field.defaultValue)})`;
|
|
294
|
+
}
|
|
295
|
+
else if (field.optional) {
|
|
296
|
+
schema = `${schema}.optional()`;
|
|
297
|
+
}
|
|
298
|
+
if (field.description)
|
|
299
|
+
schema = `${schema}.describe(${JSON.stringify(field.description)})`;
|
|
300
|
+
// Preserve IR that normal schema inference cannot express. This also keeps opaque objects
|
|
301
|
+
// opaque (rather than adding fields: []) and event any fields' original requiredness.
|
|
302
|
+
if (field.type === field_type_1.FieldType.any ||
|
|
303
|
+
field.type === field_type_1.FieldType.blob ||
|
|
304
|
+
(field.type === field_type_1.FieldType.number && (field.enumValues?.length ?? 0) > 1) ||
|
|
305
|
+
(field.type === field_type_1.FieldType.object && field.fields === undefined) ||
|
|
306
|
+
(field.type === field_type_1.FieldType.id && field.enumValues !== undefined) ||
|
|
307
|
+
(field.type === field_type_1.FieldType.boolean && (field.enumValues?.length ?? 0) > 1) ||
|
|
308
|
+
(field.enumValues !== undefined &&
|
|
309
|
+
new Set(field.enumValues).size !== field.enumValues.length) ||
|
|
310
|
+
field.defaultValue !== undefined ||
|
|
311
|
+
field.subType !== undefined ||
|
|
312
|
+
field.optional === false ||
|
|
313
|
+
field.isArray === false ||
|
|
314
|
+
field.description === "") {
|
|
315
|
+
schema = this.annotate(schema, field);
|
|
316
|
+
}
|
|
317
|
+
return schema;
|
|
318
|
+
}
|
|
319
|
+
annotate(schema, shape, valueType) {
|
|
320
|
+
this.imports.add("generatedField");
|
|
321
|
+
// A field's name belongs to the enclosing object, not its schema's shape.
|
|
322
|
+
const { name: _name, ...metadata } = shape;
|
|
323
|
+
return `generatedField(${schema}, ${this.shapeSource(metadata)}${valueType === undefined ? "" : `, ${this.fieldType(valueType)}`})`;
|
|
324
|
+
}
|
|
325
|
+
shapeSource(shape) {
|
|
326
|
+
const entries = Object.entries(shape).map(([key, value]) => {
|
|
327
|
+
const source = key === "type"
|
|
328
|
+
? this.fieldType(value)
|
|
329
|
+
: key === "fields"
|
|
330
|
+
? `[${value.map((field) => this.shapeSource(field)).join(", ")}]`
|
|
331
|
+
: dataSource(value);
|
|
332
|
+
return `${propertyKey(key)}: ${source}`;
|
|
333
|
+
});
|
|
334
|
+
return `{ ${entries.join(", ")} }`;
|
|
335
|
+
}
|
|
336
|
+
base(field, depth) {
|
|
337
|
+
if (field.type !== field_type_1.FieldType.id)
|
|
338
|
+
this.imports.add("z");
|
|
339
|
+
const values = field.enumValues;
|
|
340
|
+
switch (field.type) {
|
|
341
|
+
case field_type_1.FieldType.string:
|
|
342
|
+
if (values?.length === 1)
|
|
343
|
+
return `z.literal(${JSON.stringify(values[0])})`;
|
|
344
|
+
if (values?.length)
|
|
345
|
+
return `z.enum(${JSON.stringify(values)})`;
|
|
346
|
+
return "z.string()";
|
|
347
|
+
case field_type_1.FieldType.number:
|
|
348
|
+
if (values?.length === 1)
|
|
349
|
+
return `z.literal(${JSON.stringify(values[0])})`;
|
|
350
|
+
if (values?.length) {
|
|
351
|
+
const entries = values.map((value, index) => `v${index}: ${JSON.stringify(value)}`);
|
|
352
|
+
return `z.nativeEnum({ ${entries.join(", ")} } as const)`;
|
|
353
|
+
}
|
|
354
|
+
return "z.number()";
|
|
355
|
+
case field_type_1.FieldType.boolean:
|
|
356
|
+
return values?.length && new Set(values).size === 1
|
|
357
|
+
? `z.literal(${JSON.stringify(values[0])})`
|
|
358
|
+
: "z.boolean()";
|
|
359
|
+
case field_type_1.FieldType.id:
|
|
360
|
+
this.imports.add("zodPeerId");
|
|
361
|
+
return values?.length
|
|
362
|
+
? `zodPeerId.refine((value) => ${JSON.stringify(values)}.includes(value))`
|
|
363
|
+
: "zodPeerId";
|
|
364
|
+
case field_type_1.FieldType.Date:
|
|
365
|
+
return "z.date()";
|
|
366
|
+
case field_type_1.FieldType.blob:
|
|
367
|
+
return "z.instanceof(Uint8Array)";
|
|
368
|
+
case field_type_1.FieldType.object:
|
|
369
|
+
// The Runtime keeps keys an object's fields don't list, and so does this schema.
|
|
370
|
+
return this.objectSchema(field.fields ?? [], depth);
|
|
371
|
+
default:
|
|
372
|
+
// The annotation keeps the IR's any type even when enum values narrow the schema.
|
|
373
|
+
if (values?.length === 1)
|
|
374
|
+
return `z.literal(${JSON.stringify(values[0])})`;
|
|
375
|
+
if (values && values.length > 1) {
|
|
376
|
+
return `z.union([${values.map((value) => `z.literal(${JSON.stringify(value)})`).join(", ")}])`;
|
|
377
|
+
}
|
|
378
|
+
// z.unknown() admits an omitted key and makes it optional in zod's object types.
|
|
379
|
+
// A required scalar any still needs a value; array elements aren't record fields.
|
|
380
|
+
if (!field.optional && !field.isArray && field.defaultValue === undefined) {
|
|
381
|
+
return "z.custom<NonNullable<unknown> | null>((value) => value !== undefined)";
|
|
382
|
+
}
|
|
383
|
+
return "z.unknown()";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
fieldType(type) {
|
|
387
|
+
this.imports.add("FieldType");
|
|
388
|
+
const key = Object.entries(field_type_1.FieldType).find(([, value]) => value === type)?.[0];
|
|
389
|
+
return key ? `FieldType.${key}` : JSON.stringify(type);
|
|
390
|
+
}
|
|
391
|
+
accessLevel(level) {
|
|
392
|
+
if (!Number.isInteger(level) || level < 0 || level > 100) {
|
|
393
|
+
throw new Error("Tool access level must be an integer 0–100");
|
|
394
|
+
}
|
|
395
|
+
const named = Object.entries(access_level_1.AccessLevel).find(([, value]) => value === level)?.[0];
|
|
396
|
+
if (!named)
|
|
397
|
+
return String(level);
|
|
398
|
+
this.imports.add("AccessLevel");
|
|
399
|
+
return `AccessLevel.${named}`;
|
|
400
|
+
}
|
|
401
|
+
call(name, depth, entries) {
|
|
402
|
+
const pad = " ".repeat(depth + 1);
|
|
403
|
+
const body = entries.map(([key, value]) => `${pad}${key}: ${value},`).join("\n");
|
|
404
|
+
return `${name}({\n${body}\n${" ".repeat(depth)}})`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** Versions become numeric literals and file names, so JSON sources must supply safe integers. */
|
|
408
|
+
function assertVersion(value, label) {
|
|
409
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
410
|
+
throw new Error(`${label} must be a positive safe integer`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/** A property name as written in an object literal. */
|
|
414
|
+
function propertyKey(name) {
|
|
415
|
+
if (name === "__proto__")
|
|
416
|
+
return '["__proto__"]';
|
|
417
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
418
|
+
}
|
|
419
|
+
/** JSON data as a source expression, preserving an own __proto__ key in stored defaults. */
|
|
420
|
+
function dataSource(value) {
|
|
421
|
+
if (Array.isArray(value))
|
|
422
|
+
return `[${value.map(dataSource).join(", ")}]`;
|
|
423
|
+
if (value && typeof value === "object") {
|
|
424
|
+
return `{ ${Object.entries(value)
|
|
425
|
+
.map(([key, entry]) => `${propertyKey(key)}: ${dataSource(entry)}`)
|
|
426
|
+
.join(", ")} }`;
|
|
427
|
+
}
|
|
428
|
+
return JSON.stringify(value);
|
|
429
|
+
}
|
|
430
|
+
function isPlainData(value) {
|
|
431
|
+
try {
|
|
432
|
+
return JSON.stringify(value) !== undefined;
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/** Text for a one-line JSDoc comment. */
|
|
439
|
+
function docText(text) {
|
|
440
|
+
return text.replace(/\*\//g, "* /").replace(/\s+/g, " ").trim();
|
|
441
|
+
}
|
|
442
|
+
function words(name) {
|
|
443
|
+
return name
|
|
444
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
445
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
446
|
+
.split(/[^A-Za-z0-9]+/)
|
|
447
|
+
.filter(Boolean);
|
|
448
|
+
}
|
|
449
|
+
function pascalCase(name) {
|
|
450
|
+
const joined = words(name)
|
|
451
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
452
|
+
.join("");
|
|
453
|
+
return /^[0-9]/.test(joined) ? `Contract${joined}` : joined;
|
|
454
|
+
}
|
|
455
|
+
function camelCase(name) {
|
|
456
|
+
const [first, ...rest] = words(name);
|
|
457
|
+
if (!first)
|
|
458
|
+
return name;
|
|
459
|
+
return first.toLowerCase() + rest.map((word) => word[0].toUpperCase() + word.slice(1)).join("");
|
|
460
|
+
}
|
|
461
|
+
function kebabCase(name) {
|
|
462
|
+
return words(name)
|
|
463
|
+
.map((word) => word.toLowerCase())
|
|
464
|
+
.join("-");
|
|
465
|
+
}
|
|
466
|
+
function uniqueName(name, taken) {
|
|
467
|
+
let candidate = name;
|
|
468
|
+
for (let index = 2; taken.has(candidate); index++)
|
|
469
|
+
candidate = `${name}${index}`;
|
|
470
|
+
return candidate;
|
|
471
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type IFrozenContract, type IFrozenContracts, type IFrozenContractsCheck } from "../contracts/frozen-contracts";
|
|
2
|
+
import { type IIsolatedPackageManifest } from "../package-loader/isolated-artifact";
|
|
3
|
+
/** What {@link freezePackageContracts} takes. */
|
|
4
|
+
export interface IContractFreezeOptions {
|
|
5
|
+
/** Package root, holding `frozen-contracts.json`. Defaults to the current directory. */
|
|
6
|
+
root?: string;
|
|
7
|
+
/** The declaration module, relative to `root`, for a `/define` package. Defaults to `src/package.ts`. */
|
|
8
|
+
entry?: string;
|
|
9
|
+
/**
|
|
10
|
+
* A built bundle to read the provided contracts from instead of the declaration module: an
|
|
11
|
+
* isolated package's `package.bundle.js`, or a host-backed (legacy) package's CommonJS bundle.
|
|
12
|
+
* Relative paths are resolved from the current directory, like `contract add`'s source.
|
|
13
|
+
*/
|
|
14
|
+
bundle?: string;
|
|
15
|
+
/** Write nothing; report whether a frozen contract version changed. */
|
|
16
|
+
check?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** What {@link freezePackageContracts} found and did. */
|
|
19
|
+
export interface IContractFreezeResult {
|
|
20
|
+
/** `frozen-contracts.json`'s path. */
|
|
21
|
+
path: string;
|
|
22
|
+
/** The package version the contracts were read from. */
|
|
23
|
+
packageVersion: string;
|
|
24
|
+
/** Contract versions frozen by this run (none with `check`, or when a frozen version changed). */
|
|
25
|
+
added: IFrozenContract[];
|
|
26
|
+
/**
|
|
27
|
+
* Frozen versions changed, and those no longer served: neither provided nor named by a
|
|
28
|
+
* provided contract's `alsoImplements`.
|
|
29
|
+
*/
|
|
30
|
+
check: IFrozenContractsCheck;
|
|
31
|
+
/** The file was written. */
|
|
32
|
+
written: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Freeze the contract versions a package provides, before releasing it as stable: add each one
|
|
36
|
+
* not yet in `frozen-contracts.json`, as frozen by the package's version. With `check`, or when a
|
|
37
|
+
* frozen version's shape changed, write nothing.
|
|
38
|
+
*
|
|
39
|
+
* @param options - The package, where its contracts come from, and whether only to check.
|
|
40
|
+
*/
|
|
41
|
+
export declare function freezePackageContracts(options: IContractFreezeOptions): Promise<IContractFreezeResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Refuse a manifest that changes a contract version the package's stable releases froze, as
|
|
44
|
+
* `peers-package build` does. A package without `frozen-contracts.json` passes.
|
|
45
|
+
*
|
|
46
|
+
* @param root - The package root.
|
|
47
|
+
* @param manifest - The manifest about to be built.
|
|
48
|
+
*/
|
|
49
|
+
export declare function assertFrozenContracts(root: string, manifest: IIsolatedPackageManifest): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Read a package's `frozen-contracts.json`.
|
|
52
|
+
*
|
|
53
|
+
* @param root - The package root.
|
|
54
|
+
* @returns Its content, or undefined when the package has none.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readFrozenContracts(root: string): Promise<IFrozenContracts | undefined>;
|